Reputation: 206
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
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_type
s.
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
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