Rails 3 skip validations and callbacks - ruby-on-rails

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 | |

Related

How do I create a transaction out of multiple Rails save methods?

I'm using Rails 5. I have a model that looks like this
class CryptoIndexCurrency < ApplicationRecord
belongs_to :crypto_currency
end
I have a service method where I want to populate this table with records, which I do like so
CryptoIndexCurrency.delete_all
currencies.each do |currency|
cindex_currency = CryptoIndexCurrency.new({:crypto_currency => currency})
cindex_currency.save
end
The problem is the above is not very transactional, in as far as if something happens after the first statement, the "delete_all" will have executed but nothing else will have. What is the proper way to create a transaction here and equally as important, where do I place that code? Would like to know the Rails convention here.
I think you can just do:
CryptoIndexCurrency.transaction do
CryptoIndexCurrency.delete_all
CryptoIndexCurrency.create(currencies.map{ |c| {crypto_currency: c} })
end
If you are using Activerecord you can use the builtin transaction mechanism. Otherwise, one way would be to make sure you validate all your data and only save when everything is valid. Take a look at validates_associate and the like.
That said, if your process is inherently non validatable/nondeterministic (eg. you call external APIs to validate a payment) then the best is to ensure you have some cleaning methods that take care of your failure
If you have deterministic failures:
def new_currencies_valid?(currencies)
currencies.each do
return false if not currency.valid?(:create)
end
true
end
if new_currencies_valid?(new_currencies)
Currency.delete_all # See note
new_currencies.each(&:save)
end
A sidenote : unless you really understand what you are doing, I suggest calling destroy_all which runs callbacks on deletion (such as deleting dependent: :destroy) associations

How to skip all callbacks except paper-trail when updating an ActiveRecord model?

I have a model with paper-trail enabled. In one of my API routes, I have to run the_model.update_columns ... so that the model instance can be modified (and saved) without triggering all of the associated callbacks (these callbacks have a ton of side-effects which I don't want for this specific route).
However, I still want this change to be recorded by paper-trail. Is there a reasonable way I can achieve that?
This is simplistic and clunky, but it'd get done exactly what you want:
class ThisModel < ActiveRecord::Base
...
def update_me_no_callbacks(att_1, attr_2, attr_3, ...)
self.update_columns(
attr_1: attr_1,
attr_2: attr_2,
attr_3: attr_3,
...
)
# Do paper-trail code
end
end

ActiveRecord Callbacks List

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

Access previous value of association on record update

I have a "event" model that has many "invitations". Invitations are setup through checkboxes on the event form. When an event is updated, I wanted to compare the invitations before the update, to the invitations after the update. I want to do this as part of the validation for the event.
My problem is that I can't seem to access the old invitations in any model callback or validation. The transaction has already began at this point and since invitations are not an attribute of the event model, I can't use _was to get the old values.
I thought about trying to use a "after_initialize" callback to store this myself. These callbacks don't seem to respect the ":on" option though so I can't do this only :on :update. I don't want to run this every time a object is initialized.
Is there a better approach to this problem?
Here is the code in my update controller:
def update
params[:event][:invited_user_ids] ||= []
if #event.update_attributes(params[:event])
redirect_to #event
else
render action: "edit"
end
end
My primary goal is to make it so you can add users to an event, but you can't not remove users. I want to validate that the posted invited_user_ids contains all the users that currently are invited.
--Update
As a temporary solution I made use for the :before_remove option on the :has_many association. I set it such that it throws an ActiveRecord::RollBack exception which prevents users from being uninvited. Not exactly what I want because I can't display a validation error but it does prevent it.
Thank you,
Corsen
Could you use ActiveModel::Dirty? Something like this:
def Event < ActiveRecord::Base
validates :no_invitees_removed
def no_invitees_removed
if invitees.changed? && (invitees - invitees_was).present?
# ... add an error or re-add the missing invitees
end
end
end
Edit: I didn't notice that the OP already discounted ActiveModel::Dirty since it doesn't work on associations. My bad.
Another possibility is overriding the invited_user_ids= method to append the existing user IDs to the given array:
class Event < ActiveRecord::Base
# ...
def invited_user_ids_with_guard=(ids)
self.invited_user_ids_without_guard = self.invited_user_ids.concat(ids).uniq
end
alias_method_chain :invited_user_ids=, :guard
end
This should still work for you since update_attributes ultimately calls the individual attribute= methods.
Edit: #corsen asked in a comment why I used alias_method_chain instead of super in this example.
Calling super only works when you're overriding a method that's defined further up the inheritance chain. Mixing in a module or inheriting from another class provides a means to do this. That module or class doesn't directly "add" methods to the deriving class. Instead, it inserts itself in that class's inheritance chain. Then you can redefine methods in the deriving class without destroying the original definition of the methods (because they're still in the superclass/module).
In this case, invited_user_ids is not defined on any ancestor of Event. It's defined through metaprogramming directly on the Event class as a part of ActiveRecord. Calling super within invited_user_ids will result in a NoMethodError because it has no superclass definition, and redefining the method loses its original definition. So alias_method_chain is really the simplest way to acheive super-like behavior in this situation.
Sometimes alias_method_chain is overkill and pollutes your namespace and makes it hard to follow a stack trace. But sometimes it's the best way to change the behavior of a method without losing the original behavior. You just need to understand the difference in order to know which is appropriate.

Is there a way to prevent serialized attributes in rails from getting updated even if there are not changes?

This is probably one of the things that all new users find out about Rails sooner or later. I just realized that rails is updating all fields with the serialize keyword, without checking if anything really changed inside. In a way that is the sensible thing to do for the generic framework.
But is there a way to override this behavior? If I can keep track of whether the values in a serialized fields have changed or not, is there a way to prevent it from being pushed in the update statement? I tried using "update_attributes" and limiting the hash to the fields of interest, but rails still updates all the serialized fields.
Suggestions?
Here is a similar solution for Rails 3.1.3.
From: https://sites.google.com/site/wangsnotes/ruby/ror/z00---topics/fail-to-partial-update-with-serialized-data
Put the following code in config/initializers/
ActiveRecord::Base.class_eval do
class_attribute :no_serialize_update
self.no_serialize_update = false
end
ActiveRecord::AttributeMethods::Dirty.class_eval do
def update(*)
if partial_updates?
if self.no_serialize_update
super(changed)
else
super(changed | (attributes.keys & self.class.serialized_attributes.keys))
end
else
super
end
end
end
Yes, that was bugging me too. This is what I did for Rails 2.3.14 (or lower):
# config/initializers/nopupdateserialize.rb
module ActiveRecord
class Base
class_attribute :no_serialize_update
self.no_serialize_update = false
end
end
module ActiveRecord2
module Dirty
def self.included(receiver)
receiver.alias_method_chain :update, :dirty2
end
private
def update_with_dirty2
if partial_updates?
if self.no_serialize_update
update_without_dirty(changed)
else
update_without_dirty(changed | (attributes.keys & self.class.serialized_attributes.keys))
end
else
update_without_dirty
end
end
end
end
ActiveRecord::Base.send :include, ActiveRecord2::Dirty
Then in your controller use:
model_item.no_serialize_update = true
model_item.update_attributes(params[:model_item])
model_item.increment!(:hits)
model_item.update_attribute(:nonserializedfield => "update me")
etc.
Or define it in your model if you do not expect any changes to the serialized field once created (but update_attribute(:serialized_field => "update me" still works!)
class Model < ActiveRecord::Base
serialize :serialized_field
def no_serialize_update
true
end
end
I ran into this problem today and ended up hacking my own serializer together with a getter and setter. First I renamed the field to #{column}_raw and then used the following code in the model (for the media attribute in my case).
require 'json'
...
def media=(media)
self.media_raw = JSON.dump(media)
end
def media
JSON.parse(media_raw) if media_raw.present?
end
Now partial updates work great for me, and the field is only updated when the data is actually changed.
The problem with Joris' answer is that it hooks into the alias_method_chain chain, disabling all the chains done after (like update_with_callbacks which accounts for the problems of triggers not being called). I'll try to make a diagram to make it easier to understand.
You may start with a chain like this
update -> update_with_foo -> update_with_bar -> update_with_baz
Notice that update_without_foo points to update_with_bar and update_without_bar to update_with_baz
Since you can't directly modify update_with_bar per the inner workings of alias_method_chain you might try to hook into the chain by adding a new link (bar2) and calling update_without_bar, so:
alias_method_chain :update, :bar2
Unfortunately, this will get you the following chain:
update -> update_with_bar2 -> update_with_baz
So update_with_foo is gone!
So, knowing that alias_method_chain won't let you redefine _with methods my solution so far has been to redefine update_without_dirty and do the attribute selection there.
Not quite a solution but a good workaround in many cases for me was simply to move the serialized column(s) to an associated model - often this actually was a good fit semantically anyway.
There is also discussions in https://github.com/rails/rails/issues/8328.

Resources