Search code examples
ruby-on-railsmany-to-manyactive-model-serializerssti

Rails 5 - Object Relation Impedence and how to structure multiple inherited classes/tables


EDIT I have edited this from the original to make it easier to understand.


I understand the Object Relationship Impedance problem. I understand Rails STI and Polymorphism (the Rails way and that it's not true OO Polymorphism). I've read a TON of blogs and questions on this, and still cannot find the answer to this problem.

class Person < ApplicationRecord (ie what was ActiveRecord::Base)
end

class Employee < Person
end

class Customer < Person
end

... multiple other types of Person

Now lets say the 'client' asks to extend the system, and create some new stuff. Let's call it a Project, to which we can assign Employees:

Ok so let's create a Many-to-Many using Third Normal Form:

class Project < ApplicationRecord
  has_many :assignments
  has_many :employees, through: :assignments  
end

class Employee < Person
  has_many :assignments
  has_many :projects, through: :assignments
end

class Assignment < ApplicationRecord
  belongs_to :employee
  belongs_to :project
end

This won't work. The migration will fail, since there is no table called Employee to create the foreign-key constraints on. STI means that the 'base class' is the People table.

TWO QUESTIONS:

1 How do you solve this? (for this you may also want to chip in here)

2 How do you create serialised data properly for Projects (which should include employees, but not People or other sub-types of Person)?

ProjectSerializer < ActiveModelSerializers

  has_many :employees
  has_many :employees, through: :assignments

end

won't work, so you would have to serialise People.


UPDATE


In the migration I created Project and Assignment tables (Person already exists).

So now the database has these tables:

Project
Person
Assignment

Assignment has two foreign keys (referencing Person, since that is the table that exists, not Employee):

person_id   
project_id

Every time I try to create an assignment, this error is thrown, which I expected of course:

ActiveModel::UnknownAttributeError (unknown attribute 'employee_id' for Assignment.)

The solution according to Rails documentation (section 4.1.2.5) and every other answer I can find anywhere for this situation is to tell Rails what the foreign_key is. Like this:

class Assignment < ApplicationRecord
  belongs_to :employee, foreign_key: "person_id"
  belongs_to :project
end

But every example I find (even in the documentation) all assume that there is no inheritance - all the models are inheriting from ActiveRecord:Base (or ApplicationRecord in Rails 5).

Even though I am explicitly telling Rails that the assignment table has a foreign_key called 'person_id' that holds the id for the employee, it still cannot find it.

And finally I tried this (thanks to the answer from @camonz)

class Assignment < ApplicationRecord
  belongs_to :person, foreign_key: "person_id", foreign_type: "employee"
  belongs_to :project
end

Same error.

Is this really a model setup that Rails cannot handle?

Here is the Rails log:

I, [2016-09-22T22:54:55.088466 #12182]  INFO -- : Started POST "/assignments" for ::1 at 2016-09-22 22:54:55 +0200
I, [2016-09-22T22:54:55.095768 #12182]  INFO -- : Processing by AssignmentsController#create as JSON
I, [2016-09-22T22:54:55.096007 #12182]  INFO -- :   Parameters: {"data"=>{"attributes"=>{"status"=>"pending"}, "relationships"=>{"project"=>{"data"=>{"type"=>"projects", "id"=>"601"}}, "employee"=>{"data"=>{"type"=>"employees", "id"=>"143"}}}, "type"=>"assignments"}, "assignment"=>{}}
I, [2016-09-22T22:54:55.098032 #12182]  INFO -- : {:status=>"pending", :project_id=>"601", :employee_id=>"143"}
I, [2016-09-22T22:54:55.117411 #12182]  INFO -- : Completed 500 Internal Server Error in 21ms (ActiveRecord: 8.8ms)


F, [2016-09-22T22:54:55.119116 #12182] FATAL -- :   
F, [2016-09-22T22:54:55.119246 #12182] FATAL -- : ActiveModel::UnknownAttributeError (unknown attribute 'employee_id' for Assignment.):
F, [2016-09-22T22:54:55.119283 #12182] FATAL -- :   
F, [2016-09-22T22:54:55.119313 #12182] FATAL -- : app/controllers/assignments_controller.rb:18:in `create'

Solution

    1. On the migration, drop the FK constraints on the addressses table. On the child classes redefine the has_many relation and specify :foreign_key & :foreign_type.

    2. On your Assignment serializer, specify the belongs_to :employee, AMS should handle it correctly.

      Also take a look at the :source and :source_type options of the has_many association.