How to do a query for multiple categories - ruby-on-rails

I have 3 simple models
class Tag < ApplicationRecord
belongs_to :category
belongs_to :course
end
class Course < ApplicationRecord
has_many :tags, dependent: :destroy
has_many :categories, through: :tags
end
class Category < ApplicationRecord
has_many :tags, dependent: :destroy
has_many :courses, through: :tags
end
Given a category as a string (for example 'Japanese') it's trivial to find courses by Category.find_by_title('Japanese').courses
I'm having a hard time doing a query if categories are in an array, for example ['Japanese', 'Language'] which needs to return courses tagged as 'Japanese' and 'Language'

You can query by using joins:
Course.joins(categories: :tags).where(categories: {title: ['array condition']}, tags: {tag: ['array condition']})

Related

Rails ActiveRecord joins with multiple tables

I have a Category Model which belongs to Offer Model through a third Model OfferCategory. I also have a similar association for PlumCake Model which is associated to Category model through PlumCakeCategory Model.
Category:
has_many :offer_categories, dependent: :destroy, inverse_of: :category
has_many :offers, through: :offer_categories, source: :offer
has_many :plum_cake_categories, dependent: :destroy, inverse_of: :category
has_many :plum_cakes, through: :plum_cake_categories, source: :plum_cake
Offer:
has_many :offer_categories, dependent: :destroy, inverse_of: :offer
has_many :categories, through: :offer_categories, source: :category, dependent: :destroy
OfferCategory:
belongs_to :offer
belongs_to :category
PlumCake:
has_many :plum_cake_categories, dependent: :destroy, inverse_of: :plum_cake
has_many :categories, through: :plum_cake_categories, source: :category, dependent: :destroy
PlumCakeCategory:
belongs_to :plum_cake
belongs_to :category
and a similar association for category/plumcakes as well.
Now I want to get all categoires that the selected offers and plumcakes has. The following query gives me the list of categories that the eligible_offer_ids offers has.
Category.joins(:offer_categories).where(offer_categories: { offer_id: eligible_offer_ids })
I can fire a similar query for plum_cake and get uniq categoires of these two queries.
cat1 = Category.joins(:offer_categories).where(offer_categories: { offer_id: eligible_offer_ids })
cat2 = Category.joins(:plum_cake_categories).where(plum_cake_categories: { plum_cake_id: eligible_plum_cake_ids })
(cat1 + cat2).uniq
But Is there a way I get the same result((cat1 + cat2).uniq) in a single query?
if you don't to want change your structure:
Category.left_outer_joins(:offer_categories, :plum_cake_categories).where(offer_categories: { offer_id: eligible_offer_ids }).or(Category.left_outer_joins(:offer_categories, :plum_cake_categories).where(plum_cake_categories: { plum_cake_id: eligible_plum_cake_ids })).uniq
I think if you use rails STI(single table inheritance) you can easily implement your functionality.
if your structure be somthing like following:
class Category
has_many :offer_categories, dependent: :destroy, inverse_of: :category
has_many :offers, through: :offer_categories, source: :offer
has_many :plum_cake_categories, dependent: :destroy, inverse_of: :category
has_many :plum_cakes, through: :plum_cake_categories, source: :plum_cake
has_many :sub_categories
end
class SubCategory
belongs_to :offer
belongs_to :plum_cake
belongs_to :category
end
class OfferCategory < SubCategory
validate_presence_of :offer_id
end
class PlumCakeCategory < SubCategory
validate_presence_of :plum_cake_id
end
your query will be:
Category.joins(:sub_categories).where(sub_categories: { offer_id: eligible_offer_ids }).or(Category.joins(:sub_categories).where(sub_categories: { plum_cake_id: eligible_plum_cake_ids }))
STI documentation

Rails ActiveQueryInterface

I am facing a problem while I try to write a rails query using joins.
What I am building is a hash having candidate_answers grouped according to sections of a given question paper. (N.B. I have a pool of questions from where some are added in different sections of a questiion paper)
I have achieved my solution using map as:
exam_candidate.exam.question_paper.sections.includes(:questions).each do |section|
if section.questions.present?
section_question_hash[section] = candidate_answers.where(question_id: section.questions.map(&:id))
end
end
Since using the above creates a lot of database queries running on the background, it is not healthy to use, and thus I need to use joins. Also, I am able to write a SQL query for the same as
select b.name, group_concat(c.id) from sections b
left join question_papers_questions a on a.section_id = b.id
left join candidate_answers c on a.question_id = c.question_id
where a.question_paper_id = 3 and c.exam_candidate_id = 4
group by (b.name)
But while I try the same in rails I am having lots of issues with it.
Here's my model structure:
class ExamCandidate < ActiveRecord::Base
belongs_to :exam
belongs_to :candidate
has_many :candidate_answers, dependent: :delete_all
accepts_nested_attributes_for :candidate_answers
end
class Exam < ActiveRecord::Base
has_many :exam_candidates, dependent: :destroy
has_many :candidates, through: :exam_candidates
belongs_to :question_paper
end
class QuestionPaper < ActiveRecord::Base
has_many :exams, dependent: :nullify
has_many :exam_candidates, through: :exams
has_many :questions, through: :question_papers_questions
has_many :question_papers_questions
has_many :sections, dependent: :destroy
end
class QuestionPapersQuestion < ActiveRecord::Base
belongs_to :question
belongs_to :question_paper
belongs_to :section
end
class Question < ActiveRecord::Base
has_many :candidate_answers, through: :answers
has_many :exams, through: :question_papers
has_many :exam_candidates, through: :exams
has_many :question_papers_questions
has_many :question_papers, through: :question_papers_questions
end
class Section < ActiveRecord::Base
belongs_to :question_paper
has_many :questions, through: :question_papers_questions
has_many :question_papers_questions
end
class CandidateAnswer < ActiveRecord::Base
belongs_to :exam_candidate
belongs_to :question
end
I have given enough time on it, but being almost a newbie in rails is my disadvantage, if anyone can try it or suggest something it would be very helpful.
sections = exam.question_paper.sections.select("sections.name, group_concat(candidate_answers.id) as candidate_answer_ids")
.joins(:question_papers_questions).joins("inner join candidate_answers on question_papers_questions.question_id = candidate_answers.question_id")
.where(candidate_answers: {exam_candidate_id: id}, question_papers_questions: { question_paper_id: exam.question_paper_id })
.group("sections.name")

ActiveRecord Joins result

Doing an ActiveRecord join in RoR seems to work if I look at the generated SQL.
But what I can't figure out is why the result of that SQL isn't returned into the variable.
What I'm doing is:
class Book < ActiveRecord::Base
has_many :readings, dependent: :destroy
has_many :readers, :through => :readings
accepts_nested_attributes_for :readings
end
class Reader < ActiveRecord::Base
has_many :readings, dependent: :destroy
has_many :books, :through => :readings
accepts_nested_attributes_for :books
end
class Reading < ActiveRecord::Base
belongs_to :reader
belongs_to :book
end
Now, when asking:
result = Reading.where(:reader_id => rid, ).joins(:book).select(columns.collect{|c| c[:name]}.join(',')).flatten
It shows the correct generated SQL:
SELECT readings.id,books.title,books.author,readings.when FROM `readings` INNER JOIN `books` ON `books`.`id` = `readings`.`book_id` WHERE `readings`.`reader_id` = 2
BUT: the result variable only contains the values of the Reading record, NOT the fields of the joined table.
What am I missing?
I have made the association changes in question also:-
class Book < ActiveRecord::Base
has_many :readings, dependent: :destroy
has_many :readers, :through => :readings
accepts_nested_attributes_for :readings
end
class Reader < ActiveRecord::Base
has_many :readings, dependent: :destroy
has_many :books, :through => :readings
accepts_nested_attributes_for :books
end
class Reading < ActiveRecord::Base
belongs_to :reader
belongs_to :book
end
Query in this way:-
reader = Reader.find(rid)
result = reader.books.pluck(:name).join(',')
Ultimately, I've rewritten my helper class and fetched the various fields as I needed them. (as krishnar suggested)
Anyways: Thanx you guys for your contributions.

Arrange items in an array by the time they were added to that array?

Basically I have a Collection model and a Post model, where a Collection has many posts and a Post belongs to many collections. So I'll occasionally push posts to the #collection.posts array using <<, to replicate a post being added to a collection. Now is there a way to order the posts in #collection.posts by the time they were pushed to that array? If yes, how?
All relevant models:
user.rb
class User < ActiveRecord::Base
has_many :posts, dependent: :destroy
has_many :collections, dependent: :destroy
end
post.rb
class Post < ActiveRecord::Base
belongs_to :user
has_many :collectables
has_many :collections, through: :collectables
end
collection.rb
class Collection < ActiveRecord::Base
belongs_to :user
has_many :collectables
has_many :posts, through: :collectables
end
collectable.rb
class Collectable < ActiveRecord::Base
belongs_to :post
belongs_to :collection
end
I guess adding an order scope to the definition of the association would work:
# in collection.rb
has_many :posts,
-> { order('collectables.created_at DESC') },
through: :collectables

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?

Resources