views:

87

answers:

2
class Followup < ActiveRecord::Base
  belongs_to :post
  belongs_to :comment
end

This model needs to only have either a post or a comment, but only one of the two.

Here's the rspec for what I'm trying to do:

  it "should be impossible to have both a comment and a post" do
    followup = Followup.make
    followup.comment = Comment.make
    followup.should be_valid
    followup.post = Post.make
    followup.should_not be_valid
  end

I can see a bunch of solution to do this, but what would be the most elegant way of doing this?

+7  A: 

I think what you really want is a polymorphic association.

Ryan does a great job explaining them in Railscast #154.

class Followup < ActiveRecord::Base
  belongs_to :followupable, :polymorphic => true
end

class Post < ActiveRecord::Base
  has_many :followups, :as => :followupable
end

class Comment < ActiveRecord::Base
  has_many :followups, :as => :followupable
end
chap
thanks for the answer; I was hesitating about using polymorphism because I only had 2 associations. I'm still hesitating but that's true that it looks nice ^^
marcgg
+1  A: 

The question of elegance is of course subjective but the following example will do exactly what you want, including separate error messages for the 2 invalid conditions i.e both values provided and no values provided.

class Foo < ActiveRecord::Base  
  validate :one_and_only_one  

  def one_and_only_one()  
    errors.add_to_base("You must provide either a foo or a bar")  
      if self.foo.blank? && self.bar.blank?  
    errors.add_to_base("You cannot provide both a foo and a bar")  
      if !self.foo.blank? && !self.bar.blank?  
  end  
end  

EDIT

Thinking of more solutions then this might pass the elegance test better

errors.add_to_base("You must provide either a foo or a bar") 
  unless [f.foo, f.bar].compact.length == 1

Although this will fail on space filled fields.

Steve Weet
Thanks for the answer and +1 for the edit that I find pretty cool, but I still think @chap's solution is still nicer.
marcgg
I would agree with you. I was thinking of an alternative to polymorphism but that's the way I would have done it.
Steve Weet