Justin
Justin

Reputation: 1203

How to access the original model inside of an ActiveRecord association method

I've been using ActiveRecord association methods (such as in this post Ryan's Scraps) to perform some of my more complex 2nd level associations, while minimizing query counts. For example, I use the following association to pull all activity from everyone that a user follows (follow is a model that just contains the user_id and followed_user_id):

class User < ActiveRecord::Base
 <other associations, code, etc>
 has_many :follows do
 def activities(reload=false)
   @followed_activities = nil if reload
   @followed_activities ||= Activity.includes(:user, :comments, :comment_users).where(:user_id.in => self.collect {|e| e.followed_user_id}).order('activities.created_at DESC')
 end
 <.....>
end

This method has worked fantastically, no complaints. However, the client has requested that activities from the user itself should be included (followed_users' activities + current_user's activities).

I realize this is probably ruby 101, but I can't figure out how to access User.id from the parent (User) model. As you can see in the code, self refers to the follows relation, and returns a list of the matching rows from the follows table.

I kind of sort of got it to work sometimes by changing the following line:

@followed_activities ||= Activity.includes(:user, :comments, :comment_users).where(:user_id.in => (self.collect {|e| e.followed_user_id}) << self.first.user_id).order('activities.created_at DESC')

However, this a) feels very hackish, and b) raises an exception when there aren't any follows records (which is always the case with a new user). How else can I get the user ID?

Upvotes: 1

Views: 725

Answers (1)

m_x
m_x

Reputation: 12564

as seen in the episode 215 Asciicast, you can merge two relations using the & operator, so something like this should work:

@followed_activities    ||= Activity
                             .includes(:user, :comments, :comment_users)
                             .where(:user_id.in => (self.collect {|e| e.followed_user_id})
@current_users_activity ||= Activity
                             .includes(:user, :comments, :comment_users)
                             .where(:user_id.in => [self.first.user_id] )
@all_activities         ||= (@followed_activities & @current_users_activity).order('activities.created_at DESC')

edit :

note : in rails 3.1 the & operator is deprecated, use Relation#merge instead.

did some interesting research. This is definitely not "ruby 101" !

Actually, self in your code does refer to the Association Proxy between your User object and its associated Follow records. When you extend an association like this, you extend the proxy (as i barely understand it, the block is instance_eval'd against the proxy class).

in rails >= 3.1, there is a way to get your user id inside your block (see this doc, section "association extension") :

proxy_association.owner.id

so you should be able to do :

class User < ActiveRecord::Base

  has_many :follows do

    def activities( reload = false )
      @followed_activities = nil if reload
      @followed_activities ||= begin
        Activity
          .includes( :user, :comments, :comment_users )
          .where( :user_id.in => [proxy_association.owner.id, *map( &:followed_user_id ) ])
          .order( 'activities.created_at DESC' )
      end
    end

  end

end

Upvotes: 2

Related Questions