Rails ActiveRecord callbacks dilemma - ruby-on-rails

I am trying to update a rails record if a duplicate exists as follow:
class Rating < ActiveRecord::Base
before_create :update_rating_if_already_exists
def update_rating_if_already_exists
original_rating = Rating.where(user: self.user, article: self.article)
if original_rating.blank?
true
else
original_rating[0].update_attribute(:score, self.score)
false
end
end
end
the problem however is that when I use after_create as above, this will not work for normal usage from controller action, as the controller will first build a new instance of the class with given params and then save (not create) the new object.
However, if I change the above to an after_save, it takes care of the controller problem, but then leads to another problem:
original_rating[0].update_attribute(:score, self.score)
will try to update the original record with a save method, which will also trigger the before_save...and on, and on... and this leads to a SystemStackError: stack level too deep error.
This is the dilemma now and my question is how could I go about this?
Thanks for all contributions. :)

You can use before_save with a new_record? condition to avoid executing the callback on an update_attribute call.
before_save :update_rating_if_already_exists, if: :new_record?
def update_rating_if_already_exists
# method code
end

Related

Mongoid 3 callbacks: before_upsert vs. before_save

For Mongoid 3+, is there a diagram/description of the various callbacks?
http://mongoid.org/en/mongoid/v3/callbacks.html
For example, what's the difference between before_upsert vs. before_save. Isn't a save caused by an insert or update call? Or does save also get called by destroy?
Also, what's difference between before_xxx and around_xxx?
Cheers,
With before_xxx the code is executed before the action and with around_xxx you have the option to execute code before and after the action itself.
For example, imagine you want to update all the user belongings after destroy a user project (User has_many :proyects and Project belongs_to User) :
class ProjectsController < ApplicationController
around_destroy :destroy_belongings
def destroy_belongings
old_user = self.user
...
# Here the before_destroy ends.
yield # Here the destroy is performed itself.
# Here the after_destroy starts. It's needed to do this operation after destroy the project because, imagine, the update_belongings method calculates something related to the current number of proyects. And a simple after_destroy is not useful as we would have lost the project owner.
old_user.update_belongings
end
end
You can also see related answers here and here. Moreover this other article could be useful for you.

Rails after_save method trying to update record over and over

I'm having a weird issue, my app seem to get stuck in a loop.
I have a model called Pages which allows the user to input a URL on save I have a after_save method called process_pages which looks as follows
pages.rb (model)
class Page < ActiveRecord::Base
require 'open-uri'
after_save :process_pages
def process_pages
self.html = open(self.url).read
self.save
end
end
On saving the URL I can see in the development console that it get the HTML of the site but tries to constantly save the record over and over again stalling the page and I have to manually exit the server.
When I start back up again the record has been added and works as expected until I add another URL?
Is there anything wrong with my code which might be causing this continuous loop?
Thanks for reading!
You are stuck in a loop because your callback is triggered after save, and then the method definition itself is calling save, causing the callback to fire again.
You have some options. You can change the callback to before_save.
class Page < ActiveRecord::Base
require 'open-uri'
before_save :process_pages
def process_pages
self.html = open(self.url).read
end
end
You can change to an after_create callback that only fires once the record is created and not updated, but this may not be desired behaviour.
You can also skip the callbacks by calling update_column instead of calling save as pointed out by #BroiSatse
This is because you are resaving it all the time. Instead do:
def process_pages
update_column(:html, open(self.url).read)
end
update_column is saving one column to the database skipping all the callbacks, so it won't retrigger your after_save callback. It is however pretty pointless to do two queries while you can do one with before_save filter:
before_save :process_pages
def process_pages
self.html = open(self.url).read
end
But probably best way to go with is overriding setter for url method:
def url=(value)
super
self.html = open(self.url).read
end
This way you can use page html before the model is saved.
If you need to call process_pages only once after Creating a Page record and not after Updating a Page record then I would suggest to use after_create instead.
class Page < ActiveRecord::Base
require 'open-uri'
after_create :process_pages
def process_pages
self.html = open(self.url).read
self.save
end
end
With after_save :process_pages, process_pages method would be called every time you save a Page record. You are saving a Page record again within process_pages method which triggers the after_save callback and you start looping.
See this SO Question for difference between after_save and after_create.
You will understand better as to why you are going in loops.
So does self.url point back to the pages url? If so, when you save it, it hits your server for the page, which might cause another save and another url request, etc.

Rails 4: Skip callback

I have an auction and a bid object in my application, when someone presses the BID BUTTON it then calls the BID CREATE controller which created the bid, and then does some other things on the auction object:
BIDS CONTROLLER -> CREATE
#auction.endtime += #auction.auctiontimer
#auction.winner = #auction.arewinning
#auction.save
AUCTION MODEL
before_update :set_endtime
def set_endtime
self.endtime=self.starttime+self.auctiontimer
end
So the question is: How can C skip the "before callback" only, in this specific #auction.save
skip_callback is a complicated and not granular option.
I prefer to use an attr_accessor:
attr_accessor :skip_my_method, :skip_my_method_2
after_save{ my_method unless skip_my_method }
after_save{ my_method_2 unless skip_my_method_2 }
That way you can be declarative when skipping a callback:
model.create skip_my_method: true # skips my_method
model.create skip_my_method_2: true # skips my_method_2
ActiveSupport::Callbacks::ClassMethods#skip_callback is not threadsafe, it will remove callback-methods for time till it is being executed and hence and another thread at same time cannot get the callback-methods for execution.
Look at the informative post by Allerin - SAVE AN OBJECT SKIPPING CALLBACKS IN RAILS APPLICATION
You can try skipping callback with skip_callback
http://www.rubydoc.info/docs/rails/4.0.0/ActiveSupport/Callbacks/ClassMethods:skip_callback
You can use update_columns
See this http://edgeguides.rubyonrails.org/active_record_callbacks.html#skipping-callbacks
Is there any specific condition like when you don't have endtime then only you need to set end time if that the you can do
def set_endtime
if endtime.nil?
self.endtime=self.starttime+self.auctiontimer
end
end
OR
before_update :set_endtime if: Proc.new { |obj| obj.endtime.nil? }

Rails 3: How to identify after_commit action in observers? (create/update/destroy)

I have an observer and I register an after_commit callback.
How can I tell whether it was fired after create or update?
I can tell an item was destroyed by asking item.destroyed? but #new_record? doesn't work since the item was saved.
I was going to solve it by adding after_create/after_update and do something like #action = :create inside and check the #action at after_commit, but it seems that the observer instance is a singleton and I might just override a value before it gets to the after_commit. So I solved it in an uglier way, storing the action in a map based on the item.id on after_create/update and checking its value on after_commit. Really ugly.
Is there any other way?
Update
As #tardate said, transaction_include_action? is a good indication, though it's a private method, and in an observer it should be accessed with #send.
class ProductScoreObserver < ActiveRecord::Observer
observe :product
def after_commit(product)
if product.send(:transaction_include_action?, :destroy)
...
Unfortunately, the :on option does not work in observers.
Just make sure you test the hell of your observers (look for test_after_commit gem if you use use_transactional_fixtures) so when you upgrade to new Rails version you'll know if it still works.
(Tested on 3.2.9)
Update 2
Instead of Observers I now use ActiveSupport::Concern and after_commit :blah, on: :create works there.
I think transaction_include_action? is what you are after. It gives a reliable indication of the specific transaction in process (verified in 3.0.8).
Formally, it determines if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks.
class Item < ActiveRecord::Base
after_commit lambda {
Rails.logger.info "transaction_include_action?(:create): #{transaction_include_action?(:create)}"
Rails.logger.info "transaction_include_action?(:destroy): #{transaction_include_action?(:destroy)}"
Rails.logger.info "transaction_include_action?(:update): #{transaction_include_action?(:update)}"
}
end
Also of interest may be transaction_record_state which can be used to determine if a record was created or destroyed in a transaction. State should be one of :new_record or :destroyed.
Update for Rails 4
For those seeking to solve the problem in Rails 4, this method is now deprecated, you should use transaction_include_any_action? which accepts an array of actions.
Usage Example:
transaction_include_any_action?([:create])
I've learned today that you can do something like this:
after_commit :do_something, :on => :create
after_commit :do_something, :on => :update
Where do_something is the callback method you want to call on certain actions.
If you want to call the same callback for update and create, but not destroy, you can also use:
after_commit :do_something, :if => :persisted?
It's really not documented well and I had a hard time Googling it. Luckily, I know a few brilliant people. Hope it helps!
You can solve by using two techniques.
The approach suggested by #nathanvda i.e. checking the created_at and updated_at. If they are same, the record is newly created, else its an update.
By using virtual attributes in the model. Steps are:
Add a field in the model with the code attr_accessor newly_created
Update the same in the before_create and before_update callbacks as
def before_create (record)
record.newly_created = true
end
def before_update (record)
record.newly_created = false
end
Based on leenasn idea, I created some modules that makes it possible to use after_commit_on_updateand after_commit_on_create callbacks: https://gist.github.com/2392664
Usage:
class User < ActiveRecord::Base
include AfterCommitCallbacks
after_commit_on_create :foo
def foo
puts "foo"
end
end
class UserObserver < ActiveRecord::Observer
def after_commit_on_create(user)
puts "foo"
end
end
Take a look at the test code: https://github.com/rails/rails/blob/master/activerecord/test/cases/transaction_callbacks_test.rb
There you can find:
after_commit(:on => :create)
after_commit(:on => :update)
after_commit(:on => :destroy)
and
after_rollback(:on => :create)
after_rollback(:on => :update)
after_rollback(:on => :destroy)
I use the following code to determine whether it is a new record or not:
previous_changes[:id] && previous_changes[:id][0].nil?
It based on idea that a new record has default id equal to nil and then changes it on save.
Of course id changing is not a common case, so in most cases the second condition can be omitted.
I'm curious to know why you couldn't move your after_commit logic into after_create and after_update. Is there some important state change that happens between the latter 2 calls and after_commit?
If your create and update handling has some overlapping logic, you could just have the latter 2 methods call a third method, passing in the action:
# Tip: on ruby 1.9 you can use __callee__ to get the current method name, so you don't have to hardcode :create and :update.
class WidgetObserver < ActiveRecord::Observer
def after_create(rec)
# create-specific logic here...
handler(rec, :create)
# create-specific logic here...
end
def after_update(rec)
# update-specific logic here...
handler(rec, :update)
# update-specific logic here...
end
private
def handler(rec, action)
# overlapping logic
end
end
If you still rather use after_commit, you can use thread variables. This won't leak memory as long as dead threads are allowed to be garbage-collected.
class WidgetObserver < ActiveRecord::Observer
def after_create(rec)
warn "observer: after_create"
Thread.current[:widget_observer_action] = :create
end
def after_update(rec)
warn "observer: after_update"
Thread.current[:widget_observer_action] = :update
end
# this is needed because after_commit also runs for destroy's.
def after_destroy(rec)
warn "observer: after_destroy"
Thread.current[:widget_observer_action] = :destroy
end
def after_commit(rec)
action = Thread.current[:widget_observer_action]
warn "observer: after_commit: #{action}"
ensure
Thread.current[:widget_observer_action] = nil
end
# isn't strictly necessary, but it's good practice to keep the variable in a proper state.
def after_rollback(rec)
Thread.current[:widget_observer_action] = nil
end
end
This is similar to your 1st approach but it only uses one method (before_save or before_validate to really be safe) and I don't see why this would override any value
class ItemObserver
def before_validation(item) # or before_save
#new_record = item.new_record?
end
def after_commit(item)
#new_record ? do_this : do_that
end
end
Update
This solution doesn't work because as stated by #eleano, ItemObserver is a Singleton, it has only one instance. So if 2 Item are saved at the same time #new_record could take its value from item_1 while after_commit is triggered by item_2. To overcome this problem there should be an item.id checking/mapping to "post-synchornize" the 2 callback methods : hackish.
You can change your event hook from after_commit to after_save, to capture all create and update events. You can then use:
id_changed?
...helper in the observer. This will be true on create and false on an update.

How can I avoid running ActiveRecord 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'
...

Resources