Rails 4 Has_many :through join association with select - ruby-on-rails

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.

Related

How to group together multiple models under one name in Single Table Inheritance classes

(See below for link to sample project)
WHAT I HAVE WORKING:
I have many user types which I am handling using Single Table Inheritance in Rails, e.g.:
class User < ActiveRecord::Base
self.inheritance_column = :meta_type
scope :doctors, -> { where(meta_type: 'Doctor') }
scope :patients, -> { where(meta_type: 'Patient') }
scope :nurses, -> { where(meta_type: 'Nurse') }
scope :employees, -> { where(meta_type: 'Employee') }
end
class Doctor < User
has_many :doctor_patient_relations
has_many :patients, :through => :doctor_patient_relations
has_many :doctor_nurse_relations
has_many :nurses, :through => :doctor_nurse_relations
...
# More join tables between each type of user
end
class Patient < User
has_many :doctor_patient_relations
has_many :doctors, :through => :doctor_patient_relations
has_many :nurse_patient_relations
has_many :nurses, :through => :nurse_patient_relations
has_many :employee_patient_relations
has_many :employees, :through => :employee_patient_relations
end
In total I have 4 User types: Doctor, Nurse, Employee and Patient.
What I want to be able to do is get all of a patients' doctors, nurses, and employees with a call like this:
#this_patient.providers # => [doctor1, nurse2, employee3]
To achieve this, I thought about removing the 3 different types of join tables between a patient and a provider ( e.g. doctor_patient_relations), and replacing them all with a single table called provider_patient_relations.
NEW FILE I ADDED TO TRY TO GET THIS WORKING:
class ProviderPatientRelation < ActiveRecord::Base
belongs_to :provider, class_name: "User", :foreign_key => :provider_id
belongs_to :patient, class_name: "User", :foreign_key => :patient_id
end
and I also added this in the User.rb file:
class User < ActiveRecord::Base
...
has_many :provider_patient_relations
has_many :patients, -> { where meta_type: 'Doctor' || 'Nurse' }, :through => :provider_patient_relations, :inverse_of => :patient
has_many :providers, -> { where meta_type: 'Patient' }, :through => :provider_patient_relations, :inverse_of => :provider
end
The problem is, since I don't have a class name provider, rails is throwing an error:
NoMethodError: undefined method `_reflect_on_association' for Provider:Class
How do I tell rails to look in Doctors, Nurses, and Employees if I call #this_patient.providers?
EDIT
I have a sample project to get working, check out the readme for instructions and getting it set up:
https://github.com/waleedasif322/group-user-types-example-rails
You were very close. In your Patient model you used 'as' as if you were trying to assign it as an alias. However 'as' is used for polymorphic associations... I replaced your Patient model with the following and was able to successfully call Patient.first.providers in a console.
class Patient < User
has_many :patient_provider_relations
has_many :providers, through: :patient_provider_relations, source_type: "User"
end
I then moved the Patient Provider Relation associations to a concern:
module Patientable
extend ActiveSupport::Concern
included do
belongs_to :provider, polymorphic: true
has_many :patient_provider_relations, as: :provider
has_many :patients, through: :patient_provider_relations, source: :patient
end
end
And finally added include Patientable in your Doctor, Nurse, and Employee models.

Named many to many relations in Rails

How should I create following model in Rails 3.2? Project can have 1+ owners and 1+ users. Both of them are instances of class Person. I've thought about has_and_belongs_to_many but I don't know how to handle two separate collections of Persons for each Project.
You'll need a join model to represent each has-and-belongs-to-many relationship, and you would access using has-many-through as described here:
class ProjectOwnerLink < ActiveRecord::Base
belongs_to :project
belongs_to :owner, class_name: 'Person'
end
class ProjectUserLink < ActiveRecord::Base
belongs_to :project
belongs_to :user, class_name: 'Person'
end
class Project < ActiveRecord::Base
has_many :project_owner_links
has_many :owners, :through => :project_owner_links
has_many :project_user_links
has_many :users, :through => :project_user_links
end
class Person < ActiveRecord::Base
has_many :project_owner_links
has_many :owned_projects, :through => :project_owner_links, :source => :project
has_many :project_user_links
has_many :used_projects, :through => :project_user_links, :source => :project
end
You could define another model Participation that holds the type of the relationship, i.e. the role of the user. (Untested) code:
class Project < ActiveRecord::Base
has_many :participations
has_many :users, :through => :participations
def with_role(role)
includes(:participations).where('participation.role = ?', role)
end
def owners
users.with_role('owner')
end
def participants
users.with_role('participant')
end
end
 
class User < ActiveRecord::Base
has_many :participations
has_many :projects, :through => :participations
def with_role(role)
includes(:participations).where('participation.role = ?', role)
end
def projects_owned
projects.with_role('owner')
end
def projects_participating_in
projects.with_role('participant')
end
end
 
class Participation < ActiveRecord::Base
# has an attribute 'role'
belongs_to :project
belongs_to :user
end
Below is the demo application.
https://github.com/diatmpravin/habtm-demo.git
Please have a look, Let me know if you have any question?

access by active record query with join table record in has_many :through relation

I have item, region and category table they are join through item_region ans item_category table their relationship is as follow
class Item < ActiveRecord::Base
has_many :item_region
has_many :region, :through => :item_region
has_many :item_category
has_many :category, :through => :item_category
class Region < ActiveRecord::Base
has_many :category
has_many :item, ::through => :item_category
class ItemRegion < ActiveRecord::Base
belongs_to :item
belongs_to :region
end
class Category < ActiveRecord::Base
has_many :item, :through => :item_category
has_many :item_category
class ItemCategory < ActiveRecord::Base
belongs_to :item
belongs_to :category
end
I want to find all fields of item and category_name and region_name from region_id,category_id and item_id using join table.
Thanks.
I hope I got this one correct. What about obtaining the required item, then the desired region and category from it.
item = Item.find(item_id)
region = item.regions.where(:id => region_id)
category = item.categories.where(:id => category_id)
Another suggestion, you are better off providing plural form for your associations. Its intuitive to do object.collections for a has_many association. Note that Rails will still function with above code but that doesn't follow CoC(convention over configuration) principle of Rails. If you follow the convention, you wouldn't have to do a lot of configurations.
Check the examples here http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many
Personally, I'd prefer to have my associations in Item model as :
class Item < ActiveRecord::Base
has_many :item_regions
has_many :regions, :through => :item_regions
has_many :item_categories
has_many :categories, :through => :item_categories

2 to 1 has_many through relationship in Rails (how to connect three models together)

What would be the best way to connect following three models?
class Tournament < ActiveRecord::Base
has_many :submissions
has_many :creatures, :through => :submissions, :uniq => true
has_many :teams, :through => :submissions, :uniq => true
end
class Creature < ActiveRecord::Base
belongs_to :team
has_many :tournaments, :through => :team
end
class Submission < ActiveRecord::Base
belongs_to :tournament
belongs_to :team
end
class Team < ActiveRecord::Base
has_many :creatures
has_many :submissions
has_many :tournaments, :through => :submissions
end
I want to achieve something like this:
> team_1.tournaments[0] = tournament_1
> tournament_1.teams[0]
(returns team_1)
> team_1.tournaments[0].creatures
(returns [])
> team.tournaments[0].creatures[0] = creature_1
> creature_1.tournaments
(returns tournament_1)
What is the most efficient way to have a specific creature and a team associated with a specific tournament?
EDIT: The above is the desired behavior.. Current problem is that as soon as I add team to tournament.teams all the creatures in that team automatically have that tournament listed in creature.tournament, while I am trying to make it so that creatures are added to tournament selectively.. Is it at all possible with one join table?
Thanks!
Submission should be your join table between Tournamentsand Teams, correct?
Creature [id, team_id]
|
|
|
Team [id]
|
|
|
Submission [id, team_id, tournament_id]
|
|
|
Tournament [id]
Model relationships:
class Creature < ActiveRecord::Base
belongs_to :team
has_many :tournaments, :through => :team # this should work since Rails 3.1 or 3.2
end
class Team < ActiveRecord::Base
has_many :creatures
has_many :tournaments, :through => :submissions
has_many :submissions
end
class Submission < ActiveRecord::Base
belongs_to :team
belongs_to :tournament
end
class Tournament < ActiveRecord::Base
has_many :teams, :through => :submissions
has_many :creatures, :through => :teams # this should work since Rails 3.1 or 3.2
has_many :submissions
end
Now you should be able to call:
team_1.tournaments[0].creatures

Nested Attributes in Rails

Imagine 4 models in Rails 3.1
class Student < ActiveRecord::Base
has_many :memberships
has_many :courses, :through => :memberships
has_many :tests, :through => :courses
end
class Membership < ActiveRecord::Base
belongs_to :student
belongs_to :course
end
class Course < ActiveRecod::Base
has_many :tests
has_many :students, :through => :memberships
end
class Test < ActiveRecord::Base
belongs_to :course
end
How can I output a sorted list (ie by date) of a student's upcoming tests
(I'm guessing there is a fairly simple answer, but I've been trying in vain for a while)
My best guess is something like:
#upcomingTests = #currstudent.tests.sort_by &:testDateTime
but it seems to return an empty array
First of all, there is an slight error on your model "Course". It needs "belongs_to :student".
class Course < ActiveRecod::Base
has_many :tests
has_many :students, :through => :memberships
belongs_to :student
end
After you've created and populated a foreign key, you can create a simple named_scope on your test mode:
named_scope :ordered, :order => "created_at DESC"
Then it's just the matter of accessing it from wherever you want:
#ordered_tests = #student.tests.ordered

Resources