why is my before_* keep triggering - ruby-on-rails

I have this loop
pages.each{|page| page.update_attribute(:sort_order, self.sort_order.to_i + 1)}
I used update_attribute to skip the before_update or before_save was not being called
before_save :set_data
before_update :set_data
but the set_data is being called again....any ideas on how to avoid this
I even tried the following
pages.each do |page|
page.sort_order = self.sort_order.to_i + 1
page.save(:validate => false)
end
I am trying to update and the later pages to a sort_order + 1 so I can have the pages in some order
Any ideas

The docs say that #update_attribute() and #save() invoke callbacks.
I had the same issue a few months ago and I don't think you can save a record without the callbacks being invoked.
Edit
I just saw #update_column() in the docs, which skips callbacks.

Seems that update_column might do the trick

What about this?
Page.where(:id => pages.collect(&:id)).update_all('sort_order=sort_order+1')
The advantage to this approach is you issue a single SQL query for an arbitrary number of rows. As a note, though, this will not alter the sort_order property of any models in memory.
This side-steps the entire callback chain. The documentation for update_attribute says that validation is skipped, but callbacks are invoked.

Related

ActiveRecord callback on when object has updated virtual attribute NOT in database

CODE
# Item Model
class Item < ActiveRecord::Base
attr_accessor :paid_amount
after_save :amount_processed?
def amount_processed?
if self.try(:paid_amount)
return true
else
return false
end
end
end
# Controller snippet
...
# params = {"paid_amount" => 10}
#item.assign_attributes(params)
if #item.valid?
#item.save
end
...
Currently the callback is not running, i.e., the code never checks amount_processed?. The reason this is happening is because paid_amount isn't a db attribute for Item. But that is by design. The question is ASSUMING this design were to stay, would there be a way for me to run a callback to check amount_processed? simply based on the fact that the attribute was passed? (i.e., if you run #item.paid_amount you'd get "10" after the #item.assign_attributes).
Note that the following callbacks will not work:
after_save or after_touch because as above, the paid_amount is never saved so the #item is never updated
after_find because this runs, by definition, before the attribute assignment. So with this validation, even though amount_processed? is checked, when it is checked, #item.paid_amount = nil
Would love to combine the two...
Since the question asks how to do this GIVEN current design, a perfectly acceptable answer is to say in the current design, it's not possible. The callback will only work if the attribute is actually updated. In that case, I already have 2 strategies to tackle this, the easiest of which being moving amount_processed? to the controller level so I can check the paid_amount after the assign_attributes. The other strategy is to have a Child of Item, but this is dependent on other info about the code that, for simplicity's sake, I have withheld.
Thanks!
Ook I think I have the answer here, thanks for the comments. Willem is right, in the current design, I can ensure amount_processed? is run by using a custom validation, changing the callback to:
validate :amount_processed?
However, doing so then makes the code a bit hacky, since I'm co-opting a validation to do the work of a callback. In other words, I would have to ensure amount_processed? always returned true (at end of the if statement; obviously other work would be done with paid_amount). There are some other considerations as well looking holistically at my code.
Given that, may change the design... but this was still a very helpful exercise

How to save a model without running callbacks in Rails

I need to calculate values when saving a model in Rails. So I call calculate_averages as a callback for a Survey class:
before_save :calculate_averages
However, occasionally (and initially I have 10k records that need this operation) I need to manually update all the averages for every record. No problem, I have code like the following:
Survey.all.each do |survey|
survey.some_average = (survey.some_value + survey.some_other_value) / 2.to_f
#and some more averages...
survey.save!
end
Before even running this code, I'm worried the calculate_averages is going to get called and duplicate this and probably even cause some problems with the way I'm doing things. Ok, so then I think, well I'll just do nothing and let calculate_averages get called and do its thing. Problem there is, first, is there a way to force callbacks to get called even if you made no changes to the record?
Secondly, the way averages are calculated it's far more efficient to simply not let the callbacks get called at all and do the averages for everything all at once. Is this possible to not let callbacks get called?
I believe what you are asking for can be achieved with ActiveSupport::Callbacks. Have a look at set_callback and skip_callback.
In order to "force callbacks to get called even if you made no changes to the record", you need to register the callback to some event e.g. save, validate etc..
set_callback :save, :before, :my_before_save_callback
To skip the before_save callback, you would do:
Survey.skip_callback(:save, :before, :calculate_average).
Please reference the linked ActiveSupport::Callbacks on other supported options such as conditions and blocks to set_callback and skip_callback.
To disable en-mass callbacks use...
Survey.skip_callback(:save, :before, :calculate_averages)
Then to enable them...
Survey.set_callback(:save, :before, :calculate_average)
This skips/sets for all instances.
update_column is an ActiveRecord function which does not run any callbacks, and it also does not run validation.
Doesn't work for Rails 5
Survey.skip_callback(:save, :before, :calculate_average)
Works for Rails 5
Survey.skip_callback(:save, :before, :calculate_average, raise: false)
https://github.com/thoughtbot/factory_bot/issues/931
If you want to conditionally skip callbacks after checking for each survey you can write your custom method.
For ex.
Modified callback
before_save :calculate_averages, if: Proc.new{ |survey| !survey.skip_callback }
New instance method
def skip_callback(value = false)
#skip_callback = #skip_callback ? #skip_callback : value
end
Script to update surveys
Survey.all.each do |survey|
survey.some_average = (survey.some_value + survey.some_other_value) / 2.to_f
#and some more averages...
survey.skip_callback(true)
survey.save!
end
Its kinda hack but hope will work for you.
Rails 5.2.3 requiring an after party script to NOT trigger model events, update_column(column_name, value) did the trick:
task.update_column(task_status, ReferenceDatum::KEY_COMPLETED)
https://apidock.com/rails/ActiveRecord/Persistence/update_column
hopefully this is what you're looking for.
https://stackoverflow.com/a/6587546/2238259
For your second issue, I suspect it would be better to inspect when this calculation needs to happen, it would be best if it could be handled in batch at a specified time where network traffic is at its trough.
EDIT: Woops. I actually found 2 links but lost the first one, apparently. Hopefully you have it fixed.
For Rails 3 ActiveSupport::Callbacks gives you the necessary control. You can reset_callbacks en-masse, or use skip_callback to disable judiciously like this:
Vote.skip_callback(:save, :after, :add_points_to_user)
…after which you can operate on Vote instances with :add_points_to_user inhibited

What's the best way to save an ActiveRecord model instance?

I am still new to Ruby on Rails and in my models I often do this:
def activate
update_column(:activated, true)
update_column(:activated_at, Time.zone.now)
update_column(:activation_token, nil)
end
What difference does it make if I instead do this?
def activate
self.activated = true
self.activated_at = Time.zone.now
self.activation_token = nil
save!(:validate => false)
end
I still don't unterstand the difference between these two approaches. Which one is faster or more efficient from a database point of view?
When you call update_column as soon as the call is made, a query is generated and executed on the database. So you end up with three update queries.
But when you change the object's attributes using the second method, and then finally call save, a single query will be generated and executed to make all the changes made on the object (representing the record).
So in terms of effeciency you should always go with the second one. Remember the lesser the number of writes or queries on the database the better!
update_column :
Updates a single attribute of an object, without calling save.
Validation is skipped.
Callbacks are skipped.
updated_at/updated_on column is not updated if that column is available.
will fire 1 query for each update_column call.
where as
save :
will perform validations, callbacks, update updated_at column and will fire a single query.
For your case second option is definitely the better one and yes, you should remove :validate => false form save to avoid any validation issues.

Empty Scope with Ruby on Rails

Following Problem:
I need something like an empty scope. Which means that this scope is emtpy, but responds to all methods a scope usually responds to.
I'm currently using a little dirty hack. I simply supply "1=0" as conditions. I find this realy ugly, since it hits the database. Simply returning an empty array won't work, since the result must respond to the scoped methods.
Is there a better existing solution for this or will I need to code this myself?
Maybe some example code could help explain what i need:
class User < ActiveRecord::Base
named_scope :admins, :conditions => {:admin => true }
named_scope :none_dirty, :conditions => "1=0" # this scope is always empty
def none_broken
[]
end
def self.sum_score # okay, a bit simple, but a method like this should work!
total = 0
self.all.each do |user|
total += user.score
end
return total
end
end
User.admin.sum_score # the score i want to know
User.none_drity.sum_score # works, but hits the db
User.none_broken.sum_score # ...error, since it doesn't respond to sum_score
Rails 4 introduces the none scope.
It is to be used in instances where you have a method which returns a relation, but there is a condition in which you do not want the database to be queried.
If you want a scope to return an unaltered scope use all:
No longer will a call to Model.all execute a query immediately and return an array of records. In Rails 4, calls to Model.all is equivalent to now deprecated Model.scoped. This means that more relations can be chained to Model.all and the result will be lazily evaluated.
User.where('false')
returns an ActiveRecord::Relation with zero elements, that is a chain-able scope that won't hit the database until you actually try to access one of its elements. This is similar to PhilT's solution with ('1=0') but a little more elegant.
Sorry User.scoped is not what you want. As commented this returns everything. Should have paid more attention to the question.
I've seen where('1 = 0') suggested before and Rails should probably cache it as well.
Also, where('1 = 0') won't hit the database until you do .all, .each, or one of the calculations methods.
I thing you need User.scoped({})
How about User.where(id: nil) ?
Or User.where(_id: nil) for mongoid.
The thing you are looking for does not exist. You could implement something like this by monky patching the find method. Yet, this would be an overkill, so I recomend keeping this unless it's performance critical.
Looking at your example code indicates you may not know about aggregated queries in SQL which are exposed as calculations methods in Rails:
User.sum(:score) will give you the sum of all users' scores
Take a look at Rails Guides for more info:
http://guides.rubyonrails.org/active_record_querying.html#sum

Using the after_save callback to modify the same object without triggering the callback again (recursion)

If I add an after_save callback to an ActiveRecord model, and on that callback I use update_attribute to change the object, the callback is called again, and so a 'stack overflow' occurs (hehe, couldn't resist).
Is it possible to avoid this behavior, maybe disabling the callback during it's execution? Or is there another approach?
Thanks!
One workaround is to set a variable in the class, and check its value in the after_save.
Check it first. (if var)
Assign it to a 'false' value before calling update_attribute.
call update_attribute.
Assign it to a 'true' value.
end
This way, it'll only attempt to save twice. This will likely hit your database twice, which may or may not be desirable.
I have a vague feeling that there's something built in, but this is a fairly foolproof way to prevent a specific point of recursion in just about any application.
I would also recommend looking at the code again, as it's likely that whatever you're doing in the after_save should be done in before_save. There are times that this isn't true, but they're fairly rare.
Could you use the before_save callback instead?
I didn't see this answer, so I thought I'd add it in case it helps anyone searching on this topic. (ScottD's without_callbacks suggestion is close.)
ActiveRecord provides update_without_callbacks for this situation, but it is a private method. Use send to get access to it anyway. Being inside a callback for the object you are saving is exactly the reason to use this.
Also there is another SO thread here that covers this pretty well:
How can I avoid running ActiveRecord callbacks?
Also you can look at the plugin Without_callbacks. It adds a method to AR that lets you skip certain call backs for a given block.
Example:
def your_after_save_func
YourModel.without_callbacks(:your_after_save_func) do
Your updates/changes
end
end
Check out how update_attribute is implemented. Use the send method instead:
send(name.to_s + '=', value)
If you use before_save, you can modify any additional parameters before the save is completed, meaning you won't have to explicitly call save.
This code doesn't even attempt to address threading or concurrency issues, much like Rails proper. If you need that feature, take heed!
Basically, the idea is to keep a count at what level of recursive calls of "save" you are, and only allow after_save when you are exiting the topmost level. You'll want to add in exception handling, too.
def before_save
#attempted_save_level ||= 0
#attempted_save_level += 1
end
def after_save
if (#attempted_save_level == 1)
#fill in logic here
save #fires before_save, incrementing save_level to 2, then after_save, which returns without taking action
#fill in logic here
end
#attempted_save_level -= 1 # reset the "prevent infinite recursion" flag
end
Thanks guys, the problem is that I update other objects too (siblings if you will)... forgot to mention that part...
So before_save is out of the question, because if the save fails all the modifications to the other objects would have to be reverted and that could get messy :)
The trick is just to use #update_column:
Validations are skipped.
Callbacks are skipped.
updated_at/updated_on are not updated.
Additionally, it simply issues a single quick update query to the db.
http://apidock.com/rails/ActiveRecord/Persistence/update_columns
I had this problem too. I need to save an attribute which depends upon the object id. I solved it by using conditional invocation for the callback ...
Class Foo << ActiveRecord::Base
after_save :init_bar_attr, :if => "bar_attr.nil?" # just make sure this is false after the callback runs
def init_bar_attr
self.bar_attr = "my id is: #{self.id}"
# careful now, let's save only if we're sure the triggering condition will fail
self.save if bar_attr
end
Sometimes this is because of not specifying attr_accessible in models. When update_attribute wants to edit the attributes, if finds out they are not accessible and create new objects instead.On saving the new objects, it will get into an unending loop.
I had a need to gsub the path names in a block of text when its record was copied to a different context:
attr_accessor :original_public_path
after_save :replace_public_path, :if => :original_public_path
private
def replace_public_path
self.overview = overview.gsub(original_public_path, public_path)
self.original_public_path = nil
save
end
The key to stop the recursion was to assign the value from the attribute and then set the attribute to nil so that the :if condition isn't met on the subsequent save.
You can use after_save in association with if as follows:
after_save :after_save_callback, if: Proc.new {
//your logic when to call the callback
}
or
after_save :after_save_callback, if: :call_if_condition
def call_if_condition
//condition for when to call the :after_save_callback method
end
call_if_condition is a method. Define the scenario when to call the after_save_callback in that method

Resources