Gazoduc, your secure method gives the session to your Documents, but not
to their members. Like, say, this_document.writer which is a model,
and which should be “secured”, too. The “secure” fonction fails doing
that.
This was solved by implementing a secure method in the base class of my
model and doing all the “children” stuff by hand.
I then have some code in validate_on_update and validate_on_create
making sure my object has passed through “secure” before it goes into
the DB, so if I forget to overwrite some Rails clever stuff for
retrieving data, it breaks on save and my model stays “secured”.
:o) I understand
Besides checking for user read/write/publish access, I also use this
“secure” pipeline to manage multiple languages, passing the desired one
with user id and groups.
Good idea
If Webrick and Co are only serving one request per thread, then the
easiest piece of code for all this is:
$session = session
Hi Ben. I think that a controller instance is created per request - it
would be very blatantly unthreadsafe not to do that.
There is a class variable in ActionController::Base, allow_concurrency,
which is normally set to false. The comment on it says:
# Controls whether the application is thread-safe, so
multi-threaded servers like WEBrick know whether to apply a mutex
# around the performance of each action. Action Pack and Active
Record are by default thread-safe, but many applications
# may not be. Turned off by default.
@@allow_concurrency = false
cattr_accessor :allow_concurrency
…but AWDR says Rails is not thread-safe, and Zed, in a recent
discussion of threading, said “Mongrel has to use a “grand mutex” around
the Dispatcher.dispatch in order to prevent Rails from going crazy.
Rails just isn’t thread safe”.
So I’m puzzled. It might be that just the use of the database connection
needs protection… and there has been discussion from time to time
about having a connection pool.
I sympathise with your desire for a simple container.
So I’m puzzled. It might be that just the use of the database connection
needs protection… and there has been discussion from time to time
about having a connection pool.
I sympathise with your desire for a simple container.
regards
Justin
So we have to keep our design clean of globals of any kind, be they
class variables or ruby $globals. This should not hurt. Betting on the
reverse is a very dangerous attitude which could lead to very hard to
resolve bugs… months later when we forgot about this little “global”
hack.
through at the time.
While the mutex slows things down, the use of a single process makes
other things easier. For example, WEBrick servlets are the only runtime
that make it safe to use the memory-based stores for sessions and caches.
FastCGI and SCGI use a pool of processes, each of which handles one
request at a time.
However, it’s never been clear to me what is not thread-safe in Rails.
Even though I know absolutely nothing about ActionPack internals, I
would still hazard a
guess: instance vars. The ability to have all those @vars holding stuff
for us from the
controller to the view makes things very easy for us. But that
flexibility comes at a
price… one of the first things you learn in Java servlet programming
is NO instance vars!
Based on the generous information many of you gave, here’s my own
attempt, lib/context_system.rb
(I apologize for the poor English of my documentation).
Here we define a way for models to access a context set by the
controllers.
We add those methods to ActiveRecord::Base instances :
- context the context object
- context_session same as context[:session] (context has to be a
Hash)
- context_user same as context[:session][:user]
(defined in a LoginSystem context)
Those methods raise unless controllers had the context set before.
The context can not be nil.
Controllers who include the ContextSystem module have two ways to
define
the context :
- a “local”, thread-safe way
- a “global”, not thread-safe way
Both are always available, but the “global” methods will raise if
ActionController::Base.allow_concurrency is true.
Thus the “local” way is best practice.
-----
Let’s describe the two ways :
1) “local” methods
In your controller code, give with_context or with_session methods a
block.
Inside this block, the given context will be available to all
ActiveRecord::Base instances.
- with_context(object) { … } stores object as the context
- with_session { … } stores {:session => @session} as the
context
Example :
def update
with_session do
# update and save a model.
…
model.modifier_id = model.context_user.id
model.save
…
end
model.context # raises since we’re out of the with_session block
end
If required by ActionController::Base.allow_concurrency, a mutex makes
sure
that no more than one with_context or with_session block runs at
a given moment.
If not required, no mutex is used.
2) “global” methods
Those methods are to be used as filters :
- store_context may be called on before_filter
- store_session may be called on before_filter
- reset_context should be called on after_filter
They do not require the controller to use the local methods
with_context
or with_session for all models to know about the context.
Exemple :
require_dependency “context_system”
class FooController < ActionController::Base
def update
# update and save a model.
…
# no surrounding with_session block, but this line works :
model.modifier_id = model.context_user.id
model.save
…
end
include ContextSystem
before_filter :store_session
after_filter :reset_context
end
if ActionController::Base.allow_concurrency
thread safe mode
require ‘thread’
class ActiveRecord::Base
class << self
def context_mutex
@@context_mutex = Mutex.new unless defined? @@context_mutex
@@context_mutex
end
def with_context(context)
# protect the context with a mutex
context_mutex.synchronize do
@@context= context
result = yield
@@context= nil
result
end
end
end
def context
raise(RuntimeError, "Missing with_context or with_session block in
Controller method.", caller) unless defined?(@@context) &&
!@@context.nil?
@@context
end
end
module ContextSystem
def with_context(context)
ActiveRecord::Base.with_context(context) do
yield
end
end
def store_context(context)
raise "Not allowed when ActionController::Base.allow_concurrency
is true"
end
def reset_context
end
end
else
not thread safe mode
class ActiveRecord::Base
cattr_writer :context
def context
raise(RuntimeError, "Missing store_context or store_session
before_filter in Controller class, or with_context or with_session block
in Controller method.", caller) unless defined?(@@context) &&
!@@context.nil?
@@context
end
end
module ContextSystem
def store_context(context)
ActiveRecord::Base.context = context
end
def reset_context
ActiveRecord::Base.context = nil
end
def with_context(context)
ActiveRecord::Base.context = context
result = yield
ActiveRecord::Base.context = nil
result
end
end
end
session an user mappings on context : thread-independant
class ActiveRecord::Base
def context_session
context[:session]
end
def context_user
context[:session][:user]
end
end
module ContextSystem
def with_session
with_context({:session => @session}) do yield end
end
def store_session
store_context({:session => @session})
end
end