accessing session data in rails models - ruby-on-rails

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])

Related

Handling associations w/ null objects in Rails

I'm using the Null Object pattern in my Rails application to implement the concept of a guest user account.
Like many apps, I have a method on ApplicationController called current_user.
In the case of a non-logged in user, I want to use my guest user null object.
It works in many cases, but then there run into something like the following -
params.merge({ user: current_user })
MyModel.new(params)
Of course this fails, with the following exception.
ActiveRecord::AssociationTypeMismatch: User expected, got GuestUser
My question is, what is a way to elegantly handle this kind of case. The idea for the Null Object pattern is that you can transparently swap in this null object and have it essentially be a duck type of the real object.
It's obvious how to do that for methods being called on the object, but in this case, I want to be able to pass this in and basically have it set the association column to null, rather than needing a whole bunch of custom logic (avoiding that is the whole point of the null object pattern anyway).
A polymorphic relation isn't quite it.
Quick answer: No such thing as an elegant way to handle that (I'm not sure how elegance is quantified).
You'll have to create a concern that mimics the persistence methods of the model from which your null object is based on (User). You'll also have to write methods to appease ActiveRecord to make the associated column be nil.
Fortunately for you, this use-case has been solved
if your MyModel accepts null for user_id, then you can do
params.merge(user: current_user) unless current_user.is_a?(GuestUser)
MyModel.new(params)
Using the null object pattern here is definatly not a good idea since you need database generated ids to build associations and maintain referential integrity if you intend the user to have any kind of persistence before "registering".
Allowing a MyModel to be created without a user_id would essentially create an orphaned record and just gives you another problem of linking it to user behind the screen. Thats why your schema should not allow it in the first place.
Rather you want to create the guest user record when needed (like when a guest user adds the first item to a cart) and use a recurring task (like a Cron tab) to periodicaly clean out junk records.
I would also consider if you really want to setup guest users as a seperate class since STI and polymorphism tends to get really messy when joining. Just use a timestamp column (records when the account was activated) or an enum instead.
One option would be to override the user= method, so that it's aware of the existence of GuestUser (and can handle appropriately):
def user=(value)
if value.is_a?(GuestUser)
super(nil)
else
super
end
end
All mass-assignment methods in Rails (create, update, etc.) will use the appropriate setter to set the value. This can be easily be put into a concern if this is a common pattern in your application.
If you don't allow nil in the user_id column you have the flexibility to do something like assign a sentinel value, which you could then use in your accessor as well:
def user
if user_id == GUEST_USER_ID
GuestUser.new
else
super
end
end
I had a similar problem. I just went from assigning the object to assigning the object.id which I set to nil on the Null Object. It is kind of a hack I think though.

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 - How can I save a field value (that uses a record's ID) though a callback?

I want to create a hash that combines the creating user's user_id + the record's ID to make a MD5 hash, but only on record creation. (Reasons are long-winded but this extracts it).
I am trying:
class BlogPost < ActiveRecord::Base
after_create :hash_this
private
def hash_this
self.info_md5_hashed = (id.to_str + creator_user_id).my_MD5_hash_method
end
end
How can I make sure that the info_md5_hashed field actually gets saved to the database?
If I use before_create I would assume that the ID is not yet available? :(
If I use after_create I would assume that the ID is now available
- but do I need to do another save somehow to store the newly calculated info_md5_hashed field value?
Yes, you will have to save the record twice, since ID generation happens in the database. The only way around that is to pre-sequence an ID, but there's almost certainly no point and you should just accept that you will have to save the record twice ;)
Just call the second save from inside your after_create hook- it's ok to do this (i.e. will not be a looping recurrence issue) because the second save won't re-trigger that hook again ;)
n.b. You could always base the hash on something you know to be unique, such as the username or email, if you want to avoid a double-save.

Setting a Session Variable in a Model

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

Should I be using callbacks or should I override attributes?

What is the more "rails-like"? If I want to modify a model's property when it's set, should I do this:
def url=(url)
#remove session id
self[:url] = url.split('?s=')[0]
end
or this?
before_save do |record|
#remove session id
record.url = record.url.split('?s=')[0]
end
Is there any benefit for doing it one way or the other? If so, why? If not, which one is generally more common?
Obviously these two have different use-cases.
The first one should be done if you need to access the modified attribute before the record is saved. For example, you want to set the url and at once check the modified value against some condition before saving it to DB.
The second one fits if you only want to do something with the attribute just before saving to the database. So if you access it between the moment of setting and the moment of saving, you'll get the unmodified value.

Resources