Skip to content

Instantly share code, notes, and snippets.

@woahdae
Last active December 12, 2015 00:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save woahdae/4686951 to your computer and use it in GitHub Desktop.
Save woahdae/4686951 to your computer and use it in GitHub Desktop.
Polymorphic relationships supporting referential integrity.

Usage:

  polymorphic :commentable do
    belongs_to :post
    belongs_to :comment
    belongs_to :product
  end

Adds validations that only one is set, and an accessor ('commentable' from above) that gets and sets the polymorphic relation.

module BetterPolymorphic
def polymorphic(polymorphic_name)
reflections_prior = self.reflections
yield
# Hash subtraction
new_reflections = (self.reflections.to_a - reflections_prior.to_a).
inject({}) {|acc, (k, v)| acc[k] = v; acc}
define_method polymorphic_name do
new_reflections.each_pair do |method, reflection|
return send(method) if send(reflection.foreign_key)
end
end
define_method "#{polymorphic_name}=" do |model|
new_reflections.each_pair do |method, reflection|
if model.is_a?(reflection.class_name.constantize)
return send("#{method}=", model)
end
end
end
define_method "validate_#{polymorphic_name}_belongs_to_one_model" do
if new_reflections.map(&:last).select {|r| send(r.foreign_key)}.size < 1
errors.add(:base, 'must belong to a model')
end
end
private "validate_#{polymorphic_name}_belongs_to_one_model"
define_method "validate_#{polymorphic_name}_only_belongs_to_one_model" do
if new_reflections.map(&:last).select {|r| send(r.foreign_key) }.size > 1
errors.add(:base, 'cannot belong to more than one model')
end
end
private "validate_#{polymorphic_name}_only_belongs_to_one_model"
validate "validate_#{polymorphic_name}_belongs_to_one_model"
validate "validate_#{polymorphic_name}_only_belongs_to_one_model"
end
end
shared_examples_for :better_polymorphic do |options|
let(:polymorphic_name) { options.keys.first }
let(:polymorphic_relations) { options.values.first }
describe 'validation' do
it 'fails if it does not belong to one association' do
polymorphic_relations.each do |relation|
subject.send("#{relation}=", nil)
end
subject.should have(1).error_on(:base)
end
it 'fails if it belongs to more than one association' do
polymorphic_relations[0..1].each do |relation|
subject.send("#{relation}=", Factory.build_stubbed(relation))
end
subject.should have(1).error_on(:base)
end
end
describe "##{options.keys.first}" do
it 'returns the model referenced by the real association' do
polymorphic_relations.each do |relation|
subject.send("#{relation}=", nil)
end
relation = polymorphic_relations.first
model = Factory.build_stubbed(relation)
subject.send("#{relation}=", model)
subject.send(polymorphic_name).should == model
end
end
describe "##{options.keys.first}=" do
it 'sets the object to the real association' do
polymorphic_relations.each do |relation|
subject.send("#{relation}=", nil)
end
relation = polymorphic_relations.first
model = Factory.build_stubbed(relation)
subject.send("#{polymorphic_name}=", model)
subject.send(relation).should == model
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment