Lyrical
Lyrical

Reputation: 206

active_record-acts_as - validate parent fields on child model

I'm using Rails 4.2.0 and the active_record-acts_as gem.

This gem simulates multi-table inheritance for ActiveRecord models.

I have my parent model called Attachment with child models Specification and Release.

class Attachment < ActiveRecord::Base
  actable
end

class Specification < ActiveRecord::Base
  acts_as :attachment
end

class Release < ActiveRecord::Base
  acts_as :attachment
end

My Attachment model has fields name, actable_id, actable_type (used by acts_as gem) and the standard paperclip fields.

Specification and Release have multiple fields specific to their type (so I don't think they are good candidates for single table inheritance).

What I am trying to do is validate name on the child models instead of the parent, as different rules apply to Release and Specification.

Presence validations seem to work fine:

class Specification < ActiveRecord::Base
  acts_as :attachment

  validates :name, presence: true
end

But when I try something like:

class Specification < ActiveRecord::Base
  acts_as :attachment

  validates :name, presence: true, uniqueness: { case_sensitive: true }
end

I get the following error when calling .valid?

NoMethodError: undefined method `limit' for nil:NilClass

I wrote some custom validations which work on the child model, but I was hoping I wouldn't have to.

The main reason for validating on the child was because I use the following to get more concise error messages depending on the model (Specification, Release):

class Specification < ActiveRecord::Base
  ...

  HUMANIZED_ATTRIBUTES = {
    name: "Version"
  }

  def self.human_attribute_name(attr, options={})
    HUMANIZED_ATTRIBUTES[attr.to_sym] || super
  end
end

So it returns something like:

Version can't be blank. instead of Name can't be blank.

I also tried validating on Attachment (the parent) using something like:

with_options if: Proc.new { |x| x.actable_type == "Specification" } do |s|
  s.validates :name, presence: true, uniqueness: { case_sensitive: true }
end

But then I don't get the error messages I want. Name instead of Version.

I am probably massively over complicating things. Any ideas?

Upvotes: 1

Views: 830

Answers (2)

Lyrical
Lyrical

Reputation: 206

As Evgeny Petrov worked out, it isn't possible to validate parent attributes on the child models (probably can be done with a custom Validator).

In the end I opted for validating on the parent model using with_options if: to target certain actable_types.

And in order to sort out some custom error messages, I combined the HUMANIZED_ATTRIBUTES hash on the child class with self.human_attribute_name on the parent.

Here is an example:

class Attachment < ActiveRecord::Base
  actable

  before_validation :set_humanized_attributes

  class << self; attr_reader :humanized_attributes end
  @humanized_attributes = {}

  with_options if: proc { |x| x.actable_type == 'Release' do |s|
    s.validates :name, presence: true, uniqueness: { case_sensitive: false }
  end

  with_options if: proc { |x| x.actable_type == 'Specification' do |s|
    s.validates :name, presence: true, uniqueness: { case_sensitive: true }
  end

  def self.human_attribute_name(attr, options = {})
    humanized_attributes[attr.to_sym] || super
  end

  private

  def set_humanized_attributes
    @humanized_attributes = actable_type.constantize::HUMANIZED_ATTRIBUTES
  end
end

class Specification < ActiveRecord::Base
  acts_as :attachment
  HUMANIZED_ATTRIBUTES = {
    name: 'Version'
  }

  # child attribute validations here
end

class Release < ActiveRecord::Base
  acts_as :attachment
  HUMANIZED_ATTRIBUTES = {
    name: 'Release'
  }

  # child attribute validations here
end

In rails console:

> s = Specification.new
=> #<Specification id ... >
> s.valid?
=> false
> s.errors.full_messages
=> ["Version can't be blank"]
> r = Release.new
=> #<Release id ... >
> r.valid?
=> false
> r.errors.full_messages
=> ["Release can't be blank"]

Need to check some edge cases and make it more robust, but for now this is achieving what I required.

Upvotes: 1

Eugene Petrov
Eugene Petrov

Reputation: 1598

Just to summarize a discussion in the comments above for one that encounter similar error:


The problem was in the absence of specifications.name column in the db.

When you validate presence it checks only attribute, when you check uniqueness it seeks in the db for this column.

Upvotes: 1

Related Questions