ActiveRecord Callbacks List - ruby-on-rails

I've been going through the rails source for a while now, and I don't think there's a better way of getting the list of all callbacks other than: ActiveRecord::Callbacks::CALLBACKS – which is a constant list.
Meaning if you're using a gem like devise_invitable that adds a new callback called :invitation_accepted with the score :after and :before then ActiveRecord::Callbacks::CALLBACKS will not work.
Do you know of an easy fix, other than opening up rails modules and making sure there's an internal list of call-backs per model class?

You can call Model._save_callbacks to get a list of all callbacks on save.
You can then filter it down to what kind you need e.g. :before or :after like this:
Model._save_callbacks.select {|cb| cb.kind == :before}
Works the same for Model._destroy_callbacks etc.

The docs say "There are nineteen callbacks in total"... but they don't seem to say what all of those 19 actually are!
For those who Googled "list of all ActiveRecord callbacks" like I did, here's the list (found by using ActiveRecord::Callbacks::CALLBACKS as described in the question):
:after_initialize
:after_find
:after_touch
:before_validation
:after_validation
:before_save
:around_save
:after_save
:before_create
:around_create
:after_create
:before_update
:around_update
:after_update
:before_destroy
:around_destroy
:after_destroy
:after_commit
:after_rollback

Note that if you simply want to trigger callbacks, you can use the #run_callbacks(kind) method:
o = Order.find 123 # Created with SQL
o.run_callbacks(:create)
o.run_callbacks(:save)
o.run_callbacks(:commit)

If you're working in a Rails version prior to the ._save_callbacks method, you can use the following:
# list of callback_chain methods that return a CallbackChain
Model.methods.select { |m| m.to_s.include? "callback" }.sort
# get all methods in specific call back chain, like after_save
Model.after_save_callback_chain.collect(&:method)

I am going to propose most universal solution.
It works even when gems are declaring custom callbacks e.g. paranoia and after_real_destroy
To list all callbacks
Model.methods.select { |m| m.to_s.include? "callback" }.sort
Then you can get given callbacks if you type method name e.g.
Model._update_callbacks
Model._real_destroy_callbacks
Model._destroy_callbacks
If you list all callbacks, then you can find callback you need by checking #filter instance variable e.g.
require 'pp'
Activity._destroy_callbacks.each_with_index { |clbk,index| puts "#{index}-------\n#{clbk.pretty_inspect}" } ; nil
# [...]
#<ActiveSupport::Callbacks::Callback:0x00007ff14ee7a968
#chain_config=
{:scope=>[:kind, :name],
:terminator=>
#<Proc:0x00007ff13fb825f8#/Users/mypc/.rbenv/versions/2.3.7/lib/ruby/gems/2.3.0/gems/activemodel-4.1.16/lib/active_model/callbacks.rb:103 (lambda)>,
:skip_after_callbacks_if_terminated=>true},
#filter=
#<Proc:0x00007ff14ee7ac10#/Users/mypc/.rbenv/versions/2.3.7/lib/ruby/gems/2.3.0/gems/activerecord-4.1.16/lib/active_record/associations/builder/association.rb:135 (lambda)>,
#if=[],
#key=70337193825800,
#kind=:before,
#name=:destroy,
#unless=[]>
4-------
#<ActiveSupport::Callbacks::Callback:0x00007ff14ee3a228
#chain_config=
{:scope=>[:kind, :name],
:terminator=>
#<Proc:0x00007ff13fb825f8#/Users/mypc/.rbenv/versions/2.3.7/lib/ruby/gems/2.3.0/gems/activemodel-4.1.16/lib/active_model/callbacks.rb:103 (lambda)>,
:skip_after_callbacks_if_terminated=>true},
#filter=:audit_destroy,
#if=[],
#key=:audit_destroy,
#kind=:before,
#name=:destroy,
#unless=[]>
5-------

For after_commit callbacks, call Model._commit_callbacks.
Be mindful, however, that there's a known bug in Rails (still present in Rails 5.2.2) that after_commit callbacks are not run in the order they are declared in the model, even tough they appear in the correct order in that _commit_callbacks call.
More info: Execution order of multiple after_commit callbacks (Rails) and https://github.com/rails/rails/issues/20911

Related

Rails: Tracing Callbacks

I have a model that won't update properly with update_attributes, but will update using update_column. I'm assuming this is because a callback is interfering. Unfortunately, it's not throwing any errors, so I can't figure out where exactly the problem is coming from.
Is there a way to trace callbacks so I can go through them, one by one, until I find the culprit?
The API documentation shows how you can access the callback chain.
Here's some one liners that you can use in your console that should give you the idea:
# Print before_validate callbacks
Post._validate_callbacks.select { |cb| cb.kind.eql? :before }.each { |cb| puts cb.filter }
# Print after_update callbacks
Post._update_callbacks.select { |cb| cb.kind.eql? :after }.each { |cb| puts cb.filter }
Remember that updates to models will also call save so it's a good idea to trawl through them all.
Check to see that the params you are passing to the update_attributes() method are mass assignable.
They should be defined as :attr_accessible in your rails model otherwise they will be stripped out before saving.
class Widget < ActiveRecord::Base
attr_accessible :name
end
More info here http://guides.rubyonrails.org/security.html

Rails 3 skip validations and callbacks

I have a particularly complex model with validations and callbacks defined. The business needs now calls for a particular scenario where adding a new record requires skipping the validations and callbacks. What's the best way to do this?
This works in Rails 3:
Model.skip_callback(:create)
model.save(:validate => false)
Model.set_callback(:create)
(API docs and related question)
Use ActiveRecord::Persistence#update_column, like this:
Model.update_column(field, value)
If the goal is to simply insert or update a record without callbacks or validations, and you would like to do it without resorting to additional gems, adding conditional checks, using RAW SQL, or futzing with your exiting code in any way, it may be possible to use a "shadow object" which points to your existing db table. Like so:
class ImportedUser < ActiveRecord::Base
# To import users with no validations or callbacks
self.table_name = 'users'
end
This works with every version of Rails, is threadsafe, and completely eliminates all validations and callbacks with no modifications to your existing code. Just remember to use your new class to insert the object, like:
ImportedUser.new( person_attributes )
My take was like this (note: this disables callbacks on create, for update, delete and others you need to add them to array).
begin
[:create, :save].each{|a| self.class.skip_callback(a) } # We disable callbacks on save and create
# create new record here without callbacks, tou can also disable validations with
# .save(:validate => false)
ensure
[:create, :save].each{|a| self.class.set_callback(a) } # and we ensure that callbacks are restored
end
I would recommend NOT using the skip_callback approach since it is not thread safe. The sneaky save gem however is since it just runs straight sql. Note this will not trigger validations so you will have to call them yourself (ex: my_model.valid?).
Here are some samples from their docs:
# Update. Returns true on success, false otherwise.
existing_record.sneaky_save
# Insert. Returns true on success, false otherwise.
Model.new.sneaky_save
# Raise exception on failure.
record.sneaky_save!
What about adding a method to your model that let's you skip the callbacks?
class Foo < ActiveRecord::Base
after_save :do_stuff
def super_secret_create(attrs)
self.skip_callback(:create)
self.update_attributes(attrs)
self.save(:validate => false)
self.set_callback(:create)
end
end
If you end up using something like this, I would recommend using self in the method instead of the model name to avoid connascence of name.
I also ran across a gist from Sven Fuchs that looks nice, it's here
I wrote a simple gem for skipping validations adhoc, but it could probably be updated to include skipping call backs as well.
https://github.com/npearson72/validation_skipper
You could take the can_skip_validation_for in the gem and add functionality for also skipping callbacks. Maybe call the method can_skip_validation_and_callbacks_for
Everything else would work the same. If you want help with doing that, let me know.
This hack worked for me at last (redefined _notify_comment_observer_for_after_create method for the object):
if no_after_create_callback
def object._notify_comment_observer_for_after_create; nil; end
end
None of these will work if your validations are written into the database itself.
+------------------------------------+--------------------------------------------------+------+-----+--------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------------------------------+--------------------------------------------------+------+-----+--------------------+----------------+
| status | enum('Big','Small','Ugly','Stupid','Apologetic') | NO | | Stupid | |

Skipping callbacks and validation

Is there a way to skip callbacks and validation by doing something along these lines in Rails 3?
Object.save(:validate => false, :skip_callbacks => true)
Thanks!
Object.save(:validate => false)
works as you would expect. So far as I know you cannot turn off callbacks (unless you return false from a before_ callback, but that then aborts the transaction).
I encountered something like that before and I used this:
Model.send(:create_without_callbacks)
Model.send(:update_without_callbacks)
Skipping callbacks is a bit tricky. Some plugins and adapters add their own "essential" callbacks (acts_as_nested_set, oracle_enhanced_adapter as examples).
You could use the skip_callback and set_callback methods in checking which ones you'd be able to skip.
Some custom class methods could help:
def skip_all_callbacks(klass)
[:validation, :save, :create, :commit].each do |name|
klass.send("_#{name}_callbacks").each do |_callback|
# HACK - the oracle_enhanced_adapter write LOBs through an after_save callback (:enhanced_write_lobs)
if (_callback.filter != :enhanced_write_lobs)
klass.skip_callback(name, _callback.kind, _callback.filter)
end
end
end
end
def set_all_callbacks(klass)
[:validation, :save, :create, :commit].each do |name|
klass.send("_#{name}_callbacks").each do |_callback|
# HACK - the oracle_enhanced_adapter write LOBs through an after_save callback (:enhanced_write_lobs)
if (_callback.filter != :enhanced_write_lobs)
klass.set_callback(name, _callback.kind, _callback.filter)
end
end
end
end
For skipping callbacks in Rails 3, you can use update_all for your given purpose.
Source: update_all
The full list for skipping callbacks are here:
decrement
decrement_counter
delete
delete_all
find_by_sql
increment
increment_counter
toggle
touch
update_column
update_all
update_counters
Source: Skipping Callbacks
If you are trying to update the record skipping all callbacks and validations you could use update_columns passing the attributes hash. This method will update the columns direct on database.
For example:
object.update_columns(name: 'William')
If you want to create a new object, unfortunately I think there is no method to skip both validations and callbacks. save(:validate => false) works for validations. For callbacks you could use skip_callback but be careful, your code probably will not be thread-safe.
http://guides.rubyonrails.org/active_record_validations_callbacks.html details a small list of methods that avoid callbacks and validations - none of these include 'save' though.
However, the point of validations and callbacks is to enforce business logic. If you're avoiding them - you should ask yourself why.
See also: How can I avoid running ActiveRecord callbacks?

Delayed_job : defining time

So I'm trying to figure out how to do this cleanly. Trying to keep the example simple lets say I have an object, MyMailMeeting in which I define two times that I want to send two different emails to people in a meeting.
In my MyMailMeeting model, I want to define both methods (rather than having their own delayed job class), but I need to have those methods see the times defined within the object in order to know when to send.
def send_first
... do stuff
end
handle_asynchronously :send_first, :run_at => Proc.new { send_first_time }
Problem is that according to the documentation, send_first_time needs to be a class method, and want it to be an instance method so that I can see the times defined by the user.
How do I do this? Or do I just need to create two separate Delayed_job classes and do something like this:
Delayed::Job.enqueue(SendFirstJob.new, :run_at => send_first_time)
I believe that handle_asynchronously passes the object into Procs for its attributs so:
handle_asynchronously :send_first, :run_at => Proc.new { |obj| obj.send_first_time }
You can always roll your own async wrapper
def send_first_async(*args)
delay(:run_at => send_first_time).send_first(*args)
end
I ended up using the second method. Though it isn't as time sensitive as I would like.
If anyone has an answer on how to get send_first_time to be a variable that is based on user input, I'll gladly accept it as the right answer. Thanks.

Is there a Rails way to check which attributes have been updated in an observer?

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 ..

Resources