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.
Related
I get a depreciation warning from Rails (5.1.5) when I'm using this customer validation to prevent unwanted changes on a model:
class Post < ApplicationRecord
validate :cannot_change_after_publishing, on: :update
def cannot_change_after_publishing
return if changed_attributes.key?(:published_at) && changed_attributes[:published_at].nil?
# also used this: published_at_changed? && published_at_was.nil?
unchangeable = %w[message author other_things published_at]
errors.add(:published_at, "Oh dang!") if changed.any? {|attr| unchangeable.include?(attr)
# yeah, this isn't the actual message.
end
end
Then I get these messages:
DEPRECATION WARNING: 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.
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.keys` instead.
Which I understand, except that I'm not using these methods in an after callback. I'd like to catch this before it hits the database/gets saved.
If there's something I'm supposed to use instead (or you guys see a better way of doing this), please let me know.
I figured things out thanks to this earlier answer:
https://stackoverflow.com/a/44987787/4983172
class Post < ApplicationRecord
validate :cannot_change_after_publishing, on: :update
def cannot_change_after_publishing
return if published_at_change_to_be_saved && published_at_in_database.nil?
unchangeable = %w[message author other_things]
errors.add(:published_at, "Oh dang!") if unchangeable.any? {|attr| changed_attribute_names_to_save.include?(attr)
end
end
Again though, if anyone sees a better way, feel free to throw it in.
May below help you.
class Post < ApplicationRecord
UNCHANGABLE = %w[message author other_things].freeze
validate :cannot_change_after_publishing, on: :update
def cannot_change_after_publishing
return if saved_change_to_attribute?(:published_at) && changed_attribute_names_to_save[:published_at].nil?
errors.add(:published_at, "Oh dang!") if UNCHANGABLE.any?{|attr| saved_change_to_attribute?(attr) }
end
end
ActiveRecord::AttributeMethods::Dirty
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.
I have an ActivityObserver, which is observing tasks, and has an after_update callback.
I want to test if a particular attribute has been modified in the update.
Is there a Rails way to compare the attributes of the subject with what they were before the update, or to check if they have changed?
When an after_update callback is being executed, every ActiveModel object has a method called changed_attributes. You can check it out in your debug environment. Every ActiveRecord object has this method. It has a hash of all the values that have been changed/modified. This is also known as Dirty object.
Check out some of these tutorials
Railscasts
Dirty Object
There must be something like following in your observer.
class ActivityObserver < ActiveRecord::Observer
def after_update(activity)
if activity.attribute_name_changed?
puts "The above condition will return true or false, and this time it has returned true..!!!"
end
end
end
The above method will do. I think you were looking for this ..
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
Maybe some Ruby experts out there can shed some light on how activerecord know to do an insert or update when calling save(). What is the logic behind it? Does it check to see if the primary key is blank or something and if so does an insert, if not an update?
Whilst it's fine for some people to say "RTFM" I rather the more walk-through-but-still-entirely-useless-when-Rails-3-comes-out-and-changes-everything response:
How it works in Rails 2.3 (aka "today")
save calls create_or_update which looks like this:
def create_or_update
raise ReadOnlyRecord if readonly?
result = new_record? ? create : update
result != false
end
You can ignore the first line of this method as it only raises an error if the record is readonly (it isn't usually, but in the case of joins it may be). What we are interested in here is the second and third lines inside the method.
The second line calls new_record? which is defined as this:
# Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet; otherwise, returns false.
def new_record?
#new_record || false
end
And the variable #new_record is set when the initialize (new calls initialize, and gives us a new object, some background Ruby-fu here) method is called.
So if this #new_record is true it'll call create and if it's false it'll call update which brings us to what you're after, I think.
Furthermore, when you find a record it does not call initialize and therefore does not set #new_record. If you noticed, the code behind new_record? was #new_record || false, meaning it will return false if #new_record was not set.
Let's say for example you want to find the last Forum record, so you would do Forum.last.
This calls the last method on the Forum class, which inherits from ActiveRecord::Base
last calls the find class method.
find calls find_last
find_last calls find_initial
find_initial calls find_every
find_every calls find_by_sql
and find_by_sql calls instantiate
You'll see here that nowhere along this change is #new_record set and thus any record obtained by find will not be a new record.
Hope this helps you understand.
It principaly relies on the new_record? method.
This method returns true if it's a new record and false if it's not.
In fact it's not really hard.
When you get an existing record, it's not new. So new_record? can direcly return false.
When you create a new record (Model.new), new_record? will return true. It's a new record.
When you save that new record, it's not new anymore. The internal variable #new_record gets updated. new_record? won't return true anymore.
To see when it happens, go to ActiveRecord::Base, line 2911
self.id ||= new_id
#new_record = false
id
end
Check out activerecord's doc here and the source code there.