Accessing a session globally

43 views
Skip to first unread message

Phrogz

unread,
Apr 27, 2010, 10:46:08 PM4/27/10
to sinatrarb
My web app under development uses Sinatra and Sequel, among other
things. I use sessions to record the authenticated user who is
interacting with the application. My database models periodically
record log entries when values are changed. (Examples of this are at
the end of the message.)

My problem is that these logs need to record the user who made the
changes, but the session method is not available because I'm inside a
Sequel::Model instance, not Sinatra. See all the "active_user_id" in
the code samples below. In some cases it would be inconvenient to have
the route pass along the user id from the session (cases where I'm
currently using foo= methods to set a value). In other cases these
logs are written in a callback where I can't even pass values along if
I wanted to (see the after_create and before_save callbacks).

In Ramaze (where this application was first developed), I could use
Ramaze::Current.session inside the model to find the active session.
How can I get access to the session "globally" from outside my route
in Sinatra?

class Bug < Sequel::Model
def tag_names=( desired_tags )
# add/remove tags from a join table as necessary
BugLogEntry << {
:created_on => Time.now,
:bug_id => id,
:user_id => active_user_id,
:field_id => BugLogField::TAG_NAMES,
:new_value => tag_changes
}
end
def after_create
BUG_FIELDS.each do |field|
BugLogEntry << {
:created_on => Time.now,
:bug_id => id,
:user_id => active_user_id,
:field_id => BugLogField.const_get(field.upcase),
:new_value => self.send(field).to_s
}
end
end
def before_save
if old_bug = Bug[id]
BUG_FIELDS.each do |field|
new_value = self.send( field )
old_value = old_bug.send( field )
if new_value != old_value
BugLogEntry << {
:created_on => Time.now,
:bug_id => id,
:user_id => active_user_id,
:field_id => BugLogField.const_get(field.upcase),
:new_value => new_value,
:old_value => old_value
}
end
end
end
end
end

--
You received this message because you are subscribed to the Google Groups "sinatrarb" group.
To post to this group, send email to sina...@googlegroups.com.
To unsubscribe from this group, send email to sinatrarb+...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/sinatrarb?hl=en.

Konstantin Haase

unread,
Apr 28, 2010, 2:10:29 AM4/28/10
to sina...@googlegroups.com
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1


On Apr 28, 2010, at 04:46 , Phrogz wrote:
> In Ramaze (where this application was first developed), I could use
> Ramaze::Current.session inside the model to find the active session.
> How can I get access to the session "globally" from outside my route
> in Sinatra?
If that was possible, Sinatra would not be able to run in multithreaded environments (what happens if two sessions are active at the same time?)

Why don't you pass the session (or better: just the values you need) from your route to your model?

Also, you could implement the Ramaze behavior:

module Sinatra
module GlobalSession
attr_accessor :current_session
def self.registered(base)
base.before { base.current_session = session }
base.enable :lock, :session
end
end
register GlobalSession
end

Now you can access it like this (in a classic style app):

Sinatra::Application.current_session

Note that accessing your session from your models makes them unusable outside of a request context.

Konstantin
-----BEGIN PGP SIGNATURE-----
Version: GnuPG/MacGPG2 v2.0.14 (Darwin)

iQEcBAEBAgAGBQJL19FYAAoJEM+qkfuqK1IX0RIH/i/yx/4LJDzSW3AyIKw1aq/K
8HRV3U2TKQmgvkRPLeOpImm7EAuymNug/YjlJFY7yXtHsU8Nz2wqrnoox50teW6j
VkF8WJBPON2e+qeF8bj70gBrqbJrQl4Qw9pB8j/QNBM41WRaOX3KnyW+tSCyf6N9
JVrB+8bJ/ydD8XGm6kCZc9yJWSNFOD/SwB+dlc/qHayTxQLsL5MdvfUIvE/k72Vd
pdJUcMAVyAeVMm+SAvaDwNYaoCsF9KyhDEA7zsXXE3fSr04yCcYsfEGcK04OqV5J
qa+w9Pz/KCO86PedYCn2P2RezIITww88BmbIpEbVFLij4iblIj9rSfIfzPhhogA=
=dVTp
-----END PGP SIGNATURE-----

Phrogz

unread,
Apr 28, 2010, 2:39:23 AM4/28/10
to sinatrarb
On Apr 28, 12:10 am, Konstantin Haase <k.ha...@finn.de> wrote:
> On Apr 28, 2010, at 04:46 , Phrogz wrote:
> > How can I get access to the session "globally" from outside my route
> > in Sinatra?
>
> If that was possible, Sinatra would not be able to run in multithreaded environments (what happens if two sessions are active at the same time?)

I'm not sure. Does Thin, for example, run multiple threads off of the
same instances? I note that each request from Thin has a new
Sinatra::Application instance. I really don't understand well the
implementation details of Thin/Sinatra/Rack in terms of what is
instantiated in the same thread/process and what is (or can be)
created in a new Thread or new process.


> Why don't you pass the session (or better: just the values you need) from your route to your model?

In part because this is gross (but of course not the only way):
def tag_names=( desired_tags, user_id ) ... end
...
model.send(:tag_names=,"foo bar",session[:user_id])

But moreover because I can't send the value I need to the #before_save
and #after_create callbacks in Sequel. Or at least I don't think I
can. Perhaps I could do something like the following?
class Bug < Sequel::Model
attr_accessor :last_touched_by
def before_save
...
BugLogEntry << { ... user_id: @last_touched_by }
end
end
...
my_bug.last_touched_by = session[:user_id]
my_bug.tag_names = "foo bar"
my_bug.save

Is that thread-safe? I don't know enough to be sure.


> module Sinatra
> module GlobalSession
> attr_accessor :current_session
> def self.registered(base)
> base.before { base.current_session = session }
> base.enable :lock, :session
> end
> end
> register GlobalSession
> end

The above looks promising, based on the presence of the word "lock".
Is this safe in a multithreaded environment?


> Note that accessing your session from your models makes them unusable outside of a request context.

A good note, but not relevant to my particular case.

Thank you for your help!

Konstantin Haase

unread,
Apr 28, 2010, 3:05:48 AM4/28/10
to sina...@googlegroups.com
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1


On Apr 28, 2010, at 08:39 , Phrogz wrote:

> On Apr 28, 12:10 am, Konstantin Haase <k.ha...@finn.de> wrote:
>> On Apr 28, 2010, at 04:46 , Phrogz wrote:
>>> How can I get access to the session "globally" from outside my route
>>> in Sinatra?
>>
>> If that was possible, Sinatra would not be able to run in multithreaded environments (what happens if two sessions are active at the same time?)
>
> I'm not sure. Does Thin, for example, run multiple threads off of the
> same instances? I note that each request from Thin has a new
> Sinatra::Application instance. I really don't understand well the
> implementation details of Thin/Sinatra/Rack in terms of what is
> instantiated in the same thread/process and what is (or can be)
> created in a new Thread or new process.

No, Thin does not on its own start threads. However, two application instances (requests) can still run somewhat in parallel due to Thin's evented approach. That won't happen, though, unless you or some middleware triggers that (very unlikely).

>> Why don't you pass the session (or better: just the values you need) from your route to your model?
>
> In part because this is gross (but of course not the only way):
> def tag_names=( desired_tags, user_id ) ... end
> ...
> model.send(:tag_names=,"foo bar",session[:user_id])
>
> But moreover because I can't send the value I need to the #before_save
> and #after_create callbacks in Sequel. Or at least I don't think I
> can. Perhaps I could do something like the following?
> class Bug < Sequel::Model
> attr_accessor :last_touched_by
> def before_save
> ...
> BugLogEntry << { ... user_id: @last_touched_by }
> end
> end
> ...
> my_bug.last_touched_by = session[:user_id]
> my_bug.tag_names = "foo bar"
> my_bug.save
>
> Is that thread-safe? I don't know enough to be sure.

As you probably create another instance of Bug for every request, you don't have to bother with thread safety there.
The attr_accessor approach is what I meant by passing to the model.

>> module Sinatra
>> module GlobalSession
>> attr_accessor :current_session
>> def self.registered(base)
>> base.before { base.current_session = session }
>> base.enable :lock, :session
>> end
>> end
>> register GlobalSession
>> end
>
> The above looks promising, based on the presence of the word "lock".
> Is this safe in a multithreaded environment?

Yes. But it will essentially disable multithreading (other requests in the same ruby process will wait for the current request to finish).

>
>> Note that accessing your session from your models makes them unusable outside of a request context.
>
> A good note, but not relevant to my particular case.
>
> Thank you for your help!

-----BEGIN PGP SIGNATURE-----
Version: GnuPG/MacGPG2 v2.0.14 (Darwin)

iQEcBAEBAgAGBQJL195PAAoJEM+qkfuqK1IXPx8H/AmZtv3Twi+ww5+jPjWHy2FJ
EkuuYmHjHboIrvjUPENScv9DE4y3zD/RVuR4P+aybhOl6+VXXc/Z/KRCPNG1CZhI
a+okXEl5h7Asgu9YCJOxaHs3jv3c/oWXw7JYQySZDxCk9mCYerKvQV5kZBgXttUT
4Va4hKmm8n5/wSIrwAjgRa/YAXWPtdoVFcgDxa4j12Kc5c3fg9OeuxlJH9Ev6OBt
7nhV23H5DbS7XiMAso+HwzUTP2qmSoQvtexr607twUu7/RcoCmr9rod5lJE8meYo
WP2zqspR/5xOCzUAHJa3LOniV5Itzk9GdTwicVPwzZ87VMsKZ5r/QMaHt3j3+0c=
=2rRE
-----END PGP SIGNATURE-----

Damian Janowski

unread,
Apr 28, 2010, 9:38:10 AM4/28/10
to sina...@googlegroups.com
On Tue, Apr 27, 2010 at 11:46 PM, Phrogz <phr...@mac.com> wrote:
> My problem is that these logs need to record the user who made the
> changes, but the session method is not available because I'm inside a
> Sequel::Model instance, not Sinatra. See all the "active_user_id" in
> the code samples below. In some cases it would be inconvenient to have
> the route pass along the user id from the session (cases where I'm
> currently using foo= methods to set a value). In other cases these
> logs are written in a callback where I can't even pass values along if
> I wanted to (see the after_create and before_save callbacks).

I don't think your model should know about your web session.

However, I did use this pattern some time:

class User
def self.current
Thread.current["User.current"]
end

def self.current=(user)
Thread.current["User.current"] = user
end
end

Then, in your route, you do:

User.current = User[session[:user_id]]

Or similar.

And you can access the user currently using your application from
other parts of your code. In a thread-safe way.

Jason Rogers

unread,
Apr 28, 2010, 10:40:26 AM4/28/10
to sina...@googlegroups.com
I use this pattern as well, but I put it in a before filter in Sinatra.

before do
Thread.current[:user] = session[:user]
end
--
Jason Rogers

"I am crucified with Christ: nevertheless I live;
yet not I, but Christ liveth in me: and the life
which I now live in the flesh I live by the faith of
the Son of God, who loved me, and gave
himself for me."
Galatians 2:20

Konstantin Haase

unread,
Apr 28, 2010, 10:45:59 AM4/28/10
to sina...@googlegroups.com
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1


On Apr 28, 2010, at 16:40 , Jason Rogers wrote:

> I use this pattern as well, but I put it in a before filter in Sinatra.
>
> before do
> Thread.current[:user] = session[:user]
> end

That still does not work in an environment using an event loop.

Konstantin
-----BEGIN PGP SIGNATURE-----
Version: GnuPG/MacGPG2 v2.0.14 (Darwin)

iQEcBAEBAgAGBQJL2EopAAoJEM+qkfuqK1IXmasH+wVaD1u2IaVvzvBg6BrUK5cB
S0COZtNypuRg53Wzwec/aBdHKRXwctygWsD8AvsIWnr2nGGgEuOveYXqU3QLRtV5
qB8Pt+5DdU3IUgMvv1i5pHNPpthI+YqZqWhprzCZiAjfz70v7Alk0Au6IiP2ATQB
F4saqI2uVoIAIHMSF2L5Np+tpqFcPwT9BydbpHkOCrHoYHdsoknMVPPd18dVGplc
3q2ibhhaeOUOcXJK5Fqsy1baBwDMGydku/VChGrQ7PQgw91VOrrp53fYCzJe9Zrr
3xbs2psAOiTZoK8PYZKNHcQw52waC3lbul9JNzghtRk7cDJNiGwo2blmp+ej6YY=
=LhqY
-----END PGP SIGNATURE-----
Reply all
Reply to author
Forward
0 new messages