Model that has_many other model more than once - ruby-on-rails

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.

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

rails - why polymorphic associations

An example from this blog
class Tag < ActiveRecord::Base
attr_accessible :name, :taggable_id, :taggable_type
belongs_to :taggable, :polymorphic => true
end
class Car < ActiveRecord::Base
attr_accessible :name
has_many :tags, :as => :taggable
end
class Bike < ActiveRecord::Base
attr_accessible :name
has_many :tags, :as => :taggable
end
It looks to me we could do thing like this without polymorphic associations
class Tag < ActiveRecord::Base
attr_accessible :name,
belongs_to :cars,
belongs_to :bikes,
end
class Car < ActiveRecord::Base
attr_accessible :name
has_many :tags
end
class Bike < ActiveRecord::Base
attr_accessible :name
has_many :tags
end
What is the difference of with polymorphic associations and without?
THanks
In your last example, Car has many tags, so conventionally the "tags" table will have a field car_id. Now Bike has many tags, so one more field bike_id.
Without Polymorphic, how many fields are you going to create for the tags table? :)
Even you guarantee that there will only be two models have tag ultimately, there will also lots of null data in the table, say a bike doesn't have car_id, and that is not nice.
Polymorphism solved this problem by defining a common interface so that Car and Bike can share same operations with different sub type. http://www.princeton.edu/~achaney/tmve/wiki100k/docs/Polymorphism_in_object-oriented_programming.html
A polymorphic association allows you to create a table with relationships between multiple models.
Consider your example. Both Car and Bike can have many tags, so instead of creating two different tables, say car_tags and bike_tags, you can use a single polymorphic table named Tag which not only stores the foreign key (in a column named resource_id), but also the type of resource it's associated with (in a column named resource_type), which, in this case would be Car or Bike.
In summary, polymorphic relationships are between many different models whereas normal relationships are, generally speaking, only between two.
You can find more information in the RoR Guides; http://guides.rubyonrails.org/association_basics.html#polymorphic-associations
Hope that helps clarify things a bit.
Polymorphic associations allow for a single model to belong to multiple models on a single association[1]. Taking your example above, simply adding has_many :tags to the Car and Bike models and belongs_to :car and belongs_to :bike to the Tag model has two major shortcomings:
It introduces a glut of foreign keys of which a large quantity will have no value
It still doesn't allow for a tag to belong to more than one model
Some great resources for learning more about polymorphic associations are listed below.
RailsGuides on Polymorphic
Associations
RailsCasts #154 Polymorphic Associations
(revised)
What's the Deal with Rails' Polymorphic
Associations?

ruby on rails model

I have two models as subject and teacher
Subject model as
class Subject < ActiveRecord::Base
belongs_to :sclass
has_many :subject_teachers
attr_accessible :sub_name
end
and Teacher model as
class Teacher < ActiveRecord::Base
# attr_accessible :title, :body
has_many :sclass_teachers
has_many :subject_teachers
attr_accessible :fname, :lname, :mob, :email
end
and created their join table as subject_teacher as many to many relationship
class SubjectTeacher < ActiveRecord::Base
belongs_to :subject
belongs_to :teacher
end
but i want to access teacher name in subject model / table how can i do it.
what and where did i write the perfect code so that i get specific teacher name to specific
subject as there are MANY TO MANY relationship?
You would do:
has_many :teachers through => :subject_teachers
as #Hugo said
You can access the teachers data from subject using the has_many :through relationship
http://guides.rubyonrails.org/association_basics.html#the-has_many-through-association
What you say is a bit strange: you set up a many to many relationship, and require a specific teacher for a subject. So for a many to many relationship, you can get a list of teachers. And from that list you could take the first one. But if you know there is at max one teacher for a subject, just a has_many is a lot more convenient.
You can access the teachers by using #subject.subject_teachers.teachers, where #subject is a Subject instance.
If you use a has-many-and-belongs-to-many relationship, you can omit the intermediate class. You only need that is the relationship has additional data, like a date and time.

How to implement has_many :through relationships with Mongoid and mongodb?

Using this modified example from the Rails guides, how does one model a relational "has_many :through" association using mongoid?
The challenge is that mongoid does not support has_many :through as ActiveRecord does.
# doctor checking out patient
class Physician < ActiveRecord::Base
has_many :appointments
has_many :patients, :through => :appointments
has_many :meeting_notes, :through => :appointments
end
# notes taken during the appointment
class MeetingNote < ActiveRecord::Base
has_many :appointments
has_many :patients, :through => :appointments
has_many :physicians, :through => :appointments
end
# the patient
class Patient < ActiveRecord::Base
has_many :appointments
has_many :physicians, :through => :appointments
has_many :meeting_notes, :through => :appointments
end
# the appointment
class Appointment < ActiveRecord::Base
belongs_to :physician
belongs_to :patient
belongs_to :meeting_note
# has timestamp attribute
end
Mongoid doesn't have has_many :through or an equivalent feature. It would not be so useful with MongoDB because it does not support join queries so even if you could reference a related collection via another it would still require multiple queries.
https://github.com/mongoid/mongoid/issues/544
Normally if you have a many-many relationship in a RDBMS you would model that differently in MongoDB using a field containing an array of 'foreign' keys on either side. For example:
class Physician
include Mongoid::Document
has_and_belongs_to_many :patients
end
class Patient
include Mongoid::Document
has_and_belongs_to_many :physicians
end
In other words you would eliminate the join table and it would have a similar effect to has_many :through in terms of access to the 'other side'. But in your case thats probably not appropriate because your join table is an Appointment class which carries some extra information, not just the association.
How you model this depends to some extent on the queries that you need to run but it seems as though you will need to add the Appointment model and define associations to Patient and Physician something like this:
class Physician
include Mongoid::Document
has_many :appointments
end
class Appointment
include Mongoid::Document
belongs_to :physician
belongs_to :patient
end
class Patient
include Mongoid::Document
has_many :appointments
end
With relationships in MongoDB you always have to make a choice between embedded or associated documents. In your model I would guess that MeetingNotes are a good candidate for an embedded relationship.
class Appointment
include Mongoid::Document
embeds_many :meeting_notes
end
class MeetingNote
include Mongoid::Document
embedded_in :appointment
end
This means that you can retrieve the notes together with an appointment all together, whereas you would need multiple queries if this was an association. You just have to bear in mind the 16MB size limit for a single document which might come into play if you have a very large number of meeting notes.
Just to expand on this, here's the models extended with methods that act very similar to the has_many :through from ActiveRecord by returning a query proxy instead of an array of records:
class Physician
include Mongoid::Document
has_many :appointments
def patients
Patient.in(id: appointments.pluck(:patient_id))
end
end
class Appointment
include Mongoid::Document
belongs_to :physician
belongs_to :patient
end
class Patient
include Mongoid::Document
has_many :appointments
def physicians
Physician.in(id: appointments.pluck(:physician_id))
end
end
Steven Soroka solution is really great! I don't have the reputation to comment an answer(That's why I'm adding a new answer :P) but I think using map for a relationship is expensive(specially if your has_many relationship have hunders|thousands of records) because it gets the data from database, build each record, generates the original array and then iterates over the original array to build a new one with the values from the given block.
Using pluck is faster and maybe the fastest option.
class Physician
include Mongoid::Document
has_many :appointments
def patients
Patient.in(id: appointments.pluck(:patient_id))
end
end
class Appointment
include Mongoid::Document
belongs_to :physician
belongs_to :patient
end
class Patient
include Mongoid::Document
has_many :appointments
def physicians
Physician.in(id: appointments.pluck(:physician_id))
end
end
Here some stats with Benchmark.measure:
> Benchmark.measure { physician.appointments.map(&:patient_id) }
=> #<Benchmark::Tms:0xb671654 #label="", #real=0.114643818, #cstime=0.0, #cutime=0.0, #stime=0.010000000000000009, #utime=0.06999999999999984, #total=0.07999999999999985>
> Benchmark.measure { physician.appointments.pluck(:patient_id) }
=> #<Benchmark::Tms:0xb6f4054 #label="", #real=0.033517774, #cstime=0.0, #cutime=0.0, #stime=0.0, #utime=0.0, #total=0.0>
I am using just 250 appointments.
Don't forget to add indexes to :patient_id and :physician_id in Appointment document!
I hope it helps,
Thanks for reading!
I want to answer this question from the self-referencing association perspective, not just the has_many :through perspective.
Let's say we have a CRM with contacts. Contacts will have relationships with other contacts, but instead of creating a relationship between two different models, we’ll be creating a relationship between two instances of the same model. A contact can have many friends and be befriended by many other contacts so we’re going to have to create a many-to-many relationship.
If we are using a RDBMS and ActiveRecord, we would use has_many :through. Thus we would need to create a join model, like Friendship. This model would have two fields, a contact_id that represents the current contact who’s adding a friend and a friend_id that represents the user who’s being befriended.
But we are using MongoDB and Mongoid. As stated above, Mongoid doesn't have has_many :through or an equivalent feature. It would not be so useful with MongoDB because it does not support join queries. Therefore, in order to model a many-many relationship in a non-RDBMS database like MongoDB, you use a field containing an array of 'foreign' keys on either side.
class Contact
include Mongoid::Document
has_and_belongs_to_many :practices
end
class Practice
include Mongoid::Document
has_and_belongs_to_many :contacts
end
As the documentation states:
Many to many relationships where the inverse documents are stored in a
separate collection from the base document are defined using Mongoid’s
has_and_belongs_to_many macro. This exhibits similar behavior to
Active Record with the exception that no join collection is needed,
the foreign key ids are stored as arrays on either side of the
relation.
When defining a relation of this nature, each document is stored in
its respective collection, and each document contains a “foreign key”
reference to the other in the form of an array.
# the contact document
{
"_id" : ObjectId("4d3ed089fb60ab534684b7e9"),
"practice_ids" : [ ObjectId("4d3ed089fb60ab534684b7f2") ]
}
# the practice document
{
"_id" : ObjectId("4d3ed089fb60ab534684b7e9"),
"contact_ids" : [ ObjectId("4d3ed089fb60ab534684b7f2") ]
}
Now for a self-referencing Association in MongoDB, you have a few options.
has_many :related_contacts, :class_name => 'Contact', :inverse_of => :parent_contact
belongs_to :parent_contact, :class_name => 'Contact', :inverse_of => :related_contacts
What is difference between related contacts and contacts having many and belonging to many practices? Huge difference! One is a relationship between two entities. Other is a self-reference.

Rail Model: Using validates_uniqueness_of with scope on attributes of associated entity?

I have Model defined as below
class One <Active:Record:Base
{
has_and_belongs_to_many :twos, {:join_table => 'map__ones__twos'}
}
class Two <Active:Record:Base
{
has_and_belongs_to_many :ones, {:join_table => 'map__ones__twos'}
}
I want that name attribute of two should be unique for scope of one. That means all of twos belonging to one should have unique name. Here i can not specify some thing like below in Two model
validates_uniqueness_of :name, :scope => one_id
because on_id is not a column of twos table. Rather one_id and two_id are mapped to each other through table map_ones_twos (many to many relationship)
Please suggest
I have often found that using has_and_belongs_to_many is more trouble than it's worth. When I have many-to-many relationships, I create a join table and make a model for it. Then, the join model can have a validation on the uniqueness of the two ids.
Excuse the names. I am using the example table names from your question. In your case, this would look like:
class MapOnesTwo < ActiveRecord::Base
belongs_to :one
belongs_to :two
validates_presence_of :one_id, :two_id
validates_uniqueness_of :one_id, :scope => :two_id
end
Your One model looks like this:
class One < ActiveRecord::Base
has_many :ones_twos, :dependent => :destroy
has_many :twos, :through => :ones_twos
end
And your Two model looks like this:
class Two < ActiveRecord::Base
has_many :ones_twos, :dependent => :destroy
has_many :twos, :through => :ones_twos
end

Resources