Automating user-specific stamps on records

So, given the opinion of Rails-core regarding the it's-a-feature-not-a-bug lack of access of the session to models, what has been the "rails way" of dealing with these cases:

-- stamping records with created_by and updated_by fields with user name or id

-- writing breadcrumb logs of user actions where user-specific details must be written per record

-- I have a third case, less common, that essentially puts records on "hold" -- in an attempt to cleanup after users who abandon these holds prior to completing the work for which the hold is originally secured, I maintain a list of what records are on hold and call a cleanup function when the user visits a page that clearly is an act of abandonment (otherwise the time experation eventually results in a release). I have always used the session to store the list (which requires that models be able to store to sessions).Pushing storage off to a database doesn't solve it -- the user & data still has to be correlated, and you're right back into a scenario just like the above two.

-- I have a pessimistic locking system that has worked very well for me that's very similar in behavior to the "hold" system

All I can see are fairly lame workarounds:

-- subclass ActiveRecord so I can bury the manual passing of the session into the model a layer away from the app code

-- extend ActiveRecord to modify save and update methods (basically same approach as above)

-- pass session or user details into abstracted save/update methods that have to be written/included for every model

All these are just monkey patches IMO that fake the appearance that "the framework" is handling those details like it does for created_at and updated_at.

Sure, allowing models to read/write to a session can be abused, but so can erb in a view, or every other aspect of programming. So what. IMO, a session is a system wide bucket for dealing with state. Why there's such a rabid opinion that it is solely the domain of the controller seems rather odd. Especially when it's exactly this kind of widely used pattern (connected record transactions to users) that I would expect a framework to embrace in enabling in the name of system-level integration for the greater good which trumps MVC extremism.

created_at is handled, so why not created_by? Because of implentation details? Well, if we had this thing that stored arbitrary data about the current user that each model in a given system could deal with on its own, we could... oh, wait. We do. Sessions.

However, I recognize that debate has long lost its luster in these halls, so, enlighten a poor, ignorant soul as to god's way of handling these scenarios.

Am I really supposed to pass these details along with every model modification call, or abstract my own layer to fake appearances?

I've never considered a way other than allowing a model to access a session as a system-wide resource, so seriously... what option am I not seeing (or just not accepting as the "correct" way in an MVC extremist world).

-- gw

Greg Willits wrote:

So, given the opinion of Rails-core regarding the it's-a-feature-not-
a-bug lack of access of the session to models, what has been the
"rails way" of dealing with these cases:

-- stamping records with created_by and updated_by fields with user
name or id

Hi Greg,

Have you looked into UserStamp or acts_as_audited? I was doing a little
reading on this the other day and came across these two. If you google
the two terms "rails" and "created_by", you'll get some decent links.

For my current project, I need only to automatically stamp created_by
and updated_by, so I chose to use the simplest approach. I inherited
from AR::Base and created default before_create and before_update
methods. In my get_user method in the base controller, I included

Thread.current[:person_id] = session[:person_id]

so I can use that in the AR callbacks. Using Thread is not actually
necessary, according to the articles, but it's the shortest line between
points A and B, and that's what I'm concerned about right now.

Hope that helps at least a little.

Peace,
Phillip

Greg Willits wrote:

So, given the opinion of Rails-core regarding the it's-a-feature-not-
a-bug lack of access of the session to models, what has been the
"rails way" of dealing with these cases:

-- stamping records with created_by and updated_by fields with user
name or id

Have you looked into UserStamp or acts_as_audited?

Looked. Yeesh, far too much gymnastics for something that should be very simple.

For my current project, I need only to automatically stamp created_by
and updated_by, so I chose to use the simplest approach. I inherited
from AR::Base and created default before_create and before_update
methods. In my get_user method in the base controller, I included

Thread.current[:person_id] = session[:person_id]

so I can use that in the AR callbacks. Using Thread is not actually
necessary, according to the articles, but it's the shortest line between
points A and B, and that's what I'm concerned about right now.

That's what I have adopted for now as well. The clue to look at Thread helped. Thanks.

I think I can weasel Thread.current to solve my "hold" examples too by simply passing the pertinent session data into the thread.

It's a wonky hack IMO. Session should just be an app-wide resource, but at least this isn't a whole plugin worth of bloat.

Thanks, Phillip.

-- gw

If all you want really _is_ who last touched the record, then yes.

But think into the future. You may find that this isn't enough. Once
management have the answer to that question, then next thing they ask
is typically "What did she change?" If they could have the answer to
that, the next thing they'd come up with is "No, she didn't clear the
account balance, go back one more change in time please."

So be careful about calling auditing "something that should be very
simple". Yes, "last changed" stamps are very simple. But they're
often not enough. They're just the answer to the question management
can't have answered today. Give them the answer, and the question will
change. :slight_smile:

Ciao,
Sheldon.

Very simple = the part of collecting data on who. It should be as simple as getting info out of the session. Not allowed. Thread is next "simplest" thing AFAICT.

As for auditing, yep, been there, done that. Now that I have who, I can build my own audit trail.

I'm transcribing processes I've written in another platform. So, usually I'm just trying to grope my way around the Ruby / Rails syntax and idioms.

-- gw

Ah, I misunderstood what it was that you're balking at.

Yeah, in classic MVC, models shouldn't really know _anything_ about the
controller. Rails is pretty fanatical about the layering, so there's
no special affordance for having your models know what's going on in
your controllers.

When you start to consider how you'll deal with the "who" of it all
outside of HTTP request context (for example, in runner scripts), it
becomes clear why the separation of concerns is important.

And at that point, you'll be pretty glad that you bothered with
something like the threadlocal identity.

For example, in a document management portal I developed, a number of
automated jobs are expected to upload documents from cron jobs. The
customer didn't want this going through web services. Tying down
the "who" of the uploads was as simple as forging "script" as the
username for the current process. Something more complex was possible,
to be sure, but that was sufficient for them.

Ciao,
Sheldon.

(not an attempt to belabor or underscore a point, but an attempt to reason out my position)

My core observation is that a session shouldn't even be chained to a controller in the first place.

It's not controller-state, it is application-state that I am interested in preserving. Unfortunately, in web apps we only have one way to do that--the session. I think the Rails interpretation of the session is flawed, but it takes some expaining as to how I arrive at that opinion.

The purpose of a framework is to provide generic services that (nearly) everyone would otherwise just end up writing for themselves (efficiency, reliability, bla bla bla) -- _and_, IMO, to isolate those cases, in a well prescribed way, where breaking the normal rules for boundaries or layers provides greater benefit than adhering to the rules.

The normal channel for communication between a controller and model is of course the sending of commands from controller to model (separation, tell don't ask, etc). There are cases where you find yourself sending some data in every message. That's the type of thing you look to push down into the framework to handle automatically to relieve the app dev the tedium (efficiency) and subjectivity to error (reliability). This is where allowing models to ask controllers might be allowed as an underlayer service of the framework -- it breaks the rules, but has an overall advantageous benefit all around. So of course you look to do that in a well-described way.

If we take the example of stamping who created/last-modified a model, it is not a specialized task between two specialized components. It's a rather generic one. Still, some models may support it, some models may not. Devs are going to have to constantly reference docs to see which models do vs don't. Requiring that a user or data object be passed to models becomes a rather tedious and inefficient requirement. "Can't the system just do it?"

IMO, yes, the system should just do it. We have a task that is such a pervasive pattern across the whole of the application that it should become a service available to any part of the application to relieve the developer from the tedium. So, in a stateful environment like a desktop application, I would abstract current user data as standardized component that we would allow models to voluntarily consult as needed. If the model needs to support user stamping it can request/retrieve the info on its own.

I would expect the possibility to create a number of similar objects a in a complex application where certain components raise above the norm of an application level object into being part of a framework or service layer. In a stateful desktop or client-server application, we have a few patterns we can pick to implement this object, and we have the luxury of being able to create many independent objects to retain isolated elements of the application state where we blur the lines of normal separation. In a desktop app, if I need to retain state of some controllers, and some models, and some views, I can create separate buckets for each of these to keep them clean.

The problem we face with web apps, of course, is sustaining the state of that user object. We have one tool--the session. Be it implemented as cookie or database record, we have one bucket to put things in to sustain a state. Arguably with cookies, we could have multiple buckets, but with the classic web session (and in particular a Rails session, we have one). The purposes and uses for that tool are many, and it should therefore have some flexibility.

In our immediate use case of user stamping, the limitation of the Rails session means that if we're to relieve the developer of the tedium described above, everyone has to come up with a work around.

That work-around turns out to be "store the user in the session, then store the user again in a Thread." Hmm.

DRY?
-- storing the same data in two places
-- writing code twice that copies the same data to storage bucket
-- many developers all writing the same work-around code

Nope.

Why?
Rails interprets a session to be the exclusive territory of the controller.

Even though the reasons vary, there are several use cases for allowing model access to the web app session that when combined, I believe we find justification that a session should not be limited by Rails' current interpretation.

No, a session should not be a dumping ground for every piece of data that needs to be moved about (for all the usual design and practical reasons typically cited), but there are some fairly clear uses cases IMO where the session proves to be an efficient and effective place to store data, or the required pointer to data (in table data, memcache, cookies, files, etc), that is stateful -- and the purpose of that state is not always limited to passing data from controller to controller.

By making a session an object independent of the Rails application controller and therefore reachable by any "object" in the framework hierarchy, we improve DRYness, and we open possibilities for web apps to solve API problems more like we can in desktop apps.

-- gw

Well there's always Pratik's (somewhat tongue in cheek) solution:
http://m.onkey.org/2007/10/17/how-to-access-session-cookies-params-request-in-model
It's not a very nice workaround, nor are any of the other ones, which
reflects the general position that you shouldn't be trying to do this.
More generally, Rails' position is that the very concept of
application state is something that is not the concern of the model.

Fred

I'm transcribing processes I've written in another platform. So,
usually I'm just trying to grope my way around the Ruby / Rails
syntax and idioms.

Ah, I misunderstood what it was that you're balking at.

Yeah, in classic MVC, models shouldn't really know _anything_ about
the
controller. Rails is pretty fanatical about the layering, so there's
no special affordance for having your models know what's going on in
your controllers.

(not an attempt to belabor or underscore a point, but an attempt to
reason out my position)

My core observation is that a session shouldn't even be chained to a
controller in the first place.

It's not controller-state, it is application-state that I am
interested in preserving. Unfortunately, in web apps we only have one
way to do that--the session. I think the Rails interpretation of the
session is flawed, but it takes some expaining as to how I arrive at
that opinion.

Well there's always Pratik's (somewhat tongue in cheek) solution:
http://m.onkey.org/2007/10/17/how-to-access-session-cookies-params-request-in-model

Indeed. The source of my original post's reference to "rabid opinion." (and I might say that post goes too far in enabling the wrong things for the wrong reasons--which I understand is his point, and agree with 97.42%)

It's not a very nice workaround, nor are any of the other ones, which
reflects the general position that you shouldn't be trying to do this.

It's either this or a lot of duplicated code, or work-around code that just let's it happen anyway.

More generally, Rails' position is that the very concept of
application state is something that is not the concern of the model.

Of which I say there are valid exceptions to the rule, (wrong things for the right reasons), and thus we arrive at the start of our circle :slight_smile:

-- gw