habtm multiple times with the same model - ruby-on-rails

I am trying to model a publications. A publication can have multiple authors and editors. Since it is possible that one person is an author of one publication and an editor of another, no separate models for Authors and Editors:
class Publication < ActiveRecord::Base
has_and_belongs_to_many :authors, :class_name=>'Person'
has_and_belongs_to_many :editors, :class_name=>'Person'
end
The above code doesn't work, because it uses the same join table. Now I now that I can specify the name of the join table, but there is a warning in the API documentation is a warning about that which I don't understand:
:join_table:
Specify the name of the join table if the default based on lexical order
isn’t what you want. WARNING: If
you’re overwriting the table name of
either class, the table_name method
MUST be declared underneath any
has_and_belongs_to_many declaration in
order to work.

It means that if class Publication is linked to a table with no standard name, example "my_publications":
class Publication < ActiveRecord::Base
set_table_name "my_publication"
end
The set table name should be put behind the habtm declaration for this to work:
class Publication < ActiveRecord::Base
has_and_belongs_to_many :authors, :class_name=>'Person'
has_and_belongs_to_many :editors, :class_name=>'Person'
set_table_name "my_publication"
end

I'd generally consider this a case where you want to use has_many :through.

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

HMT Naming Similarity Issue

I have two two models: Company and CompanyType. I need an association setup where a company can have and belong to many types. Based off the rails convention of naming join models I'm in sort've a bind. I can't name my join model CompanyType because that obviously exists. What do you do in these sort've situations?
class Company < ApplicationRecord
end
class CompanyType < ApplicationRecord
end
If you are not going to attach more stuff to it, you can skip creating the join model by defining a has_and_belongs_to_many association:
class Company < ApplicationRecord
has_and_belongs_to_many :company_types
end
class CompanyType < ApplicationRecord
has_and_belongs_to_many :companies
end
# No join model needed
You still need a migration to create the table though. See: http://apidock.com/rails/v4.2.1/ActiveRecord/Associations/ClassMethods/has_and_belongs_to_many
If you need the join model, you can follow the method explained in the same document, I guess.

Modeling ruby subclasses in ActiveRecord

I have two classes:
class Comment > ActiveRecord::Base
belongs_to :tasks
end
class Work > ActiveRecord::Base
belongs_to :tasks
end
each also includes a common field called content: string (along with the standard fields for user_id, created_at, etc)
belonging to
class Task < ActiveRecord::Base
has_many :comments
has_many :works
end
What I want to do now is add a parent class called Activity for Comment and Work so that I can pull all Comments and Works from their respective tables for a specified Task containing the common fields, and I'm not sure how to back fit this in Ruby and Rails.
What you are looking for is called single table inheritance.
class Comment < Activity
belongs_to :tasks
end
class Work < Activity
belongs_to :tasks
end
You will not have a Comments table or a Works table. You will only have an Activitys table. In the table, make a column called type that holds a string. Rails considers this type column special and will know that you are referring to another model as opposed to just a string.

rails, model naming question

I'm creating a model called Chats. And I want to assign users to a discussion. They are either a part of the Chats or they aren't...
So I create one model Chats.
What's the standard Rails naming convention for the other table?
ChatUsers?
While has_and_belongs_to_many is an ok option here, I recommend going with has_many :through instead.
In essence you will have an explicit join model, which you can call something like ChatSession.
class Chat < ActiveRecord::Base
has_many :chat_sessions
has_many :users, :through => :chat_sessions
end
class User < ActiveRecord::Base
has_many :chat_sessions
has_many :chats, :through => :chat_sessions
end
class ChatSession < ActiveRecord::Base
belongs_to :user
belongs_to :chat
end
Now you will need a table called chat_sessions with columns :user_id, and :chat_id in it. This is your join table.
Advantage
You get a model which is fully under your control, and isn't just a dumb join table managed by rails. So for example, if you want to track number of messages particular user left in particular chat, it could be a column in chat_sessions table. Presence of :through renders habtm unneeded in most cases. There is no complexity overhead either.
If it is a join table, it would be both table names joined by '_' and in alphabetical order of table names:
chats_users
This is called a has_and_belongs_to_many association in rails. You basically have two models that call has_and_belongs_to_many and create a linking table that uses the two models in the name (alphabetical and plural).
models:
class Chat < ActiveRecord::Base
has_and_belongs_to_many :users
end
class user < ActiveRecord::Base
has_and_belongs_to_many :chats
end
Then your tables would be
chats
users
chats_users

In Rails, what is the difference using "has_many with belongs_to" vs "has_many with has_one"?

For example, in
class Student < ActiveRecord::Base
has_many :awards
end
class Awards < ActiveRecord::Base
belongs_to :student
end
the above should be the correct usage, but what if we use
class Student < ActiveRecord::Base
has_many :awards
end
class Awards < ActiveRecord::Base
has_one :student
end
doesn't the above also make possible student.awards as an array of Award objects, and award.student as a Student object which is the recipient of the award, so works the same way as the method at the top of post?
has_one is used in case of a one-to-one relationship, not in one-to-many relationships.
Correct use of has_one:
class Student < ActiveRecord::Base
has_one :id_card
end
class IdCard < ActiveRecord::Base
belongs_to :student
end
The two examples are not equivalent.
has_many and belongs_to work as a pair where there is a 'many to one' relationship.
In the database this would look like:
**Students**
Name
Email
...
**Awards**
Name
student_id <-- !IMPORTANT!
...
Each Student has many awards hence has_many :awards
Each Award 'belongs to' a Student hence belongs_to :student
Notice that the belongs_to is applied to the table with the foreign key student_id. This is important.
OK - so what happens where there is a 'one to one' relationship?
If each student could only have a single award then the database tables could look exactly the same but the model should not be able to return a collection of items.
This is where we need the has_one declaration. This would be applied to the Student model in this case. Why? Because the relationship is the same in both directions but Active Record needs to know where to find the foreign key.
If the database tables were the other way around with each Student having an award_id then the Student would get the belongs_to and the Award would get the has_one.
Hope that makes it clear?
It does seem a little odd to be that a student could 'belong to' an award if you use natural language. But that is how the rails active record domain specific language is written.
It gets even more unnatural sounding when you look at the 'many to many' relationship with 'has_many_and_belongs_to'. Here there is a joining table which site between your main tables like
students_awards
student_id
award_id
In this situation neither the Students nor the Awards table have a foreign key but both will carry the has_many_and_belongs_to :other_table declaration. Both tables are able to join to multiple rows of the other. Each Student can have more than one Award. Each Award can be applied to many Students.
The has_one declaration is only used where there is a 'one-to-one' relationship and the table it applies to does not have the foreign key.

Resources