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
Related
Recently I had to create a couple of records in a non-rails app database table based on a previous record. It got me thinking of how would I do this in a rails app. I tried a couple of things in the Console, but nothing works.
I want to do something like this:
001> user = User.new(User.first)
I know this doesn't work but hopefully it will show you what I an thinking. User is a large table/model, and I only need to change a few fields. So, if I can set up a new record with the same values in User.first, I can then edit the fields I need to before .save-ing the record.
Thanks for any help.
I think what you want is:
user = User.first.dup
user.assign_attributes(email: "myemail#test.test")
user.save
The first line uses dup to create a copy of the object. The copy is not yet saved to the database. Replace dup with clone if you're using an old version of Rails (<3.1).
In the second line, assign_attributes alters the attributes of the object, still without saving it to the database. If you were working with an object already saved in the database, you could use update instead of assign_attributes to change the attributes of the object and save the changes in one go. That won't work here, because we haven't saved our duplicate user yet. More details on that here.
The third line finally saves the new object to the database. It saves time to just do this once, at the end.
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.
I have a webpage that tracks budgets containing a LOT of variables, stored in 40+ columns. Over time, adjustments are made to these budgets, but I need to be able to track changes over time and year to year. I tried adding a private method to my model that should create a duplicate of the existing record triggered by a :before_update callback. However, it's not working. The update changes the existing record, and the original is not preserved at all.
Model:
class Budget < ActiveRecord::Base
before_update :copy_budget
private
def copy_budget
#budget = Budget.find(params[:id])
#budget.dup
#budget.save
end
end
I'm still learning rails, (this is in Rails 4) and I think this would have been the best way to do this. If not, is there a better way to set the form to ALWAYS post a new record instead of routing to update if a record already exists?
Currently the form_for line looks like this:
<%= form_for(#budget) do |f| %>
Everything works as it should, with the exception of the duplication not happening. What am I missing? Is it possible the .dup function is also duplicating the :id? This is assigned by auto-increment in the MySQL db I an using, so if .dup is copying EVERYTHING, is there a way to copy all of the data except the :id into a new record?
Thanks in advance for any suggestions.
the dup method returns the new object without an id, it doesn't update it in place. Since your copy_budget method is already an instance method on Budget, you also would not need to (and you wouldn't even be able to, since params aren't accessible in models) look up the budget by id and instead could just use the current instance (self). So the following changed would fix the copy_budget method for you, but you are still copying an already modified object, just before it gets saved to the database
def copy_budget
copy_of_budget = self.dup
copy_of_budget.save
end
it would work the way you're expecting it to work. However, you aren't linking the copy in anyway to the current version of the Budget (no way to tell Budget id = 1 is an older version of Budget id = 2). I'd recommend taking a look at a gem such as PaperTrail (I'm sure there are lots of others if that one doesn't suit your needs) which has already thought through a lot of the problems and features with keeping a history of record changed.
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 :)
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.