How can I go about rescuing an error that was generated in an after_save callback, and then ultimately display it to the user? The code in my model looks something like this:
Class MyModel
after_save :call_other_class_responsible_for_parsing
def call_other_class_responsible_for_parsing
# this method is used by multiple models
ModelTwo.parse_css
end
end
In my controller, I currently redirect the user elsewhere if the update was successful, however, I consider the update to be successful if it passed all of the existing validations and there were no errors in the callback (from the Less::Parser).
EDIT:
I mixed up my thoughts in my original question. MyModel gets saved from it's corresponding controller, which then runs the after_save callback from the model. Inside call_other_class_responsible_for_parsing, there is a call to another model, let's say ModelTwo, which does the Less parsing. I've tried using code like this:
def self.parse_css
#my_model = MyModel.find(1)
css_to_compile = Less::Parser.new.parse(css).to_css
rescue Less::Error => error
#my_model.errors[:base] << "Error message"
false
end
end
But the false does not prevent the transaction from succeeding, therefore a redirect happens.
You could use transaction :
def create
MyModel.transaction do
#my_model.save
#my_model.my_method
end
rescue ActiveRecord::RecordInvalid => exception
# rescue active record exception here
rescue Less::Error => exception
# rescue less error here
end
This remove the need for a callback.
I hope this helps!
Callback chains are implicitly wrapped in a transaction. When a callback returns false or raises an exception then the whole transaction is rolled back and saving fails.
In your case, you're parsing some CSS, so I'm not sure whether after_save is the right place for this. I recommend you give validations a try. Consider the following:
class MyModel < ActiveRecord::Base
validate :valid_css
private
def parsed_css
#parsed_css ||= Less::Parser.new.parse(css).to_css
end
def valid_css
parsed_css
rescue Less::Error => error
errors.add(:css, "Cannot parse CSS: #{error}")
end
end
This will parse the CSS before saving the object and add an error if it's invalid. Also, the result of #to_css will be stored so that you won't need to recompute it. This is the approach that I'd recommend.
If you'd like to stick with after_save then you should raise an exception to abort the transaction. In your case, it's simply about not rescuing Less::Error:
class MyModel < ActiveRecord::Base
after_save :compile_css
private
def compile_css
css_to_compile = Less::Parser.new.parse(css).to_css
end
end
If you're having trouble deciding which approach to use then leave a question below and I'll help.
I'm trying to run all callback methods manually inside a method. For example, I want to run all "before_destroy" methods inside my model.
Is there a way to manually trigger this? For example, something like:
def some_method
# ...
trigger(:before_destroy)
end
which will then run all methods that I have declared with "before_destroy :...."
Any ideas?
If you're happy to run both :before and :after hooks, you can try run_callbacks.
From the docs:
run_callbacks(kind, &block)
Runs the callbacks for the given event.
Calls the before and around callbacks in the order they were set, yields the block (if given one), and then runs the after callbacks in reverse order.
If the callback chain was halted, returns false. Otherwise returns the result of the block, or true if no block is given.
run_callbacks :save do
save
end
class Foo < ActiveRecord::Base
def destroy_method_1
end
def destroy_method_2
end
before_destroy :destroy_method_1, :destroy_method_2
DESTROY_METHODS = [:destroy_method_1, :destroy_method_2]
def some_method
DESTROY_METHODS.each {|m| send(m) }
end
end
I have a model that uses after_update to log changes. There is a case where I would like to make a change to the model without activating this logging mechanism. Is there a way to pass in a parameter to after_update, or skip it all together?
I would like a nice solution to this, and am willing to remove after_update if there is a better way to go about it.
I would go with the approach of adding a boolean to the model as suggested but would then write a method to help set and clear the flag after your update. e.g.
def without_logging_changes_to(model)
# store current value of the flag so it can be restored
# when we leave the block
remembered_value = model.log_update
model.log_update = false
begin
yield
ensure
model.log_update = remembered_value
end
end
Then to use it:
without_logging_changes_to my_model do
my_model.update_attributes(updates)
end
You could add a boolean to your model that is something like log_last_update and check that in the after_update callback.
class MyModel < ActiveRecord::Base
after_update :do_something
attr_accessor :should_do_something
def should_do_something?
should_do_something != false
end
def do_something
if should_do_something?
...
end
end
end
y = MyModel.new
y.save! # callback is triggered
n = MyModel.new
n.should_do_something = false
n.save! # callback isn't triggered
In Rails 2 you can use private ActiveRecord methods
update_without_callbacks
create_without_callbacks
They can be invoked via the send method:
# Update attributes on your model
your_model.some_attribute = some_value
# Update model without callbacks
your_model.send(:update_without_callbacks)
I have some models that have after_save callbacks. Usually that's fine, but in some situations, like when creating development data, I want to save the models without having the callbacks run. Is there a simple way to do that? Something akin to...
Person#save( :run_callbacks => false )
or
Person#save_without_callbacks
I looked in the Rails docs and didn't find anything. However in my experience the Rails docs don't always tell the whole story.
UPDATE
I found a blog post that explains how you can remove callbacks from a model like this:
Foo.after_save.clear
I couldn't find where that method is documented but it seems to work.
Use update_column (Rails >= v3.1) or update_columns (Rails >= 4.0) to skip callbacks and validations. Also with these methods, updated_at is not updated.
#Rails >= v3.1 only
#person.update_column(:some_attribute, 'value')
#Rails >= v4.0 only
#person.update_columns(attributes)
http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-update_column
#2: Skipping callbacks that also works while creating an object
class Person < ActiveRecord::Base
attr_accessor :skip_some_callbacks
before_validation :do_something
after_validation :do_something_else
skip_callback :validation, :before, :do_something, if: :skip_some_callbacks
skip_callback :validation, :after, :do_something_else, if: :skip_some_callbacks
end
person = Person.new(person_params)
person.skip_some_callbacks = true
person.save
UPDATE (2020)
Apparently Rails has always supported :if and :unless options, so above code can be simplified as:
class Person < ActiveRecord::Base
attr_accessor :skip_some_callbacks
before_validation :do_something, unless: :skip_some_callbacks
after_validation :do_something_else, unless: :skip_some_callbacks
end
person = Person.new(person_params)
person.skip_some_callbacks = true
person.save
This solution is Rails 2 only.
I just investigated this and I think I have a solution. There are two ActiveRecord private methods that you can use:
update_without_callbacks
create_without_callbacks
You're going to have to use send to call these methods. examples:
p = Person.new(:name => 'foo')
p.send(:create_without_callbacks)
p = Person.find(1)
p.send(:update_without_callbacks)
This is definitely something that you'll only really want to use in the console or while doing some random tests. Hope this helps!
Updated:
#Vikrant Chaudhary's solution seems better:
#Rails >= v3.1 only
#person.update_column(:some_attribute, 'value')
#Rails >= v4.0 only
#person.update_columns(attributes)
My original answer :
see this link: How to skip ActiveRecord callbacks?
in Rails3,
assume we have a class definition:
class User < ActiveRecord::Base
after_save :generate_nick_name
end
Approach1:
User.send(:create_without_callbacks)
User.send(:update_without_callbacks)
Approach2:
When you want to skip them in your rspec files or whatever, try this:
User.skip_callback(:save, :after, :generate_nick_name)
User.create!()
NOTE: once this is done, if you are not in rspec environment, you should reset the callbacks:
User.set_callback(:save, :after, :generate_nick_name)
works fine for me on rails 3.0.5
If the goal is to simply insert 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, consider using a "shadow object" pointing to your existing db table. Like so:
class ImportedPerson < ActiveRecord::Base
self.table_name = 'people'
end
This works with every version of Rails, is threadsafe, and completely eliminates all validations and callbacks with no modifications to your existing code. You can just toss that class declaration in right before your actual import, and you should be good to go. Just remember to use your new class to insert the object, like:
ImportedPerson.new( person_attributes )
rails 3:
MyModel.send("_#{symbol}_callbacks") # list
MyModel.reset_callbacks symbol # reset
You could try something like this in your Person model:
after_save :something_cool, :unless => :skip_callbacks
def skip_callbacks
ENV[RAILS_ENV] == 'development' # or something more complicated
end
EDIT: after_save is not a symbol, but that's at least the 1,000th time I've tried to make it one.
You can use update_columns:
User.first.update_columns({:name => "sebastian", :age => 25})
Updates the given attributes of an object, without calling save, hence skipping validations and callbacks.
The only way to prevent all after_save callbacks is to have the first one return false.
Perhaps you could try something like (untested):
class MyModel < ActiveRecord::Base
attr_accessor :skip_after_save
def after_save
return false if #skip_after_save
... blah blah ...
end
end
...
m = MyModel.new # ... etc etc
m.skip_after_save = true
m.save
Looks like one way to handle this in Rails 2.3 (since update_without_callbacks is gone, etc.), would be to use update_all, which is one of the methods that skips callbacks as per section 12 of the Rails Guide to validations and callbacks.
Also, note that if you are doing something in your after_ callback, that does a calculation based on many association (i.e. a has_many assoc, where you also do accepts_nested_attributes_for), you will need to reload the association, in case as part of the save, one of its members was deleted.
The most up-voted answer might seem confusing in some cases.
You can use just a simple if check if you would like to skip a callback, like this:
after_save :set_title, if: -> { !new_record? && self.name_changed? }
with Rails 6 you can now use the insert methods
from the documentation:
Inserts multiple records into the database in a single SQL INSERT
statement. It does not instantiate any models nor does it trigger
Active Record callbacks or validations. Though passed values go
through Active Record's type casting and serialization.
https://gist.github.com/576546
just dump this monkey-patch into config/initializers/skip_callbacks.rb
then
Project.skip_callbacks { #project.save }
or the like.
all credit to the author
A solution that should work across all versions of Rails without the use of a gem or plugin is simply to issue update statements directly. eg
ActiveRecord::Base.connection.execute "update table set foo = bar where id = #{self.id}"
This may (or may not) be an option depending on how complex your update is. This works well for eg updating flags on a record from within an after_save callback (without retriggering the callback).
When I need full control over the callback, I create another attribute that is used as a switch. Simple and effective:
Model:
class MyModel < ActiveRecord::Base
before_save :do_stuff, unless: :skip_do_stuff_callback
attr_accessor :skip_do_stuff_callback
def do_stuff
puts 'do stuff callback'
end
end
Test:
m = MyModel.new()
# Fire callbacks
m.save
# Without firing callbacks
m.skip_do_stuff_callback = true
m.save
# Fire callbacks again
m.skip_do_stuff_callback = false
m.save
I needed a solution for Rails 4, so I came up with this:
app/models/concerns/save_without_callbacks.rb
module SaveWithoutCallbacks
def self.included(base)
base.const_set(:WithoutCallbacks,
Class.new(ActiveRecord::Base) do
self.table_name = base.table_name
end
)
end
def save_without_callbacks
new_record? ? create_without_callbacks : update_without_callbacks
end
def create_without_callbacks
plain_model = self.class.const_get(:WithoutCallbacks)
plain_record = plain_model.create(self.attributes)
self.id = plain_record.id
self.created_at = Time.zone.now
self.updated_at = Time.zone.now
#new_record = false
true
end
def update_without_callbacks
update_attributes = attributes.except(self.class.primary_key)
update_attributes['created_at'] = Time.zone.now
update_attributes['updated_at'] = Time.zone.now
update_columns update_attributes
end
end
in any model:
include SaveWithoutCallbacks
then you can:
record.save_without_callbacks
or
Model::WithoutCallbacks.create(attributes)
# for rails 3
if !ActiveRecord::Base.private_method_defined? :update_without_callbacks
def update_without_callbacks
attributes_with_values = arel_attributes_values(false, false, attribute_names)
return false if attributes_with_values.empty?
self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).arel.update(attributes_with_values)
end
end
None of these points to without_callbacks plugin that just does what you need ...
class MyModel < ActiveRecord::Base
before_save :do_something_before_save
def after_save
raise RuntimeError, "after_save called"
end
def do_something_before_save
raise RuntimeError, "do_something_before_save called"
end
end
o = MyModel.new
MyModel.without_callbacks(:before_save, :after_save) do
o.save # no exceptions raised
end
http://github.com/cjbottaro/without_callbacks works with Rails 2.x
I wrote a plugin that implements update_without_callbacks in Rails 3:
http://github.com/dball/skip_activerecord_callbacks
The right solution, I think, is to rewrite your models to avoid callbacks in the first place, but if that's impractical in the near term, this plugin may help.
If you are using Rails 2. You could use SQL query for updating your column without running callbacks and validations.
YourModel.connection.execute("UPDATE your_models SET your_models.column_name=#{value} WHERE your_models.id=#{ym.id}")
I think it should work in any rails versions.
For creating test data in Rails you use this hack:
record = Something.new(attrs)
ActiveRecord::Persistence.instance_method(:create_record).bind(record).call
https://coderwall.com/p/y3yp2q/edit
You can use sneaky-save gem: https://rubygems.org/gems/sneaky-save.
Note this cannot help in saving associations along without validations. It throws error 'created_at cannot be null' as it directly inserts the sql query unlike a model. To implement this, we need to update all auto generated columns of db.
For custom callbacks, use an attr_accessor and an unless in the callback.
Define your model as follows:
class Person << ActiveRecord::Base
attr_accessor :skip_after_save_callbacks
after_save :do_something, unless: :skip_after_save_callbacks
end
And then if you need to save the record without hitting the after_save callbacks you defined, set the skip_after_save_callbacks virtual attribute to true.
person.skip_after_save_callbacks #=> nil
person.save # By default, this *will* call `do_something` after saving.
person.skip_after_save_callbacks = true
person.save # This *will not* call `do_something` after saving.
person.skip_after_save_callbacks = nil # Always good to return this value back to its default so you don't accidentally skip callbacks.
Why would you want to be able to do this in development? Surely this will mean you are building your application with invalid data and as such it will behave strangely and not as you expect in production.
If you want to populate your dev db with data a better approach would be to build a rake task that used the faker gem to build valid data and import it into the db creating as many or few records as you desire, but if you are heel bent on it and have a good reason I guess that update_without_callbacks and create_without_callbacks will work fine, but when you are trying to bend rails to your will, ask yourself you have a good reason and if what you are doing is really a good idea.
One option is to have a separate model for such manipulations, using the same table:
class NoCallbacksModel < ActiveRecord::Base
set_table_name 'table_name_of_model_that_has_callbacks'
include CommonModelMethods # if there are
:
:
end
(Same approach might make things easier for bypassing validations)
Stephan
Another way would be to use validation hooks instead of callbacks. For example:
class Person < ActiveRecord::Base
validate_on_create :do_something
def do_something
"something clever goes here"
end
end
That way you can get the do_something by default, but you can easily override it with:
#person = Person.new
#person.save(false)
Something that should work with all versions of ActiveRecord without depending on options or activerecord methods that may or may not exist.
module PlainModel
def self.included(base)
plainclass = Class.new(ActiveRecord::Base) do
self.table_name = base.table_name
end
base.const_set(:Plain, plainclass)
end
end
# usage
class User < ActiveRecord::Base
include PlainModel
validates_presence_of :email
end
User.create(email: "") # fail due to validation
User::Plain.create(email: "") # success. no validation, no callbacks
user = User::Plain.find(1)
user.email = ""
user.save
TLDR: use a "different activerecord model" over the same table
I faced the same problem when I wanted to run a Rake Task but without running the callbacks for every record I was saving.
This worked for me (Rails 5), and it must work for almost every version of Rails:
class MyModel < ApplicationRecord
attr_accessor :skip_callbacks
before_create :callback1
before_update :callback2
before_destroy :callback3
private
def callback1
return true if #skip_callbacks
puts "Runs callback1"
# Your code
end
def callback2
return true if #skip_callbacks
puts "Runs callback2"
# Your code
end
# Same for callback3 and so on....
end
The way it works is that it just returns true in the first line of the method it skip_callbacks is true, so it doesn't run the rest of the code in the method.
To skip callbacks you just need to set skip_callbacks to true before saving, creating, destroying:
rec = MyModel.new() # Or Mymodel.find()
rec.skip_callbacks = true
rec.save
Not the cleanest way, but you could wrap the callback code in a condition that checks the Rails environment.
if Rails.env == 'production'
...