Scope for Self-joining HABTM Association
Asked Answered
S

1

0

With Rails 3.1.3 on Ruby 1.9.2, I have:

class User < ActiveRecord::Base
  has_and_belongs_to_many :states
end

class State < ActiveRecord::Base
  has_and_belongs_to_many :users
end

A User is associated with many States, and a State with many Users. Given a User, I want to find all other Users who are in any of her states.

I tried:

class User < ActiveRecord::Base
  scope :in_state, lambda { |states| joins(:states).where(:states => states) }
end

User.in_state(current_user.states).all

With this error (formatted for readability):

Mysql2::Error: Not unique table/alias: 'states_users': 
SELECT COUNT(*) 
FROM `users` 
  INNER JOIN `states_users` ON `states_users`.`user_id` = `users`.`id` 
  INNER JOIN `states` ON `states`.`id` = `states_users`.`state_id` 
  LEFT OUTER JOIN states_users on users.id = states_users.user_id AND states_users.state_id IS NULL 
WHERE `states`.`id` IN (8)

I realize I need some way to alias one of the states_users references, but I'm at a loss for figuring out how to do this with scope.

Here's the SQL I would write:

SELECT u2.*
FROM users u
  INNER JOIN states_users su ON su.user_id = u.id
  INNER JOIN states s ON s.id = su.state_id
  INNER JOIN states_users su2 ON s.id = su2.state_id
  INNER JOIN users u2 ON u2.id = su2.user_id
WHERE u.id = 771

Thoughts?

Thanks.

UPDATE 12/14/2011 @ 10:48 AM:

Here's what's working:

scope :in_states_of, lambda { |user|
  joins('INNER JOIN states_users su ON su.user_id = users.id ' +
        'INNER JOIN states s ON s.id = su.state_id '           +
        'INNER JOIN states_users su2 ON s.id = su2.state_id '  +
        'INNER JOIN users u ON u.id = su2.user_id').
  where("u.id = ?", user.id) }

User.in_states_of(current_user)

It's not particularly elegant, but it's working. You have to alias the users reference for the "incoming" User so you can chain this scope with others, such as:

scope :active, where(active => true)

User.in_states_of(current_user).active

Any suggestions for improvement would be most welcome.

Selfsown answered 14/12, 2011 at 8:55 Comment(0)
S
0

Here's what's working:

scope :in_states_of, lambda { |user|
  joins('INNER JOIN states_users su ON su.user_id = users.id ' +
        'INNER JOIN states s ON s.id = su.state_id '           +
        'INNER JOIN states_users su2 ON s.id = su2.state_id '  +
        'INNER JOIN users u ON u.id = su2.user_id').
  where("u.id = ?", user.id) }

User.in_states_of(current_user)

It's not particularly elegant, but it's working. You have to alias the users reference for the "incoming" User so you can chain this scope with others, such as:

scope :active, where(active => true)

User.in_states_of(current_user).active

Any suggestions for improvement would be most welcome.

Selfsown answered 11/9, 2012 at 3:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.