0

In the given case a User may:

  • invite many invitees (has_many :invitations)
  • accept one invitation (has_one :invitation)

According to http://guides.rubyonrails.org/association_basics.html#the-has_many-through-association a has_many :through association should allow me to use a shortcut like the one in the following functional test.

However, it fails with the error noted in the comment.

snippet from functional test:

assert_difference('Invitation.count') do # WORKS
  post :create, :user => { :email => invitee_email, :password => "1password" }
end

@invitee = User.find_by_email(invitee_email)
@invitation = Invitation.find_by_issuer_id_and_invitee_id(@issuer.id, @invitee.id)
assert @invitation.valid? # WORKS
assert_present @invitation.issuer # WORKS
assert_present @invitation.invitee # WORKS

# TODO: repair
assert_present @issuer.invitees # FAILS with "[] is blank"
assert_present @invitee.issuer # FAILS with "nil is blank"

snippet from the method under test:

@issuer.create_invitation(:invitee => @invitee, :accepted_at => Time.now)
# tested as well - also fails the test:
# Invitation.create!(:issuer => @issuer, :invitee => @invitee, :accepted_at => Time.now)

relevant parts of invitation.rb:

belongs_to :issuer, :class_name => "User"
belongs_to :invitee, :class_name => "User"

validates_presence_of :issuer
validates_presence_of :invitee

relevant parts of user.rb:

has_many :invitations, :foreign_key => 'invitee_id', :dependent => :destroy
has_many :invitees, :through => :invitations
has_one :invitation, :foreign_key => 'issuer_id', :dependent => :destroy
has_one :issuer, :through => :invitation

Now I wonder:

  • What is the correct 'shortcut'?
  • Are my models set up correctly in the first place?
user569825
  • 2,369
  • 1
  • 25
  • 45

1 Answers1

0

I guess you're trying to design the logic like this: User 1 can invite user 2, user 3, then the data is:

issuer_id, invitee_id
1,         2
1,         3

Suppose user 2 accepted the invitation, the data become:

issuer_id, invitee_id
1,         2
1,         3
2,         1

What if user 2 invites user 1 later? If my guess is right, your design wouldn't work.

I think you also have some misunderstanding about the associations. has_many :invitations, :foreign_key => 'invitee_id' means a user, as an invitee, received many invitations. And has_one :invitation, :foreign_key => 'issuer_id' means a user, as an issuer, sent an invitation.

Yanhao
  • 5,264
  • 1
  • 22
  • 15
  • In my use case an Invitation is *only* created when a new user signs up. So **if user 2 accepts the invitation** the following will go to the DB: `issuer_id=1, invitee_id=2`. If he never accepts, the app will simply never know about him at all. However, I think you are right about the **:foreign_key**! When I exchange them, suddenly no Invitation is created (`"Invitation.count" didn't change by 1.`) - which I am currently unable to see the reason for. – user569825 Mar 13 '12 at 07:49
  • It seems what was missing additionally (*after* correcting the :foreign_key's) was including the @issuer in the attributes/parameters of **create_invitation**: `@issuer.create_invitation(:issuer => @issuer, :invitee => @invitee, :accepted_at => Time.now)` --- I had expected Rails to know the `:issuer => @issuer,` part as I was calling the method from @issuer in the first place. Apparantly that was wrong. Thanks for your help! – user569825 Mar 13 '12 at 08:04
  • Add: just noticed it should be `@invitee.create_invitation` (as opposed to @issuer.create_invitation). Otherwise the collection of invitees (!) will be overwritten. – user569825 Mar 13 '12 at 20:51