Models accessing the session


#1

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?


#2

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


#3

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…


#4

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 :slight_smile: 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?


#5

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 :slight_smile: 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


#6

#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


#7

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.


#8

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


#9

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 :slight_smile: 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


#10

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.


#11

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.


#12

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


#13

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.


#14

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)
(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)
.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)


#15

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


#16

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)


#17

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:

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.

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


#18

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:

[...]

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 !


#19

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 ?


#20

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.