Rails: has_many and belongs_to - ruby-on-rails

I have two models in my app:
Position:
class Position < ActiveRecord::Base
belongs_to :report_to_position, class_name: 'Position', foreign_key: 'report_to_position_id'
has_many :employees
end
Employee:
class Employee < ActiveRecord::Base
belongs_to :position
def boss
self.position.report_to_position.employee
end
end
As you can see in boss getter I need to get one employee in this relation. The problem is Position class has many employees. How can I get just one object (boss for many employees) with this model logic?
Thanks!

You can't get the boss with this association setup, but you can get a boss. That is: you can get a superior. The Position class has_many :employees, which gives you two problems:
There's nothing in the Employee class to say "this is my direct superior".
There's nothing in the whole system to say "only one person can hold a position".
There's nothing in the Position class to say "this person is the boss".
That might be OK; if it's OK to get an (essentially random) superior for a given Employee, your boss method could read as follows:
def boss
self.position.report_to_position.employees.first
end
Overall, though, I'd rethink your schema. It's OK to keep your org chart and employee data separate, but you should consider moving the boss/subordinates associations to the Employee itself via a self join:
class Employee < ActiveRecord::Base
has_many :subordinates, class_name: "Employee", foreign_key: "boss_id"
belongs_to :boss, class_name: "Employee"
belongs_to :position
end

Structure
To help define Alex P's answer, you'll need to look at how you're defining the boss record in your associations, and tables.
The problem I can see is that because all your employee associative data is all the same, you have no way to determine who is the "boss" or not, hence your issue
There are two ways to ensure you have the boss association defined -
In the ActiveRecord associations
In the database / Model
--
Association
Probably the most reliable way to do this is to use an association, as described by Alex P:
#app/models/employee.rb
Class Employee < ActiveRecord::Base
belongs_to :boss, class_name: "Employee"
has_one :boss, class_name: "Employee", foreign_key: "boss_id"
end
--
Model
The way I would do it is to use the Ancestry gem -
#app/models/employee.rb
Class Employee < ActiveRecord::Base
has_ancestry
end
This has to be coupled with a datatable column called ancestry (string):
This will allow you to give each employee a Parent (who could be a Boss or a Manager etc). The beauty of this setup will be that you can create a real "tree" structure - employees will be able to have multiple managers / bosses as required

Related

Rails ActiveRecord equivalent of Laravel ORM `attach()` method for polymorphic has_many :through

I'm transitioning from "Laravel ORM" to "Rails Active Record" and I couldn't find how do you do something like this:
$this->people()->attach($person['id'], ['role' => $role]);
Explanation for Laravel code snippet
People is a polymorphic association to the class that is being accessed via $this via the Role class. The function above, creates a record in the middle table (roles/peopleables) like this:
id: {{generically defined}}
people_id: $person['id']
role: $role
peopleable_type: $this->type
peopleable_id: $this->id
How the association is defined on the Laravel end:
class XYZ {
...
public function people()
{
return $this->morphToMany(People::class, 'peopleable')->withPivot('role','id');
}
...
}
My efforts in Ruby
Here is how I made the association in Ruby:
class Peopleable < ApplicationRecord
belongs_to :people
belongs_to :peopleable, polymorphic: true
end
class People < ApplicationRecord
has_many :peopleables
end
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end
I have seen the operation << but I don't know if there is any way to set an additional value on the pivot table while triggering this operation. [in this case the roles or peopleables tables; I use these two terms interchangeably in this app.]
PS. So, basically the question is how to define additional values on the pivot table in a polymorphic-many association in ActiveRecord and dynamically set those values while initiating an attachment relationship
Description of Functionality
Our application has a limitless [generally speaking, not that there is no computational limits!] content type: post, novel, poem, etc.
Each of these content types can be associated to individuals who play certain roles: editor, author, translator, etc.
So, for example:
X is the translator of Post#1. X, Y and Z are authors of Post#1.
There is a distinct People model and each content type has its own unique model [for example: Post, Poem, etc].
The idea of :through is referring to the 'Role class' or 'the pivot table' [whichever way you want to understand it] that the polymorphic association is recorded on it.
In addition to the information regarding a simple polymorphic relationship, there is also the kind of role that is recorded on the pivot table.
For example, X is both the author and the translator for Post#1, so there are two rows with the same people_id, peopleable_type and peopleable_id, however they have different values for role.
From what I understand given your description, I think you have this models (I'll change the names to what I understand they are, hope it's clear enough):
class Person < ApplicationRecord # using singular for models
has_many :person_roles
end
class Poem < ApplicationRecord
has_many :person_roles, as: :content
end
class Novel < ApplicationRecord
has_many :person_roles, as: :content
end
etc...
class PersonRole < ApplicationRecord
belongs_to :person
belongs_to :content, polymorphic: true
# you should have a "role" column on your table
end
So a Person is associated to a "content" (Novel, Poem, etc) via the join model PersonRole with a specific role. A Person that is the author of some novel and the editor of some peom would have two PersonRole records.
So, if you have a person and you want to assign a new role on some content, you can just do:
person.person_roles.create(role: :author, content: some_poem)
or
PersonRole.create(person: person, role: :author, content: some_poem)
or
some_poem.person_roles.create(person: person, role: :author)
You have two things in play here: "belongs_to :content, polymorphic: true" is covers the part of this being a polymorphic association. Then you have the "PersonRole" table that covers the part you know as "pivot table" (join table/model on rails).
Note that :through in rails has other meaning, you may want to get all the poems that a user is an author of, you could then have a "has_many :poems, through: :person_roles" association (that won't actually work, it's more complex than that in this case because you have a polymorphic association, you'll need to configure the association with some extra options like source and scope for this to work, I'm just using it as an example of what we understand as a has many :through association).
Rails is 'convention over configuration'. Models' must be in singular 'Person'.
ActiveRecord has has_many ... through and polymorphic association
"Assignable" and "Assignments" are more natural to read than "peoplable"
class Person < ApplicationRecord
has_many :assignments, as: :assignable
has_many :roles, through: :assignments
end
class Role < ApplicationRecord
has_many :assignments
has_many :people, through: :assignments
end
class Assignment
belongs_to :role
belongs_to :assignable, polymorphic: true
end
You can read more Rails has_many :through Polymorphic Association by Sean C Davis

Modelling Many Rails Associations

I'm trying to wrap my head around how I should model my database for a parent model with many has_one associations (20+). I have one model called House which is the parent model of all the other models:
class House < ActiveRecord::Base
has_one :kitchen
has_one :basement
has_one :garage
has_one :common_room
#... Many other child models
end
All the child models contain unique properties specific to their own class. I've thought about STI, but there isn't really any shared functionality or inputs that I can use across models. I've also thought of making one 'super model', but that doesn't really follow Rails best practices, plus it would contain over 200 columns. Is there another design pattern or structure that I can use to model this efficiently so that I can reduce database calls?
class House
has_many :rooms
Room::TYPES.each do |room_type|
has_one room_type, -> { where(room_type: room_type) }, class_name: "Room"
end
class Room
belongs_to :house
TYPES = %i/kitchen basement garage common_room etc/.freeze
end
In your migration make sure to add_index :rooms, [:type, :house_id], unique: true. Unless a house can have more than 1 type of room. If that is the case, I think a different approach is needed.
To your second question, it depends really, what type of database are you using? If its PostgreSQL you could use hstore and store those as a properties hash. Or you could serialize you db to take that as a hash. Or you could have another model that room has many of. For instance has_many :properties and make a property model that would store that information. Really depends on what else you want to do with the information
Why not using Polymorphism in Rails? It would be much simpler
class House < ActiveRecord::Base
has_many :properties, as: :property_house
end
class Kitchen < ActiveRecord::Base
belongs_to :property_house, polymorphic: true
end
class Garage < ActiveRecord::Base
belongs_to :property_house, polymorphic: true
end
For more information, go here: http://terenceponce.com/blog/2012/03/02/polymorphic-associations-in-rails-32/

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.

Proper Rails Association to use

I am trying to create an association between two tables. A student table and a computer table.
A computer can only ever be assigned to one student (at any one time) but a student can be assigned to multiple computers.
This is what I currently have in mind. Setting up a has-many through relationship and modifying it a bit.
class Student < ActiveRecord::Base
has_many :assignemnts
has_many :computers, :through => :assignments
end
class Computer < ActiveRecord::Base
has_one :assignment
has_one :student, :through => :assignments
end
class Assignment < ActiveRecord::Base
belongs_to :student
belongs_to :computer
end
Does this seem like the best way to handle this problem? Or something better sound out quickly to the experts here. Thanks!
You need first to decide if a simple one-to many relationship is enough for you.
If yes, it gets a lot easier, because you can get rid of the Assignment-class and table.
Your database-table "computers" then needs a student_id column, with a non-unique index
Your models should look like this:
class Computer < ActiveRecord::Base
belongs_to :student
end
class Student < ActiveRecord::Base
has_many :computers, :dependent => :nullify
end
"dependent nullify" because you don't want to delete a computer when a student is deleted, but instead mark it as free.
Each of your computers can only be assigned to a single student, but you can reassign it to a different student, for example in the next year.
Actually your approach is fine, as one offered by #alexkv. It is more discussion, than question.
Another thing if you want to use mapping table for some other purposes, like storing additional fields - then your approach is the best thing. In has_many :through table for the join model has a primary key and can contain attributes just like any other model.
From api.rubyonrails.org:
Choosing which way to build a many-to-many relationship is not always
simple. If you need to work with the relationship model as its own
entity, use has_many :through. Use has_and_belongs_to_many when
working with legacy schemas or when you never work directly with the
relationship itself.
I can advise you read this, to understand what approach better to choose in your situation:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
http://blog.hasmanythrough.com/2006/4/20/many-to-many-dance-off
You can also use has_and_belongs_to_many method. In your case it will be:
class Student < ActiveRecord::Base
has_many :assignemnts
has_and_belongs_to_many :computers, :join_table => 'assignments',
end
class Computer < ActiveRecord::Base
has_one :assignment
has_and_belongs_to_many :student, :join_table => 'assignments',
end
or you can rename assignments table to computers_students and remove join_table
class Student < ActiveRecord::Base
has_many :assignemnts
has_and_belongs_to_many :computers
end
class Computer < ActiveRecord::Base
has_one :assignment
has_and_belongs_to_many :student
end

has_many relation doesn't seems right or logical in business perceptive, needed some thing like belongs_to_many?

My situation is like this.
Company has many users and users may belongs to many companies.
And current implementation is something like below.
class Company
has_many :employments
has_many :users, :through => :employments
end
class Employment
belongs_to :company
belongs_to :user
end
class User
has_many :employments
has_many :companies, :through => :employments #This doesn't looks correct
end
It works, but "user has many companies" doesn't looks logically meaningful. It must be some thing like belongs_to_many companies.
Do I need to use has_and_belongs_to_many?
Can some one please suggest the right way for representing these relationships?
If you are not going to add some special behaviour to Employment class and you don't really need it, then you should propably better use has_and_belongs_to_many. This way it will still have some table called CompanyUser, but there will not be any such class in the code.
Added:
There are just 3 possbile conections between 2 objects: 1-to-1, 1-to-many and many-to-many. Rails indicates with has_* or belongs_to what of the 2 objects will get a foreign key. So:
1-to-1:
has_one and belongs_to are used. The object with belongs_to gets the FK.
1-to-many:
has_many and belongs_to are used. The object with belongs_to gets the FK.
many-to-many:
has_many[through] or has_and_belongs_to_many are used in both objects. No-one gets a FK, instead a ConnectionTable is used.
So there is no such thing as belongs_to_many, because it would be the same as has_many - providing the same relation 1-to-many.

Resources