ActiveRecord select through multiple HABTM - ruby-on-rails

I have three models:
class User < ActiveRecord::Base
has_and_belongs_to_many :groups
end
class Group < ActiveRecord::Base
has_and_belongs_to_many :channels
has_and_belongs_to_many :users
end
class Channel < ActiveRecord::Base
has_and_belongs_to_many :groups
end
What's the most efficient way to get all the channels (with no duplicates) for a particular user?
So effectively:
SELECT DISTINCT name FROM users
JOIN groups_users ON users.id=groups_users.user_id
JOIN channels_groups ON groups_users.group_id=channels_groups.group_id
JOIN channels ON channels_groups.channel_id=channels.id;

This should do it:
user = User.first
channels = user.groups.flat_map(&:channels).uniq

Related

Rails 5 ActiveRecord Query - Possible to join 3 tables?

Given the following models:
User: id
UserPosition: user_id, job_title_id
JobTitle: id | title
With Rails 5, how can I do something like:
current_user.job_title
What would I need to lookup UserPosition and then JobTitle to get the title?
Is this possible with one query?
You can do this through associations like this:
class User < ApplicationRecord
has_many :user_positions
has_many :job_titles, through: :user_positions
end
class UserPositions < ApplicationRecord
belongs_to :user
belongs_to :job_title
end
class JobTitle < ApplicationRecord
has_many :user_positions
has_many :users, through: :user_positions
end
Here's the documentation for a many to many relationship in Rails.
With your relationships defined as:
class User < ApplicationRecord
has_many :user_positions
end
class JobTitle < ApplicationRecord
has_many :user_positions
end
class UserPosition < ApplicationRecord
belongs_to :user
belongs_to :job_title
end
Then you can use joins, with both models job_title and user_position, through the user model, and knowing the user.id, so, then you can use pluck to get the needed attribute:
User.joins(user_positions: :job_title).where(id: 1).pluck('job_titles.title')
Which would give you an SQL query like:
SELECT job_titles.title
FROM "users"
INNER JOIN "user_positions"
ON "user_positions"."user_id" = "users"."id"
INNER JOIN "job_titles"
ON "job_titles"."id" = "user_positions"."job_title_id"
WHERE (users.id = 1)

has_many and belongs_to with join table

I have Users and Trucks. I want the ability to say #truck.drivers << #users and #user.truck = #truck.
The solution is simple until I want the relationship to be stored in a join table.
# tables
:users
:id
:truck_drivers
:user_id
:truck_id
:truck
:id
I've gotten it to where I can say #truck.drivers << #user and #user.trucks << #truck, but I would like to limit a user to occupy one truck at a time, for my sanity.
Is it possible? A has_many/belongs_to with a join table? Or should I try a different approach? I'm not using a third model for the join table. It's just a table. Here's what I have so far.
class User < ApplicationRecord
has_and_belongs_to_many :trucks,
join_table: :truck_drivers, # points to the table
class_name: :Truck # looks for the truck model in the table
end
class Truck < ApplicationRecord
belongs_to :company
has_and_belongs_to_many :drivers,
join_table: :truck_drivers,
class_name: :User
end
The reason I need a join table in the first place is because each User can have many Roles: Admin, Manager, Driver, Customer Service, etc. I thought it didn't make sense to add a truck_id to all the users if all the users are not going to be using trucks.
It seems like you ought to be able to do something like:
#user.trucks << #truck unless #user.trucks.any?
Yes this is a standard strategy with rails using the :through keyword.
rails documentation: http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
Make a model called TruckUser with truck_id and user_id
then edit your classes:
class User < ApplicationRecord
has_many :truck_users
has_many :trucks, through: :truck_users
end
class Truck < ApplicationRecord
belongs_to :company
has_many :truck_users
has_many :drivers, through: :truck_users
end
class TruckUser < ApplicationRecord
belongs_to :truck
belongs_to :user
end

we find all contract of a user and all jobs of contract of that particular user

I have two models one is User and another is Contract.
These are my models
class User < ApplicationRecord
has_many :user_jobs ,dependent: :destroy
has_many :contracts ,through: :user_jobs
end
class Contract < ApplicationRecord
has_many :user_jobs ,dependent: :destroy
has_many :users ,through: :user_jobs
end
class UserJob < ApplicationRecord
belongs_to :user
belongs_to :contract
end
we have to find all unique contracts of a user
If you want only user's jobs then this will be perfect :
Contract.joins([:user_jobs=> [:user]]).where("users.id = ?",user_id).distinct
or directly
Contract.joins(:user_jobs).where("user_jobs.user_id = ?",user_id).distinct
To find all unique contracts of a user, you can make a LEFT OUTER JOIN using includes which would also help you to eager load the association:
Contract.includes(user_jobs: :user).where(user_jobs: {user_id: user_id}).uniq
You can make a join involving several tables. (contracts, user_jobs & users).
# user_id = 1
Contract.includes(:users).where(users: {id: user_id}).distinct
But I think is better to make a user query to avoid the 3 table join as follows
# user = User.find(1)
user.contracts.distinct
Hope it helps

Possible to filter an ActiveRecord query based on multiple "belongs_to" relations?

class Post < ActiveRecord::Base
belongs_to :user
belongs_to :category
end
class User < ActiveRecord::Base
has_many :posts
end
class Category < ActiveRecord::Base
has_many :posts
end
I want to get all the posts that have a relation to user AND category. Is any similar possible:
user.category.posts
Or do I need to do:
user.posts.where(category_id: category.id)
You have 1-M relationship between User and Post.
In User model association should be has_many :posts (note plural)and not has_many :post(singular).
Update your model User as below:
class User < ActiveRecord::Base
has_many :posts ## Plural posts
end
To answer below question:
get all the posts that have a relation to user AND category
Assuming that you have local variables user and category as an instance of User model and Category model respectively.
For example:
user = User.find(1); ## Get user with id 1
category = Category.find(1); ## Get category with id 1
user.posts.where(category_id: category.id) ## would get you what you need.
Also, user.category.posts will not work as User and Category models are not associated.
Try:-
user.posts.where(category_id: category.id)
Change your association like
class Post < ActiveRecord::Base
belongs_to :user
belongs_to :category
end
class User < ActiveRecord::Base
has_many :post
belongs_to :category or has_one :category
end
class Category < ActiveRecord::Base
has_one :user or belongs_to :user
has_many :posts
end
in users table add column category_id,or add column user_id in categories,because you don't have relation between those two tables.if you don't have relation you can't use association API's.then you have to use manual API to fetch the data.like how you have mentioned your question.

Querying many to many relation in Ruby on Rails

Let's say I have an app where users could rate books. Tables are users(id), books(id) and rating(user_id, book_id, value). I've made these models
class Rating < ActiveRecord::Base
belongs_to :user
belongs_to :book
end
class User < ActiveRecord::Base
has_many :ratings
end
class Book < ActiveRecord::Base
has_many :ratings
end
I want to get a list of all (both rated and unrated) books with their ratings made by current user. It's easy in SQL with outer join but I can't figure out a way to do it in Rails 3.
According to LEFT OUTER joins in Rails 3 you'll have to specify the outer join in SQL...
it's quite simple in rails too. You probably should add a relationship in user with books as well.
class User < ActiveRecord::Base
has_many :ratings
has_many :users, :through => :ratings
end
current_user.books.includes(:ratings).all
should work.

Resources