How to scope a join table? - ruby-on-rails

I'm using filterrific gem, and need to scope the following.
I have 3 tables. And I would like scope the User model , that returns all university names.that all User belongs through Colleges. Colleges is the join table here. My models are as below (Rails 4.2):
class User < ActiveRecord::Base
has_many :colleges
has_many :universities, :through => colleges
# my current scope which is not working at all
scope :user_university, -> (user_university){joins(colleges: [ {university: :name}]).where("universities.name = ? ", user_university)}
end
class Colleges < ActiveRecord::Base
belongs_to :user
belongs_to :university
end
class University < ActiveRecord::Base
has_many :colleges
has_many :users, :through => colleges
end
I keep getting errors and I'm unsure how to scope a model through a join table.

Let try as this:
scope :user_university, -> (university_name) {
joins(colleges: :university).where(universities: {name: university_name})
}
I rename the parameter user_university to university_name btw, user_university is kind of confusing!

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 activerecords with nested includes

I have the following models:
class BusinessProcess < ActiveRecord::Base
has_many :todos
end
class Todo < ActiveRecord::Base
has_one :row
end
class Row < ActiveRecord::Base
has_many :users
end
How can I count the number of rows in a BusinessProcess that has rows on a specific user?
Something like:
#businessProcess.todos.includes(XXX).where(users.id=?,1).count
#businessProcess.todos.includes(:row => :users).where("users.id=?",1).count
According to your associations, I'd rather go with just joining the tables like:
class Todo < ActiveRecord::Base
has_one :row
has_many: users, through: :row
scope :by_user_id, ->(user_id) {
joins(:users).where("users.id = ?", user_id)
}
end
and then:
#business_process.todos.by_user_id(1).count
Maybe you also could think of moving the where condition into a scope of Row, but that is more a responsibility thingie.
You also could read about ARel as an alternative: The N+1 problem and ARel.

How to scope model through two level association in Rails?

I have models User, Tagging, Tag
User.rb
has_one :tagging
Tagging.rb
belongs_to :user
belongs_to :tag
Tag.rb
has_many :taggings
I want to scope the User based on the Tag's name. How to do it?
Currently, I know how to do the scope under one level association, for example:
scope :with_tag_id, -> (tag_id) {joins(:tagging).where(taggings: {tag_id: tag_id})}
But how to do more levels?
Try this
class User < ActiveRecord::Base
scope :by_tag_name, ->(tag_name) { joins(tagging: :tag).where("tags.name=?", tag_id, tag_name)
end

complex query... how to join many classes in rails?

I have the following associations:
class Venue < ActiveRecord::Base
has_many :sales
end
class Sale < ActiveRecord::Base
has_many :sale_lines
has_many :beverages, through: :sale_lines
end
class SaleLine < ActiveRecord::Base
belongs_to :sale
belongs_to :beverage
end
class Beverage < ActiveRecord::Base
has_many :sale_lines
has_many :sales, through: :sale_lines
has_many :recipes
has_many :products, through: :recipes
end
class Recipe < ActiveRecord::Base
belongs_to :beverage
belongs_to :product
end
class Product < ActiveRecord::Base
has_many :recipes
has_many :beverages, through: :recipes
end
I wan't to see the quantity of products sold by each venue, so basically I have to multiply the recipe.quantity by the sale_line.quantity of an specific product.
I would like to call #venue.calc_sales(product) to get the quantity sold of product.
Inside the class Venue I am trying to calculating it by:
class Venue < ActiveRecord::Base
has_many :sales
def calc_sales(product)
sales.joins(:sale_lines, :beverages, :recipes).where('recipes.product_id = ?', product.id).sum('sale_lines.quantity * recipe.quantity')
end
end
However, I can't access the recipes in that way.
Any idea on how to achieve it?
For the joins, you have to use a Hash to join a already-joined table. It's hard to explain, but here are some examples:
Venue.joins(:sales, :beverages) : This implies that the relations :sales and :beverages are declared on the Venue model.
Venue.joins(:sales => :beverages) : This implies that the relation :sales exists on the Venue model, and the relation :beverages exists on the Sale model.
Consider this:
Venue
has_one :sale
Venue.joins(:sales) : This would not work, you have to use the exact same name as the relation between the Venue model & Sale model.
Venue.joins(:sale) : This would work because you used the same name of the relation.
Attention: You have to use the pluralized name in the where clause:
Venue.joins(:sale).where(:sales => { :id => sale.id })
^^ ^^ # See the plural
In your case, you can do something like this:
sales.joins(:sale_lines => { :beverage => :recipes })
.where(:recipes => { :product_id => product.id })
.sum('sale_lines.quantity * recipes.quantity')

has_many :through association through two different associations

I have four model classes:
class Group < ActiveRecord::Base
has_many :projects
has_many :personal_blogs
end
class Project < ActiveRecord::Base
has_many :events, :as => :event_producer
end
class PersonalBlog < ActiveRecord::Base
has_many :events, :as => :event_producer
end
class Event < ActiveRecord::Base
belongs_to :event_producer, :polymorphic => true
end
I want to find all of the events for a particular group. I figure this is a has_many :through association, but how do I specify a has_many on Group that finds all events in the projects or personal_blogs of a group? I could, of course, specify two associations and concatenate the results, but then I have to re-sort, limit, condition, etc. in Ruby, which could potentially be a performance nightmare with many events. I'd like to do this within ActiveRecord to avoid such a nightmare.
You could define a method in the Group class like next:
class Group < ActiveRecord::Base
has_many :projects
has_many :personal_blogs
def events
Event.find(:all, :conditions => ['(type = ? AND event_producer_id IN (?)) OR (type = ? AND event_producer IN (?))', 'project', project_ids, 'personal_blog', personal_blog_ids])
end
end
If you don't like SQL like the previous one, it's always possible to use Single Table Inheritance. This solution depends on your classes attributes and behavior, but will allow you to use a "has_many through" association.
Why not just do:
class Group < ActiveRecord::Base
has_many :projects
has_many :personal_blogs
def all_events
projects.events + personal_blogs.events
end
end
If you need an association that you can preload you could specify a custom 'from' query that includes the group id with the event.
class Group < ActiveRecord::Base
has_many :projects
has_many :personal_blogs
has_many :events, -> {from(<<~SQL)}
(
SELECT DISTINCT e.*, g.id as group_id
FROM events e
LEFT JOIN projects p ON p.event_id = e.id
LEFT JOIN personal_blogs pb ON pb.event_id = e.id
INNER JOIN groups g ON g.id IN (p.group_id, pb.group_id)
) as events
SQL
end

Resources