rails callback when save fails - ruby-on-rails

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.

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.

Using save in after_find callback

I have a status field that needs to be updated to expired if the status is new and has passed a certain period of time.
Is there any issue with doing a save inside the after_find callback to update the record when it is loaded? Is there any other more appropriate callback for this?
There's no inherent issue with it, it will work. after_find is called anytime an existing record is instantiated. save inserts the attributes into the database, but doesn't re-instantiate the object, so you don't have to worry about unwanted recursion by calling save inside that callback. A similar callback would be after_initialize, the only difference being that after_initialize is called on new objects as well. after_find would be more appropriate.
Whether or not it's the best approach to the problem though is debatable. It's probably the easiest and quickest to set up. But you're relying on objects to be instantiated by your program for data integrity. What if you need to do a database dump? Sergio's suggestion is probably a better approach overall.
What I normally do is have a periodic background job (sidekiq/delayed_job or the like) that will find all freshly-expired records and update their flag. Much less surprising than a write in an after_find callback.
Something to think about: suppose that you load 100 records for showing in a view and find all of them expired. So instead of 1 query, you perform 101 query (one select and 100 updates). It directly affects page load time and it gets worse the more records you load at once. Whereas in my proposed approach it's only two queries, one of them out-of-band mass-update, not affecting page load at all.

Rails validations: update at the same time as creation

I'm having some trouble trying to figure out how to order ActiveRecord writes to make my validations be happy, and I'm not sure what to search for this kind of problem.
The problem is that before the request would occur, everything would be valid; after the transformations would occur, everything would be valid again; but while the transformation is happening, since it's impacting more than one model instance, the database would enter an invalid state if I update each model one by one without taking into account both changes at the same time. I'd love some suggestions!
Background
I have a model called HelpRequest and another called HelperAssignments.
The rule is that a HelpRequest may have 0 or 1 active HelperAssignments. But if a Helper cannot complete the request, they may reassign it to another Helper, creating a new HelperAssignment. Since we need the history of assignments to a particular HelpRequest, there may be a number of HelperAssignments for a HelpRequest, but only one is active.
As a result, the HelperAssignment table has a few relevant attributes:
help_request_id: Refers to the HelpRequest corresponding to this assignment.
close_status: If this is set to reassigned, reassignment_id must be present.
reassignment_id: For a given help_request_id, only one may be nil (i.e. it is the current active assignment)
Problem
When a reassignment happens...
... if I create the new HelperAssignment first, it would break validations because more than one active HelperAssignment for the request would be present :(
... if I update the old HelperAssignment first to have a close_status of reassigned, the new HelperAssignment wouldn't exist yet so I couldn't get its ID, and therefore the validations would fail.
Is there an idiomatic way to do this transformation? I'd like to avoid a) disabling validations for this particular type of requests, or b) adding an extra database state for "being in the process of reassigning". Looks like enforcing referential integrity in models can get a little tricky in Rails... thanks in advance!

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.

Should a model method call 'save' itself?

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.

Resources