How can I create new records with has_many :through and honor :conditions?

Eric picture Eric · Oct 3, 2009 · Viewed 8.4k times · Source

Let's say I have a Course in which Students can enroll via a Membership (e.g. a has_and_belongs_to_many relationsip of Courses and Students). Some memberships are for students who are just observing the class (not for credit, etc.), so:

class Course < ActiveRecord::Base
  has_many :memberships

  has_many :students,
           :through => :memberships

  has_many :observers,
           :through => :memberships,
           :source => :student,
           :conditions => { :memberships => { :observer => true }}
end

Here's what works great:

observers = Course.find(37).observers

Here's what doesn't work:

new_observer = Course.find(37).observers.build(:name => 'Joe Student')

I would have thought that one could build new records using the association and that would have generated:

  1. A new Student record ('Joe Student')
  2. A new Membership record (course_id = 37, student_id = (joe), observer = true)

But instead I get:

ActiveRecord::AssociationTypeMismatch: Membership expected, got Array

I'm sure I'm totally confused about how this and would appreciate any insights! I've also tried to do this with named scopes on the Membership model, but I can't seem to get has_many to use a scope in the association.

Thanks so much for any help possible!

Answer

Ryan Bigg picture Ryan Bigg · Oct 4, 2009

I believe that you have encountered a Rails bug. I tried the same thing on my box (2.3.4) and it gives me the same error which doesn't seem right at all. Additionally I also tried the work around of:

course = Course.first
course.observers << Student.create(:name => "Joe Student")
course.save

But this creates a membership with the observer field set to false!

The final ugly workaround I came up with was creating the Membershiprecord manually:

Membership.create!(:course => Course.first, :student => Student.first, :observer => true)

I've created a ticket for this and I'll be investigating further after breakfast.

EDIT: I have, as promised, investigated further and found if you change your :conditions Hash to an Array such as:

:conditions => ["memberships.observer = ?", true]

It works as intended. I also have a github repository with example code and instructions to duplicate.