keeping 2 model records in sync - callbacks infinite loop - ruby-on-rails

I have 2 records of the same model, and I want to keep some of the data on these records in sync.
I was going to do a after_save callback (or maybe observer) to trigger updating the other record, but I am afraid this is going to cause an infinite loop of saves because the other record will cause a callback.
I read here that you can bypass callbacks on save, but these approaches seem to be hackish and not consistent between rails 2 and 3 (we are moving to rails 3 in a couple months).
Is there a better option?

You can create attr_accessor:
attr_accessor :dont_run_callback
after_save :my_callback
def my_callback
MyModel.find(1).update_attributes(..., :dont_run_callback => true) unless dont_run_callback
end
something like that

You can use the update_columns method while updating the 2nd record based on updates on the first one and vice versa.

Related

Running validations when using `update_all`

According to the Rails docs here and here, using update_all does not do the following -
It skips validations
It does not update the updated_at field
It silently ignores the :limit and :order methods
I'm trying to go through my code base and remove instances of update_all, particularly because of the first point.
Is there a way to still have the convenience of update_all and still run validations? I understand that I can loop through each record and save it, but that's not only messier visually but also more more inefficient because it executes N SQL statements instead of 1
# before
User.where(status: "active").update_all(status: "inactive")
# after
User.where(status: "active").each { |u| u.update(status: "inactive") }
Thanks!
Edit: I'm using Rails 4.2
Unfortunately update_all is way faster because it doesn't instantiate an active record object for each record and instead deals directly with the database. In your case, since you need validations and callbacks, you'll need to instantiate the objects and so you're best bet is iterating in batches of 1000 and performing the update as originally shown. Such as:
User.where(status: "active").find_each { |u| u.update(status: "inactive") }
The find_each method only loads 1000 objects at a time thus not overloading the garbage collector. If you have bulk records in the hundreds of thousands of rows I'd consider going back to update_all or moving the updating to a background task since it can easily cause a timeout when deployed.

How to avoid a circular loop

I think I'm being dense here because I keep getting a stack too deep error...
I have a Child and a Parent relational objects. I want 2 things to happen:
if you try to update the Child, you cannot update its status_id to 1 unless it has a Parent association
if you create a Parent and then attach it to the Child, then the Child's status should be auto-set to 1.
Here's how the Parent association gets added:
parent = Parent.new
if parent.save
child.update_attributes(parent_id:1)
end
I have these callbacks on the Child model:
validate :mark_complete
after_update :set_complete
# this callback is here because there is a way to update the Child model attributes
def mark_complete
if self.status_id == 1 && self.parent.blank?
errors[:base] << ""
end
end
def set_complete
if self.logistic.present?
self.update_attribute(:status_id, 1)
end
end
The code above is actually not that efficient because it's 2 db hits when ideally it would be 1, done all at once. But I find it too brain draining to figure out why... I'm not sure why it's not even working, and therefore can't even begin to think about making this a singular db transaction.
EXAMPLE
Hopefully this helps clarify. Imagine a Charge model and an Item model. Each Item has a Charge. The Item also has an attribute paid. Two things:
If you update the Item, you cannot update the paid to true until the Item has been associated with a Charge object
If you link a Charge object to a Item by updating the charge_id attribute on the Item, then code should save you time and auto set the paid as true
There's a lot that I find confusing here, but it seems to me that you call :set_complete after_update and within set_complete you are updating attributes, thus you seem to have a perpetual loop there. There might be other loops that I can't see but that one stands out to me.
One way to avoid a circularly recursive situation like this is to provide a flag as a parameter (or otherwise) that will stop the loop from continuing.
In this case, (though I am not sure about the case entirely) I think you could provide a flag indicating the origin of the call. If the origin of the update is a charge being attached, then pass a flag that will stop the check from happening or modify it to keep the loop from happening. Perhaps a secondary set of logic is in order for such a case?
I faced a stack level too deep problem some time back when working with ActiveRecord callbacks.
In my case the problem was with update_attribute after the update goes through the callback i.e. set_complete in your case is called again in which the update_attribute is triggered again in turn and this repeats endlessly.
I got around that by using update_column instead which does not trigger any callbacks or validations however setting a flag is what was advised more often online.
At this point I do not have an answer for reducing your database write operations, and will add to this answer if I can think of anything.
Hope this helps

Rails Active record validations - should I validate non-user generated data

I am not sure if I understand totally active record validation role.
Of course, if a user inputs data (like an email or a country), I can and should validate its existence, its uniqueness or its inclusion in a list of countries
But for example, if I have methods in the backend that change an attribute page_clicked or click_date or even the column update_at, that I "control" i.e 'is not generated by a user's input', should I use active record validations ?
I'm asking this because on a very 'hot database' (need speed for millions of frequent updates), I wonder if checking on each update that updated_at is a datetime, and that if a clicked column is true/false and nothing esle is really necessary as the user is not the one inputting/controlling these data but I am through Rails custom methods I wrote
Thanks
I don't think there is a general satisfying answer to your question. It's up to you to enforce validation or not.
Remember that you don't have to use ActiveRecord for validation, you can also use your DBMS to ensure that:
a value will never be NULL (one of the most annoying errors)
a value has the correct TYPE
a FOREIGN KEY always points to an existing row in another table
and depending on your DBMS, a lot more is possible
If you need high INSERT speed and want to go with raw SQL INSERTS, putting some validation in your database can prevent nasty application errors later.
Validations should guard your database and its job should be to stop saving the records that are considered invalid by your application.
There is no hard rule on what is valid record you have to decide it your self by adding the validations. If the record wont pass the validation step it is simply not going to be saved to the database.
From Active Record Callbacks:
3.1 Creating an Object
before_validation
after_validation
before_save
around_save
before_create
around_create
after_create
after_save
after_commit/after_rollback
3.2 Updating an Object
before_validation
after_validation
before_save
around_save
before_update
around_update
after_update
after_save
after_commit/after_rollback
You can see that validation hooks run at the beginning of the object life cycle.
So in your case instead of asking your self a question:
Should I use active record validations if the record is not generated by a user's input.
You should ask your self:
Is this record invalid without page_clicked or click_date(aka them being nil)
UPDATE
If you consider record to be invalid but worrying about speed problems with running validations I would do the validations to make sure that all the records in the database are valid and try to find the way to optimise the speed somewhere else. Plus not 100% sure but time spend on saving invalid records and filtering them later on will be probably much longer then validating in the first place.
When performance is really a priority and that I am sure that we developers / the server are the only ones who can manipulate specific attributes of a Model, I will
Make sure that I create a separate method / wrapper method for this specific action.
In this specific method, I call .save (validate: false) instead of the usual .save
I still write validations for the said attributes for developers' reference to prevent future development errors, and in case a new developer comes in and accidentally save an invalid record, precisely just because there's no validation to safeguard it.
Or, I will use .update_column instead of .save (validate: false) to perform a direct DB call, skipping Model validations and callbacks (If you also do not want callbacks to be called).
Note that .update_column is different from .update.

Increment views in Rails call also the model callbacks

I have a dumb question.
To record views in my content I'm incrementing a value in my db with something like this:
#gallery.increment! :impressions_count
I have a lot of before_save callbacks in my model, and for every view I call also the callback. I don't want this, and I know that I can also call a skip_callback .
Is there a smarter way to keep track of impressions avoiding this problem?
You could do this:
Gallery.increment_counter(:impressions_count, #gallery.id)
I might also choose this way, update column is the standard way to avoid callbacks and updated_at changes.
#gallery.update_column(:impressions_count, impressions_count + 1)

Rails after_save callback to create an associated model based on column_changed?

I have an ActiveRecord model with a status column. When the model is saved with a status change I need to write to a history file the change of status and who was responsible for the change. I was thinking an after_save callback would work great, but I can't use the status_changed? dynamic method to determine that the history write is necessary to execute. I don't want to write to the history if the model is saved but the status wasn't changed. My only thought on handling it right now is to use an instance variable flag to determine if the after_save should execute. Any ideas?
This may have changed since the question was posted, but the after_save callback should have the *_changed? dynamic methods available and set correctly:
class Order
after_save :handle_status_changed, :if => :status_changed?
end
or
class Order
after_save :handle_status_changed
def handle_status_changed
return unless status_changed?
...
end
end
Works correctly for me w/ Rails 2.3.2.
Use a before_save callback instead. Then you have access to both the new and old status values. Callbacks are wrapped in a transaction, so if the save fails or is canceled by another callback, the history write will be rolled back as well.
I see two solutions:
Like you said: add a variable flag and run callback when it is set.
Run save_history after updating your record.
Example:
old_status = #record.status
if #record.update\_attributes(params[:record])
save_history_here if old_status != #record.status
flash[:notice] = "Successful!"
...
else
...
end
Has anyone not heard of database triggers?
If you write an on_update database trigger on the database server, then every time a record gets updated, it will create a historical copy of the previous record's values in the associated audit table.
This is one of the main things I despise about Rails. It spends so much time trying to do everything for the developer that it fools developers into thinking that they have to follow such vulgar courses of action as writing specialized rails methods to do what the freaking database server already is fully capable of doing all by itself.
shakes head at Rails once again

Resources