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.
Related
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 :)
I am using virtual attributes to break out a comma separated parameter list in the view, and then trying to recombine them to save into my Active Record model.
Given the real attribute "ad_columns" that defaults to "1,1,1,1"
I am breaking them out in to individual attributes for the form:
attr_accessor :top_rows
#Getter
def top_rows
split = ad_rows.split(',', 4)
split[0]
end
#Setter
def top_rows=(trows)
ad_rows_will_change!
self.ad_rows = [trows, self.right_rows, self.bottom_rows, self.left_rows].join(",")
end
Then repeating this for right, bottom, left and right.
Given an object if I call "object.top_rows" I do get "1", and if I update it in irb:
object.top_rows = "3"
Then it updates the ad_rows real attribute properly. I can see that the object has changes, and when I do an
object.save
The changes are updated in the database.
The problem is, this is NOT working from the view. It will not save to the database. I have even used logger.info to see if the model has changed and it will show that "ad_rows" has indeed been changed, yet active record is still NOT updating the real attribute.
I can't figure out why this is happening. Am I just doing it wrong? :) Thanks.
Why would it work in irb but not the view?
I would start by getting rid of attr_accessor :tops_rows.
That line creates additional and possibly conflicting instance methods for getting and setting an instance variable #top_rows and won't touch the ActiveRecord attributes.
As far as why it won't work in the view, I would check that you have added :ad_rows to attr_accessible in the model. This allows ActiveRecord to make changes to the column as part of batch operations (more than one attribute being changed). Documentation.
It seems that since the attribute :ad_rows was not being passed in the parameters, it was not being recognized as changed by the controller.
I had to add:
object.ad_rows_will_change!
To my controller to force it to save the ad_rows column.
I am not sure if this is the best solution, but it is working for now.
I'm using mongoid, and I want to migrate my documents one at a time. To do this, I've tried doing the migration in an after_initialize callback, but I can't seem to save() from there without triggering the same validation and infinite recursion. Is there a better callback to use, or a different thing I should be looking at?
Have you tried passing :validate => false into the save? That allows you to bypass validations.
As per the original question,
instance.update_attributes(new_attr_hash)
will immediately save after updating the instance, but you can't bypass validations with update_attributes.
I'd use before_save, and then go through the whole collection and save them again outside the callback, instead of just initializing them like I assume you were doing before.
In my Rails application I'm trying to update a model's attribute using update_attribute in an after_create callback. I can successfully update the attribute, but for some reason all the model's other attributes are also updated when I do so. So, even though the model's name attribute (for example) has not changed it is set (to it's current value) in the database update query.
Is this the expected behaviour in Rails (2.3.8), or am I doing something wrong?
Yes I believe this is consistent behaviour because that instance of your model that was just created has not been reloaded. Therefore the 'changed' attributes have not been reset.
Sorry if that's not a very clear explanation. To demonstrate, run the debugger in your after_create method. E.g.
def my_after_save_callback
require 'ruby-debug'; debugger
update_attribute(:foo, "bar")
end
Then when the debugger starts run:
p self.changed
An array of all the attributes that have been modified for this object will be returned. ActiveRecord will update all these attributes the next time the object is saved.
One way around this is to reload the object before updating the attribute.
def my_after_save_callback
reload
update_attribute(:foo, "bar")
end
This will reset the 'changed' attributes and only the specific attribute you modify will be updated in the SQL query.
Hope that makes sense :-)
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