Should a model method call 'save' itself? - ruby-on-rails

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.

Related

rails callback when save fails

In Rails 5 I've implemented a series of relationships that cause a chicken-and-egg problem when saving one complex model. (IDs are needed to relate objects, but don't exist until after they're saved.)
I'll need to create and save objects the hard way, but I need to clean up after myself if save fails, so I don't end up with a database full of empty objects.
From the model, how do I ensure my clean-up code runs if and only if a save fails? The standard list of callbacks doesn't seem to cover this case, unless I'm missing something.
Model callbacks are one of the most overused and misused features in Rails. They are great for adding simple callbacks to the lifecycle of a model but very hard to control when they are fired (like in your tests where they slow everything down) or to tap into the flow to add application logic.
If your callback ever effects more than the model defining the callback thats a very good sign that you should reconsider using a callback.
In this case what you most likely want is a transaction:
A.transaction do
begin
a = A.create!(some_params)
a.bs.create!(some_other_params)
rescue ActiveRecord::RecordInvalid
a
end
end
This wraps the operation in a database transaction that is rolled back if either operation fails - leaving the database untouched.
You can either inline this in the controller or wrap it in a service object.

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.

Is it better practice to pass data into a model method from the controller or requery the database in the method for the information?

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.

How can I distinguish between direct instantiation and instantiation through an association in Rails?

I have an after_initialize callback that I would like to happen whenever the model is created or instantiated directly, as opposed to loaded through an association in some other place. Something like this:
after_initialize :check_status, if: "instantiated_directly?"
such that MyModel.find(1) will trigger the check, but other_model.my_model will not.
The status is a state variable for a long process, which only needs to be verified before some long-running process starts. I want to prevent the user from loading the model if the process seems to be in progress. I'd like to still be able to acces attributes in that model for various other reasons elsewhere.
Preventing the read is probably not the best vector for ameliorating this issue, nor is a model that tries to reach out of its own scope to determine how to load a wise idea. I would instead recommend you simply add a validation to ensure the record can be modified without interfering with your background processes, a la…
validate :safe_to_edit?
def safe_to_edit?
some_state_variable
end
Your model also exposes a safe_to_edit? method here that allows your controller or another object to determine the best behavior independently of checking validations, such as if you had a client-side service polling to flag the user when it was safe to edit, or you wanted to delay response and try again in a few seconds in a background job, etc.

How can I see the request in an AR Observer for audit purposes?

I have a simple Observer that is watching certain models and writing the model to_json to a table along with the model name. I want to also track who performed the change, but I can't figure out how to access the current user.
Perhaps there is a simple gem or bit of code I can put in ApplicationController or something like that that could help?
There's no clean way to do this that I can think of. You could try using the User.current pattern but I'm not a fan of that method and I'm not sure it would communicate to observers properly.
In cases like these where I want to know who (last) changed the object, I usually add a 'changed_by' and 'changed_at' type of attribute to the model itself, and set those in the controller. That would make it much easier to leave an audit trail as well. You could even use attr_accessor if you don't actually want to store the values in the database.

Resources