Rails Associations Through Multiple Levels - ruby-on-rails

I am relatively new to Rails and am working on a project that involves multiple, "nested" levels of data. I am having issues making the proper associations so I can get all the child elements of a model 3 levels higher. Here is an example of the models:
class Country < ActiveRecord::Base
has_many :states
end
class State < ActiveRecord::Base
belongs_to :country
has_many :cities
end
class City < ActiveRecord::Base
belongs_to :state
has_many :people
end
class Person < ActiveRecord::Base
belongs_to :city
end
I have implemented a relationship in the Country model, has_many :cities, :through => :states and have tried to call Country.first.cities.all, which works. However, I am having issues accessing all the people in a given country when I try Country.first.cities.all.people.all in the People controller.
What is the best way to deal with this sort of association situation? Should I add a foreign key to each of the children tables, such as country_id, so that I can get all the People in a Country? Any suggestions would be appreciated.

The reason is that Country.first.cities.all is an array, and each of it's elements has the method people, instead of the entire collection of cities. You'll notice that this works:
Country.first.cities.first.people.all
Because the first city of the first country has the people method. To get a list of all people in a country, you could do the following in a single query:
People.joins(:city => {:state => :country})
.where(:country => {:id => Country.first.id}).all

It's beacouse
Country.first.cities.all
is a collection of cities and it doesn't have people method.
You should go with
Country.first.cities.all.each do |city|
city.people
end

Related

Model that has_many other model more than once

I have a Movie model, and a Person model.
The Movie model should have actors, writers and producers groups.
The actors, writers and producers are groups of persons, all from the same Person model.
What would be the best way to model this?
Thanks.
EDIT: Each person could be an actor, a writer and a producer at the same time. And they all have the same attributes.
EDIT 2:
What I want to do is something like this:
Class Movie < ActiveRecord::Base
attr_accessible :name, :image, :information, :duration, :director, etc...
has_many :persons, as: :writers <-- (IDK if this is possible)
has_many :persons, as: :producers <-- (IDK if this is possible)
has_many :persons, as: :actors <-- (IDK if this is possible)
end
Class Person < ActiveRecord::Base
attr_accessible :birthdate, :birthplace, :height, :information, :name, etc..
end
and creating groups in the Movie model, so I can call them like this:
#writers = #movie.writers
#actors = #movie.actors
#producers = #movie.producers
all made-up by Person objects, persons which could be any of the 3 types.
A Person could be involved in many other movies.
If you don't want different models, why not just add a profession column to your Person (or Movie) model? Assuming they have pretty much the same attributes, they can all be handled by the same table. You could use multiple: true, to allow to choose multiple professions per person.
P.S. Could you elaborate why you use a separate Movie model for these professions?
Edit:
If you have many professions and a person can have multiple professions at the same time, you might consider using a has_many :through relationship. As in:
class Person
has_many :assignments
has_many :professions, through: assignments
end
class Assignment
belongs_to :person_id
belongs_to :profession_id
end
class Profession
has_many :assignments
has_many :persons, through: assignments
end
This way, you could add additional attributes in the join model if necessary.
It all depends on how different the attributes are for your actors, writers and producers. If they will all have the same attributes (or mostly the same attributes), you could use single table inheritance. Have one of the attributes in your Person model be an attribute called type and this will trigger STI.
The use of STI or not depends on your tolerance for null values in your database. If the number of shared attributes between actors, writers and producers is low, you will end up with a number of null values and it might be better to have a different class for each one.
The official docs are limited on STI but I found a couple of interesting blog posts that go into more detail on implementation:
http://blog.thirst.co/post/14885390861/rails-single-table-inheritance
http://www.christopherbloom.com/2012/02/01/notes-on-sti-in-rails-3-0/
Given the new information you can do
Class Movie < ActiveRecord::Base
has_many :writers, :class_name => 'Person', :conditions => ['role = "writer"']
has_many :producers, :class_name => 'Person', :conditions => ['role = "producer"']
has_many :actors, :class_name => 'Person', :conditions => ['role = "actor"']
end
the conditions inside the :conditions will be different depending how you implement the roles assignation.
You have all the information here:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
Use activerecord :through option
http://guides.rubyonrails.org/association_basics.html#the-has_many-through-association
You can use STI - Single Table Inheritance. For this you need to have type attribute in your Person model which will store the type of the Person.
class Person < ActiveRecord::Base
end
class Actor < Person
end
class Writer < Person
end
class Producer < Person
end
Given that all the roles have the same attribute and therefore can use the same class and model. If you want to have multiple roles at the same time your best bet to my understanding is to use an atribute role in the Person model. You can use a has_many association.

Model association between Company, Employee, and department

I am working in Ruby on Rails 3. And trying to map out three models which mimic the data of a Company its employees and their respective departments.
In arrived at the following solution:
class Company < ActiveRecord::Base
has_many :departments
has_many :employees, through => :departments
end
class Department < ActiveRecord::Base
belongs_to :company
has_many :employees
has_one :department_description
end
class DepartmentDescription < ActiveRecord::Base
belongs_to :department
end
class Employee < ActiveRecord::Base
belongs_to :department
end
Is this the 'correct' way to associate these models?
I think your last response may explain why you are struggling to find a correct way to associate these models.
It seems that you see your Department merely as a join_table, and that may be due to the fact that you don't fully understand the has_many => :through construction and that it actually allows your Department to be a proper model with many attributes and methods in it, hence also a 'description' attribute.
To create a separate DepartmentDescription model is actually a waste of resource. Chad Fowler has a few good examples for :has_many => through and nested resources in his Rails Recipes... so check that out.

ActiveRecord query question

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.

Two has_many links between the same models

I have users which have products through a habtm link, which is working.
I want to add a link between the user model and the product model, to keep track of the creator of that product (who doesn't always own the product, of course)
But when I write in my user and product models a new link, the application screws up because I can't distinguish the creator of a product from the owner of (a lot of) products.
Can you help me ? Here is my models :
class Product < ActiveRecord::Base
belongs_to :group
has_and_belongs_to_many :authors
has_and_belongs_to_many :users # THIS IS OK (with appart table)
has_many :users, :as => creator # THIS LINE DOES NOT WORK AT THE MOMENT
end
class User < ActiveRecord::Base
has_and_belongs_to_many :products
belongs_to :user # THIS LINE DOES NOT WORK AT THE MOMENT
default_scope :order => "username ASC"
end
The database is ok, and I can store the user_id under the creator column from my product, but the link product.creator.name doesn't work (because of the model is not correct, I presume), I can only read the user_id which is in the column but not get the user object with all his attributes.
rem : user.products works perfectly, but only when I remove my new link for creator...
Thanks !
The :as syntax is for polymorphic associations - this is not what you want. Your comments about your column names are a bit ambiguous, so I'm going to assume that you have a user_id column in your products table which is the id of the creator of that product (I'm only including the relevant associations)...
class Product < ActiveRecord::Base
has_and_belongs_to_many :users
belongs_to :creator, :foreign_key => :user_id, :class_name => "User"
end
class User < ActiveRecord::Base
has_and_belongs_to_many :products
has_many :owned_products, :class_name => "Product"
end
There is a nice gem for that: https://github.com/spovich/userstamp
I used it in my project and it works good - you just adds 'stampable' to your models and then creator_id (and updater_id) are filled authomaticly.

Setting a type for one side of a many-to-many association with a join model

I set up a public github app (see: https://github.com/greenplastik/testapp to download) to work through a problem I'm having with specifying a type on one side of a many-to-many association between two models, via a join model.
Given Person and Book models and a Book_Person join model, I want to be able to do the following:
#book = Book.first
#book.people # lists people for book
#book.authors # lists author-type people for book
#book.editors # lists editor-type people for book
and
#person = Person.first
#person.books # lists books for people
This app was set up in part using the instructions found through Google. There's a link to those instructions in the README of my testapp.
I tried, as best I could, to remove the inconsistencies and typos. I can't get it to work.
Any help would be appreciated. I've included the sqlite database for easier testing on your end.
I'm sorry i haven't got time to post neither a full solution or a tested one, but this should at least put you on the right track..
The best way to achieve what you're looking for is with Single Table Inheritance used with a polymorphic relationship.
I'd suggest redefining Author and Editor to be subclasses of Person
As in
class Person < ActiveRecord::Base
has_many :book_people
has_many :books, :through => :book_people
end
class Author < Person
end
class Editor < Person
end
class BookPerson < ActiveRecord::Base
belongs_to :person, :polymorphic => true
belongs_to :book
end
class Book < ActiveRecord::Base
has_many :book_people
has_many :people, :through => :book_people
has_many :authors, :through => :book_people, :source => :person, :source_type => "Author"
has_many :editors, :through => :book_people, :source => :person, :source_type => "Editor"
end
However this would be suboptimal if a single person could fill all three roles. There's probably a way around that by using the same STI name for the three of them. But then you'd make it harder to query for all authors.

Resources