Topic: Refactor into scope

I can't quite figure out how to refactor this into a scope... I'm thinking of scoping the Post object and passing it in the user into the lambda, but its the joins and relationships I'm having a problem with.
Here is the User model method I'd like to get into a scope or just perform it via SQL.

  
  def related_posts
     posts = []
     posts << self.teams.map(&:posts)
     posts << self.events.map(&:posts)
     posts.flatten!
     posts.uniq
  end

Basically , a user can make a post with a team or event association....and sometimes an event might be a team event, hence the uniq call. I just want to return posts associated with that user. Using rails 3.

Thx!

Re: Refactor into scope

It's not really a scope, it is more of an instance method like you have it.  But it can be simplified by has_many :through to get the event and team specific posts.

class User < ...
  has_many :teams
  has_many :events
  has_many :team_posts, :through => :teams, :class => "Post"
  has_many :event_posts, :through => :events, :class => "Post"
  
  def related_posts
     (event_posts | team_posts).uniq
  end
end

So you have these methods now:
user.event_posts
user.team_posts
user.related_posts

Re: Refactor into scope

Exactly what I was thinking about... I just didn't quite get there. Awesome.

How does that Binary OR operator work exactly on an array?

Edit: also  the :class attr is not valid for has_many, so I'm thinking you meant :source?

Trying to piece it together with the rest of my code...

  has_many :memberships, :dependent => :delete_all
  has_many :participations, :dependent => :delete_all
  
  has_many :events, :through => :participations
  has_many :teams ,:through => :memberships
  has_many :posts, :class_name => "Post", :foreign_key => "author_id"
  
  has_many :team_posts , :through => :teams, :source => :posts
  has_many :event_posts, :through => :events, :source => :posts

I get an error because its trying to do a query on 'events.user_id' but it needs to be looking through the 'participations' join table, which I guess it doesn't do. Hmm....

Last edited by jordanp (2011-02-25 15:17:26)

Re: Refactor into scope

Ah, good catch, I mean class_name => ...
I think the error is b/c you are going too many has_many :through's down ...
You can't has_many :through, through a has_many :through.
This might help - https://github.com/releod/nested_has_many_through

But you may be able to do it this way, not sure -

has_many :events, :through => :participations do
  def posts
    all.map(&:posts)
  end
end
has_many :teams, :through => :memberships do
  def posts
    all.map(&:posts)
  end
end

def related_posts
  (teams.posts | events.posts).uniq
end

Open up the console and play around with "|" (union operator).

["a","b","c"] | ["d","e"] => ["a","b","c","d","e"]

If you want the intersection you'd use "&" between the two arrays.

Re: Refactor into scope

Yes, nested has_many :through , that seems to be the issue. Doing a search for that came up with some interesting options.

http://geoff.evason.name/2010/04/23/nes … able-join/
Looks like option 3 (back to scopes)

But there is no easier/better way to do this that I can find as of Rails 3. That gem stopped development < Rails 2.3

Thanks for your help!

Re: Refactor into scope

Hmm ... sorry, thought that I had read that that fork was working for rails 3.

Re: Refactor into scope

No problem.

At least I did refactor it to:

  def related_posts
     (teams.map(&:posts) | events.map(&:posts)).flatten
  end


If it becomes a performance issue, I'll take a look at the SQL side again.

Last edited by jordanp (2011-02-25 16:39:09)

Re: Refactor into scope

Actually the best way would be to combine the two queries you're triggering into a single query union. ActiveRecord doesn't support union queries as of Rails 2.3 (I don't know if that's the case in Rails 3), so there are 3 roads u can go down:

1. https://github.com/tsmango/union
2. write yourself your own sql query and then find_by_sql
3. forget all of this and follow the suggestions above, which will trigger 2 separate queries.

Re: Refactor into scope

I think it would be good to add scope to Post model:
scope :related_to, lamda{|user| :joins => [:team, :event], :conditions => {:teams => {:user_id => user.id}, :events => {:user_id => user.id}}

And then your related_posts will look like:
def related_posts
Post.related_to(self)
end

PS: Not sure about syntax correctness, but I think it should work.

Re: Refactor into scope

Thanks moderator. It's an honor to be part of the Social discussion. I do take pride in forum discussion. Thanks for such a great contribution.
Plumbing New Bedford
Plumbing Marlborough
Lowell Plumbing
Plumbing Methuen
Plumbing Natick

Last edited by dave84brady (2012-01-11 06:34:47)