Updating timestamps with #update_all - ruby-on-rails

When I have list of ids that I want to update their property the updated_at field in the database doesn't seem to change, here is what I mean :
ids = [2,4,51,124,33]
MyObj.where(:id => ids).update_all(:closed => true)
After this update is executed updated_at field doesn't change. However when I enter rails console with rails c and do this :
obj = MyObj.find(2)
obj.closed = false;
obj.save!
After this statement updated_at field changes value. Why is this? I'm relying on this updated_at field in my app as I'm listening to the updates and doing whole app flow when this happens?
Edit
I just found out from dax answer that :
Timestamps
Note that ActiveRecord will not update the timestamp fields (updated_at/updated_on) when using update_all().
I don't want to be updating one record at a time, is there a way around this? without resorting to sql level?

#update_all does not instantiate models.
As such, it does not trigger callbacks nor validations - and timestamp update is made in a callback.
Edit about edit :
If you want to keep the "one query to rule them all", you can update updated_at as well as :closed :
MyObj.where(:id => ids).update_all(closed: true, updated_at: DateTime.now)
But be aware validations are still not run.

Updates all, This method constructs a single SQL UPDATE statement and sends it straight to the database. It does not instantiate the involved models and it does not trigger Active Record callbacks or validations. Values passed to update_all will not go through ActiveRecord's type-casting behavior. It should receive only values that can be passed as-is to the SQL database.
As such, it does not trigger callbacks nor validations - and timestamp update is made in a callback.update_at is a call back for reference http://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-update_all

Timestamps
Note that ActiveRecord will not update the timestamp fields (updated_at/updated_on) when using update_all().
source: http://apidock.com/rails/ActiveRecord/Relation/update_all

If anyone is interested I did make a gist that outlines how to roll it yourself.
https://gist.github.com/timm-oh/9b702a15f61a5dd20d5814b607dc411d
It's a super simple implementation just to get the job done.
If you feel like there is room for improvement please comment on the gist :)

Related

rails set datetime db column to be the same value as updated_at

I'm generating a document from a model, this document always needs to match the lastest version of the model, and I can't recreate it every time I load it, because it's heavy and it's sent to docusign.
I figured I could use the updated_at field with and additional document_version:datetime db column, so that the model can be updated, but when appropriate I can check if updated_at == document_version and know if I need to reproduce the document.
in my code I have:
model.update(document_version: model.updated_at)
this doesn't work, I'm just setting the document version to the previous value of updated_at, the update method changes it.
model.update(document_version: model.updated_at, updated_at: model.updated_at)
the explicit setting of updated_at seems to be ignored.
my best option would be to update the document_version to whatever updated_at is going to be updated to.
I'm not sure how to do that
I'm not sure this is the best way to do this, but to strictly answer the question, this is what you can do.
model.update_column(:document_version, model.updated_at)
or updating updated_at directly too
time = Time.current
model.update_columns(document_version: time, updated_at: time)
update_column does what it says, it only updates a single column without updating the updated_at column.
From the docs
This is the fastest way to update attributes because it goes straight
to the database, but take into account that in consequence the regular
update procedures are totally bypassed. In particular:
Validations are skipped.
Callbacks are skipped.
updated_at/updated_on are not updated.

Update record if not updated in database using Rails

I need to save an object in the database only if this was not updated by others.
For example,
p = Person.find_by_id(1)
p.token = '83747'
Here, I would like to the object to be saved in the db only when this has not been updated in the database. Something like the query below.
update persons set token='83747' where id=p.id and updated_at != p.updated
I figured that I can use lock_version to achieve this. But, my need it to avoid updating this when running from cron job. Is there a way to achieve this using Active record. And there are callbacks on the model.
Just to clarify further, I am trying not to update the record in the database, if I have the stale copy of the record to avoid overwriting the changes made by someone else.
How can this be achieved without having to use lock_version and also ensuring that callbacks works?
You can use both created_at and updated_at default attributes (if this is sufficient enough in your case), by something like the following
p = Person.find_by(1)
# if has never been updated yet
if p.created_at != p.updated_at
p.token = '83747'
p.save
end

How to update a model's "updated_at" field only for a subset of column updates?

There is a typical blog application. Each user has_many posts. Each post has_many tags.
I'm sorting each post by updated_at so the most recently updated post will show up on top. So for example, if I update a post's content, the post will come up to the top. However, this also happens when I just add a tag, since a tag is connected to its corresponding post.
I only want the content update to change updated_at field. I don't want updated_at for a post to be changed because I added a tag. Is there a way to do this? Or any other way to achieve something like this? Thank you!
Two approaches spring to mind:
Don't use :updated_at for the purpose you are using it for. Instead create a new column, say :post_updated_at, and update it manually on each save that you want to cause the post to move to the top. Rails provides a convenient model mehod for this:
mypost.touch :post_updated_at
When you are updating a column and want :updated_at to remain untouched, use the #update_column method, which directly updates the column in the database with the value you give it. Note that it writes the value to the database verbatim, so you will have to be clever if the column in question is a fancy serialize column or similar.
Here's a replacement for save which lets you blacklist "boring" attributes, ie those which don't trigger a timestamp update. It relies on the Rails 4+ update_columns method to suppress callback methods during a save (be careful about suppressing those).
# Here we configure attribs that won't trigger the timestamp update
IGNORED_ATTRIBS = Set.new %w(reviewed_at view_count)
# A "save" replacement
def fussy_save
# Calculate any ignored attributes that are pending a save
all_changed_attribs = Set.new(self.changed_attributes.keys)
ignored_changed_attribs = IGNORED_ATTRIBS.intersection all_changed_attribs
# Save the ignored attributes without triggering updated_at
if ignored_changed_attribs.present?
self.update_columns self.attributes.slice(*ignored_changed_attribs)
end
# perform a normal save only if there are additional attributes present
if !all_changed_attribs.subset?(IGNORED_ATTRIBS)
save
end
end
Implementation note:
The final line above is a regular save. I initially tried doing a save without the check, on the basis that nothing should happen if the ignored attributes have already been saved (since save does nothing, and doesn't update timestamp, if no attribs are saved). But I discovered that changed_attributes is actually Rails' internal, definitive, reference, not just a convenience for developers (not too surprising :). It feels dangerous to start messing with that, so instead I just perform a check and all changed attributes will be saved in the SQL (including the ones just saved by update_columns), but only if there are non-ignored attributes present.

Which callback does ActiveRecord use to record timestamp?

I'm just wondering here whether any of you guys know when ActiveRecord use it's "magic" to record the timestamp (e.g. created_at, updated_at).
What i mean when is, at which callback ? (if AR use callback at all).
I'm asking this because I want to create an auto-updating column (that record sequential number for each object) and I want to replicate AR way to do this as much as possible.
EDITED:
It seems that AR does it between after_validation and before_create/before_update. You can do some tests for this by creating a presence validation for created_at column and inserting new record with blank created_at, it would return an error.
I don't know where AR does it, but the proper place for what you describe sounds like before_create
In Rails 3.2.12, this code is located in lib/active_record/timestamp.rb.
As you mention in your question and DGM suggests, Rails will update the timestamps when creating or updating, so sticking your code in before_create and before_update should work.
You may also want to take a look at the ActiveRecord counter_cache functionality. ActiveRecord supports creation of a column that can automatically be incremented/decremented. Additionally, you can perform more complicated logic.

Rails 2.3.8 make an active record dirty

I have an ActiveRecord object. It is updated using the nested attributes of a parent object. The problem is it has some non-database fields which if set need to trigger an after_save event. The problem that I am having is that if no database attributes are updated the after_save never fires, but I need it to.
ActiveRecord.partial_updates = false is a default in my app. The save never happens...
I tried to update the updated_at = DateTime.now and it does not trigger the record to be saved. Only when one of it's other properties gets updates does the save actually trigger.
Note Rails 2.3.8
Though I haven't tested it, it's possible that including ActiveModel::Dirty and telling it about those non-database fields will allow ActiveRelation pick them up.
More info: http://railsapi.com/doc/rails-v3.0.3/classes/ActiveModel/Dirty.html
Basically I manually created a setter for the non database property and invoked id_will_change! which made the whole object dirty. This works for my needs.

Resources