Can someone tell if there is a reason models shouldn''t access the session? Models could easily be made to access the session or some other shared persistant data store such as a file or a table. _This is the problem_, a model needs to know certain information eg. the id of the current user. But the model has no idea about anything outside of its self, because it can''t access any kind of shared var. The only way to tell the model is to pass it at creation time, but this does not seem very pretty, because its not a case of passing the bit of information once and thats it, its set for all models. You have to do it every time. Is there a way I should be looking at this problem within the MCV restraints? -- Posted via http://www.ruby-forum.com/.
On Apr 4, 2006, at 10:05, Kris wrote:> Can someone tell if there is a reason models shouldn''t access the > session? > Models could easily be made to access the session or some other shared > persistant data store such as a file or a table. > > _This is the problem_, a model needs to know certain information > eg. the > id of the current user. But the model has no idea about anything > outside > of its self, because it can''t access any kind of shared var. > > The only way to tell the model is to pass it at creation time, but > this > does not seem very pretty, because its not a case of passing the > bit of > information once and thats it, its set for all models. You have to > do it > every time.In MVC it is the responsibility of the controller to fetch session data and pass it to models through API calls. If the model needs the user id, or needs the user itself, its API will require them accordingly. -- fxn
Xavier Noria wrote:> In MVC it is the responsibility of the controller to fetch session data > and pass it to models through API calls. If the model needs the user id, > or needs the user itself, its API will require them accordingly.Depending on how strict you''re being, you could say that the model shouldn''t even know about user ids, only user objects, and that the controller is responsible for generating one from the other and passing the user object to the model in the API. That''s more of a stylistic thing, though... -- Alex
Xavier Noria wrote:> On Apr 4, 2006, at 10:05, Kris wrote: > >> >> The only way to tell the model is to pass it at creation time, but >> this >> does not seem very pretty, because its not a case of passing the >> bit of >> information once and thats it, its set for all models. You have to >> do it >> every time. > > In MVC it is the responsibility of the controller to fetch session > data and pass it to models through API calls. If the model needs the > user id, or needs the user itself, its API will require them > accordingly. > > -- fxnThis is what I suspected :) So basically what you are saying is that if a model requires the user id you have to pass it eg. document = Document.new(current_user.id) or document = Document.new document.set_user_id(current_user.id) Would it break MCV to give models access to the session? Its just that the above example are ok if you are dealing with a few models with where the assosiations don''t need to know the same information. document = Document.new attachments = document.set_user_id(current_user.id).attachments.set_user_id(current_user.id).find(:all) Quickly it starts to look less nice, and that only passing one parameter. I believe there is a plugin knocking about that adds the current users id to a user_id field... something like this may be adaptable? -- Posted via http://www.ruby-forum.com/.
On Tue, Apr 04, 2006 at 11:33:43AM +0200, kris wrote: } Xavier Noria wrote: } > On Apr 4, 2006, at 10:05, Kris wrote: } >> The only way to tell the model is to pass it at creation time, but } >> this does not seem very pretty, because its not a case of passing the } >> bit of information once and thats it, its set for all models. You have } >> to do it every time. } > } > In MVC it is the responsibility of the controller to fetch session } > data and pass it to models through API calls. If the model needs the } > user id, or needs the user itself, its API will require them } > accordingly. } > } > -- fxn } } This is what I suspected :) So basically what you are saying is that if } a model requires the user id you have to pass it eg. } } document = Document.new(current_user.id) } } or } } document = Document.new } document.set_user_id(current_user.id) } } Would it break MCV to give models access to the session? Yes. The value of MVC (not MCV, incidentally) is that the lower layers do not depend on the higher layers. The model code can be completely unaware of the the controller code, or even its existence. Likewise, the controller code can be completely unaware of the view code. This allows tests to be run on the models without the additional complexity of the controllers, for example. It also means that any layer can be replaced by another layer offering the same API to the higher layer(s) and using the same API of the lower layer(s). This is good design. It is perfectly reasonable for the model to be aware of some context, but it should be made aware through an API called on it, rather than by using an API of a higher layer. How can you run unit tests on your models if your model needs access to a controller session? It''s possible, certainly, but it is a mess. } Its just that the above example are ok if you are dealing with a few } models with where the assosiations don''t need to know the same } information. } } document = Document.new } attachments = } document.set_user_id(current_user.id).attachments.set_user_id(current_user.id).find(:all) } } Quickly it starts to look less nice, and that only passing one } parameter. [...] If you are in such a situation, you probably have a design problem. I have a situation in which Event models hide or translate some of their attributes based on the current user. I have attr_accessor :wrt in the Event model, and I have an _event partial that sets the wrt variable to the current user. Since the partial renders each Event, there is only one place I have to set things. The models don''t need to be aware of the session. --Greg
#if krisleech /* Apr 04, 11:33 */> > I believe there is a plugin knocking about that adds the current users > id to a user_id field... something like this may be adaptable? >#endif /* krisleech@interkonect.com */ Have a read through: (or should that be :through ?) http://www.koziarski.net/archives/2005/07/16/environment I think that might be of some use to you. -- keys: http://codex.net/gpg.asc You can never plan the future by the past. -- Edmund Burke
Thanks for the reply. "Yes. The value of MVC (not MCV, incidentally) is that the lower layers> do not depend on the higher layers. The model code can be completely > unaware of the the controller code, or even its existence."As suspected.. "It is perfectly reasonable for the model to be aware of some context,> but it should be made aware through an API called on it, rather than by > using an API of a higher layer."When you say API you mean the public methods? So basically you call a method which sets some internal attribute which the model can then refer to. [I guess there is no way to make that attribute stick so that you dont have to keep setting it each time you create an instance of a model, like setting a class attribute instead of an instance attribute?] So if I wanted to have models which had a dynamic datasource, say I could choose the table the model was working on, I would give the model an attribute called say current_table_name. The current_table_name attribute would have a default. Pseudo Code class document << AR attribute :current_table_name, :default => ''document_'' + Date.now.year set_table_name :current_table_name # attribute setter def current_table_name=(new_table_name) set_table_name new_table_name.to_sym end end Gregory Seidman wrote:> On Tue, Apr 04, 2006 at 11:33:43AM +0200, kris wrote: > } Xavier Noria wrote: > } > On Apr 4, 2006, at 10:05, Kris wrote: > } >> The only way to tell the model is to pass it at creation time, but > } >> this does not seem very pretty, because its not a case of passing > the > } >> bit of information once and thats it, its set for all models. You > have > } >> to do it every time. > } > > } > In MVC it is the responsibility of the controller to fetch session > } > data and pass it to models through API calls. If the model needs the > } > user id, or needs the user itself, its API will require them > } > accordingly. > } > > } > -- fxn > } > } This is what I suspected :) So basically what you are saying is that > if > } a model requires the user id you have to pass it eg. > } > } document = Document.new(current_user.id) > } > } or > } > } document = Document.new > } document.set_user_id(current_user.id) > } > } Would it break MCV to give models access to the session? > > Yes. The value of MVC (not MCV, incidentally) is that the lower layers > do > not depend on the higher layers. The model code can be completely > unaware of the the controller code, or even its existence. Likewise, the > controller code can be completely unaware of the view code. This allows > tests to be run on the models without the additional complexity of the > controllers, for example. It also means that any layer can be replaced > by > another layer offering the same API to the higher layer(s) and using the > same API of the lower layer(s). This is good design. > > It is perfectly reasonable for the model to be aware of some context, > but > it should be made aware through an API called on it, rather than by > using > an API of a higher layer. How can you run unit tests on your models if > your > model needs access to a controller session? It''s possible, certainly, > but > it is a mess. > > } Its just that the above example are ok if you are dealing with a few > } models with where the assosiations don''t need to know the same > } information. > } > } document = Document.new > } attachments > } > document.set_user_id(current_user.id).attachments.set_user_id(current_user.id).find(:all) > } > } Quickly it starts to look less nice, and that only passing one > } parameter. > [...] > > If you are in such a situation, you probably have a design problem. I > have > a situation in which Event models hide or translate some of their > attributes based on the current user. I have attr_accessor :wrt in the > Event model, and I have an _event partial that sets the wrt variable to > the > current user. Since the partial renders each Event, there is only one > place > I have to set things. The models don''t need to be aware of the session. > > --Greg-- Posted via http://www.ruby-forum.com/.
Kris wrote:> Can someone tell if there is a reason models shouldn''t access the > session? > Models could easily be made to access the session or some other shared > persistant data store such as a file or a table. > > _This is the problem_, a model needs to know certain information eg. the > id of the current user. But the model has no idea about anything outside > of its self, because it can''t access any kind of shared var. > > The only way to tell the model is to pass it at creation time, but this > does not seem very pretty, because its not a case of passing the bit of > information once and thats it, its set for all models. You have to do it > every time. > > Is there a way I should be looking at this problem within the MCV > restraints?I solved this problem by using (pseudo code) def secure result = yield if result.kind_of? Array result.map { |r| r.set_visitor(session[:visitor_id]) } else result.set_visitor(session[:visitor_id]) end result end With this in you can now do : doc = secure { Document.new } or docs = secure { Document.find(:all) } If you are interested in more in-depth view of my solution ( also securing find with scopes) : gaspard AT teti. DOT ch. -- Posted via http://www.ruby-forum.com/.
On Tue, Apr 04, 2006 at 03:39:35PM +0200, kris wrote: [...] } > "It is perfectly reasonable for the model to be aware of some context, } > but it should be made aware through an API called on it, rather than by } > using an API of a higher layer." } } When you say API you mean the public methods? In a general sense, yes. } So basically you call a method which sets some internal attribute which } the model can then refer to. [I guess there is no way to make that } attribute stick so that you dont have to keep setting it each time you } create an instance of a model, like setting a class attribute instead of } an instance attribute?] That would be a very bad idea. While a particular Rails server (e.g. WEBrick) may not have more than one action in progress, you should always assume that it could. That means that you could have two (or more) actions being served concurrently for different users, but only one place to store the user. } So if I wanted to have models which had a dynamic datasource, say I } could choose the table the model was working on, I would give the model } an attribute called say current_table_name. The current_table_name } attribute would have a default. [...] I am not at all clear on what you are trying to accomplish. --Greg
>> document.set_user_id(current_user.id).attachments.set_user_id >> (current_user.id).find(:all) >> } >> } Quickly it starts to look less nice, and that only passing one >> } parameter.You''re not using associations properly here current_user.documents.attachments will likely return the same results as the line you wrote if I understand your schema. When I first started writing OO code, it took me a while to convert my functional background into the OO world. Reading Aaron Hillegass''s book, Cocoa Programming For Mac OS X, was a big help. If you do it right, you''ll never end up with code looking like what you wrote above. Code like that likely means that you are doing something wrong. Good OO should read easily. -- -- Tom Mornini
I won''t enter in discussion on MVC, because I *do* need giving session to models : I want them to be able to store a creation/modification/destroy event in the database, holding *who* did that to them. My desire for this feature is stronger than perfect MVC compliance. If I have problem with testing, well I''ll whip my back. Quoting Gazoduc :> I solved this problem by using (pseudo code) > > def secure > result = yield > if result.kind_of? Array > result.map { |r| r.set_visitor(session[:visitor_id]) } > else > result.set_visitor(session[:visitor_id]) > end > result > end > > With this in you can now do : > > doc = secure { Document.new } > > or > > docs = secure { Document.find(:all) }This is a good idea. There is no ambiguity on which session in given to the models : the one used by the running controller. There is no risk that concurrent requests may badly mix data. However, you did not answer to the problem raised by Kris :> document = Document.new > attachments = document.set_user_id(current_user.id).attachments.set_user_id(current_user.id).find(:all) > > Quickly it starts to look less nice, and that only passing one parameterGazoduc, 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. We definitely have a problem here. - A single instance of a controller class holds the state (the session) it wishes to give to models. - *Thus* this single controller is responsible for giving this state to the models it creates. Maybe this *thus* can be taken *granted*. Damnit, ActiveRecord is responsible for loading many models from a single one, which the controller also wish they hold know its state. (all the objects created from the belongs_to, has_many, etc. associations) So this single controller wishes it could give to *ActiveRecord* its state, hoping ActiveRecord will give to all models objects it will create from now on. But isn''t ActiveRecord shared among several controllers ? Is our goal impossible to reach without modifying the whole ActiveRecord framework ? OMG ! BTW, this whole *state* stuff makes me think this is a binding we want to give to models. -- Posted via http://www.ruby-forum.com/.
Vincent AE Scott wrote:> Have a read through: (or should that be :through ?) > http://www.koziarski.net/archives/2005/07/16/environment > > I think that might be of some use to you.Well, no : he uses a class attribute, which is not :secure when requests are concurrent. -- Posted via http://www.ruby-forum.com/.
On Apr 8, 2006, at 10:17, lagroue wrote:> I won''t enter in discussion on MVC, because I *do* need giving session > to models : I want them to be able to store a > creation/modification/destroy event in the database, holding *who* did > that to them.The updates of relationships are done by AR itself, so looks like you need some global magic that sets the user_id transparently. I mean, Document.save(user) won''t work because the (has_many) Authors children won''t be passed the user by AR. Without more details, it does not sound DRY to manually set the user throughout all the models. I don''t know whether this may be helpful, but as starting point you could have a look at acts_as_paranoid and see how it modifies AR to deal with a special column in a really transparent way. -- fxn
Xavier Noria wrote:> On Apr 8, 2006, at 10:17, lagroue wrote: > >> I won''t enter in discussion on MVC, because I *do* need giving session >> to models : I want them to be able to store a >> creation/modification/destroy event in the database, holding *who* did >> that to them. > > I don''t know whether this may be helpful, but as starting point you > could have a look at acts_as_paranoid and see how it modifies AR to > deal with a special column in a really transparent way.I''ll do. Thanks. Here is the message I was writing before I read your advice : ---- Another solution, the with_session controller method : PURPOSE ====== I''d like to write code like : with_session do # inside this block, *ALL* models know the session. ... end Maybe we''d define the more general with the with_context controller method : with_context (object) do # inside this block, *ALL* models share the same context object. ... end Typical (scaffold) controller code with session context : def update with_session do @model = Model.find(param[:id]) if @model.update_attributes(params[:model]) redirect_to :action => ''show'', :id => @model.id else render :action => ''edit'' end end end And maybe the Model uses this session somewhere, maybe in a after_update callback. Or the views do. Who knowns. @session is available to all models, and that''s the main purpose. My own purpose is only storing who did update the model, so I''d write : def update @model = Model.find(param[:id]) if with_session do @model.update_attributes(params[:model]) end redirect_to :action => ''show'', :id => @model.id else render :action => ''edit'' end end That''s the code I''d like to be able to write. Now, how to have it work ? IMPLEMENTATION ============= Controllers implementation -------------------------- What could this with_context method look like ? It has to be aware of other concurrent controllers running, so it has to look like : module ContextController # to be included in Controller classes # the general with_context method def with_context (context) <some synchronization stuff>(context) do yield end end # the particular with_session def with_session with_context(@session) do yield end end end Say we let ActiveRecord::Base be responsible of the synchronization : module ContextController def with_context(context) ActiveRecord::Base.with_context(context) do yield end end def with_session with_context(@session) do yield end end end Now we have to define ActiveRecord::Base::with_context : ActiveRecord implementation --------------------------- class ActiveRecord::Base # the context available to ALL models cattr_accessor :context # the with_context method class << self def with_context(context) <some mutex>.synchronize do @@context= context yield end end end end So we need a mutex. require ''thread'' class ActiveRecord::Base cattr_accessor :context class << self def context_mutex unless defined? @@context_mutex @@context_mutex = Mutex.new end @@context_mutex end def with_context(context) context_mutex.synchronize do @@context= context yield end end end end WRAP IT ALL UP ============= If I wrap all this up, I have the lib/context_system.rb : require ''thread'' class ActiveRecord::Base cattr_accessor :context class << self def context_mutex unless defined? @@context_mutex @@context_mutex = Mutex.new end @@context_mutex end def with_context(context) context_mutex.synchronize do @@context= context yield end end end end module ContextSystem def with_context(context) ActiveRecord::Base.with_context(context) do yield end end def with_session with_context(@session) do yield end end end Now, in controllers/application.rb : require_dependency "context_system" class ApplicationController < ActionController::Base include ContextSystem end I''m now able to write code like I used at the beginning of this post. And it works as expected. FINAL WORD ========= What do you think ? Of course all this synchronization stuff can''t be seen a perfectly clean way of achieving our goal. But ? Hu ? :o) -- Posted via http://www.ruby-forum.com/.
lagroue wrote:> Vincent AE Scott wrote: >> Have a read through: (or should that be :through ?) >> http://www.koziarski.net/archives/2005/07/16/environment >> >> I think that might be of some use to you. > > Well, no : he uses a class attribute, which is not :secure when requests > are concurrent.Within a given process (which might be WEBrick, or Mongrel, or a FastCGI process, or an SCGI process) Rails only handles one request at a time. So the approach Koz explains on that page is safe. regards Justin
Xavier Noria wrote:> On Apr 8, 2006, at 10:17, lagroue wrote: > >> I won''t enter in discussion on MVC, because I *do* need giving session >> to models : I want them to be able to store a >> creation/modification/destroy event in the database, holding *who* did >> that to them. > > The updates of relationships are done by AR itself, so looks like you > need some global magic that sets the user_id transparently. > > I mean, Document.save(user) won''t work because the (has_many) Authors > children won''t be passed the user by AR. Without more details, it > does not sound DRY to manually set the user throughout all the models.You''re perfectly right, that''s why I chose the with_session global system I described above.> I don''t know whether this may be helpful, but as starting point you > could have a look at acts_as_paranoid and see how it modifies AR to > deal with a special column in a really transparent way.I had great hope. But acts_as_paranoid alter models by giving them a timestamp, and certainly not some data from the controllers. I''m close to be quite sure the whole AR stuff (and especially associations) has to be updated if we want a controller to spread a context in the models it uses. And this would not be the actual solution : imagine that a controllers updates the @session[:user] model. This object has been created *before* the controller was awake. Thus the controller did not give its session to that user. And we''re doomed again. -- Posted via http://www.ruby-forum.com/.
Justin Forder wrote:> Within a given process (which might be WEBrick, or Mongrel, or a FastCGI > process, or an SCGI process) Rails only handles one request at a time. > So the approach Koz explains on that page is safe.Not that I doubt what you say, Justin, but : how do you know that ? I''m starving for reference documents/web pages on this subject. Thanks by advance :o) -- Posted via http://www.ruby-forum.com/.
lagroue wrote:> Justin Forder wrote: >> Within a given process (which might be WEBrick, or Mongrel, or a FastCGI >> process, or an SCGI process) Rails only handles one request at a time. >> So the approach Koz explains on that page is safe. > > Not that I doubt what you say, Justin, but : how do you know that ? I''m > starving for reference documents/web pages on this subject. > > Thanks by advance :o) >Good question! I know this because I''ve been reading the list for a year, taking a particular interest in discussions of Rails architecture. In the "Selecting a Production Platform" section of "Agile Web Development with Rails" there''s the following text: <quote> WEBrick takes the servlet approach. It has a single long-running process that handles each concurrent request in a thread. As we?ve discussed, WEBrick is a great way of getting up and running quickly but not a particularly attractive approach for heavy-duty use. One of the reasons is the lack of thread-safety in Action Pack, which forces WEBrick to place a mutex at the gate of dynamic requests and let only one request 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. </quote> 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. regards Justin
Justin Forder wrote:> lagroue wrote: >> Not that I doubt what you say, Justin, but : how do you know that ? I''m >> starving for reference documents/web pages on this subject. >> > Good question! I know this because I''ve been reading the list for a > year, taking a particular interest in discussions of Rails architecture.:''-/ I''ve been in the party for a week only... :o)> In the "Selecting a Production Platform" section of "Agile Web > Development with Rails" there''s the following text: > > <quote> > [...] > </quote> > > 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.Thank you *very very* much, Justin. Regarding the subject on the thread, I''ll stuck on http://www.koziarski.net/archives/2005/07/16/environment approach, so, and stop fearing concurrency bugs. Really, thanks. I''m discovering the Ruby/Rails programs/people/web sites, and still have to dig my way into all this new stuff ! -- Posted via http://www.ruby-forum.com/.
lagroue wrote:> 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". 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. If Webrick and Co are only serving one request per thread, then the easiest piece of code for all this is: $session = session Isn''t it ? -- Posted via http://www.ruby-forum.com/.
Gazoduc wrote:> lagroue wrote: >> 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 > > Isn''t it ?Well, yes. This is just like PHP coding, though. -- Posted via http://www.ruby-forum.com/.
Justin Forder wrote:> In the "Selecting a Production Platform" section of "Agile Web > Development with Rails" there''s the following text: > > <quote> > WEBrick takes the servlet approach. It has a single long-running process > that handles each concurrent request in a thread. As we?ve discussed, > WEBrick is a great way of getting up and running quickly but not a > particularly attractive approach for heavy-duty use. One of the reasons > is the lack of thread-safety in Action Pack, which forces WEBrick to > place a mutex at the gate of dynamic requests and let only one request > 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. > </quote> > > 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! I could be totally wrong though... b
Ben Munat wrote:> Justin Forder wrote: >> [...] >> 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! > > I could be totally wrong though...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. regards Justin
Gazoduc
2006-Apr-08 22:17 UTC
[Rails] Let''t keep globals out of our rails (upsetting MCV? )
Justin Forder wrote:> 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 > > JustinSo 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. Let''s keep globals out of our rails. -- Posted via http://www.ruby-forum.com/.
lagroue
2006-Apr-10 09:53 UTC
[Rails] Re: Let''t keep globals out of our rails (upsetting MCV? )
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 -- Posted via http://www.ruby-forum.com/.
Tobias Lütke
2006-Apr-10 13:13 UTC
[Rails] Re: Let''t keep globals out of our rails (upsetting MCV? )
These are just global variable with syntactic sugar. On 4/10/06, lagroue <lagroue@free.fr> wrote:> 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 > > -- > Posted via http://www.ruby-forum.com/. > _______________________________________________ > Rails mailing list > Rails@lists.rubyonrails.org > http://lists.rubyonrails.org/mailman/listinfo/rails >-- Tobi http://shopify.com - modern e-commerce software http://typo.leetsoft.com - Open source weblog engine http://blog.leetsoft.com - Technical weblog
lagroue
2006-Apr-10 14:43 UTC
[Rails] Re: Re: Let''t keep globals out of our rails (upsetting MCV?
Tobias L?tke wrote:> These are just global variable with syntactic sugar.Yeah. Except code build around my "syntactic sugar" will raise if it has been done in the lazy way, AND that Rails becomes concurrent. Of course I''d love to have more experience, and dig into ActiveRecord API to implement such feature *without* globals. So, sweety, it''s up to you now. -- Posted via http://www.ruby-forum.com/.