Finding siblings through a joins table in Rails - ruby-on-rails

I have models User, Group, and Membership with the following structure:
class User < ApplicationRecord
has_many :memberships
has_many :groups, through: :memberships
end
class Group < ApplicationRecord
has_many :memberships
has_many :users, through: :memberships
end
class Membership < ApplicationRecord
belongs_to :user
belongs_to :group
end
Basically, Membership is the joins table for User and Group.
Given a User user, how can I find its sibling users who all belong to at least one of the same group? That is, something like
user.groups.users # assuming such a line were possible
I'd like to do it in a single query and solely in Active Record, but I'm OK with two queries if that's faster or much more readable. (The DB language is PSQL if that helps as well.)

There are some ways to combine JOINs and sub-selects to get exactly one DB query, try this one:
User
.joins(:memberships)
.where.not(id: user.id)
.where(
memberships: {
group_id: user.memberships.select(:group_id)})
P.S. Don't forget about indexes on all *_id columns to get fast queries.
P.P.S. One more way: 2 sub-selects, 1 DB query. Test which one suits your requirements better:
user_groups_rel = user
.memberships
.select(:group_id)
groups_users_rel = Membership
.select(:user_id)
.where(group_id: user_groups_rel)
User
.where.not(id: user.id)
.where(id: groups_users_rel)

Try the following by using includes:
groups = User.groups.includes(:users)
groups.each do |group|
puts "user = #{group.user}"
end

Related

How to find unassociated records Rails Activerecord

I have these three models
class User < ApplicationRecord
has_many :subscribes
has_many :groups, through: :subscribes
end
class Group < ApplicationRecord
has_many :subscribes
has_many :users, through: :subscribes
end
class Subscribe < ApplicationRecord
belongs_to :user
belongs_to :group
end
I want to query in a clean way, all the groups that the user is not in. How can I do this?
If user has groups association, it means it has group_ids method
Group.where.not(id: user.group_ids)
And also you can explicitly extract such ids
Group.where.not(id: Subscribe.select(:group_id).where(user_id: user.id))
One way to do this would be to use the "where" method in ActiveRecord.
For example:
Group.where("id NOT IN (?)", user.groups.map(&:id))
This would give you all of the groups that the user is not in.
you can also use a subquery for this:
Group.where("id NOT IN (SELECT group_id FROM subscribes WHERE user_id = ?)", user.id)
This would give you the same result as the previous example, but without using the .map method.
"
You can find all the group ids where the user is not present like below:
group_ids = Subscribe.where.not(user_id: user.id).uniq
and then find the group records by querying the groups with these ids:
groups = Group.where(id: group_ids)

Retrieve only associated records

I need to find out the records which are at least associated to any one of the records.
I've this relationship:
Class Category < ActiveRecord::Base
has_many :services
end
Class Service < ActiveRecord::Base
has_many :team_leaders, through: :allocations
has_many :workers, through: :allocations
end
I need to find out only those services which has at least one worker or one teamleader associated to it. How to do it?
I don't know how you could do this without writing some SQL, but this is how I would have done it:
Service.includes(:team_leaders, :workers).where('team_leaders.id is not null OR workers.id is not null').references(:team_leaders, :workers).all
Edit: adding .references (see comments below)
c = Category.first
c.services.each do |service|
if Allocation.exists?(:service_id => service.id)
puts service.name
puts service.service_name
end
end
It will list only those services which has associated workers and team_leaders.
Try this query
Service.joins(:team_leaders, :workers).where('team_leaders.id is not null or workers.id is not null')

Rails active record query: Find records on one side of a has_many :through that haven't been used with a particular record on the other side

I have a has_many :through relationship set up like so
class Situation < ActiveRecord::Base
has_many :notifications
has_many :notiftypes, through: :notifications
end
class Notification < ActiveRecord::Base
belongs_to :situation
belongs_to :notiftype
end
class Notiftype < ActiveRecord::Base
has_many :notifications
has_many :situations, through: :notifications
end
So, a Situation has many Notifications, which can be of many types (Notiftype).
My problem is trying to query for the notiftypes that have not been set for a particular situation.
Want to find records with no associated records in Rails 3
The answers in that question get me close, but only to the point of finding Notiftypes that have not been set AT ALL.
If this were the standard :situation has_many :notiftypes I could just do a Left Outer Join like so
myquery = Notiftype.joins('LEFT OUTER JOIN situations ON situations.notiftype_id = notiftype.id').where('notiftype_id IS NULL')
but I'm really not sure how to do this with the intermediate table between them.
I have been trying consecutive joins but it's not working. I'm not sure how to join the two separated tables.
Can anyone explain the right way to query the db? I am using SQLite, Rails 3.1, Ruby 1.9.2 right now, but likely Postgresql in the future.
Try this:
class Situation < ActiveRecord::Base
# ...
has_many :notiftypes, through: :notifications do
def missing(reload=false)
#missing_notiftypes = nil if reload
#missing_notiftypes ||= proxy_owner.notiftype_ids.empty? ?
Notiftype.all :
Notiftype.where("id NOT IN (?)", proxy_owner.notiftype_ids)
end
end
end
Now to get the missing Notiftype
situation.notiftypes.missing
If you want to further optimize this to use one SQL rather than two you can do the following:
class Situation < ActiveRecord::Base
# ...
has_many :notiftypes, through: :notifications do
def missing(reload=false)
#missing_notiftypes = nil if reload
#missing_notiftypes ||= Notiftype.joins("
LEFT OUTER JOIN (#{proxy_owner.notiftypes.to_sql}) A
ON A.id = notiftypes.id").
where("A.id IS NULL")
end
end
end
You can access the missing Notifytypes as:
situation.notiftypes.missing

Making record available only to certain models in Rails 3

I have a weird design question. I have a model called Article, which has a bunch of attributes. I also have an article search which does something like this:
Article.project_active.pending.search(params)
where search builds a query based on certain params. I'd like to be able to limit results based on a user, that is, to have some articles have only a subset of users which can see them.
For instance, I have an article A that I assign to writers 1,2,3,4. I want them to be able to see A, but if User 5 searches, I don't want that user to see. Also, I'd like to be able to assign some articles to ALL users.
Not sure if that was clear, but I'm looking for the best way to do this. Should I just store a serialized array with a list of user_id's and have -1 in there if it's available to All?
Thanks!
I would create a join table between Users and Articles called view_permissions to indicate that a user has permission to view a specific article.
class ViewPermission
belongs_to :article
belongs_to :user
end
class User
has_many :view_permissions
end
class Article
has_many :view_permissions
end
For example, if you wanted User 1 to be able to view Article 3 you would do the following:
ViewPermission.create(:user_id => 1, :article_id => 3)
You could then scope your articles based on the view permissions and a user:
class Article
scope :viewable_by, lambda{ |user| joins(:view_permissions).where('view_permissions.user_id = ?', user.id) }
end
To search for articles viewable by a specific user, say with id 1, you could do this:
Article.viewable_by(User.find(1)).project_active.pending.search(params)
Finally, if you want to assign an article to all users, you should add an viewable_by_all boolean attribute to articles table that when set to true allows an article to be viewable by all users. Then modify your scope to take that into account:
class Article
scope :viewable_by, lambda{ |user|
joins('LEFT JOIN view_permissions on view_permissions.article_id = articles.id')
.where('articles.viewable_by_all = true OR view_permissions.user_id = ?', user.id)
.group('articles.id')
}
end
If an Article can be assigned to multiple Writers and a Writer can be assigned to multiple Articles, I would create an Assignment model:
class Assignment < AR::Base
belongs_to :writer
belongs_to :article
end
Then you can use has_many :through:
class Article < AR::Base
has_many :assignments
has_many :writers, :through => :assignments
end
class Writer < AR::Base
has_many :assignments
has_many :articles, :through => :assignments
end

How do I use ActiveRecord to find unrelated records?

I have a many-to-many relationship set up through a join model. Essentially, I allow people to express interests in activities.
class Activity < ActiveRecord::Base
has_many :personal_interests
has_many :people, :through => :personal_interests
end
class Person < ActiveRecord::Base
has_many :personal_interests
has_many :activities, :through => :personal_interests
end
class PersonalInterest < ActiveRecord::Base
belongs_to :person
belongs_to :activity
end
I now want to find out: in which activities has a particular user not expressed interest? This must include activities that have other people interested as well as activities with exactly zero people interested.
A successful (but inefficent) method were two separate queries:
(Activity.all - this_person.interests).first
How can I neatly express this query in ActiveRecord? Is there a (reliable, well-kept) plugin that abstracts the queries?
I think the easiest way will be to just use an SQL where clause fragment via the :conditions parameter.
For example:
Activity.all(:conditions => ['not exists (select 1 from personal_interests where person_id = ? and activity_id = activities.id)', this_person.id])
Totally untested, and probably doesn't work exactly right, but you get the idea.

Resources