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.
Related
I have a model in my Rails app for a SalesOpportunity, and running a SWOT analysis (Strength, Weakness, Opportunity, Threat) to decide how good the SalesOpportunity is. Swots belong_to SalesOpportunities and therefore in the SalesOpportunity Model I have a method called update_swot_score which iterates through the Swot objects and calculates a score based on parameters I'm feeding in. All of this works fine.
What I'm wondering is whether I need to add a field to my SalesOpportunity model (let's call it swot_score for simplicity) and to update the instance variable at the end of the update_swot_score method using #swot_score = "results of my calculation", or whether I can directly access a result of the update_swot_score method (ideally in my view - I'll display different partials depending on the result).
What is the Rails way of doing this? Is there a performance efficiency to be gained by using either method?
i will suggest to add in the db as a dedicated column to store score...there are few good reasons as why you should do it:-
dedicated column to store only score
easily maintainable even you decide to add/edit new score type in future
Can be called/updated/modified anywhere throughout your application
can use callbacks in future as well(on_create,on_update) as its in a Model
Performance wise,its awesome as you can cache it..can also counter_cache and expire too.
Hope it helps
What's the best practice to get information into a model method from the controller? Should you pass the information as a parameter or requery the database in the model? For instance,
Method #1
magazines_controller.rb
#magazine = Magazine.find(article.magazine_id)
articles_method(#magazine)
Method #2
magazine.rb
def articles_method
magazine = Magazine.find(self.magazine_id)
end
My guess is passing it as a parameter. If so, are there any cases where requerying the database is better? Like if there are large collections that need to be passed?
Your question is very broad so I'll try to answer with a few scenarios. Generally I don't like making other queries in my models, in order to decouple them when they're not explicitly related (they're already associations declared in the model class). In the cases where something needs to happen across very different models I'll pass just the values I need into the method that will be doing the work. To use your example, if I had an articles_method which needs, say, the pages of a magazine, I'd pass the pages in rather than the whole magazine object. Pass just what the method needs and nothing more
Method #1
_magazines_controller.rb_
#magazine = Magazine.find(article.magazine_id)
articles_method(#magazine.pages, #magazine.metadata)
Keep the interface simple and pass values in rather the models.
Other cases include passing ids into asynchronous method calls, e.g. delayed job, sidekiq. In this case you would want your method to do the database query in order to grab the most recent version of the object at the time the method executes. So passing in an id to your async method would be better than passing the actual model or even the values - those values might have been updated shortly before the async method ran.
When your method is calling out to many models, or you're passing in many object and/or values; I'd instead implement the service object or interactor pattern and create a new class to be responsible for this interaction. Instantiate it with references to every model involved (either by id and requery, serialization, or directly passing the object in) and then have the service/interactor do the work in a run or perform method PageFooterPrinter.new(mag.pages, other_obj.stuff).run.
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])
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
Let's say that we have a method inside a model that
needs to called only on saved records
may update the model itself and thus the model needs to be saved again afterwords
Should the "save" calls happen inside the method itself like the following code
def result
save! if new_record?
# do some funky stuff here that may also change the model state
# ...
# And calculate the return value
search_result = "foo" # Let's say "foo" is the value we calculated
save! if changed?
search_result # return
end
Or should the external observer (the controller) be responsible for calling save as needed?
If your method really, really needs to do all that, so be it.
However, I would make it clear from looking at the method why you're doing that (comments might be good here), and would definitely make this a bang_method! so that it is clear to whoever invokes it that this method is liable to mess with the object as much as it likes.
Also, the method name result (which, I know, probably isn't your real method name) somewhat implies that you're just fetching data, and little more. Maybe load_result! would be more appropriate here, to make it clearer that you're not just accessing an attribute, but are, in fact, performing heavy operations to get it.
There are definitely times when it is necessary for a model to persist itself. But it's worth considering whether save is the best method for your application.
In a current example, we have a model that processes a file asynchronously in a long-running method (we are spinning the process off using sidekiq.) Inside the method, a persistent attribute is updated regularly so the status information is available to other requests.
We're using update_column rather than save, because
We don't want or need the overhead of the AR callbacks, and we particularly want to skip validation to ensure the update occurs surely and immediately.
We only need to update a single attribute. Using update_column avoids the need to determine whether any other attributes need to be saved (or not saved.)
Inside the model, methods like
update_column
save(:validate => false) (granted, same method, but different options)
touch
etc, may often be a more appropriate way to persist changes than a plain save.
When does a program save data on a file?
a) Only when user requires it (directly or indirectly)? -- this is controller case
b) Only when the program achieves part of its correctness and data integrity? -- this is model case
c) Both.
I would vote for (c). I hope this discrimination straightens things a bit.
Additionally, from a object-oriented design point of view, method save() belongs to the public contract of its class; it can be invoked by anyone. Given this, a class is responsible for its public contract and, if needed, an object can invoke its own methods at will.