ruby-on-railsrubyactiverecordpolymorphismsingle-table-inheritance

Ruby on Rails ActiveRecord Single Table Inheritance has_one to superclass


I have a Base class which contains STI and named procedures, looking like this:

class Base < ActiveRecord
  self.abstract_class = true
  self.table_name = "procedures"
  self.inheritance_column = nil
end

We also have multiple superclasses named for isntance ProcedureA and ProcedureB, looking like this:

class ProcedureA < Base
end

class ProcedureB < Base
end

A procedure can contain another procedure so i'd like to add this to the base class. But when retrieving the other procedure i'd like to know the actual class name. So this works:

class Base < ActiveRecord
  self.abstract_class = true
  self.table_name = "procedures"
  self.inheritance_column = nil

  has_one :parent, class_name: "Base"
end

But when retrieving it form the database it returns is as the Base class but not the specific class that was assigned.

For instance:

procedure_a = ProcedureA.create!()
procedure_b = ProcedureB.create!()
procedure_a.parent = procedure_b
procedure_a.save!

procedure_a.parent => Is ProcedureB
ProcedureA.first.parent => Is Base class

So at this point i don't know how to get the actual class instead of the base class. I tried playing with polymorphic: true but doesn't work.


Solution

  • There is a lot of confusion going on here.

    To create a self referential assocation you need to use belongs_to not has_one.

    class User
      belongs_to :manager, 
        class_name: 'User',
        optional: true
      has_many :subordinates,
        class_name: 'User',
        foreign_key: :manager_id
    end
    

    After all the foreign key is stored on this models table.

    But then there is also the fact that your Base is actively sabotaging Single Table Inheritance. It should just read:

    class Base < ActiveRecord
      self.table_name = "procedures" # all children will be stored in this table
    end
    

    self.abstract_class = true tells ActiveRecord to ignore this class when resolving the table name of its children. That's the opposite of what you want.

    self.inheritance_column = nil disables the type inferance that you need for single table inheritance to work.

    The way STI in Rails works you don't actually have to do anything. Any class that inherits from ActiveRecord::Base and is not abstract is a STI parent class by default. All you need to do is add a type column and subclasses and bob's your uncle.

    Adding the self-referential assocation with STI is pretty much the same as above example but with a twist:

    class Base < ActiveRecord
      belongs_to :parent,
        class_name: 'Base',
        optional: true
    end
    

    class_name: 'Base', seems kind of odd but its really just telling Rails which table to look for the assocatiated records on. When loading the record the type inferance will override what class is instanciated anyways (or at least it would if you didn't sabotage it).

    Note that this is not a case where you want a polymorphic assocation. Thats used when you have a single assocation that points to multiple different tables.