1

In my Rails 3 project, I have a user model with a self referential join, through the follow model. I want to use this join table to find activity related to the followed user. I have almost everything set up correctly, except that the query generated by the join is totally ignoring the :primary_key option on the join model.

Here is the relevant schema for the relevant models:

  create_table "users", :force => true do |t|
    t.string   "email",                                 :default => "",    :null => false
    t.string   "first_name"
    t.string   "last_name"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  create_table "follows", :force => true do |t|
    t.integer  "user_id"
    t.integer  "followed_user_id"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  create_table "activities", :force => true do |t|
    t.integer  "user_id"
    t.text     "body"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

Here's the associations in the models

class User < ActiveRecord::Base
    has_many :follows
    has_many :followed_users, :through => :follows
  has_many :followed_activities, :through => :follows
    has_many :activities
end
class Follow < ActiveRecord::Base
    belongs_to :user
    belongs_to :followed_user, :class_name => "User"
    has_many :followed_activities, :primary_key => :followed_user, :foreign_key => :user_id, :class_name => "Activity"
end

The following work just fine:

u = User.first
u.follows # returns corresponding records from the follows table
u.followed_users # returns all users that u is following
u.followed_users.first.activities # returns all activity records corresponding to the first person the user is following
Follow.first.activities # same as the previous

However, the following just returns an empty array:

u.followed_activities

Here is the sql that is generated from the last statement:

  Activity Load (0.2ms)  SELECT `activities`.* FROM `activities` INNER JOIN `follows` ON `activities`.user_id = `follows`.id WHERE ((`follows`.user_id = 1))

The reason it isn't working is because it is trying to join use 'follows'.id as the primary key rather than 'follows'.followed_user.

Is this a bug, or do I have to repeat the :primary_key declaration somewhere on the user model? I can't find any mention anywhere in the Rails api, or anywhere else online.

Rails Version: 3.0.7

Justin
  • 1,203
  • 8
  • 15

2 Answers2

1

I've found it intuitive to daisy chain relationships with the 'nested_has_many_through' gem, http://rubygems.org/gems/nested_has_many_through which will be a standard part of rails 3.1 and could give you another tool to tackle the issue here

It will let you do something like this:

class Author < User
  has_many :posts
  has_many :categories, :through => :posts, :uniq => true
  has_many :similar_posts, :through => :categories, :source => :posts
  has_many :similar_authors, :through => :similar_posts, :source => :author, :uniq => true
  has_many :posts_of_similar_authors, :through => :similar_authors, :source => :posts, :uniq => true
  has_many :commenters, :through => :posts, :uniq => true
end

class Post < ActiveRecord::Base
  belongs_to :author
  belongs_to :category
  has_many :comments
  has_many :commenters, :through => :comments, :source => :user, :uniq => true
end

This has super-simplified my queries and collections. I hope you find an answer to your problem, it's a tough one!

thejonster
  • 156
  • 2
  • 10
  • If given no other options I will try daisy chaining my query using the method you describe (user.followed_users.activities), but the way I've structured it there is only a simple has_many :through. The only difference is that I'm using a non-standard primary_key on join table. – Justin Jul 16 '11 at 06:21
  • Nice, thank you! I didn't know they had added this to 3.1. has_many -> :through -> :source was just what I needed to make my complex query work without resorting to massive SQL statements. – poetmountain Mar 01 '12 at 05:04
0

Justin, you have 2 associations called "followed_activities". sure, they have different context (different models), but I'd like to ask you to try method inside the association block like this:

has_many :followed_users, :through => :follows do
  def activities
  end
end
Anatoly
  • 15,298
  • 5
  • 53
  • 77
  • Good point on having the "followed_activities" in two models. I only put the association in the follow model because it was the only way I could figure out how to get my has_many :through association to work (I couldn't get the :source declaration to work here). I'm trying to work out how to make it work with the association block as you mentioned. What would I put inside the block? A finder method? – Justin Jul 16 '11 at 08:05
  • yes, finder method or iterator if you want to collect an Array. the original idea to have the methods inside associations was in this book [Advanced Rails Recipes](http://pragprog.com/book/fr_arr/advanced-rails-recipes) – Anatoly Jul 16 '11 at 10:38
  • I forgot I had that book! Using another trick from the book, I expounded on the idea a bit. I can't figure out how to do this in the comments, so I've updated your post a bit. – Justin Jul 16 '11 at 22:28