I can't catch the changed attributes on after_save callback but I can catch them on after_update callback. I think after_save is just a combination of after_create and after_update. I'd appreciate it if someone give me at least a hint.
class Student < ApplicationRecord
after_save: after_save_callback
def after_save_callback
if username_changed? ### This is always false
# do something
end
end
end
student = Student.create(name: 'John Doe', username: 'abcdef')
student.username = '123456'
student.save
My Rails version is 5.0.7.
You can use saved_change_to_username?
The behavior of attribute_changed? inside of after callbacks will be changing in the next version of Rails. The new return value will reflect the behavior of calling the method after save returned (e.g. the opposite of what it returns now). To maintain the current behavior, use saved_change_to_attribute? instead.
Is there a way to track changes to model on after_commit when a record is created? I have tried using dirty module and was able to track changes when the record was updated, but when record is created changes are not recorded.
You can't use the rails changed? method, as it will always return false. To track changes after the transaction is committed, use the previous_changes method. It will return a hash with attribute name as key. You can can then check if your attribute_name is in the hash:
after_commit :foo
def foo
if previous_changes[attribute_name]
#do your task
end
end
obj.update_attribute(:only_one_field, 'Some Value')
obj.update_attributes(field1: 'value', field2: 'value2', field3: 'value3')
Both of these will update an object without having to explicitly tell ActiveRecord to update.
Rails API says:
update_attribute
Updates a single attribute and saves the record without going through the normal validation procedure. This is especially useful for boolean flags on existing records. The regular update_attribute method in Base is replaced with this when the validations module is mixed in, which it is by default.
update_attributes
Updates all the attributes from the passed-in Hash and saves the record. If the object is invalid, the saving will fail and false will be returned.
So if I don't want to have the object validated I should use #update_attribute. What if I have this update on a #before_save, will it stackoverflow?
My question is does #update_attribute also bypass the before save or just the validation.
Also, what is the correct syntax to pass a hash to #update_attributes ... check out my example at the top.
Please refer to update_attribute. On clicking show source you will get following code
# File vendor/rails/activerecord/lib/active_record/base.rb, line 2614
2614: def update_attribute(name, value)
2615: send(name.to_s + '=', value)
2616: save(false)
2617: end
and now refer update_attributes and look at its code you get
# File vendor/rails/activerecord/lib/active_record/base.rb, line 2621
2621: def update_attributes(attributes)
2622: self.attributes = attributes
2623: save
2624: end
the difference between two is update_attribute uses save(false) whereas update_attributes uses save or you can say save(true).
Sorry for the long description but what I want to say is important. save(perform_validation = true), if perform_validation is false it bypasses (skips will be the proper word) all the validations associated with save.
For second question
Also, what is the correct syntax to pass a hash to update_attributes... check out my example at the top.
Your example is correct.
Object.update_attributes(:field1 => "value", :field2 => "value2", :field3 => "value3")
or
Object.update_attributes :field1 => "value", :field2 => "value2", :field3 => "value3"
or if you get all fields data & name in a hash say params[:user] here use just
Object.update_attributes(params[:user])
Tip: update_attribute is being deprecated in Rails 4 via Commit a7f4b0a1. It removes update_attribute in favor of update_column.
update_attribute
This method update single attribute of object without invoking model based validation.
obj = Model.find_by_id(params[:id])
obj.update_attribute :language, “java”
update_attributes
This method update multiple attribute of single object and also pass model based validation.
attributes = {:name => “BalaChandar”, :age => 23}
obj = Model.find_by_id(params[:id])
obj.update_attributes(attributes)
Hope this answer will clear out when to use what method of active record.
Also worth noting is that with update_attribute, the desired attribute to be updated doesn't need to be white listed with attr_accessible to update it as opposed to the mass assignment method update_attributes which will only update attr_accessible specified attributes.
update_attribute simply updates only one attribute of a model, but we can pass multiple attributes in update_attributes method.
Example:
user = User.last
#update_attribute
user.update_attribute(:status, "active")
It pass the validation
#update_attributes
user.update_attributes(first_name: 'update name', status: "active")
it doesn't update if validation fails.
You might be interested in visiting this blog post concerning all the possible ways to assign an attribute or update record (updated to Rails 4) update_attribute, update, update_column, update_columns etc. http://www.davidverhasselt.com/set-attributes-in-activerecord/. For example it differs in aspects such as running validations, touching object's updated_at or triggering callbacks.
As an answer to the OP's question update_attribute does not by pass callbacks.
Great answers.
notice that as for ruby 1.9 and above you could (and i think should) use the new hash syntax for update_attributes:
Model.update_attributes(column1: "data", column2: "data")
update_attribute and update_attributes are similar, but
with one big difference: update_attribute does not run validations.
Also:
update_attribute is used to update record with single attribute.
Model.update_attribute(:column_name, column_value1)
update_attributes is used to update record with multiple attributes.
Model.update_attributes(:column_name1 => column_value1, :column_name2 => column_value2, ...)
These two methods are really easy to confuse given their similar names and works. Therefore, update_attribute is being removed in favor of update_column.
Now, in Rails4 you can use Model.update_column(:column_name, column_value) at the place of Model.update_attribute(:column_name, column_value)
Click here to get more info about update_column.
To answer your question, update_attribute skips pre save "validations" but it still runs any other callbacks like after_save etc. So if you really want to "just update the column and skip any AR cruft" then you need to use (apparently)
Model.update_all(...) see https://stackoverflow.com/a/7243777/32453
Recently I ran into update_attribute vs. update_attributes and validation issue, so similar names, so different behavior, so confusing.
In order to pass hash to update_attribute and bypass validation you can do:
object = Object.new
object.attributes = {
field1: 'value',
field2: 'value2',
field3: 'value3'
}
object.save!(validate: false)
I think your question is if having an update_attribute in a before_save will lead to and endless loop (of update_attribute calls in before_save callbacks, originally triggered by an update_attribute call)
I'm pretty sure it does bypass the before_save callback since it doesn't actually save the record. You can also save a record without triggering validations by using
Model.save false
I'd like to create a callback function in rails that executes after a model is saved.
I have this model, Claim that has a attribute 'status' which changes depending on the state of the claim, possible values are pending, endorsed, approved, rejected
The database has 'state' with the default value of 'pending'.
I'd like to perform certain tasks after the model is created on the first time or updated from one state to another, depending on which state it changes from.
My idea is to have a function in the model:
after_save :check_state
def check_state
# if status changed from nil to pending (created)
do this
# if status changed from pending to approved
performthistask
end
My question is how do I check for the previous value before the change within the model?
You should look at ActiveModel::Dirty module:
You should be able to perform following actions on your Claim model:
claim.status_changed? # returns true if 'status' attribute has changed
claim.status_was # returns the previous value of 'status' attribute
claim.status_change # => ['old value', 'new value'] returns the old and
# new value for 'status' attribute
claim.name = 'Bob'
claim.changed # => ["name"]
claim.changes # => {"name" => ["Bill", "Bob"]}
Oh! the joys of Rails!
you can use this
self.changed
it return an array of all columns that changed in this record
you can also use
self.changes
which returns a hash of columns that changed and before and after results as arrays
For Rails 5.1+, you should use active record attribute method: saved_change_to_attribute?
saved_change_to_attribute?(attr_name, **options)`
Did this attribute change when we last saved? This method can be
invoked as saved_change_to_name? instead of
saved_change_to_attribute?("name"). Behaves similarly to
attribute_changed?. This method is useful in after callbacks to
determine if the call to save changed a certain attribute.
Options
from When passed, this method will return false unless the original
value is equal to the given option
to When passed, this method will return false unless the value was
changed to the given value
So your model will look like this, if you want to call some method based on the change in attribute value:
class Claim < ApplicationRecord
after_save :do_this, if: Proc.new { saved_change_to_status?(from: nil, to: 'pending') }
after_save :do_that, if: Proc.new { saved_change_to_status?(from: 'pending', to: 'approved') }
def do_this
..
..
end
def do_that
..
..
end
end
And if you don't want to check for value change in callback, you can do the following::
class Claim < ApplicationRecord
after_save: :do_this, if: saved_change_to_status?
def do_this
..
..
end
end
I recommend you have a look at one of the available state machine plugins:
acts_as_state_machine
alter_ego
Either one will let you setup states and transitions between states. Very useful and easy way of handling your requirements.
I've seen the question rise in many places, so I wrote a tiny rubygem for it, to make the code a little nicer (and avoid a million if/else statements everywhere): https://github.com/ronna-s/on_change.
I hope that helps.
You will be much better off using a well tested solution such as the state_machine gem.
ActiveRecord use to call after_save callback each time save method is called even if the model was not changed and no insert/update query spawned.
This is the default behaviour actually. And that is ok in most cases.
But some of the after_save callbacks are sensitive to the thing that if the model was actually saved or not.
Is there a way to determine if the model was actually saved in the after_save?
I am running the following test code:
class Stage < ActiveRecord::Base
after_save do
pp changes
end
end
s = Stage.first
s.name = "q1"
s.save!
ActiveRecord use to call after_save
callback each time save method is
called even if the model was not
changed and no insert/update query
spawned.
ActiveRecord executes :after_save callbacks each time the record is successfully saved regardless it was changed.
# record invalid, after_save not triggered
Record.new.save
# record valid, after_save triggered
r = Record.new(:attr => value)
# record valid and not changed, after_save triggered
r.save
What you want to know is if the record is changed, not if the record is saved.
You can easily accomplish this using record.changed?
class Record
after_save :do_something_if_changed
protected
def do_something_if_changed
if changed?
# ...
end
end
end
After a save, check to see if the object saved is a new object
a = ModelName.new
a.id = 1
a.save
a.new_record?
#=> false
For anyone else landing here, latest rails 5 has a model method saved_changes? and a corresponding method saved_changes which shows those changes.
book = Book.first
book.saved_changes?
=> false
book.title = "new"
book.save
book.saved_changes?
=> true
book.saved_changes
=> {"title" => ["old", "new"]},