Extracting data using rails query from a join table - ruby-on-rails

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)})

Related

Rails naming convention for join tables to specialized tables

I get that if you have a posts and a categories table that the join table will be posts_categories. However you might have more than one type of category.
If we decide to create specialized category tables for each object type we would create a posts_categories table which would be a table of categories specifically for post objects. What would the many-to-many join table be called between posts and posts_categories?
If I were you, I would create the join table (categorizations) with additional column(s):
rails g model Categorization post:references category:references new_column:new_type ....
### models/post.rb
class Post < ApplicationRecord
has_many :categorizations
has_many :categories, through: :categorizations
end
## models/categorization
class Categorization < ApplicationRecord
belongs_to :post
belongs_to :category
## You can add new columns as many as you want, just like other tables
end
# models/category.rb
class Category < ApplicationRecord
has_many :categorizations
has_many :posts, through: :categorizations
end
I'm not sure I can fully understand your question. But, if you want create a many-to-many relationship between Post and Category, you can try as bellow:
# post.rb
class Post < ApplicationRecord
has_many :post_categories
has_many :categories, through: :post_categories
end
and
#post_category.rb
class PostCategory < ApplicationRecord
belongs_to :post
belongs_to :category
end
and
# category.rb
class Category < ApplicationRecord
has_many :post_categories
has_many :posts, through: :post_categories
end
Hope it helps.

Specifying Conditions on Multiple Nested Eager Loaded Associations

I'm trying to query on ActiveRecord multiple nested eager loaded associations with conditions like so:
user.books.includes(slot: [room: :school]).where("books.slot.room.school.id = 1")
Obviously this query is wrong, but basically what I'm trying to reach is a relation of user.books for a certain school.
My model structure is:
class User < ActiveRecord::Base
has_many :books
has_many :slots, through: :books
has_many :rooms, through: :slots
has_many :schools
end
class Book < ActiveRecord::Base
belongs_to :user
belongs_to :slot
end
class Slot < ActiveRecord::Base
has_many :books
belongs_to :room
end
class Room < ActiveRecord::Base
belongs_to :school
has_many :slots
end
class School < ActiveRecord::Base
has_many :users
has_many :rooms
has_many :slots, through: :rooms
has_many :books, through: :slots
end
Any ideas? Thanks in advance.
includes eager loads association records, while joins does what you want to do.
This question has been asked here numerous times. Please make sure that you look and try to find a similar question here before you ask one.
So you want to change your code like this:
user.books.joins(slot: [room: :school]).where(schools: { id: 1 })

Active record query using joins

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 })

Rails 4 has_many :through association with :source

I was following the answer laid out at the link below to set up a many_to_many relationships on my Rails 4 app. (New to rails, here.)
Implement "Add to favorites" in Rails 3 & 4
I have Users and Exercises, and I want users to be able to have Favorite Exercises. I created a join table called FavoriteExercise with user_id and exercise_id as columns. I've got it populating, and it seems to be working fine, but I'm not able to use it to call directly to my favorites. 
Meaning, I want to type:
user.favorite = #list of exercises that have been favorited
I get this error when I try to load that list in my browser:
SQLite3::SQLException: no such column: exercises.favorite_exercise_id:
SELECT "exercises".* FROM "exercises" INNER JOIN "favorite_exercises"
ON "exercises"."favorite_exercise_id" = "favorite_exercises"."id"
WHERE > "favorite_exercises"."user_id" = ?
My models:
class User < ActiveRecord::Base
has_many :workouts
has_many :exercises
has_many :favorite_exercises
has_many :favorites, through: :favorite_exercises, source: :exercises
class Exercise < ActiveRecord::Base
belongs_to :user
has_many :workouts, :through => :exercises_workouts
has_many :favorites
has_many :favorited_by, through: :favorite_exercises, source: :exercises
class FavoriteExercise < ActiveRecord::Base
has_many :exercises
has_many :users
I just tried switching FavoriteExercise to 'belongs_to' instead of 'has_many, because it seems maybe that's the way that should go? but then I get this error:
uninitialized constant User::Exercises
Just trying to figure out how to set up the tables and associations so I can call .favorites on a user and get all their favorites.
If you want the list of exercises of the user and at the same, the list of favorite exercise of the user, then I think your join table should just be users_exercises wherein it will list all the exercises by the users. To list the favorite exercises, just add a boolean field indicating if the exercise is a user favorite and add a :scope to get all the favorite exercises.
So in your migration file:
users_exercises should have user_id, exercise_id, is_favorite
Then in your model:
class User < ActiveRecord::Base
has_many :workouts
has_many :users_exercises
has_many :exercises, through: :users_exercises
scope :favorite_exercises, -> {
joins(:users_exercises).
where("users_exercises.is_favorite = ?", true)
}
class Exercise < ActiveRecord::Base
has_many :workouts, :through => :exercises_workouts
has_many :users_exercises
has_many :users, through: :users_exercises
class UsersExercise < ActiveRecord::Base
belongs_to :exercise
belongs_to :user
You just need to simplify your model logic as follow:
class User < ActiveRecord::Base
has_many :workouts
has_many :exercises
has_many :favorite_exercises
has_many :favorites, through: :favorite_exercises, class_name: "Exercise"
class Exercise < ActiveRecord::Base
belongs_to :user
has_many :workouts, :through => :exercises_workouts
has_many :favorite_exercises
has_many :favorited_by, through: :favorite_exercises, class_name: "User"
class FavoriteExercise < ActiveRecord::Base
belongs_to :favorited_by
belongs_to :favorite
Then you can call user.excercises or excercise.users in your User/Excercise instance.
user.excercises = #list of exercises that have been favorited
Is that the many-to-many relationship you want?

has_many :through Association working with one-to-many

rails 4.2
ruby 2.1
I have two very basic models (product and tag) with has_many association through another model (taggings).
I have another model (category) with one-to-many connection with the aforementioned model (product).
Question:
How to show in view the tag list of products with a specific product's category?
In other words: Is it possible to list all tags from a particular category of product?
Models:
class Product < ActiveRecord::Base
has_many :taggings
has_many :tags, through: :taggings
belongs_to :category, counter_cache: true
end
class Tag < ActiveRecord::Base
has_many :taggings
has_many :products, through: :taggings
end
class Tagging < ActiveRecord::Base
belongs_to :product
belongs_to :tag, counter_cache: :products_count
end
class Category < ActiveRecord::Base
has_many :products
end
Quickest way is category_object.products.map(&:tags).flatten . Can be improved. :)
category has many products and product has many tags. Mapping tags method on each product. Flatten to remove duplicates.
You can add a product_tags association to the Category class:
class Category < ActiveRecord::Base
has_many :products
has_many :product_tags, -> { uniq }, through: :products
end
When you access the product_tags association, Rails will use a SELECT DISTINCT query so you won't end up with duplicate tags and the DB will eliminate duplicates.
If the above doesn't feel natural for your model, then you can also use the following (assuming c is a Category instance):
Tag.joins(:products).where(products: { category: c})
The DB query will be very similar to the other example.

Resources