Search code examples
ruby-on-railseloquentrails-activerecordpolymorphic-associations

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.


Solution

  • 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).