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?
Related
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 am using Rails 3.2.19 and am having issues when using more than one if as a conditional in a callback.
The following mixed double condition works (evaluates first_condtion and second_condition)
after_save :after_save_method, unless: :first_condition?, if: :second_condition?
But if I use two if conditions the after_save_method is executed everytime. (It seems like is taking only the last condition)
after_save :after_save_method, if: :first_condition?, if: :second_condition?
I also tried to combine the two conditions with '&&' and that didn't work.
after_save :after_save_method, if: :first_condition? && :second_condition?
Why can't I use if more than once?
The apidoc has an example with unless and if at http://edgeguides.rubyonrails.org/active_record_callbacks.html#multiple-conditions-for-callbacks, but does not say anything about not allowing two "ifs."
My solution for this was to pull all the necessary code into a method and only evaluates that method, but I just want to make sure about the if stuff.
I suggest that you create a private method that knows about the logic you want:
after_save :after_save_method, if: :first_condition_and_second_condition?
private
def first_condition_and_second_condition?
first_condition? && second_condition?
end
You can use a Proc for this purpose.
after_save :after_save_method, :if => Proc.new{ first_condition? && second_condition? }
As stated by #tristanm here you can do something like this:
before_save do
if first_condition? && second_condition?
after_save_method
end
end
This is one of the options #tristanm suggested, check his answer if you want another option.
Worth mentioning that adding two separate if: conditions doesn't work because what you are passing as an argument to that method is a Ruby hash of options. The second if: overwrites the first.
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
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
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 | |