Forum: Ruby on Rails Models accessing the session...

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Kris (Guest)
on 2006-04-04 12:05
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?
Xavier N. (Guest)
on 2006-04-04 12:23
(Received via mailing list)
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
Alex Y. (Guest)
on 2006-04-04 13:25
(Received via mailing list)
Xavier N. 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...
kris (Guest)
on 2006-04-04 13:33
Xavier N. 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?

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?
Gregory S. (Guest)
on 2006-04-04 16:55
(Received via mailing list)
On Tue, Apr 04, 2006 at 11:33:43AM +0200, kris wrote:
} Xavier N. 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
Vincent AE Scott (Guest)
on 2006-04-04 17:14
(Received via mailing list)
#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 /* removed_email_address@domain.invalid */

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
kris (Guest)
on 2006-04-04 17:39
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 S. wrote:
> On Tue, Apr 04, 2006 at 11:33:43AM +0200, kris wrote:
> } Xavier N. 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
Gazoduc (Guest)
on 2006-04-04 18:02
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.
Gregory S. (Guest)
on 2006-04-04 19:42
(Received via mailing list)
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
Tom M. (Guest)
on 2006-04-05 09:36
(Received via mailing list)
>> 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 M.
lagroue (Guest)
on 2006-04-08 12:17
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 parameter

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.

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.
lagroue (Guest)
on 2006-04-08 12:43
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.
Xavier N. (Guest)
on 2006-04-08 13:06
(Received via mailing list)
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
lagroue (Guest)
on 2006-04-08 14:09
Xavier N. 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)
lagroue (Guest)
on 2006-04-08 14:19
Xavier N. 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.
Justin F. (Guest)
on 2006-04-08 14:19
(Received via mailing list)
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
lagroue (Guest)
on 2006-04-08 14:28
Justin F. 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)
Justin F. (Guest)
on 2006-04-08 15:11
(Received via mailing list)
lagroue wrote:
> Justin F. 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
lagroue (Guest)
on 2006-04-08 15:49
Justin F. 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 !
Gazoduc (Guest)
on 2006-04-08 15:57
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 ?
lagroue (Guest)
on 2006-04-08 16:22
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.
Ben M. (Guest)
on 2006-04-08 22:35
(Received via mailing list)
Justin F. wrote:
> 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
Justin F. (Guest)
on 2006-04-09 01:54
(Received via mailing list)
Ben M. wrote:

>
> 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 (Guest)
on 2006-04-09 02:17
Justin F. 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
>
>    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.

Let's keep globals out of our rails.
lagroue (Guest)
on 2006-04-10 13:52
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
Tobias Lütke (Guest)
on 2006-04-10 17:16
(Received via mailing list)
These are just global variable with syntactic sugar.

On 4/10/06, lagroue <removed_email_address@domain.invalid> wrote:
> Hash)
> # - a "local", thread-safe way
> #
> #
> # end
> # Those methods are to be used as filters :
> # require_dependency "context_system"
> #   include ContextSystem
>   require 'thread'
>       def with_context(context)
>     def context
>       ActiveRecord::Base.with_context(context) do
>     end
>     def context
>     def store_context(context)
>       ActiveRecord::Base.context = nil
>     context[:session]
>     with_context({:session => @session}) do yield end
> Rails mailing list
> removed_email_address@domain.invalid
> 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 (Guest)
on 2006-04-10 18:43
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.
This topic is locked and can not be replied to.