I've been stuck on this all day. I have a setup like the one below. I'm trying to define friends using the group_memberships association.
class User < ActiveRecord::Base
has_many :group_memberships
has_many :groups, :through => :group_memberships
has_many :friends # what goes here? <<
end
class GroupMembership < ActiveRecord::Base
belongs_to :user
belongs_to :role
belongs_to :group
end
class Role < ActiveRecord::Base
has_many :group_memberships
end
class Group < ActiveRecord::Base
has_many :group_memberships
has_many :users, :through > :group_memberships
end
I'd like to do this without creating a join table for friends, unless it's completely crazy to do it without.
The group_membership table contains user_id and group_id linking one user to one group.
I'd trying to get
#user.friends
to return users with common group_memberships using the group_id.
has_many :friends, :through => :group_memberships, :source => :group
Nothing I've tried works, but I'll chalk that up to my complete misunderstanding of the above code.
Unfortunately Rails doesn't let you nest has_many's more than 2 deep.. Forgetting about naming it friends for a moment (let's call it users instead), this would theoretically be what you'd want:
has_many :group_memberships
has_many :groups, :through => :group_memberships
has_many :users, :through => groups
Except that this doesn't work. If you try it you'll see this not-so-helpful error message which comes from this bit of code, specifically source_reflection.options[:through].nil?. That is, the through isn't allowed to have a through itself.
Instead, you may want to do something like this:
Solution 1
class User < ActiveRecord::Base
has_many :group_memberships
has_many :groups, :through => :group_memberships
def friends
groups.with_users.map(&:users).flatten.uniq.reject{|u| u == self}
end
end
class Group < ActiveRecord::Base
has_many :group_memberships
has_many :users, :through => :group_memberships
named_scope :with_users, :include => :users
end
Solution 2
Use the nested_has_many_through plugin that Radar mentioned. It looks like at least one fork of it on github has been updated to work on the latest Rails.
Solution 3 (just for kicks)
or, just for kicks, you could do it with one big SQL query:
class User < ActiveRecord::Base
has_many :group_memberships
has_many :groups, :through => :group_memberships
def friends
sql = <<-SQL
SELECT users.* FROM users, (
SELECT DISTINCT gm2.user_id AS user_id
FROM group_memberships gm, groups g, group_memberships gm2
WHERE gm.user_id = ? AND g.id = gm.group_id AND gm2.group_id = g.id AND gm2.user_id != ?
) AS user_ids
WHERE users.id = user_ids.user_id
SQL
User.find_by_sql([sql, id, id])
end
end
Use the nested_has_many_through plugin.
delegate :users, :to => 'group'
Related
So my models looks like this:
class Group
has_many :memberships
has_many :users, :through => :memberships
end
class Membership
belongs_to :user
belongs_to :group
end
class User
has_many :memberships
has_many :groups, :through => :memberships
end
My question is, how can I find a user who created group in view? Cause when I am iterating like this, it doesnt work:
<= #groups.each do |group| =>
(What now? I have group and want to know who was a creator. I cant just type simply group.membership.username
<= end =>
And I cant just type #groups.memberships.each do because I use another parameters like group title, image, etc.
I am implementing groups feature. I am stuck at searching members of a group.
Let say
Class Group
has_many :group_memberships
has_many :members, :source => :user, :through => :group_memberships
end
Class GroupMembership
belongs_to :group
belongs_to :user
end
Class User
has_many :group_memberships
has_many :groups, :through => :group_memberships
has_one :profile, :dependent => :destroy
end
Class Profile
belongs_to :user
end
How to search group members, with searchable fields in profile
Could something like below work for you?
group = Group.find(id)
users = group.users.joins(:profiles).where("profile.age>18")
You can do this with the help of eager loading -
just Specify Condition on Eager Loaded Associations like -
group = Group.last
users = group.users.includes(:profiles).where("profiles.name" => "xyz")
For more details refer -
http://guides.rubyonrails.org/active_record_querying.html#specifying-conditions-on-eager-loaded-associations
I have the following models:
class User < ActiveRecord::Base
has_many :groups, :through => :memberships
has_many :memberships, :class_name => "User::Group"
end
class Group < ActiveRecord::Base
has_many :users, :through => :members
has_many :members, :class_name => "User::Group"
end
class User::Group < ActiveRecord::Base
belongs_to :user
belongs_to :group
end
I make the following association call:
#user.groups
And it returns an array of User::Groups, instead. Am I overlooking something?
I changed the name of the join table to User::Membership, and now everything works as expected. It seems that naming the last part of the namespace after another model was causing some sort of interference, though I'm not sure why. Does anyone have a more informed explanation?
given two models:
User
has_many :group_members
has_many :groups, :through => :group_members
GroupMember
belongs_to :group
belongs_to :user
Group
has_many :group_members
has_many :users, :through => :group_members
has_many :group_invites
GroupInvite
belongs_to :user
Thanks to Rails, I can easily get all of a user's groups with:
groups = current_user.groups
I then take this list of groups and iterate through to create a JSON object like so:
groups.each do |group|
....
group.group_members.count
group.invites.first_code
etc etc . The problem is this results in a large number of queries because I haven't found a way to use includes(:) to query for all the group_members and invites for all the groups with one query versus a query for every each loop. Any suggestions on how you can use includes like so:
current_user.groups.includes(:group_members, :invites) ?
Try this:
GroupMember
belongs_to :group
belongs_to :user
Group
has_many :group_members
has_many :users, :through => :group_members
has_many :invites, :
has_many :invitees, :through => :invites, :source => :user
Invite
belongs_to :group
belongs_to :user
Now you can:
current_user.groups.includes(:invitees)
I'm trying to tidy up my code by using named_scopes in Rails 2.3.x but where I'm struggling with the has_many :through associations. I'm wondering if I'm putting the scopes in the wrong place...
Here's some pseudo code below. The problem is that the :accepted named scope is replicated twice... I could of course call :accepted something different but these are the statuses on the table and it seems wrong to call them something different. Can anyone shed light on whether I'm doing the following correctly or not?
I know Rails 3 is out but it's still in beta and it's a big project I'm doing so I can't use it in production yet.
class Person < ActiveRecord::Base
has_many :connections
has_many :contacts, :through => :connections
named_scope :accepted, :conditions => ["connections.status = ?", Connection::ACCEPTED]
# the :accepted named_scope is duplicated
named_scope :accepted, :conditions => ["memberships.status = ?", Membership::ACCEPTED]
end
class Group < ActiveRecord::Base
has_many :memberships
has_many :members, :through => :memberships
end
class Connection < ActiveRecord::Base
belongs_to :person
belongs_to :contact, :class_name => "Person", :foreign_key => "contact_id"
end
class Membership < ActiveRecord::Base
belongs_to :person
belongs_to :group
end
I'm trying to run something like person.contacts.accepted and group.members.accepted which are two different things. Shouldn't the named_scopes be in the Membership and Connection classes?
However if you try putting the named scopes in the Membership and Connection classes then you get this error (because Person.find(2).contacts returns an array of Persons which doesn't have an 'accepted' method:
>> Person.find(2).contacts.accepted
NoMethodError: undefined method `accepted' for #<Class:0x108641f28>
One solution is to just call the two different named scope something different in the Person class or even to create separate associations (ie. has_many :accepted_members and has_many :accepted_contacts) but it seems hackish and in reality I have many more than just accepted (ie. banned members, ignored connections, pending, requested etc etc)
You answered your own question:
Shouldn't the named_scopes be in the Membership and Connection classes?
Yes, they should be. This will let you call them as you wanted. It's also logically where they belong.
If you want something on Person that checks both, you can do something like:
named_scope :accepted, :conditions => ["connections.status = ? OR memberships.status = ?", Connection::ACCEPTED, Membership::ACCEPTED]
Maybe you want this to be an AND? not sure.
I'm sure this is not the best way and I believe you can do this on person and group models, but I also believe the following will work for you:
# models
class Person < ActiveRecord::Base
has_many :connections
has_many :contacts, :through => :connections
has_many :memberships
has_many :groups, :through => :memberships
end
class Group < ActiveRecord::Base
has_many :memberships
has_many :members, :through => :memberships
end
class Connection < ActiveRecord::Base
belongs_to :person
belongs_to :contact, :class_name => "Person", :foreign_key => "contact_id"
named_scope :accepted, :conditions => ["status = ?", Connection::ACCEPTED]
end
class Membership < ActiveRecord::Base
belongs_to :person
belongs_to :group
named_scope :accepted, :conditions => ["status = ?", Membership::ACCEPTED]
end
# controller
# get person's accepted contacts
#person = Person.first
#person.connections.accepted.map(&:contact)
# get group's accepted members
#group = Group.first
#group.memberships.accepted.map(&:person)