Reputation: 1203
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
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