Setting a Session Variable in a Model - ruby-on-rails

With Rails 3, How can you set a session variable in a model
session[:cannot_reason] = "no such item"
I'd like to set the above in my user model. Right now I get this error:
undefined local variable or method `session' for #<User:0x00000102eb9c38>
Ideas? Thanks

There's some unnecessary cargo-culting regarding whether or not models should have access to session data. I think this is silly, since session is really just another form of persistant storage (albeit for a much shorter time frame) and, in Rails, it seems ok to have your domain object also be able to persist itself.
That being said, the not very clean way to do it would be to pass the session hash as a parameter into the User method that will operate on it:
class User < ...
def mymethod(session, count)
session[:count] = count
end
end
In your controller, you would then do something like:
def update
# ...
user.mymethod(session, count)
end
Imagining that mymethod is implementing some business logic, this will modify the session hash appropriately. You don't have to pass the session hash back out to the controller because Ruby passes around references to objects (like a Hash)--modifications are made destructively to those referenced objects.
A word of advice: The above example, in my opinion, is smelly. This is because User is an ActiveRecord model which (I'm assuming) persists to a database and we're adding behavior that makes it also persist to a session cookie. To me, this violates SRP and should be avoided.
The way I would implement this would be to extract the session storage logic out to a separate domain object that encapsulates the reason for its existence. Maybe, for example, count isn't just a "count", it's the total of the number of items in the user's temporary cart.
class TemporaryCart
def initialize(session)
#session = session
end
def add_item
# ... other item adding logic
#session[:temporary_cart][:num_items] += 1
end
end
Now your controller would look like this:
def update
# ...
TemporaryCart.new(session).add_item
end
This is much more revealing and opens the door for an obvious way to abstract out session access code if you find yourself using session storage a lot. Also notice that I namespaced the data in the session hash (but didn't show this implementation). I recommend you do this so you don't step on your own toes when adding other data.

In short, you can't. By design, models don't have access to cookies and the session. If you to access items in the session from your model, you'll need to explicitly pass them in from the controller.

The session object is not visible in models. Either pass it as a parameter to a method in your model (IMHO bad) or define a method in your model which returns what you want and then store it in the session (from your controller).
class User < ...
def item_status
return :no_such_item
end
end
In your controller
session[:item_status] = current_user.item_status

Related

Rails Active Record model lifecycle

I have an Active Record model method that's basically just a database query, and I'd like to cache the results, ideally as simply as as via a local variable in the model:
my_data = method_already_called ? stored_results : do_query
This made me realise that I don't really understand the object lifecycle of an Active Record model, and all the Rails guides really tell you is about callbacks. Specifically, I can guess that the object will be created when the user wants to retrieve some data associated with that object, but I have no idea when that object is going to be destroyed.
At a practical level, say a user requests some information, which causes an AR object to be created, take some instance data from the DB and manipulate it before presenting it to the user. How long does that object hang around in memory if the user wants to instruct it to do something based upon that information?
Thanks in advance.
EDIT: I'm specifically interested in the behaviour of Rails 5.1 on Ruby 2.4.
In practice, as long as you keep a reference to this instance. In most cases - until a request is finished.
class Model
# most common memoization pattern
def something
#cached_result ||= do_query
end
end
So, when your model will be instantiated (in controller/service/etc), it will be available as long as you can reference it. On the next request, #cached_result will be re-calculated.
If you want to cache something between requests, you can use CacheStore:
class Model
def something
Rails.cache.fetch("cache_key") do
do_query
end
end
end
Do not treat cache as permanent store though. But this will allow you to cache something between requests and for some period of time.
btw, #cached_result will be calculated for each model instance. If you do something like Model.where(field: "value") that returns 2+ instances, each of them will do do_query on the first call.

Rails - Should I define default attribute value in model level or in db level?

In rails, we can simply define default attribute value in model level with one macro statement from gem 'default_value_for' or by ourself. On the other side, we can define it in the database level with migration 'default' option. I am confused about which is the best practice of rails, or which way should we used in different scenarios?
Any reply is appreciate! :D
Things you might want to consider.
When you set the default in the application then
the default can be changed easily and
the default can be dynamic and
there are ways to bypass the default
When you set the default in the database then
changing the default needs a migration (what might be a problem for huge tables)
it is harder to implement dynamic defaults and then
there is no way to bypass the default in the application (more secure)
Another option you might want to consider is to override the attributes getter method. This approach only uses the default value when an empty (or invalid value) is returned from the database, it doesn't change the way of storing new values.
def foo
super || 'a default value'
end
Setting defaults in the database is generally preferable as ActiveRecord is built around a convention over configuration approach where the database table drives the model - and not the other way around.
However database defaults are not very smart - they will always be applied and always have the same static value* (well unless you change the DB schema). In most cases this does not matter.
However if the default value requires some sort of computation like for example setting the default country of a user by IP based geolocation you would need to set the defaults on the application level (model).
Other examples would be:
columns that store serialized data (not native JSON columns)
relations that should default to a certain record.
Where do you set default values in the application?
in the model
The simplest example is using model callbacks:
class Thing
after_initialize :set_defaults, if: :new_record?
private
def set_defaults
self.foo = 'bar'
end
end
The con of model callbacks is that it can be very difficult to control exactly where in your application flow it is happing. For example if the initialization block is expensive you don't want it happening in all your tests.
In the controller
If the default value relies on the context of the request such as this example which uses Geocoder for IP based geolocation:
class User < ActiveRecord::Base
def set_default_location(geo)
u.city = geo.city
u.zipcode = geo.postal_code
u.country = geo.country_code
end
end
class UserController
def new
#user = User.new do |u|
u.set_default_location(request.safe_location)
end
end
end
The con is that this can lead to bloated controllers if not carefully kept in check.
In a PORO / Factory.
Some would argue that placing too much of your business logic inside your ORM classes (subclasses of ActiveRecord::Base) leads to violation of the Single Responsiblity Principle and makes your application overly brittle.
module ManagerFactory
def self.new(attributes = {})
user = User.new(attributes)
user.add_role(:manager)
user
end
end
I would say when to use it and when not to use it based on the data you want to set as default.
If what you have in mind is a static value to be set as default, example: false, 0, user, if you something dynamic in mind to be set as default value. Life a randomly generated UUID or a date which changed with respect to the day of creation then you can use default_value_for gem.

Preventing user model from loading multiple times in Rails

I have a situation where the user model is used multiple times in the same query in different places. For example: I want to iterate through all the groups the users is a member of, and get a unique list of all his/her friends that are also members of these groups.
Right now the user model gets reloaded every time, which results in:
1) Performance hit
2) Ideally I would use an instance variable to hold the unique list of friends, but I cannot do it since the user model gets reloaded.
It would make a lot of sense for the user model to be global for the duration of the request - any idea how I could achieve that?
Assuming that with "the user model" you actually means the current user instance (the User model is used to reference the model defined bu the class User) you normally use a memoization technique.
For instance, given you have a current_user method in your controller, you can do
def current_user
return #current if defined?(#current)
# In the following line load the current user. It's the first time
# this method is requested. Further calls will return the cached instance.
#current = ...
end
You can set a user like this:
def current_user
#current_user ||= User.find(1)
end

accessing session data in rails models

i need to check whether a Thing's id is in my session variable.
my instinct is to add a method to the Thing model which checks to see whether the Thing's id is in an array stored in the session variable.
class Thing < ActiveRecord::Base
...
def pinned?
session[:pinned].include? self.id
end
end
The functionality I am trying to create is the user selects a bunch of different things for a list, which are then stored in a per-user session and then retrieved at a later date for printing.
Rails seems to prevent session access in the model without some hackery, which I'm keen to avoid. Maybe i'm approaching the problem the wrong way round, or the pinned? function belongs somewhere else?
Thanks.
Maybe pass it to the method as an argument instead of accessing it directly?
def pinned?(things)
things.include? self.id
end
thing.pinned?(session[:pinned])

Need help with a code smell

In my Rails application I'm trying to make the controllers skinnier and am having difficulty with one object that I keep having to pass around.
The object represents a user's facebook session and is instantiated off of the session so it exists in the controller side of things. Many model methods use it, so it is repeatedly passed in as an argument all over the place.
This smells like something, how to DRY it up the Rails way? Thanks!
First, I would recommend using a system similar to Authlogic for your authentication. This gives you two bonuses:
You have proven, stable, tested authentication for your application
The sessions are based like Models, so you can do this kind of stuff...
class Widget < ActiveRecord::Base
def do_facebook_stuff
UserSession.find #This gets you the current session
UserSession.find.record # This gets your the user for the current session
end
end
Now you no longer need to pass the session information in, as you can do basic model-style lookups to find it. In addition to this, Authlogic has a plugin architecture that supports Facebook Connect, which may help you further.
I can give you the CakePHP way (which was originally designed to be like rails).
All CakePHP models extend the same parent AppModel, and all controllers extend an AppController.
I would make an empty parameter in the AppModel that represents your object. Then in the AppController I would store the object in the current model's parameter, if the object exists. There is a callback in the CakePHP AppController called beforeFilter() which fires before any code in the controller. The ideal place to check for the object and store it in the model would be in whatever equivalent Rails has of this beforeFilter callback.
That is unless all models don't use the object. If that is true, you could put the parameter in only the Models that use it (instead of the parent), and then in the beforeFilter of the AppModel you can check first if the Model has that empty parameter.
I know it's not Ruby, but it would look like this:
public function beforeFilter() {
if (isset($this->{$this->modelName}->yourObjectParameter)) {
$this->{$this->modelName}->yourObjectParameter = $this->yourObject;
}
}
$this->modelName is a string that corresponds to the name of the current model. the { } around $this->modelName in PHP is called complex syntax. It basically converts the string into the model object. Not sure how to do the same thing in Ruby.
You can take your method to application controller, something like this
class ApplicationController < ActionController::Base
before_filter :get_facebook_session
def get_facebook_session
#facebook_session = <your code >
end
end
And you can access #facebook_session variable from your controllers and views
cheers
sameera

Resources