Active record query using joins - ruby-on-rails

I have three different models User, Order, Product:-
class User < ActiveRecord::Base
has_many :orders
has_many :products
end
class Product < ActiveRecord::Base
has_many :orders
end
class Order < ActiveRecord::Base
belongs_to :user
belongs_to :product
end
How to find all the products which an user has ordered using a one line active-record query?

Another options here is using has_many :through relation:
Add to User model
has_many :products, through: :orders
and now user.products does the trick

This is explained in the ActiveRecord documentation:
Product.joins(:orders).where(orders: { user_id: user.id })

I got it finally through this
Product.joins(:orders).where(order: { user_id: user.id })

Related

Extracting data using rails query from a join table

I have users table, books table and books_users join table. In the users_controller.rb I am trying extract the users who have filtered_books. Please help me to resolve that problem.
user.rb
has_many :books_users, dependent: :destroy
has_and_belongs_to_many :books, join_table: :books_users
book.rb
has_and_belongs_to_many :users
books_user.rb
belongs_to :user
belongs_to :book
users_controller.rb
def filter_users
#filtered_books = Fiction.find(params[:ID]).books
#users = **I want only those users who have filtered_books**
end
has_and_belongs_to_many does not actually use a join model. What you are looking for is has_many through:
class User < ApplicationRecord
has_many :book_users
has_many :books, through: :book_users
end
class Book < ApplicationRecord
has_many :book_users
has_many :users, through: :book_users
end
class BookUser < ApplicationRecord
belongs_to :book
belongs_to :user
end
If you want to add categories to books you would do it by adding a Category model and another join table. Not by creating a Fiction model which will just create a crazy amount of code duplication if you want multiple categories.
class Book < ApplicationRecord
has_many :book_users
has_many :users, through: :book_users
has_many :book_categories
has_many :categories, through: :book_categories
end
class BookCategory < ApplicationRecord
belongs_to :book
belongs_to :category
end
class Category < ApplicationRecord
has_many :book_categories
has_many :books, through: :book_categories
end
If you want to query for users that follow a certain book you can do it by using an inner join with a condition on books:
User.joins(:books)
.where(books: { title: 'Lord Of The Rings' })
If you want to get books that have a certain category:
Book.joins(:categories)
.where(categories: { name: 'Fiction' })
Then for the grand finale - to query users with a relation to at least one book that's categorized with "Fiction" you would do:
User.joins(books: :categories)
.where(categories: { name: 'Fiction' })
# or if you have an id
User.joins(books: :categories)
.where(categories: { id: params[:category_id] })
You can also add an indirect association that lets you go straight from categories to users:
class Category < ApplicationRecord
# ...
has_many :users, though: :books
end
category = Category.includes(:users)
.find(params[:id])
users = category.users
See:
The has_many :through Association
Joining nested assocations.
Specifying Conditions on Joined Tables
From looking at the code i am assuming that Book model has fiction_id as well because of the has_many association shown in this line Fiction.find(params[:ID]).books. There could be two approaches achieve this. First one could be that you use #filtered_books variable and extract users from it like #filtered_books.collect {|b| b.users}.flatten to extract all the users. Second approach could be through associations using fiction_id which could be something like User.joins(:books).where(books: {id: #filtererd_books.pluck(:id)})

Rails has_many OR condition

I've got a task where Garage should fetch cars from User model or if User doesn't have cars Garage should fetch cars from itself.
Ideally, I want to have all rails API for has_many relation. Is there any chance to do that?
class Garage
has_many :cars, through: :user || has_many :cars # pseudo-code
end
I would just name the associations differently and use a method to load the cars:
has_many :cars
has_many :user_cars, through: :user, source: :cars
def all_cars
user_cars.presence || cars
end
The full code necessary to solve your problem is unclear, but broadly speaking here's how I would go about performing this structural migration.
Firstly, define two clear associations:
class Garage < ApplicationRecord
has_many :user_cars, through: :user, class_name: 'Car'
has_many :cars
end
Then add some method(s) that attempt to fetch both, as you described:
class Garage < ApplicationRecord
has_many :user_cars, through: :user, class_name: 'Car'
has_many :cars
def cars
user_cars.presence || super
end
end
Then, when the data has all been migrated to the new structure, you can delete the old association:
class Garage < ApplicationRecord
has_many :cars, through: :user
end

Activerecord has_many :through through multiple models

I'm trying to access all comments from a given user with user.comments. The query is to go through two different models, which likely both return results. My relations are set up as follow:
class User < ActiveRecord::Base
has_many :organisers
has_many :participants
has_many :comments, through: :participants / :organisers (see explenation below)
end
class Organiser < ActiveRecord::Base
belongs_to :user
end
class Participant < ActiveRecord::Base
belongs_to :user
end
class Comment < ActiveRecord::Base
belongs_to :organiser
belongs_to :participant
end
A comment is validated to belong to either a participant, or an organiser.
I'm not sure how to go about this. I've tried
has_many :comments, through: :participants
has_many :comments, through: :organisers
and
has_many :comments, through: [:organisers, :participants]
But that last one isn't rails. Is there a proper way to do this? Thanks!
has_many :comments, ->(user) {
unscope(where: :user_id).
left_joins(:organizer, :participant).
where('organizers.user_id = ? OR participants.user_id = ?', user.id, user.id)
}
The unscope is to remove the comments.user_id = ? clause (which is added by default when you define a has_many relation).
The left_joins is called on Comment, so you need to pass in the relation names as defined on Comment, hence the singulars in this example.
I found a solution after many tries. You can use a scope with param in your last has_many sentence in the User model:
has_many :comments, -> (user) {where organiser: user.organisers}, through: :participants
The "user" param represet the User object whom is calling the comments method.
For anyone coming across this using polymorphic associations, the following worked for me inspired by magni- and Carlos Jimenez' answers:
has_many :comments, -> (user) {
unscope(where: :user_id).
where(commentable: [user.organizers, user.participants])
}
Since we couldn't use has_many, through here because comments come from both of organisers and participants. I just think there are 2 solutions here:
Solution #1 Define comments method:
class User < ActiveRecord::Base
def comments
Comment.joins([{organiser: :user}, {participant: :user}])
.where(users: {id: self.id})
end
end
So then your query to find comments is:
User.first.comments
Solution #2 Use scope in Comment
class Comment < ActiveRecord::Base
scope :from_user, -> (user) {
joins([{organiser: :user}, {participant: :user}]).where(users: {id: user.id})
}
end
So your query will be like:
user = User.first
comments = Comment.from_user(user)
Since we couldn't use has_many, through here because comments come from both of organizers and participants. I just think there are 2 solutions here:
Basically you can still change the foreign key to accept the self.id automatically with Rails here
User.first.comments
class User
has_many :comments, -> { joins([{ organiser: :user }, { participant: :user }]) }, through: :participants, foreign_key: "users.id"
end
I believe your associations would be confused, as user.comments wouldn't know whether it's going through Participant or Organiser, so the best option would be to declare two different joins (with different names):
http://guides.rubyonrails.org/association_basics.html#self-joins

Association where client orders from products. How to setup relation

I'm fiddling with rails and trying to build a small app for practicing purposes:
I want a client to order one or more products
I have a client table, a product table and last an order table which has a client_id and product_id
Now, I'm not quite sure how to set up a good relation between these table as in: client goes to product page, chooses product and saves the order.
Any help about which model should have which relation is greatly appreciated.
You can set up associations like this
Class Client < ActiveRecord::Base
has_many :orders
has_many :products,through: :orders
end
Class Product < ActiveRecord::Base
has_many :orders
has_many :clients,through: :orders
end
Class Order < ActiveRecord::Base
belongs_to :client
belongs_to :product
end
For more details,see these Guides
The association should look something like this
Class Client < ActiveRecord::Base
has_many :orders
has_many :products,through: :orders
end
Class Product < ActiveRecord::Base
has_many :orders
has_many :clients,through: :orders
end
Class Order < ActiveRecord::Base
belongs_to :client
belongs_to :product
end
You could do it like this:
#app/models/product.rb
has_many :orders
has_many :clients, through: :orders
#app/models/order.rb
belongs_to :client
has_many :order_products
has_many :products, through: :order_products
#app/models/client.rb
has_many :orders
has_many :products, through: :orders
The way to handle the creation of a new order is to set a uuid for it, and then create another join model which will handle order products. You could do this with the order model, but I felt it best to describe the basic way, as it will give you something to work from
--
uuid
We like to set uuid columns for sensitive data like orders:
#migration
add_column :orders, :uuid, :varchar, after: :id
#app/models/order.rb
before_create :set_uuid
private
def set_uuid
unless self.uuid
loop do
token = SecureRandom.hex(5)
break token unless self.class.exists?(uuid: token)
end
end
end
This means every order will have as many products as you want, accessible like this:
#user.orders.first.products #-> returns order products
--
Edit - just saw #Pavan's answer. Sorry if it's the same - hope it helps!
You have two approaches
1) set has_and_belongs_to_many between client and product model.
class Client < ActiveRecord::Base
has_and_belongs_to_many :products
end
class Product < ActiveRecord::Base
has_and_belongs_to_many :clients
end
2) set through relation between the client and production through using keyword through
class Client < ActiveRecord::Base
has_many :orders
ha_smany :products, through: orders
end
class Product < ActiveRecord::Base
has_many :orders
ha_smany :clients, through: orders
end
I am suggesting you to go with second option since you have an intermediate model.

Rails: stringing together multiple has_many relationships

I have three models which look something like this:
Class User < ActiveRecord::Base
has_many :comments
end
Class Comment < ActiveRecord::Base
belongs_to :user
has_many :votes
end
Class Vote < ActiveRecord::Base
belongs_to :comment
end
Now I want to get all the votes associated with a user's comments like so:
#user.comments.votes
But this throws the error:
undefined method `votes' for #<ActiveRecord::Relation:0x3f6f8a0>
This seems like it should work, but I suspect ActiveRecord is coughing on the deeper has_many relationship. I've hacked together an SQL query that gets the desired results, but I suspect there's a cleaner way using purely ActiveRecord. Any tips?
You should use a has_many :through association
In your case it would be
Class User < ActiveRecord::Base
has_many :comments
has_many :votes, :through => :comments
end
Class Comment < ActiveRecord::Base
belongs_to :user
has_many :votes
end
Class Vote < ActiveRecord::Base
belongs_to :comment
end
And then simply get the votes with
#user.votes
Try this:
Vote.joins(comment: :user).where(users: {id: #user.id})

Resources