Topic: hm:t i'm having a brain cramp

I'm spacing how to get has_many :through to work. Running on Edge (5603), here's the problem.

I have my models:

class Project < ActiveRecord::Base
    has_many    :member_projects, :dependent => :destroy
    has_many    :members, :through => :member_projects, :uniq => true
end

class Member < ActiveRecord::Base
    has_many    :member_projects, :dependent => :destroy
    has_many    :projects, :through => :member_projects, :uniq => true
end

class MemberProject < ActiveRecord::Base
    belongs_to :member
    belongs_to :project
end

The relevant database schema snippet is:

create_table :member_projects do |t|
    t.column :member_id,    :integer
    t.column :project_id,   :integer
    t.column :created_at,   :datetime
    t.column :comments,     :text
end

Now, in script/console, I try this:

>> member = Member.find(:first)
=> #<Member:0x2414648 and lots of other expected stuff>
>> project = Project.find(:first)
=> #<Project:0x23e2954 and lots of other expected stuff>
>> member_project = MemberProject.create(:comments => 'just testing')
=> #<MemberProject:0x23c1344 and some other expected stuff>
>> member_project.save
=> true
>> member.projects << member_project
ActiveRecord::AssociationTypeMismatch: Project expected, got MemberProject
    from ./script/../config/../config/../vendor/rails/activerecord/lib/active_record/associations/association_proxy.rb:152:in `raise_on_type_mismatch'
    from ./script/../config/../config/../vendor/rails/activerecord/lib/active_record/associations/has_many_through_association.rb:59:in `<<'
    from ./script/../config/../config/../vendor/rails/activerecord/lib/active_record/associations/has_many_through_association.rb:58:in `<<'
    from ./script/../config/../config/../vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb:59:in `transaction'
    from ./script/../config/../config/../vendor/rails/activerecord/lib/active_record/transactions.rb:95:in `transaction'
    from ./script/../config/../config/../vendor/rails/activerecord/lib/active_record/associations/has_many_through_association.rb:57:in `<<'
    from (irb):6

I understand that edge has made this a proxy association, but I'm not sure how that works. Any hints?

Thanks

Re: hm:t i'm having a brain cramp

I'm not certain what you are attempting to do. The member.projects<<() method is expecting a Project model to be passed. It will then create the member_project join model automatically (AFAIK).

Perhaps this is what you want?

member.member_projects << member_project

This will just set up the member side of the member_project model - the member_project doesn't know anything about the project that was found earlier.

Railscasts - Free Ruby on Rails Screencasts

Re: hm:t i'm having a brain cramp

I think you nailed it. The deal is I really haven't needed attributes on a join model up till now. hm:t is just a touch strange insofar as you can do:

a_member.projects << Project.find_or_create_by_name('my fine project')

and it goes merrily on its way without populating the attributes of the join model. Everything else works perfectly. Just no attributes. There's no "magic" way I can see to get to the attributes after this creation. I can poke through the join table, but there's no magic way. It makes me wonder why making hm:t a proxy collection was such a big deal? What am I missing?

The steps seem to be:

1. Create join object
2. Add join object to table 1's join fk
3. Add join object to table 2's join fk

Ok, I'm still lost. I just assigned my Project to a Member and I can do:

a_member.projects.each{|m| puts "*** #{m}"

and

a_project.each{|p| puts "*** #{p}"}

But now how do I say, gee on #{created_on}, this project was assigned to that member and the comments were #{comments}?

I know this is the right tool for the job, but I'm just not getting the pieces to fall into place.

Thanks

Re: hm:t i'm having a brain cramp

The << method is great if you want to treat the join like a simple HABTM, but once you get into extra attributes, it is better to create the join model on its own.

Also, it might help to give the join model a different name to make it stand out on its own. Let's call it Assignment as I noticed you used that word in the last post.

@assignment = Assignment.new(:member => member, :project => project, :comment => 'Hello world!')

As for your last question, you don't want to loop through the projects directly. Instead, you should loop through the assignments then fetch the project name, etc. through the join.

<% for assignment in @member.assignments %>
  Project: <%=h assignment.project.name %>
  Comment: <%=h assignment.comment %>
<% end %>

Same when going the other way:

<% for assignment in @project.assignments %>
  Assigned to: <%=h assignment.member.name %>
  On: <%=h assignment.created_at %>
<% end %>

Last edited by ryanb (2006-11-21 20:20:17)

Railscasts - Free Ruby on Rails Screencasts