Rails multiple joins AR query and where clause - ruby-on-rails

I have the following models:
class Product < ActiveRecord::Base
has_many :product_recommendation_sets, :dependent => :destroy
has_many :recommendation_sets, :through => :product_recommendation_sets
end
class ProductRecommendationSet < ActiveRecord::Base
belongs_to :product
belongs_to :recommendation_set
end
class RecommendationSet < ActiveRecord::Base
has_many :product_recommendation_sets, :dependent => :destroy
has_many :products, :through => :product_recommendation_sets
has_many :recommendation_recommendation_sets, :dependent => :destroy
has_many :recommendations, :through => :recommendation_recommendation_sets
end
class RecommendationRecommendationSet < ActiveRecord::Base
belongs_to :recommendation
belongs_to :recommendation_set
end
class Recommendation < ActiveRecord::Base
has_many :recommendation_recommendation_sets, :dependent => :destroy
has_many :recommendations, :through => :recommendation_recommendation_sets
end
Im trying to select all recommendations where product_id = x, by doing:
RecommendationSet.joins(:products, :recommendations).where(product_id:1)
However I get an unknown column error. How can I join select all recommendations by a given product_id.
Psudo code:
Find recommendation_sets where product_id = ?. Find recommendations where recommendation_set_id = ?.

You were pretty close, in your case this should work:
RecommendationSet.joins(:products, :recommendations).where(products: { id: 1 })
Remember that in the where clause you have to use the table's name in the conditons hash, not the relation's name.
As an Example, consider these relations:
User belongs_to :group
Group has_many :users
Notice the syntax (plural / singular):
User.joins(:group).where(groups: { name: 'Admin' })
# ^ ^
Group.joins(:users).where(users: { id: 15 })
# ^ ^

Related

ruby on rails. has many through association

I have a genre and movie models they are associated with has many through. when calling from browser i'm receiving error about categorization which is join model.
Error: undefined local variable or method `categorizations' for Class:0x00000007be0120>
This is the controller...
class GenreController < ApplicationController
def updateList
result=Net::HTTP.get(URI.parse('url'))
json = JSON.parse(result)
json['genres'].each do |data|
obj = Genre.new(
tmdb_id: data['id'],
name: data['name']
)
end
end
end
this is model
class Genre < ActiveRecord::Base
has_many :categorizations
has_many :movies, :through => categorizations
end
i know that there should be validations for this but right now i'm just populating genre table. There should be a create but i'm just trying to see the result.
EDIT:
Model for categorization
class Categorization < ActiveRecord::Base
belongs_to :movie
belongs_to :genre
end
Model for Movies
class Movie < ActiveRecord::Base
has_many :roles
has_many :actors, :through => :roles
has_many :watchlists
has_many :users, :through => :watchlists
has_many :categorizations
has_many :genres, :through => :categorizations
has_many :videos
end
has_many :movies, :through => categorizations
You need to have a colon before "categorizations":
has_many :movies, :through => :categorizations

Joining one table two times

My db schema:
tournaments(id, ...)
teams(tournament_id, id, ...)
matches(tournament_id, id, team_id_home, team_id_away, ...)
Models:
class Tournament < ActiveRecord::Base
has_many :teams, dependent: :destroy
has_many :matches, dependent: :destroy
...
end
class Team < ActiveRecord::Base
belongs_to :tournament
...
end
class Match < ActiveRecord::Base
belongs_to :tournament
has_many :teams
...
end
I would like to have the following data in my view:
match_id team_id_home team_id_away team_id_home_name team_id_away_name
So, I'm asking for help with the following query (I'm trying to get team's names, but having problem with joining):
#matches = #tournament.matches.where(:tournament => #tournament).joins(:teams).paginate(page: params[:page])
I'm fairly new to rails, but you should be able to setup your associations like this: (going from memory)
class Match < ActiveRecord::Base
belongs_to :tournament
has_one :home_team, :class_name => "Team", :foreign_key => "team_id_home"
has_one :away_team, :class_name => "Team", :foreign_key => "team_id_away"
end
#####
m = Match.first
m.away_team.team_name
m.home_tam.team_name
Or in your case:
#matches = #tournament.matches.paginate(page: params[:page])
I don't think you need the where function: the has_many association tells rails to only pull matching matches.
It is belongs_to, not has_one in Match model.
class Match < ActiveRecord::Base
belongs_to :tournament
belongs_to :home_team, :class_name => "Team", :foreign_key => "team_id_home"
belongs_to :away_team, :class_name => "Team", :foreign_key => "team_id_away"
end
class Team < ActiveRecord::Base
belongs_to :tournament
has_many :matches
end
Now I can use tournament.home_team.name in my view

Rails 4 Has_many :through join association with select

I am trying to upgrade a rails 3.0 app to rails 4.0. One of the behaviour I noticed is the relationship between the models stopped working.
Assume we have the following models:
class Student < ActiveRecord::Base
has_many :teacher_students
has_many :teachers, :through => :teacher_students, :select => 'teacher_students.met_with_parent, teachers.*'
# The Rails 4 syntax
has_many :teachers, -> { select('teacher_students.met_with_parent, teachers.*') }, :through => :teacher_students
end
class Teacher < ActiveRecord::Base
has_many :teacher_students
has_many :students, :through => :teacher_students, :select => 'teacher_students.met_with_parent, students.*'
end
class TeacherStudent < ActiveRecord::Base
belongs_to :teacher
belongs_to :student
# Boolean column called 'met_with_parent'
end
Now we are able to do:
teacher = Teacher.first
students = teacher.students
students.each do |student|
student.met_with_parent # Accessing this column which is part of the join table
end
This worked for Rails 3.0, but now on Rails 4.0 I am getting Unknown column 'met_with_parent' in 'field list' I believe Rails 4 is trying to be smart and not loading the entire given join tables.
I personally would recommend the following approach, using scopes:
class Student < ActiveRecord::Base
has_many :teacher_students
has_many :teachers, :through => :teacher_students
end
class Teacher < ActiveRecord::Base
has_many :teacher_students
has_many :students, :through => :teacher_students
scope :met_with_parent, -> { joins(:teacher_students).where('teacher_students.met_with_student = ?', true) }
end
class TeacherStudent < ActiveRecord::Base
belongs_to :teacher
belongs_to :student
end
Then you can do the following:
Teacher.first.students.met_with_parent
This allows you to maintain the relationships AND filter when needed.

Rails find and calculate using has_many through releationship

I have following models:
Product:
class Product < ActiveRecord::Base
has_many :product_recommendations, :dependent => :destroy
has_many :recommendations, :through => :product_recommendations
end
ProductRecommendation:
class ProductRecommendation < ActiveRecord::Base
belongs_to :recommendation
belongs_to :product
end
Recommendation:
class Recommendation < ActiveRecord::Base
has_many :product_recommendations, :dependent => :destroy
has_many :products, :through => :product_recommendations
has_many :recommendation_ratings, :dependent => :destroy
has_many :ratings, :through => :recommendation_ratings
end
Rating:
class Rating < ActiveRecord::Base
has_many :recommendation_ratings, :dependent => :destroy
has_many :recommendations, :through => :recommendation_ratings
end
RecommendationRating:
class RecommendationRating < ActiveRecord::Base
belongs_to :recommendation
belongs_to :rating
end
How would I be able to calculate the average rating for a given recommendation? My ratings table contains just 4 records, (Ratings 1-4), and I have an action in my recommendation controller which updates the RecommendationsRatings join table to associate a rating with a recommendation. Thanks!
have you tried:
class Product < ActiveRecord::Base
has_many :product_recommendations, :dependent => :destroy
has_many :recommendations, :through => :product_recommendations
has_many :ratings, :through => :recommendations
end
and then
p = Product.find 1
p.ratings.average(:rating)
this is assuming the Ratings model has an int / float called rating which stores the rating

how to avoid duplicates in a has_many :through relationship?

How can I achieve the following? I have two models (blogs and readers) and a JOIN table that will allow me to have an N:M relationship between them:
class Blog < ActiveRecord::Base
has_many :blogs_readers, :dependent => :destroy
has_many :readers, :through => :blogs_readers
end
class Reader < ActiveRecord::Base
has_many :blogs_readers, :dependent => :destroy
has_many :blogs, :through => :blogs_readers
end
class BlogsReaders < ActiveRecord::Base
belongs_to :blog
belongs_to :reader
end
What I want to do now, is add readers to different blogs. The condition, though, is that I can only add a reader to a blog ONCE. So there mustn't be any duplicates (same readerID, same blogID) in the BlogsReaders table. How can I achieve this?
The second question is, how do I get a list of blog that the readers isn't subscribed to already (e.g. to fill a drop-down select list, which can then be used to add the reader to another blog)?
Simpler solution that's built into Rails:
class Blog < ActiveRecord::Base
has_many :blogs_readers, :dependent => :destroy
has_many :readers, :through => :blogs_readers, :uniq => true
end
class Reader < ActiveRecord::Base
has_many :blogs_readers, :dependent => :destroy
has_many :blogs, :through => :blogs_readers, :uniq => true
end
class BlogsReaders < ActiveRecord::Base
belongs_to :blog
belongs_to :reader
end
Note adding the :uniq => true option to the has_many call.
Also you might want to consider has_and_belongs_to_many between Blog and Reader, unless you have some other attributes you'd like to have on the join model (which you don't, currently). That method also has a :uniq opiton.
Note that this doesn't prevent you from creating the entries in the table, but it does ensure that when you query the collection you get only one of each object.
Update
In Rails 4 the way to do it is via a scope block. The Above changes to.
class Blog < ActiveRecord::Base
has_many :blogs_readers, dependent: :destroy
has_many :readers, -> { uniq }, through: :blogs_readers
end
class Reader < ActiveRecord::Base
has_many :blogs_readers, dependent: :destroy
has_many :blogs, -> { uniq }, through: :blogs_readers
end
class BlogsReaders < ActiveRecord::Base
belongs_to :blog
belongs_to :reader
end
Update for Rails 5
The use of uniq in the scope block will cause an error NoMethodError: undefined method 'extensions' for []:Array. Use distinct instead :
class Blog < ActiveRecord::Base
has_many :blogs_readers, dependent: :destroy
has_many :readers, -> { distinct }, through: :blogs_readers
end
class Reader < ActiveRecord::Base
has_many :blogs_readers, dependent: :destroy
has_many :blogs, -> { distinct }, through: :blogs_readers
end
class BlogsReaders < ActiveRecord::Base
belongs_to :blog
belongs_to :reader
end
This should take care of your first question:
class BlogsReaders < ActiveRecord::Base
belongs_to :blog
belongs_to :reader
validates_uniqueness_of :reader_id, :scope => :blog_id
end
The Rails 5.1 way
class Blog < ActiveRecord::Base
has_many :blogs_readers, dependent: :destroy
has_many :readers, -> { distinct }, through: :blogs_readers
end
class Reader < ActiveRecord::Base
has_many :blogs_readers, dependent: :destroy
has_many :blogs, -> { distinct }, through: :blogs_readers
end
class BlogsReaders < ActiveRecord::Base
belongs_to :blog
belongs_to :reader
end
What about:
Blog.find(:all,
:conditions => ['id NOT IN (?)', the_reader.blog_ids])
Rails takes care of the collection of ids for us with association methods! :)
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
The answer at this link shows how to override the "<<" method to achieve what you are looking for without raising exceptions or creating a separate method: Rails idiom to avoid duplicates in has_many :through
The top answer currently says to use uniq in the proc:
class Blog < ActiveRecord::Base
has_many :blogs_readers, dependent: :destroy
has_many :readers, -> { uniq }, through: :blogs_readers
end
This however kicks the relation into an array and can break things that are expecting to perform operations on a relation, not an array.
If you use distinct it keeps it as a relation:
class Blog < ActiveRecord::Base
has_many :blogs_readers, dependent: :destroy
has_many :readers, -> { distinct }, through: :blogs_readers
end
I'm thinking someone will come along with a better answer than this.
the_reader = Reader.find(:first, :include => :blogs)
Blog.find(:all,
:conditions => ['id NOT IN (?)', the_reader.blogs.map(&:id)])
[edit]
Please see Josh's answer below. It's the way to go. (I knew there was a better way out there ;)
I do the following for Rails 6
class BlogsReaders < ActiveRecord::Base
belongs_to :blog
belongs_to :reader
validates :blog_id, uniqueness: { scope: :reader_id }
end
Don't forget to create database constraint to prevent violations of a uniqueness.
Easiest way is to serialize the relationship into an array:
class Blog < ActiveRecord::Base
has_many :blogs_readers, :dependent => :destroy
has_many :readers, :through => :blogs_readers
serialize :reader_ids, Array
end
Then when assigning values to readers, you apply them as
blog.reader_ids = [1,2,3,4]
When assigning relationships this way, duplicates are automatically removed.

Resources