Using save in after_find callback - ruby-on-rails

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.

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 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 apply model method on records that already exist on server through console?

The issue that my application is already alive and bunch of user's use data, in which I found bug and fixed it in the code. But how can I modify their data with edited method, which is actually is before_save callback?
I have their input for this record stored in another column, I need to modify output.
How I can do this?
Do you mind if the updated_at value of your model changes?
If not, I suppose you could loop through your model collection calling the save method.
> Foo.all.each(&:save)
If you go this way, you better stop your application before you make changes to the database.

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.

updating a model while in before_update

I've been wondering, suppose I have a model with an attribute that in every instance is dependent on that same attribute in other instances. The best example for this would be an order attribute for items in a list.
The best place to update the rest of the items' order attributes would be in a before_update callback method, were you have both the item's old and new values.
But now whenever you update the other items in the list the callback is going to be called again, and again...
I'm looking for an elegant way of solving this.
I have heard about the :update_without_callbacks method, but i don't want to use a private method, and also i feel like adding extra attributes would be unnecessary.
Got any good ideas? Thanks in advance!
One way would be to use update_all to set the order of all the other items in bulk.
That way you would efficiently limit the number of queries to one and prevent any callbacks from being triggered.
https://github.com/rails/rails/blob/83e42d52e37a33682fcac856330fd5d06e5a529c/activerecord/lib/active_record/relation.rb#L274
I feel the fact that you have to do this type of update across entries suggests you haven't properly conceptualized your problem. Why not create a List model that has the order attribute, and then create a one-to-many relationship between the List model and the Item model. This way, there's only one place to update the ordering information and no need for complicated and brittle callbacks.

Resources