I have an Item object that has a boolean value called has_instore_image that when set in a before_save method must be set to 0 rather than false. Is there a reason why this is so? Is this the expected behavior? I'm using Rails 3.2.19.
code:
class Item < ActiveRecord::Base
attr_accessible :has_instore_image
before_save :set_has_instore_image
def set_has_instore_image
if self.instore_images.count>0
self.has_instore_image=true
else
self.has_instore_image=0
#self.has_instore_image=false
end
end
From the Rails docs on callbacks:
If a before_* callback returns false, all the later callbacks and the associated action are cancelled.
So what happens is that, as the last expression of your callback evaluates to false, then the callback chain is stopped and the save action fails.
You can remedy it like this:
def set_has_instore_image
if self.instore_images.count>0
self.has_instore_image=true
else
self.has_instore_image=false
end
true
end
In fact, it's considered good practice to end all your before_*callback definitions returning true to avoid this very same problem.
Related
I'm setting up an after_save callback in my model observer to send a notification only if the model's published attribute was changed from false to true. Since methods such as changed? are only useful before the model is saved, the way I'm currently (and unsuccessfully) trying to do so is as follows:
def before_save(blog)
#og_published = blog.published?
end
def after_save(blog)
if #og_published == false and blog.published? == true
Notification.send(...)
end
end
Does anyone have any suggestions as to the best way to handle this, preferably using model observer callbacks (so as not to pollute my controller code)?
Rails 5.1+
Use saved_change_to_published?:
class SomeModel < ActiveRecord::Base
after_update :send_notification_after_change
def send_notification_after_change
Notification.send(…) if (saved_change_to_published? && self.published == true)
end
end
Or if you prefer, saved_change_to_attribute?(:published).
Rails 3–5.1
Warning
This approach works through Rails 5.1 (but is deprecated in 5.1 and has breaking changes in 5.2). You can read about the change in this pull request.
In your after_update filter on the model you can use _changed? accessor. So for example:
class SomeModel < ActiveRecord::Base
after_update :send_notification_after_change
def send_notification_after_change
Notification.send(...) if (self.published_changed? && self.published == true)
end
end
It just works.
For those who want to know the changes just made in an after_save callback:
Rails 5.1 and greater
model.saved_changes
Rails < 5.1
model.previous_changes
Also see: http://api.rubyonrails.org/classes/ActiveModel/Dirty.html#method-i-previous_changes
To anyone seeing this later on, as it currently (Aug. 2017) tops google: It is worth mentioning, that this behavior will be altered in Rails 5.2, and has deprecation warnings as of Rails 5.1, as ActiveModel::Dirty changed a bit.
What do I change?
If you're using attribute_changed? method in the after_*-callbacks, you'll see a warning like:
DEPRECATION WARNING: The behavior of attribute_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_change_to_attribute? instead. (called from some_callback at /PATH_TO/app/models/user.rb:15)
As it mentions, you could fix this easily by replacing the function with saved_change_to_attribute?. So for example, name_changed? becomes saved_change_to_name?.
Likewise, if you're using the attribute_change to get the before-after values, this changes as well and throws the following:
DEPRECATION WARNING: The behavior of attribute_change 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_change_to_attribute instead. (called from some_callback at /PATH_TO/app/models/user.rb:20)
Again, as it mentions, the method changes name to saved_change_to_attribute which returns ["old", "new"].
or use saved_changes, which returns all the changes, and these can be accessed as saved_changes['attribute'].
In case you can do this on before_save instead of after_save, you'll be able to use this:
self.changed
it returns an array of all changed columns in this record.
you can also use:
self.changes
which returns a hash of columns that changed and before and after results as arrays
The "selected" answer didn't work for me. I'm using rails 3.1 with CouchRest::Model (based on Active Model). The _changed? methods don't return true for changed attributes in the after_update hook, only in the before_update hook. I was able to get it to work using the (new?) around_update hook:
class SomeModel < ActiveRecord::Base
around_update :send_notification_after_change
def send_notification_after_change
should_send_it = self.published_changed? && self.published == true
yield
Notification.send(...) if should_send_it
end
end
you can add a condition to the after_update like so:
class SomeModel < ActiveRecord::Base
after_update :send_notification, if: :published_changed?
...
end
there's no need to add a condition within the send_notification method itself.
You just add an accessor who define what you change
class Post < AR::Base
attr_reader :what_changed
before_filter :what_changed?
def what_changed?
#what_changed = changes || []
end
after_filter :action_on_changes
def action_on_changes
#what_changed.each do |change|
p change
end
end
end
I have an action that simply toggles the #active attribute to the opposite boolean state:
If #blog.active == true then update it to inactive
If #blog.active == false then update it to active
I got the following custom action in a controller to work, but there has to be some Rails way to more elegantly do this:
class BlogsController < ApplicationController
...
def toggle_active
if #blog.active?
#blog.update(active: false)
else
#blog.update(active: true)
end
end
end
Is there a Rails way of updating a boolean attribute to the opposite boolean state?
ActiveRecord has the toggle and toggle! methods which do this. Just keep in mind that the toggle! method skips validation checks.
class BlogsController < ApplicationController
...
def toggle_active
#blog.toggle!(:active)
end
end
If you want to run validations you can do
#blog.toggle(:active).save
You can read the source code for the methods here
Active Record's toggle and toggle! methods handle toggling a Boolean attribute:
def toggle_active
#blog.toggle(:active).save
end
def toggle_active
#blog.toggle!(:active)
end
toggle changes the attribute, but doesn't save the change to the database (so a separate call to save or save! is needed).
toggle! changes the attribute and saves the record, but bypasses the model's validations.
Rails provides toggle for this, but it's not atomic, and you still have to call save and consider validations. The bang-method alternative, will handle persistence for you, but validations are skipped.
It's probably better to be explicit about what you're wanting to do here. If this action is the result of a form POST or an XHR request, accept the actual value you want to set (RESTful) instead of expecting the client to be aware of the current state of the database.
So while this works:
#blog.toggle(:active)
…it's non-deterministic and can result in the opposite of the desired action occurring. I would recommend this instead:
class BlogStatusController < ApplicationController
# ... boilerplate to load #blog
def update
#blog.update(status_params)
end
protected
def status_params
params.require(:blog).permit(status: [:active])
end
end
If you need an atomic toggle, you can easily achieve it with a SQL statement wrapped in a convenient method name: https://stackoverflow.com/a/24218418/203130
This should work:
#blog.update(active: !#blog.active?)
! is your friend:
!false # => true
!true # => false
You could try this:
#blog.update(active: !#blog.active?)
That's pretty compact.
I have a model
class Vehicule < ActiveRecord::Base
before_validation :set_principal, :if =>:new_record?
private
def set_principal
self.principal ||= !!principal
end
end
If I test it, when I do Vehicule.new.valid? it always return false. Why did I had to add ̀true in the function to pass the test ?
private
def set_principal
self.principal ||= !!principal
true
end
For starters, failed validations populate an errors object in your record instance. You can see the errors by running model.errors.full_messages which is always useful for debugging.
Secondly, test for the presence of a value using the present? method rather than using the awkward !! approach which returns false when called on nil. It would make your code much more clear but it's also useful because it recognizes "empty" objects as well. In other words "".present? == false and [].present? == false and {}.present? == false nil.present? == false and so on. There's also the blank? method which is the logical opposite of present?.
Finally, be mindful of your return value when using the :before_validation callback. Unlike other callbacks it will prevent validation if the function returns false.
because not not nil => false.
In IRB:
foo = nil
!!foo => false
if you try Vehicule.new(principal: 'WORKS').valid?
def set_principal
self.principal ||= !!principal
end
It should pass.
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? }
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)