
Security News
NIST Under Federal Audit for NVD Processing Backlog and Delays
As vulnerability data bottlenecks grow, the federal government is formally investigating NIST’s handling of the National Vulnerability Database.
objectified_sessions
Advanced tools
Encapsulate and carefully manage access to your Rails session by modeling it as an object that you add fields and methods to, rather than a free-for-all Hash.
By default, Rails models your session as a Hash. While this makes it really easy to use, it also makes it really easy
to make a mess: as your app grows, you have to search the entire codebase for usage of session
(a pretty common
word that's certain to be used in many irrelevant ways, as well) to figure out how it's being used. It's easy for it
to grow almost without bound, and hard to keep a team of developers in sync about how it's being used. Further, the
otherwise-extremely-nice
CookieStore exacerbates these problems
— because you no longer have the power to change the sessions that are now stored in users' browsers, as cookies.
Using ObjectifiedSessions:
And, best of all, you can migrate to ObjectifiedSessions completely incrementally; it interoperates perfectly with traditional session-handling code. You can migrate call site by call site, at your own pace; there's no need to migrate all at once, or even migrate all code for a given session key all at once.
ObjectifiedSessions supports:
These are, however, just the versions it's tested against; ObjectifiedSessions contains no code that should be at all particularly dependent on exact Ruby or Rails versions, and should be compatible with a broad set of versions.
Current build status:
Add this line to your application's Gemfile:
gem 'objectified_sessions'
And then execute:
$ bundle
Or install it yourself as:
$ gem install objectified_sessions
Simply installing the Gem won't break anything. However, before the #objsession
call from inside a controller will
work, you need to create the class that implements your session. The simplest way to do this is by running
rails generate objectified_session
; this will write a file to lib/objsession.rb
that defines an empty
objectified session.
To start storing data, you need to define one or more fields on your session:
class Objsession < ::ObjectifiedSessions::Base
field :last_login
field :user_id
end
...and now you can use it from controllers (or anywhere else, if you pass around the #objsession
object) via:
objsession.last_login = Time.now
User.find(objsession.user_id)
...and so on.
The fields you define map exactly to traditional session fields — given the above, objsession.user_id
and
session[:user_id]
will always return exactly the same value, and assigning one will assign the other. In other
words, ObjectifiedSessions is not doing anything magical or scary to your session; rather, it's simply giving you a
very clean, maintainable interface on top of the session
you already know and love. You can assign any value to a
field that is supported by Rails' traditional session
, from an integer to an array of disparate Objects, or anything
else you want.
Already, you have a single point where all known session fields are defined (assuming you're not using any old-style
calls to #session
). Read on for more benefits.
You can, of course, define methods on this class that do anything you want — write fields, read fields, or simply answer questions:
class Objsession < ::ObjectifiedSessions::Base
field :last_login
field :user_id
def logged_in!(user)
self.last_login = Time.now unless self.last_login >= 5.minutes.ago
self.user_id = user.id
end
def logged_in_today?
self.last_login >= Time.now.at_midnight
end
end
...and then, in your controllers, you can say:
def login!
my_user = User.where(:username => params[:username])
if my_user.password_matches?(params[:password])
objsession.logged_in!(my_user)
end
end
def some_other_action
@logged_in_today = objsession.logged_in_today?
end
If you'd like to ensure your fields aren't modified outside the class, you can make them private:
class Objsession < ::ObjectifiedSessions::Base
field :last_login, :visibility => :private
field :user_id, :visibility => :private
def logged_in!(user)
self.last_login = Time.now unless self.last_login >= 5.minutes.ago
self.user_id = user.id
end
def logged_in_today?
self.last_login >= Time.now.at_midnight
end
end
Now, if someone says objsession.last_login = Time.now
in a controller, or objsession.user_id
,
they'll get a NoMethodError
. Like all Ruby code, you can, of course, use #send
to work around this if you need to.
If you want all methods to be private, you can set the default visibility, and then set fields' accessors to be public if you want them to be:
class Objsession < ::ObjectifiedSessions::Base
default_visibility :private
field :last_login
field :user_id
field :nickname, :visibility => :public
end
super
You can override accessor methods; super
will work properly, and you can also access properties using Hash-style
access (which is always private, unless you use public :[], :[]=
to make it public):
class Objsession < ::ObjectifiedSessions::Base
field :user_type
def user_type=(new_type)
unless [ :guest, :normal, :admin ].include?(new_type)
raise ArgumentError, "Invalid user type: #{new_type}"
end
super(new_type)
end
def user_type
super || :normal
end
def is_admin?
self[:user_type] == :admin
end
end
Unlike database columns, the names of session keys are embedded in every single instance of stored session data. You're often stuck in the tension between wanting to use long names to make your code readable, and short names to save precious session-storage space.
Enter storage aliases:
class Objsession < ::ObjectifiedSessions::Base
field :custom_background_color, :storage => :cbc
end
Now, your controller looks like:
if objsession.custom_background_color
...
end
...while you're now using three, rather than 23, bytes of storage space for the key for that field.
IMPORTANT: Changing the storage alias for a field, or setting one, will cause all existing data for that field to disappear. (Hopefully this is obvious; this is because ObjectifiedSessions will now be looking under a different key for that data.) It is, however, safe to do the reverse, by renaming a field and setting its storage alias to be its old name.
By default, ObjectifiedSessions will let you store any arbitrary Ruby object in the session — just like the default Rails session support will. However, this can cause significant issues, particularly with sessions; if you put, for example, a User model into the session, and then pull it out later (perhaps months later!), any attributes you added in the mean time will simply not be present. This is dangerous.
If you add this to your session class:
allowed_value_types :primitive
...then you will receive an ArgumentError
if you try to store anything in the session other than nil
, true
,
false
, a String
, a Symbol
, a Numeric
(including both integers and floating-point numbers), or a Time
.
If you instead set this as follows:
allowed_value_types :primitive_and_compound
...then the rules are relaxed to also include Array
s and Hash
es, as long as their constituent elements are valid
simple scalars or themselves Array
s or Hash
es. (In other words, you can nest these data types as deeply as you
would like.)
If, for some reason, you need it, allowed_value_types :anything
is the default setting.
Let's say you (probably wisely) stop supporting custom background colors, and remove that field. So far, so good.
Time passes, and now you introduce a session field saying whether or not the user has behaved consistently on your site in some way — a "consistent behavior check". You add an appropriate session field:
class Objsession < ::ObjectifiedSessions::Base
field :consistent_behavior_check, :storage => :cbc
end
Uh-oh. Now you're going to start interpreting whatever data was there for your old custom_background_color
field
as consistent_behavior_check
data, and bad, bad things may happen. (Using a CookieStore often makes this problem
worse, since sessions can last an arbitrarily long time unless you set a cookie timeout — which has other
disadvantages.)
To avoid this, when you remove a field, don't remove it entirely from the session class; instead, use the retired
keyword instead of field
:
class Objsession < ::ObjectifiedSessions::Base
retired :custom_background_color, :storage => :cbc
end
Now, when you add the new consistent_behavior_check
field...
class Objsession < ::ObjectifiedSessions::Base
field :consistent_behavior_check, :storage => :cbc
retired :custom_background_color, :storage => :cbc
end
...you'll get an error:
ObjectifiedSessions::Errors::DuplicateFieldStorageNameError (Class Objsession already has a field, :custom_background_color, with storage name "cbc"; you can't define field :consistent_behavior_check with that same storage name.)
Particularly if you're using the CookieStore to store session data, values for fields you no longer use may still be
sitting in the session, taking up valuable space. You can tell ObjectifiedSessions to automatically remove any data
that isn't defined as a field
:
class Objsession < ::ObjectifiedSessions::Base
unknown_fields :delete
field :user_id
field :last_login
...
end
Now, if, for example, a session is found that has a field cbc
set, ObjectifiedSessions will automatically delete that
key from the session.
Important Notes — BEFORE you use this feature, read these:
session[:foo]
, and you have no field :foo
declared in your objsession
, then ObjectifiedSessions
will go around deleting field :foo
from your session, breaking your code in horrible, horrible ways.
Be absolutely certain that one of the following is true: (a) you're only using objsession
to access session
data, (b) you've defined a field
in your objsession
for any data that your traditional session code touches,
or (c) use a prefix
, as discussed below.memcached
, for example, or tracking keys
used in all cookie-based sessions over the course of a whole day, can be very valuable, too.)unknown_fields :delete
and don't want this behavior, use the inactive
keyword instead of retired
; it
behaves identically (you can't access the data, and you can't define a field that conflicts), but it won't delete
the data for that key.inactive
until you're certain.objsession
from inside a controller. This is intentional — we don't want ObjectifiedSessions to add any
overhead whatsoever until you need it. If you want to ensure that this happens on every request, simply add a
before_filter
that calls objsession
. (You don't need to read or write any fields, so simply calling
objsession
is sufficient.)In certain cases, you may want ObjectifiedSessions to manage (and keep tidy) new session code, but want to make sure it cannot conflict at all with existing session data. In this case, you can set a prefix; this is a key under which all session data managed by ObjectifiedSessions will be stored.
For example — without the prefix:
class Objsession < ::ObjectifiedSessions::Base
field :user_id
field :last_login
end
objsession.user_id = 123
objsession.last_login = Time.now
session[:user_id] # => 123
session[:last_login] # => Thu Dec 26 19:35:55 -0600 2013
But with the prefix:
class Objsession < ::ObjectifiedSessions::Base
prefix :p
field :user_id
field :last_login
end
objsession.user_id = 123
objsession.last_login = Time.now
session[:user_id] # => nil
session[:last_login] # => nil
session[:p] # => { 'user_id' => 123, 'last_login' => Thu Dec 26 19:35:55 -0600 2013 }
session[:p]['user_id'] # => 123
session[:p]['last_login'] # Thu Dec 26 19:35:55 -0600 2013
Think carefully before you use this feature. In many cases, it is simply not necessary; ObjectifiedSessions
interoperates just fine with traditional session-handling code. The only case where it's really required is if you have
a very large base of code using the traditional session
object, and you want to introduce ObjectifiedSessions bit
by bit, and use unknown_fields :delete
. This should be a very rare case, however.
Changing the prefix will make all your existing data disappear! Hopefully this is obvious, but setting the prefix makes ObjectifiedSessions look in a different place when reading or writing data; this means that changing it will cause all existing data to effectively disappear. Think carefully, choose whether to use a prefix or not, and then leave it alone.
ObjectifiedSessions acts as a HashWithIndifferentAccess internally, so you can use either a String or a Symbol to access a given field when using Hash syntax, and you'll get the exact same result. It always talks to the Session using Strings, but this should be irrelevant in almost all cases.
(The only case where this actually matters is if you use a prefix; data stored under the prefix will be a Hash with Strings as keys, not Symbols.)
If, for some reason, you want the class you use for your objectified session to be called something other than
Objsession
, you can change it like so in config/application.rb
:
ObjectifiedSessions.session_class = :MyObjectifiedSession
# or ObjectifiedSessions.session_class = 'MyObjectifiedSession'
# or ObjectifiedSessions.session_class = MyObjectifiedSession
# ...i.e., you can set a Class object itself
If you use either the String or Symbol form, then ObjectifiedSessions will attempt to require
the corresponding
file before resolving the class (but won't fail if that doesn't work — only if it still can't resolve the
class afterwards). This means that the class you use does need to either already be loaded, or the file it's in needs
to be named correctly and on one of Rails' load_paths
.
You can call #fields on the objectified-session object to get back an Array of Symbols, listing the fields that can
be set on the session. You can call #fields on the objectified-session object to get back an Array of Symbols, listing
the fields that have something set (besides nil — note, in this case, that false
is distinct from nil
)
at present.
Calling #to_s
or #inspect
(which produce the same result) on the objectified session will produce a nice string
containing, in alphabetical order, all data that's set on the session. Long data is abbreviated at forty characters;
passing an argument of false
to either of these methods will remove such abbreivation.
If you have an existing application and want to migrate to ObjectifiedSessions bit by bit, here's how I'd do it:
rails generate objectified_session
).field
declared in the ObjectifiedSession for whatever key the traditional session-handling
code is using.objsession
and the new methods.The key point is that you don't have to migrate to ObjectifiedSessions all at once, or even all code that uses a single session field all at once.
Once you're done, and you're completely certain you've eliminated all use of traditional session code (and checked
for Gems, plugins, or other code that may be using the session without your knowledge), you can set
unknown_fields :delete
, if you'd like.
git checkout -b my-new-feature
)git commit -am 'Add some feature'
)git push origin my-new-feature
)ObjectifiedSessions is very thoroughly tested, including both system specs (that test the entire system at once) and unit specs (that test each class individually).
To run these specs:
cd objectified_sessions
(the root of the gem).bundle install
bundle exec rspec spec
will run all specs. (Or just rake
.)FAQs
Unknown package
We found that objectified_sessions demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
As vulnerability data bottlenecks grow, the federal government is formally investigating NIST’s handling of the National Vulnerability Database.
Research
Security News
Socket’s Threat Research Team has uncovered 60 npm packages using post-install scripts to silently exfiltrate hostnames, IP addresses, DNS servers, and user directories to a Discord-controlled endpoint.
Security News
TypeScript Native Previews offers a 10x faster Go-based compiler, now available on npm for public testing with early editor and language support.