LoginSystem : make @session available to models


#1

Hello.

LoginSystem is cool, it populates the @session instance variable of
controllers with many useful info.

The fact is : it would be cool to let models know about the session,
too.
For instance, after_update and after_create callbacks could store who
did what on *what.

Aim : having ActiveRecord::Base::session defined, returning the @session
of the controller which manipulates the model.

Here’s a way to do that. I was wondering whether it was good ruby and
good rails because I’m a real newbie. I don’t know either if the class
member I added to ActiveRecord::Base doesn’t break when several users
use the application, leading to multiple sessions.

First, let controllers give @session to models :

(In controllers/application.rb)
class ApplicationController < ActionController::Base
include LoginSystem
before_filter :export_session_to_models
def export_session_to_models
ActiveRecord::Base.set_session { @session }
end
end

Now, let models know the set_session method, and let’s define
ActiveRecord::Base::session:

(In controllers/application.rb)

This unless is required for proper behavior in FastCGI context

unless defined? ActiveRecord::Base::session
class ActiveRecord::Base
@@session = nil
class << self
def set_session(&action)
@@session = action
end
def session
@@session.call()
end
end
def session
self.class.session
end
end
end

Is this code OK ?


Now that this framework is set, I can store who did what on what on any
database update :

(In controllers/application.rb)
class ActiveRecord::Base
after_update :update_history
after_create :create_history
before_destroy :destroy_history

def update_history
history (‘update’)
end
def create_history
history (‘create’)
end
def destroy_history
history (‘destroy’)
end
def history(action)
unless kind_of?(History)
history = History.new(:action => action)
history.ref = self
history.save
end
end
end

With, roughly, a History model like :
:user_id # who performed the action
:action # the action
:date # when
:ref_table # the table of the altered model
:ref_id # the id of the altered model

(In models/history.rb)
class History < ActiveRecord::Base
def ref=(object)
write_attribute(‘ref_table’, object.class.table_name)
write_attribute(‘ref_id’, object.id)
end

before_create :fix_history
before_update :fix_history

def fix_history
write_attribute(‘user_id’, session[:user].id) if user_id.nil?
write_attribute(‘date’, Time.now) if date.nil?
end
end


#2

lagroue wrote:

Hello.

LoginSystem is cool, it populates the @session instance variable of
controllers with many useful info.

The fact is : it would be cool to let models know about the session,
too.
For instance, after_update and after_create callbacks could store who
did what on *what.

Aim : having ActiveRecord::Base::session defined, returning the @session
of the controller which manipulates the model.

OK. Thanks to http://www.koziarski.net/archives/2005/07/16/environment
I understand things better.

LoginSystem has nothing to do with that.

class ActiveRecord::Base
cattr_accessor :session
end

class ApplicationController < ActionController::Base
before_filter :export_session_to_models
def export_session_to_models
ActiveRecord::Base.session= @session
end
end

Is much sufficient.

My history framework should read :

(in controllers/application.rb)

class ActiveRecord::Base
cattr_accessor :session
end

this is the actual unless required by FastCGI

unless defined? ActiveRecord::Base::history
class ActiveRecord::Base
after_update :update_history
after_create :create_history
before_destroy :destroy_history
def update_history
history (‘update’)
end
def create_history
history (‘create’)
end
def destroy_history
history (‘destroy’)
end
def history(action)
unless kind_of?(History)
history = History.new(:action => action)
history.ref = self
# without the fastcgi unless, the number of historics saved
grows !!
history.save
end
end
end
end


#3

On Thu, Apr 06, 2006 at 12:51:01PM +0200, lagroue wrote:
[…]
} The fact is : it would be cool to let models know about the session,
} too.
[…]

This should be a FAQ. It gets asked all the time. As such, I have added
it
to the FAQ at http://wiki.rubyonrails.com/rails/pages/FAQ#mvc

–Greg


#4

Good idea Lagrou, I’ve just tried your solution :

class ActiveRecord::Base
cattr_accessor :account
after_update :update_log

def update_log
puts(“store to log here”)
end
end

but there’s something strange, update_log method is called SIX times
even if there’s only ONE @article.save call in article_controller

And the more I cycle throught list->post (then save)->list the more
update_log is called (14 times on second cycle)

I don’t understand why

Did you had this problem too ?


#5

Nuno wrote:

Good idea Lagrou, I’ve just tried your solution :

class ActiveRecord::Base
cattr_accessor :account
after_update :update_log

def update_log
puts(“store to log here”)
end
end

but there’s something strange, update_log method is called SIX times
even if there’s only ONE @article.save call in article_controller

And the more I cycle throught list->post (then save)->list the more
update_log is called (14 times on second cycle)

I don’t understand why

Did you had this problem too ?

Yeah, I do have it.

I thought, by mistake, that this unless defined?
ActiveRecord::Base::history (unless defined?
ActiveRecord::Base::update_log) would fix that.

More investigation lead me to that fact :

When ActiveRecord::Base is altered in controllers/application.rb :

  • callbacks are triggered too many times on FastCGI mode (too many
    times, precisely, an increasing number of times at each request. The
    tracebacks are the same for each spurious callback trigger)
  • callbacks are NOT triggered on CGI mode.

So the main problem may be controllers/application.rb.

I’m investigating oher ways to inject code.

Thanks for your answer !


#6

OK.

So far, I came up with this solution for my session/history feature :

I defined lib/history_system.rb :

class ActiveRecord::Base
cattr_accessor :session
end

module HistoryModel

def update_history
	history ('update')
end

def create_history
	history ('create')
end

def destroy_history
	history ('destroy')
end

def history(action)
	unless kind_of?(History)
		history = History.new(:subtype => action)
		history.ref = self
		history.save
	end
end

end

module HistorySystem

protected

def prepare_history
	ActiveRecord::Base.session= @session
end

end

In controllers/applications.rb :

require_dependency “login_system”
require_dependency “history_system”

class ApplicationController < ActionController::Base
include LoginSystem
model :user

include HistorySystem
before_filter :prepare_history

end

In every models/model.rb I want its model to have history features :
(I gave up the idea of giving it to every ActiveRecord::Base subclass)

require_dependency “history_system”

class < ActiveRecord::Base

include HistoryModel
after_update :update_history
after_create :create_history
before_destroy :destroy_history

end

The History model has already been described in a post before.

So. That works, in CGI and FastCGI modes, there is no longer too many
calls, BUT I could not inject that code in any ActiveRecord::Base
subclass.

Consider that as further exercice !

Thanks again.


#7

Gazoduc wrote:

lagroue <lagroue@…> writes:

module HistorySystem

protected

def prepare_history
ActiveRecord::Base.session= session
end
end

Isn’t this going to break if you have two concurrent requests on the
same
process (that is sharing the same class variables) ?

I posted a solution to this issue here :
http://www.ruby-forum.com/topic/60732#58279

It’s the stuff with secure { }

That’s exactly the kind of answer I was waiting.

I admit I have difficulties finding clear documents explaining how
(when) Rails creates instances. On CGI mode, a new ruby process is
launched, so I guess there is no persistency whatsoever except the
@session which may be marshalled somewhere.

But in FastCGI mode, I’m still blinded by a total fog.

I infer theories on Rails from the odd behaviors it shows when I try
some edgies solutions, but I still miss a reference document. sigh

Thanks for your solution, I’m gonna read it right now.


#8

lagroue <lagroue@…> writes:

module HistorySystem

protected

def prepare_history
ActiveRecord::Base.session= session
end
end

Isn’t this going to break if you have two concurrent requests on the
same
process (that is sharing the same class variables) ?

I posted a solution to this issue here :
http://www.ruby-forum.com/topic/60732#58279

It’s the stuff with secure { }