How can I disable a validation and callbacks in a rails STI derived model?
Asked Answered
A

7

20

Given a model

class BaseModel < ActiveRecord::Base
  validates_presence_of :parent_id
  before_save :frobnicate_widgets
end

and a derived model (the underlying database table has a type field - this is simple rails STI)

class DerivedModel < BaseModel
end

DerivedModel will in good OO fashion inherit all the behaviour from BaseModel, including the validates_presence_of :parent_id. I would like to turn the validation off for DerivedModel, and prevent the callback methods from firing, preferably without modifying or otherwise breaking BaseModel

What's the easiest and most robust way to do this?

Acerbity answered 10/11, 2008 at 22:52 Comment(0)
T
49

I like to use the following pattern:

class Parent < ActiveRecord::Base
  validate_uniqueness_of :column_name, :if => :validate_uniqueness_of_column_name?
  def validate_uniqueness_of_column_name?
    true
  end
end

class Child < Parent
  def validate_uniqueness_of_column_name?
    false
  end
end

It would be nice if rails provided a skip_validation method to get around this, but this pattern works and handles complex interactions well.

Taxidermy answered 28/3, 2009 at 8:27 Comment(0)
R
8

As a variation of the answer by @Jacob Rothstein, you can create a method in parent:

class Parent < ActiveRecord::Base
  validate_uniqueness_of :column_name, :unless => :child?
  def child?
    is_a? Child
  end
end

class Child < Parent
end

The benefit of this approach is you not need to create multiple methods for each column name you need to disable validation for in Child class.

Rutharuthann answered 18/4, 2012 at 11:6 Comment(1)
Shorter: validate_uniqueness_of :column_name, :unless => "is_a? Child"Zonate
A
3

From poking around in the source (I'm currently on rails 1.2.6), the callbacks are relatively straightforward.

It turns out that the before_validation_on_create, before_save etc methods, if not invoked with any arguments, will return the array which holds all the current callbacks assigned to that 'callback site'

To clear the before_save ones, you can simply do

before_save.clear

and it seems to work

Acerbity answered 10/11, 2008 at 23:45 Comment(0)
A
2

A cleaner way is this one:

class Parent < ActiveRecord::Base
  validate :column_name, uniqueness: true, if: 'self.class == Parent'
end


class Child < Parent
end

Or you can use it also in this way:

class Parent < ActiveRecord::Base
  validate :column_name, uniqueness: true, if: :check_base

  private

  def check_base
    self.class == Parent
  end
end


class Child < Parent
end

So, uniqueness validation is done if the instance class of model is Parent.

  1. Instance class of Child is Child and differs from Parent.
  2. Instance class of Parent is Parent and is the same as Parent.
Azelea answered 25/2, 2015 at 20:40 Comment(1)
Hey.. This is a great way you suggested. I am using a class from a gem, so I can't change the Parent class and the function "check_base". Can you suggest me a way where I add some sort of validation skip in the child class.Tectrix
G
2

Since rails 3.0 you can also access the validators class method to manipulate get a list of all validations. However, you can not manipulate the set of validations via this Array.

At least as of rails 5.0 you however seem to be able to manipulate the _validators (undocumented) method.

Using this method you can modify the validations in the subclass like e.g.:

class Child < Parent
  # add additional conditions if necessary
  _validators.reject! { |attribute, _| attribute == :parent_id } 
end

While this uses an undocumented method, is has the benefit of not requiring the superclass to know anything about the child's implementation.

Gelding answered 14/9, 2017 at 7:41 Comment(2)
reject! - delete elements that match from current array. Return the array if something was rejected, or nil when none.Raychel
beware with the use of documented api, maybe someday they make a undocumented changes about it.Raychel
A
0

Again poking around in the source, it seems that validations can be run either on every save, or updates/creates only. This maps to

:validate => all saves
:validate_on_create => creations only
:validate_on_update => updates only

To clear them, you can use write_inheritable_attribute, like this:

write_inheritable_attribute :validate, nil
Acerbity answered 10/11, 2008 at 23:47 Comment(0)
A
0

Here is a slight variation of RubyDev's that I've been using in mongoid 3.

class Parent
  include Mongoid::Document
  validates :column_name , uniqueness: true, unless: Proc.new {|r| r._type == "Child"}
end

class Child < Parent
end

It has been working pretty good for me so far.

Affinitive answered 30/1, 2013 at 7:29 Comment(1)
Awareness of existing Child class in the Parent isn't the best approach. I'm trying to cope with it in other way.Bullock

© 2022 - 2024 — McMap. All rights reserved.