views:

409

answers:

2

I want to (as an example) create a has_many association to all posts by friends of a person, something like has_many :remote_posts to give me something like person > friends > person > posts.

..here is how I would go about it

script/generate model post title:string person_id:integer
script/generate model friendship person_id:integer friend_id:integer
script/generate model person name:string


class Person < ActiveRecord::Base
  has_many :posts
  has_many :friendships, :foreign_key => 'friend_id'
  has_many :people, :through => :friendships
  has_many :remote_posts, :class_name => 'Post', :through => :people, :source => :posts
end
class Friendship < ActiveRecord::Base
  belongs_to :person
  #also has a 'friend_id' to see who the friendship is aimed at
end
class Post < ActiveRecord::Base
  belongs_to :person
end


# generate some people and friends
{'frank' => ['bob','phil'], 'bob' => ['phil']}.each {|k,v|
  v.each {|f| 
    Friendship.create(
      :person_id => Person.find_or_create_by_name(f).id,
      :friend_id => Person.find_or_create_by_name(k).id
    )
  }
}
# generate some posts
Person.all.each {|p|
  p.posts.create({:title => "Post by #{p.name}"})
}


Now,

Person.first.friendships  # ..works
Person.first.people  # (friends) ..works
Person.first.posts # ..works
Person.first.remote_posts #....

...and I get this error..

ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: people.person_id: SELECT "posts".* FROM "posts" INNER JOIN "people" ON "posts".person_id = "people".id WHERE (("people".person_id = 1))

Aside from the foreign key error - seems like the friendships association isn't coming into play at all. I was thinking that this might be because of the :source => :posts, since the posts association would come into it twice.

I could write some finder sql (and that is what I have working at the moment), though I'd sooner do it this way.

Any ideas of how to get this to work?

A: 

How about this:

In the FriendShip class, add:

has_many :posts, :through => :person


and in the Person class, change the remote_posts to:

has_many :remote_posts, :class_name => 'Post',
         :through => :friendships, :source => :person
JRL
JRL, That is way better!Unfortunately it still doesn't work completely (perhaps due to an unrelated problem)when you call: Person.find_by_name('frank').remote_posts..it fires off this SQL: SELECT "posts".* FROM "posts" INNER JOIN "friendships" ON "posts".id = "friendships".person_id WHERE (("friendships".friend_id = 2)) It should have joined on posts.person_id instead of posts.id (so it returns the posts with the same id as your friends)I tried putting in some :foreign_key's, but doesn't seem to work.Thanks for your reply!
Damnit - I should have known it wouldn't format properly.. Problem is that person#remote_posts uses post.id instead of post.person_id - but I think that's 80% there!
A: 

How about a nested has_many :through relationship. This seems to work for me:

class Friendship < ActiveRecord::Base
  belongs_to :person
  belongs_to :friend, :class_name => 'Person'
  has_many :posts, :through => :friend, :source => :posts
end
class Person < ActiveRecord::Base
  has_many :posts
  has_many :friendships, :foreign_key => 'friend_id'
  has_many :people, :through => :friendships
  has_many :remote_posts, :through => :friendships, :source => :posts
end

Note: this requires this nested_has_many_through plugin. (Note: direct linking to github repos seems to be broken... but that repo is there despite the error message.)

ScottJ