Devise before it saves the record, it checks if attributes changed and if so it performs special actions:
def send_devise_notification(notification, *args)
# If the record is new or changed then delay the
# delivery until the after_commit callback otherwise
# send now because after_commit will not be called.
if new_record? || changed?
pending_notifications << [notification, args]
else
# Devise: send emails with background job
devise_mailer.send(notification, self, *args).deliver_later
end
end
http://www.rubydoc.info/github/plataformatec/devise/Devise%2FModels%2FAuthenticatable%3Asend_devise_notification
The following line gives me an depreaction now:
if new_record? || changed?
DEPRECATION WARNING: The behavior of '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_changes?' instead.
When I use saved_changes? instead of changed? the code won't work correctly anymore, because in this step the record is not yet saved
e.g.
user.email = "hello#example.com"
user.changed? => true
user.saved_changes? => false
Which method should I use instead? How can I prevent the depreaction warning? Thanks
The message means that changed? will behave differently inside after callbacks like after_create or after_save. because the record will be already saved you can use saved_changes? instead and it will work well on those callbacks
But if you want to use it on before callbacks for example before_save
then leave changed? and don't replace it because it will work normally as previous
If you don't care about if the object is saved or not. you can just check them both new_record? || saved_changes? || changed?
For the example you mentioned regarding changing the user email. devise will send the confirmation after save so saved_changes? only should work well!
You should to use saved_changes? instead.
Related
I have a model with an attribute car
I want to add a field car_created_at
that will have as a value the timestamp of the first time car actually assigned a value.
What is the proper way to do that? maybe after_update hook?
According to the trusty rails docs, after_update only runs on updates.
after_save runs both when created and updated. So I would use that.
And beware that just doing save in the hook will cause infinite call stack, meaning save will trigger after_save, in which save is called.
So you will have to use update_columns.
after_save :touch_car_created
def touch_car_created
if car_created_at.nil?
update_columns(car_created_at: Time.zone.now)
end
end
I would prefer to go with before_save because this callback get called before object creation and updation.
# This callback works only when car value has changed
# and car_created_at is nil.
before_save :assign_car_created_time, if: Proc.new { car_changed? && car_created_at.nil? }
private
# Keep it private so it won't be accessed publicly.
def assign_car_created_time
# We only need to assign the value
self.car_created_at = Time.zone.now
end
Hope this works for you.
I get the following deprecation warning:
DEPRECATION WARNING: The behavior of `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_changes?` instead.
for this code:
def send_devise_notification(notification, *args)
# If the record is new or changed then delay the
# delivery until the after_commit callback otherwise
# send now because after_commit will not be called.
if new_record? || changed?
pending_notifications << [notification, args]
else
devise_mailer.send(notification, self, *args).deliver_later
end
end
Can somebody explain me the Deprecation Warning with an example? I'm not sure if I understand correctly what's meant with The new return value will reflect the behavior of calling the method after "save" returned
Can I now simply replace changed? with saved_changes?? Thanks
As I understand it works like this. Now #changed? returns false in after callbacks until you change an attribute. So #changed? on a record behave in after callbacks as if you've just fetched a record by doing #find (or, as the message says, if you have a record after calling #save). #saved_changes? answers the question: did the last call to #save have any changes to change? So in the cases I can think of you can safely switch to this method in after callbacks.
I've got a model which has a video attached with Paperclip. After it saves I use the saved video to generate a thumbnail. I need to do this after every save, even when a new video hasn't been uploaded, because the user can change the time where the thumbnail is captured.
I am currently using after_post_process to do this, but it will only generate the thumbnail when uploading a file (this is a callback which is part of Paperclip).
I would ideally use an after_save callback like this:
after_save :save_thumbnail
def save_thumbnail
#generate thumbnail...
self.update_attributes(
:thumbnail_file_name => File.basename(thumb),
:thumbnail_content_type => 'image/jpeg'
)
end
Unfortunately update_attributes calls save, which then calls the before_save callback causing an infinite loop. Is there a simple way to circumvent this behaviour?
Any update_attribute in an after_save callback will cause recursion, in Rails3+.
What should be done is:
after_save :updater!
# Awesome Ruby code
# ...
# ...
private
def updater!
self.update_column(:column_name, new_value) # This will skip validation gracefully.
end
Here is some documentation about it: https://guides.rubyonrails.org/active_record_callbacks.html#skipping-callbacks
You could wrap it in a conditional, something like:
def save_thumbnail
if File.basename(thumb) != thumbnail_file_name
self.update_attributes(
:thumbnail_file_name => File.basename(thumb),
:thumbnail_content_type => 'image/jpeg'
)
end
end
That way it would only run once.
Rails 2:
Model.send(:create_without_callbacks)
Model.send(:update_without_callbacks)
Rails 3:
Vote.skip_callback(:save, :after, :add_points_to_user)
See this question:
How to skip ActiveRecord callbacks?
You can(and should) check if you actually need to update the thumbnail:
after_save :save_thumbnail
def save_thumbnail
if capture_time_changed? #assuming capture_time contains time when the thumbnail has to be captured
#generate thumbnail...
self.update_attributes(
:thumbnail_file_name => File.basename(thumb),
:thumbnail_content_type => 'image/jpeg'
)
end
end
Here you can read more about 'dirty' attributes: http://apidock.com/rails/ActiveRecord/Dirty
Although I'm not sure if it still can see the attribute changes in after_save. You can use a member variable to indicate changes in case it can't.
You can run it as a before_save instead.
After it has been validated, update the thumbnail, then let it go on to be saved, but just use the assignment methods
before_save :save_thumbnail
def save_thumbnail
self.thumbnail_file_name = File.basename(thumb),
self.thumbnail_content_type = 'image/jpeg'
end
Since that won't call save, you wont recurse, but it will immediately be saved after the method exits.
Something like that should work, unless there is an explicit reason you need it in after save.
Since you are not updating a separate object, but the same one, this will save you a database call as well. This is How i do timestamps and things like that too.
I have a model with a callback that runs after_update:
after_update :set_state
protected
def set_state
if self.valid?
self.state = 'complete'
else
self.state = 'in_progress'
end
end
But it doesn't actually save those values, why not? Regardless of if the model is valid or not it won't even write anything, even if i remove the if self.valid? condition, I can't seem to save the state.
Um, this might sound dumb, do I need to run save on it?
update
Actually, I can't run save there because it results in an infinite loop. [sighs]
after_update is run after update, so also after save. You can use update_attribute to save this value, or just call save (I'm not sure if there don't be any recurence). Eventualy you can assign it in before_update (list of availble options is here). On the other side invalid object will not be saved anyway, so why you want to assign here the state?
Judging by the fact that the examples in ActiveRecord documentation do things like this:
def before_save(record)
record.credit_card_number = encrypt(record.credit_card_number)
end
def after_save(record)
record.credit_card_number = decrypt(record.credit_card_number)
end
you do need to save the record yourself.
after_update works on the object in memory not on the record in the table. To update attributes in the DB do the following
after_update :set_state
protected
def set_state
if self.valid?
self.update_attribute('state', 'complete')
else
self.update_attribute('state', 'in_progress')
end
end
I am writing a plugin that provides drafting for models. A delete action is a draftable action and I do not always want to delete the origin until that deletion is published. So I wrote my own destroy method to help out with this. Everything works exactly as I want things to except, custom callbacks for :before_destroy and :after_destroy are no longer being triggered.
Any ideas on how to:
rebind callbacks to my destroy method
works some alias_method_chain voodoo
get a list of model callbacks so I can call them manual
solve this problem another way
Here is my destroy method:
def destroy
if self.attribute_names.include?('draft') && self.skip_draft == false
if handle_destroy # if true is returned
super # go ahead and destroy as normal
end
else
super
end
end
Update: I just found this:
correct way to override activerecordbasedestroy, but that seems like the proposed technique does not accomodate for callbacks either. Is there a way to have my cake and eat it too?
I was wrong about the callbacks not being called when super is called. I ended up relying on the exact code I initially posted. I changed how my handle_destroy method returned
I'll show you how I figured out how it is possible to fire the callbacks in the event you want to explicitly fire the callbacks.
def destroy
if self.attribute_names.include?('draft') && self.skip_draft == false
if handle_destroy # if true is returned
super # go ahead and destroy as normal
else
# Execute all custom callbacks that are not dependent type callbacks (ie: if the callback method name contains "dependent")
# Dependent callbacks delete records and this is not what the drafting system is all about.
(self.class.before_destroy_callback_chain + self.class.after_destroy_callback_chain).each do |cb|
unless (cb.method.kind_of?(Symbol) && cb.method.to_s.match(/dependent/))
cb.call(self)
end
end
end
else
# normal delete
super
end
end