Instance method, named_scope or association extension - ruby-on-rails

class User < ActiveRecord::Base
has_many :memberships
has_many :groups, :through => :memberships
class Group < ActiveRecord::Base
has_many :memberships
has_many :users, :through => :memberships
def moderators
# relationship_id is in the memberships table
self.users.find(:all, :conditions => ['relationship_id = ?', 1])
end
end
Given the above code, is this the best way to implement the moderators method? My goal is to be able to find all the users with a "moderator" relationship within a given group. Right now I can use the method to iterate over all the moderators
# ...
#group.moderators
I considered an association extension which makes sense to use here because I'm asking for a subset of users that meet a condition. But the syntax seems redundant to ask for users that are moderators
# Association extension - seems redundant
#group.users.moderators
I considered a named_scope but I couldn't figure out how to implement it without errors. Even if I could the named_scope will return all the moderators across all the groups which isn't what I want.
# named_scope: returns all the moderators across all the groups
moderators = Group.moderators
I'm wondering what the best practice here is and why I might want to us an association extension (or named_scope) over the instance method given that it allows for a more concise syntax?

Add an association on Group class:
class Group < ActiveRecord::Base
has_many :memberships
has_many :users, :through => :memberships
has_many :moderators, :source => :user, :through => :memberships,
:conditions => ['relationship_id = ?', 1]
end
Now you can do the following:
#group.moderators
#group.moderators.size
#group.moderators.find_by_city(..)

Related

Joining multiple relationships from separate tables into one association

I have several models that are relevant to this question..
UserGroupPermission
UserGroup
User
UserPermission
Permission
Essentially, A User can be assigned a permission through either their UserGroup or directly. UserGroups are tied to specific permissions via the UserGroupPermission model. Users are tied to specific permissions via the UserPermission model.
The end result is to be able to call User.permissions and return every permission assigned to that user either directly or through a UserGroup.
Here is my current model
class User < ActiveRecord::Base
belongs_to :user_group
#Permissions
has_many :user_permissions
has_many :user_group_permissions, :through => :user_group
has_many :permissions, :through => :user_group_permissions #and :user_permissions
end
I've search high and low for a solution to this, but can't seem to find my exact case. Thanks in advance!
Edit: I've been told that is unclear what I'm asking. Hopefully this will help: Basically the User has_many :permissions, :through => :user_group_permissions and :user_permissions, but you can't use an and to join the results together. I'm trying to retrieve and combine the results of both of those relations into one.
Edit 2: I've been doing some work. This code gives the desired result, but I'm not sure how well it would work for potentially large databases.
#Permissions
has_many :user_permissions
has_many :user_group_permissions, :through => :user_group
#has_many :permissions, :through => :user_group_permissions
#has_many :permissions, :through => :user_permissions
def permissions
all_permissions = []
UserGroupPermission.where('user_group_id = ?', user_group_id).each do |ugp|
all_permissions.append(Permission.find(ugp.permission_id))
end
UserPermission.where('user_id = ?', id).each do |p|
all_permissions.append(Permission.find(p.permission_id))
end
return all_permissions
end

Querying through a self join reference in rails

I have a model of users and things
Users can have one leader and many followers which are other users. User can also have things
So I have these definitions which use a self join relationship:
class User < ActiveRecord::Base
has_many :things, dependent: :destroy
has_many :followers, :class_name => 'User', :foreign_key => 'leader_id'
belongs_to :leader, :class_name => 'User', :foreign_key => 'leader_id'
end
class Thing < ActiveRecord::Base
belongs_to :user
end
So I can query ask for a list of things that a user has by asking, for example User.first.things. I can also get a list of followers of a user with User.first.followers.
How do I get a list of things that a user's followers have. I think I might need to use a has_many through relationship but I can't seem to figure it out as I'm not sure how to deal with the fact that a Leader can have things through a 'follower' but also directly themselves
Thanks
Something like this:
def all_things
Thing.where(:user_id => followers.map(&:id).push(id))
end
It returns a scope so you should be able to continue the chain, for example:
User.first.all_things.visible
Update
If you are only interested in the followers' things without adding the user's things to the batch it is better is you do it directly with a has_many through:
has_many :followers_things, :through => :followers, :source => :things
Check this other SO thread
How about:
all_things = (your_user.followers.each { |f| f.things }).flatten
This should return an array containing things that belong to all your_user's followers.

Rails 3 associations

class User < ActiveRecord::Base
has_many :books
has_many :book_users
has_many :books, :through => :book_users
end
class Book < ActiveRecord::Base
belongs_to :user
has_many :book_users
has_many :users, :through => :book_users
end
An user can write many books
An book can belong to only one user
An user can be a reader of different books
An book can be read by different users
User.books
should give me the books the user has written
User.books_read
should give me the books, are read by this user
How accomplish this ?
Second question, what is the simplest method to delete book_read from the user ?
I mean
User.method_name(book_id) # what's the method name ?
First question:
You either use :source or :class_name.
has_many :books_read, :class_name => "Book", :through => :book_users
I don't know for sure if :class_name works with has_many :through. If it doesn't work, try this:
has_many :books_read, :source => :book, :through => :book_users
That should do the trick.
Second question:
As far as I know there isn't really a simple method to delete books from the books_read relation. You could create your own method to accomplish this. Just make sure you delete records from :book_users and not the has_many :through relation. Or else the books themselves will be deleted.
def delete_book(book_id)
self.book_users.find_by_book_id(book_id).destroy
end
When using Rails 3 you can't use the find_by_... helper and need to use where.
def delete_book(book_id)
self.book_users.where(:book_id => book_id).destroy
end
Now you can call this function as follows:
User.find(1).delete_book(2)
I hope that helps you out.

Rails named_scope across multiple tables

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)

Linking Two Models Together in Ruby on Rails

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'

Resources