I have a rails 3 application.
I have the following scenario: There are students and teachers who have interactions. Based on some of these interactions, a teacher may elect to give a student a reward, i.e. a student_reward.
class Student
has_many :interactions
has_many :teachers, :through=>:interaction
class Teacher
has_many :interactions
has_many :students, :through=>:interaction
has_many :rewards
class Interaction
belongs_to :teacher
belongs_to :student
has_many :student_rewards
has_many :rewards, :through=>:student_reward
class Reward
belongs_to :teacher
has_many :student_rewards
class StudentRewards
belongs_to :reward
belongs_to :interaction
How would an expert code an efficient approach to fetching all of the rewards that a student's teachers have [not necessarily ones that the student has won] and also list info on the teachers in the display?
I tried this, but I have to separately get the teacher information in the view, and this is bad. (assuming the student_id=1):
#rewards = Reward.joins(:teacher => :interactions)
.where("interactions.student_id=1")
.paginate(:page => params[:page], :per_page => 5)
Questions:
Is this the best way to do it?
When I am iterating through this in the view, I have to issue additional queries to display information about the teacher [name, deomographics]. How can I fetch this more efficiently?
I want to be able to do this:
<% for reward in #rewards%>
<%= reward.name, reward.teacher.name, reward.teacher.bio%><br>
<%end%>
If you have the student, then getting the rewards for the student's teacher is easily obtained like so:
#student = Student.find(1)
#rewards = []
#rewards += #student.teachers.collect {|teacher| teacher.rewards }
The relationship you set in Student has_many :teachers, :through=>:interaction makes this possible.
Related
Sorry for the ignorant question, I'm new to Ruby on Rails.
I have 3 models - Employer, Employee and Announcement
The Announcement model belongs_to the Employer
The Employer has_many Announcements
The Employee belongs_to the Employer
Currently I'm able to display the Announcements that belong_to an Employer using
#announcements = Announcement.where(:employer_id => current_employer.id)
I'm having trouble display the Announcements that belong to an Employer to the Employee.
What query would I use to do this
If you want to frequently use this Employee and Announcement relationship then make a new relation of has_many through like:
Class Employee
has_one :employer
has_many :announcements, through: :employer
end
Then you can directly have announcements belonging to the employee by simply doing:
#employee.announcements
And for deep understanding you can refer to 2.4 The has_many :through Association
Given the information that you have provided I think you want to Display the announcements for a specific employees, employer. Eg:
Employee -> Employer -> Announcement
In this case you could do something like:
#employeer = Employee.employeer #assuming this is a one-to-one relationship
#announcements = Announcement.where(:employer_id => #employeer.id)
You should be able to display all of the Employer's announcements using
current_employer.announcements
Assuming the Employer has_many Employees, given a single #employee
#employee.employer.announcements
As per your your question requirement. You are not having proper association. You should have following association.
employer has_many announcements
Class Employer
has_many :announcements
has_many :employees, through :announcements
end
Class Employee
belongs_to :announcement
end
Class Announcement
belongs_to :employer, through :announcements
belongs_to :announcement
end
Now find it like this :
#announcements = current_employer.announcements
And employees like this :
#announcements.each do |announcement|
#employees = announcement.employees
end
Associations
Firstly, you should not be calling a .where query on your associative data:
#announcements = Announcement.where(:employer_id => current_employer.id)
Since you're new to RoR, I'd recommend having a quick read of the ActiveRecord Association documentation to give you a much richer understanding of how the associations work.
Specifically, you want to make sure that every time you have associated data, you should be able to call it from your "parent" object. ActiveRecord will take your associations & automatically load the required data (with the help of foreign_keys):
#announcements = current_employer.accouncements
--
Fix
In order to get it working, you'll want to do this:
#app/models/employer.rb
class Employer < ActiveRecord::Base
has_many :employees
has_many :announcements
end
#app/models/employee.rb
class Employee < ActiveRecord::Base
belongs_to :employer
delegate :announcements, to: :employer
end
#app/models/announcement.rb
class Announcement < ActiveRecord::Base
belongs_to :employer
end
This will give you the ability to call the following:
#employee = current_employer.employees.find 1
#announcements = #employee.announcements
I am beginning my first rails application and I would just like to make sure that my associations will work like I am hoping. Here is an overview of the models in my application:
class School < ActiveRecord::Base
has_many :courses
has_many :teachers, :through => :courses
has_many :students, :through => :courses
end
class Course < ActiveRecord::Base
belongs_to :school
belongs_to :teacher
has_and_belongs_to_many :students
end
class Teacher < ActiveRecord::Base
has_many :courses
has_many :students, :through => :courses
end
class Student < ActiveRecord::Base
has_and_belongs_to_many :courses
has_many :teachers, :through => :courses
end
The one thing I have noticed so far is that if I try to get the teachers or students in a school it will return the same record multiple times. I am aware I can just call uniq but I would prefer not having to doing that. So I am wondering if there is a better way I can do this.
Were it me, I would not subordinate teachers or students to courses, but to the school directly. Does a teacher not exist without a course? Likewise a student?
And I'd use defined models for linking teachers and students to courses, not the generic habtm -- defined models let you easily store additional attributes - students might have grades for courses for instance.
Perhaps:
School, has many faculty, has many students, has many courses.
Faculty, has many teachings, has many courses through teachings.
Student, has many registrations, has many courses through registrations.
Course, has many registrations, has many students through registrations, has many teachings, has many faculty through teachings.
Teaching, belongs to faculty, belongs to course.
Registration, belongs to student, belongs to course.
Regarding your interest in a unique constraint, you can actually specify the has_many :through to only return unique records.
has_many :teachers, through: :courses, -> { distinct }
Check out the Rails Guide on the topic.
Regarding an additional :school_id, If a Student or Teacher's association to a School is defined purely by their enrollment in or teaching of a Course, I see no immediate issue with not specifying a :school_id column on your Teacher and Parent
Bear with me as I'm a Rails noob. I have a basic league system. League -> Seasons -> Divisions -> Teams. I would like my Divisions and Teams to belong to any number of seasons. My models are as follows...
class Season < ActiveRecord::Base
belongs_to :league
has_many :season_division_teams
has_many :divisions, :through => :season_division_teams
end
class Division < ActiveRecord::Base
belongs_to :league
has_many :seasons, :through => :season_division_teams
has_many :season_division_teams
has_many :teams, :through => :season_division_teams
end
class Team < ActiveRecord::Base
belongs_to :league
has_many :season_division_teams
has_many :seasons, :through => :season_division_teams
has_many :divisions, :through => :season_division_teams
end
class SeasonDivisionTeam < ActiveRecord::Base
belongs_to :season
belongs_to :division
belongs_to :team
end
What I want to be able to do ultimately is say given that i'm in season '1', what divisions do I have, and then given those divisions what teams are part of those divisions (for the given season).
Season.Find(1).divisions.all do |division|
# Do something with the teams that make of the division (for that season)
# division.teams.count
end
I just can't get my head around how to do this in Rails, which seems very simple to me in SQL.
Thanks in advance for the help.
Edit:
Just to clarify, I'm trying to get the teams for a given season (with their divisions). With my model I know I can get the divisions for a given season. I just need to the the teams for those divisions and season.
Thanks again.
Use includes to eager-load the associations and avoid the N+1 performance hit when getting the teams:
#season = Season.includes(:divisions => :teams).find(1)
divisions = season.divisions
teams = divisions.map(&:teams).flatten.uniq
If you want to get all divisions and teams for that season only, then do this differently. Firstly, add the following line to your Season model:
has_many :teams, :through => :season_division_teams
Now you can simply do:
#season = Season.find(1)
divisions = #season.divisions
teams = #season.teams
Note that there's not much point in eager loading here since the database will only be hit twice (rather than once and then once again per division).
UPDATE
Lastly, if you want to get all divisions for the season, and then all teams within those divisions for that season, do as follows. You could use this code:
#season = Season.find(1)
divisions = #season.divisions
divisions.each do |division|
puts "Division: #{division.name}"
division.teams.where(:season_division_teams => {:season_id => #season.id}).each do |team|
puts "Team: #{team.name}"
end
end
But using eager-loading is the preferred method like so:
#season = Season.find(1)
divisions = #season.divisions.includes(:season_division_teams => :teams).
where(:divisions => {:season_division_teams => {:season_id => #season.id}})
divisions.each do |division|
puts "Division: #{division.name}"
division.teams.each do |team|
puts "Team: #{team.name}"
end
end
This should eager load all teams for the associated divisions that meet the conditions that we merged in.
Having some challenges, need to keep track of the courses an Employee have taken. This is what i have these tables so far
Course: course_id,name,category_of_course...#holds Course related details
Employee: employee_id, name...#holds Employee details
progress:course_id,employee_id, status ...#holds which course have been
taken and by whom
in My Models, i have this relationships:
Employee: has_many :courses
Please how do i populate a Select List with the Courses that an Employee have not taken(assuming after registering for a course, it's flagged as taken). Im using rails 3.0.9 with MySql.
Thank You
You need to define
belongs_to :employee
belongs_to :course
(in your progress model)
You can then define
#Employee
has_many :progresses
has_many :courses, :through => :progresses
#Course
has_many :progresses
has_many :employees, :through => :progresses
The select list will be:
= form_for #something do |f|
= f.select :course_id, Course.all.select{|x| not #employee.courses.include?(x)}.collect{|x|[x.name,x.id]} #Ideally this logic should reside in the model.
Right now I'm building a social media app, where i want an user to have a rating per category, how would the association go? The way it needs to be setup it's Each user will have a different rating in each category.
I'm think that
belongs_to :user
belongs_to :category
in the UserCategoryRating model.
and
has_many :user_category_ratings, through => :category
on the User model, Is this the correct approach?
The UserCategoryRating table has the User_id column, Category_id column, and the rating column, that updates each time an user gets votes (The rating it's just the AVG between votes and the score based on 1-5)
UPDATE: If I'm understanding you correctly, here is a diagram of the simple design you'd like:
And this would be the basic skeleton of your classes:
class User < ActiveRecord::Base
has_many :ratings
# has_many :categories, :through => :ratings
end
class Category < ActiveRecord::Base
has_many :ratings
# has_many :users, :through => :ratings
end
class Rating < ActiveRecord::Base
belongs_to :user
belongs_to :category
validates_uniqueness_of :user_id, :scope => [:category_id]
end
Will allow for these query:
#category_ratings_by_user = Rating.where("ratings.user_id = ? AND ratings.category_id = ?", user_id, category_id)
#specific_rating = user.ratings.where("ratings.category_id = ?", category_id)
# make nice model methods, you know the deal
# ... if you added the has_many :through,
#john = User.find_by_name("john")
# Two ways to collect all categories that john's ratings belong to:
#johns_categories_1 = #john.ratings.collect { |rating| rating.category }
#johns_categories_2 = #john.categories
#categories_john_likes = #john.categories.where("categories.rating >= ?", 7)
I'm just unsure as to why you want this has_many, :through (this doesn't seem like a many to many -- a rating only belongs to one user, correct?).
I will use the following data model:
class User
has_many :user_categories
has_many :categories, :through => :user_categories
end
class UserCategory
belongs_to :user
belongs_to :category
# this model stores the average score also.
end
class Category
has_many :user_categories
has_many :users, :through => :user_categories
end
Now when you want to update the score of a user for a category
uc = u.user_categories.find_by_category_id(id)
uc.score = score
uc.save