Rails has_many :through scoped with joins - ruby-on-rails

I'm trying to create a has_many :through association with a custom scope. The problem is that the scope has a joins with a 3rd model, and that seems to be breaking the association.
These are the models:
class Student < ApplicationRecord
has_many :lesson_applications, through: :lesson_requests
has_many :lessons, through: :lesson_applications
has_many :scoped_lessons, -> { custom_scope }, through: :lesson_applications, source: :lesson
has_many :tutors, through: :scoped_lessons # Broken association
end
class Lesson < ApplicationRecord
belongs_to :lesson_application
has_one :tutor, through: :lesson_application
has_one :time_range, as: :time_rangeable
scope :custom_scope, (lambda {
joins(:time_range).where('time_ranges.attribute BETWEEN ? AND ?', value1, value2)
})
end
class LessonApplication < ApplicationRecord
belongs_to :lesson_request
belongs_to :tutor
has_one :lesson
end
class Tutor < ApplicationRecord
has_many :lesson_applications
has_many :lessons, through: lesson_applications
end
class TimeRange < ApplicationRecord
belongs_to :time_rangeable, polymorphic: true
end
I expected to get the student's tutors from the scoped lessons, but instead I get an missing FROM-clause entry for table 'time_ranges'. Which is clearly and error that can be seen on the generated query.
The generated query when I do student.tutors is:
SELECT "tutors".* FROM "tutors" INNER JOIN "lesson_applications" ON "tutors"."id" = "lesson_applications"."tutor_id" INNER JOIN "lessons" ON "lesson_applications"."id" = "lessons"."lesson_application_id" INNER JOIN "lesson_applications" "lesson_applications_tutors" ON "lessons"."lesson_application_id" = "lesson_applications_tutors"."id" INNER JOIN "lesson_requests" ON "lesson_applications_tutors"."lesson_request_id" = "lesson_requests"."id" WHERE "lesson_requests"."student_id" = $1 AND (time_ranges.start BETWEEN '2018-04-11 20:27:34.798992' AND '2018-04-11 20:27:34.799090') LIMIT $2
The method I'm using temporarily instead of the association is:
class Student < ApplicationRecord
def tutors
scoped_lessons.map(&:tutor).uniq
end
end
I am using ruby 2.5.1 with Rails 5.1.6
Thank you in advance.
Edit: some associations were wrong.

Related

How to find the elements that have no relation (with an active status)?

I would like to find all sites who have no relation with an active statut?
Site.joins(:site_links).where("site_links.active = ? ", true).group('sites.id').having('COUNT(site_links.*) = 0')
For example, I have a first site who have 5 relations. In this five relations, one is active. I have a second site who have 3 relations, but no one of this is active.
I would like to get only the second site.
class Site < ApplicationRecord
has_many :site_links, dependent: :destroy
has_many :links, through: :site_links
end
class SiteLink < ApplicationRecord
belongs_to :site
belongs_to :link
end
class Link < ApplicationRecord
has_many :site_links, dependent: :destroy
has_many :sites, through: :site_links
end
You can use SQL NOT EXISTS
Site.where("NOT EXISTS (SELECT *
FROM site_links
WHERE (site_id = sites.id)
AND active = true)")
If you have set has_many association properly you can do this
Site.includes(:site_links).where.not(site_links: { active: true })

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

Trouble update has_many :through record

I am having an issue updating a has_many through record. Here is my setup:
class TastingGroup < ActiveRecord::Base
has_many :group_wine
has_many :wines, through: :group_wine
end
class GroupWine < ActiveRecord::Base
belongs_to :tasting_group
belongs_to :wine
end
class Wine < ActiveRecord::Base
has_many :group_wine
has_many :tasting_groups, through: :group_wine
end
I was trying to use the acts_as_list for this, because the order of the wines in a TastinGroup matter, so I have added a 'position' attribute to the GroupWine model.
However, when I try to even update a GroupWine record, I get the following error, and here is what I am doing.
gw = GroupWine.first
#<GroupWine:0x007fd9f7c38b50> {
:wine_id => 1,
:tasting_group_id => 1,
:position => nil
}
gw.position = 1
gw.save
And here is the error I get...
ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column 'group_wines.' in 'where clause': UPDATE `group_wines` SET `position` = 3 WHERE `group_wines`.`` IS NULL
What is up with the NULL for group_wines, and why is it adding that where clause?
Thanks.
Try pluralizing the group_wine object to group_wines
class TastingGroup < ActiveRecord::Base
has_many :group_wines
has_many :wines, through: :group_wines
end
class GroupWine < ActiveRecord::Base
belongs_to :tasting_group
belongs_to :wine
end
class Wine < ActiveRecord::Base
has_many :group_wines
has_many :tasting_groups, through: :group_wines
end

How can I create a join without sql code in rails?

I can't make a join in rails, I don't know what is wrong here. my classes are:
class Serie < ActiveRecord::Base
has_many :user_serie
has_many :user, :through => :user_serie
end
class UserSerie < ActiveRecord::Base
belongs_to :serie
belongs_to :user
end
and
class User < ActiveRecord::Base
has_many :user_serie
has_many :serie, :through => :user_serie
end
and the select is:
#series = Serie.all :joins => :user
so the generated select is:
SELECT "series".* FROM "series"
INNER JOIN "user_series" ON "series"."id" = "user_series"."serie_id"
INNER JOIN "users"
ON 0
AND "users"
AND 'id'
AND "users"."id"
AND 0
AND "user_series"
AND 'user_id'
AND "user_series"."user_id"
AND "users"."id" = "user_series"."user_id"
What can I do to make this select works?
I've tried to make the has_many with plural, but then I have this error:
uninitialized constant Serie::UserSeries
You have wrong relations, should be:
class Serie < ActiveRecord::Base
has_many :user_series
has_many :users, :through => :user_series
end
class UserSerie < ActiveRecord::Base
belongs_to :serie
belongs_to :user
end
class User < ActiveRecord::Base
has_many :user_series
has_many :series, :through => :user_series
end
When you get exception with uninitialized class, then use explicit :class_name like below:
class Serie < ActiveRecord::Base
has_many :user_series, :class_name => "UserSerie"
# or "::UserSerie", i'm not 100% sure which one, rather just as I wrote
has_many :users, :through => :user_series
end

Resources