I have a model, Card
, with multiple subclasses through single table inheritance. I have another model, CardSet
, with multiple subclasses through single table inheritance. Cards have many CardSets, and CardSets have many Cards, both through Memberships.
Given that a class FooCard
is a subclass of Card
, and class FooSet
is a subclass of CardSet
, I wanted to validate that FooSet instances only have associations with Cards of type FooCard.
After a lot of experimentation, study and liberal plagiarism of the Ruby on Rails ActiveRecord::Validations
code, I wrote the following custom validation in lib/custom_validators.rb
, which I load with require 'custom_validators'
at the end of config/environment.rb
.
The below code is working exactly as I intended! The only problem I have is that I don't like using code in my apps that I don't understand. The call to validate
in lib/custom_validators.rb
(below) was stolen piecemeal from different places in ActiveRecord::Validations
and I just don't understand how it works. It seems that it calls an empty method that somehow accepts a block, even though the method is empty?(!) I understand the Membership.find()
part fine, it's the block that wraps around it I'm having trouble with.
Can anyone help me understand what is happening in that validate
method call?
module ActiveRecord
module Validations
module ClassMethods
def validates_association_type( *attr_names )
attr_name = attr_names.first.to_s
attr_name_singular = attr_name.singularize
attr_type = attr_names.last[:is].to_s
validate do |record|
membership = Membership.find(
:first,
:include => attr_name_singular.to_sym, :conditions => [
"#{attr_name_singular}_id IN (?) AND #{attr_name}.type <> ?",
record.send( attr_name ).map(&:id),
attr_type
]
)
unless membership.nil?
record.errors.add_to_base( "allows only #{attr_name} of type #{attr_type}" )
end
end
end
end
end
end
This validator is added to the FooSet class like this:
class FooSet < CardSet
validates_association_type :cards, :is => "FooCard"
end
If you are interested, the various models are set up like this:
class Card < ActiveRecord::Base
has_many :memberships
has_many :card_sets, :through => :memberships
end
class Membership < ActiveRecord::Base
belongs_to :card
belongs_to :card_set
end
class CardSet < ActiveRecord::Base
has_many :memberships
has_many :cards, :through => :memberships
end
class FooCard < Card
end
class BarCard < Card
end
class FooSet < CardSet
validates_association_type :cards, :is => "FooCard"
end
class BarSet < CardSet
validates_association_type :cards, :is => "BarCard"
end