0

I have a rails 4 app with STI models:

# models/person.rb
def Person < ActiveRecord::Base

end

# models/director.rb
def Director < Person

end

# models/actor.rb
def Director < Person

end

But because one person can be an actor and an director simultaneously, I want STI with many types like:

person = Person.first
person.type = "Director, Actor"
person.save

Actor.first.id => 1

Director.first.id => 1

Is there mechanism in rails or gem for realize this?

innerwhisper
  • 1
  • 1
  • 2

3 Answers3

0

Rails does not support this and I'm not aware of any gems that support this as described (i.e. multiple subclass names in the type column).

There is gem at https://github.com/mhuggins/multiple_table_inheritance which uses separate tables for the subclasses and you can always use mixins as an alternative to inheritance.

Peter Alfvin
  • 28,599
  • 8
  • 68
  • 106
0

I believe the more Rails idiomatic way to do something similar would be via scopes, which would allow you to do:

person = Person.first
person.position = 'Director, Actor'
person.save

person.directors.first.id => 1
person.actors.first.id => 1

And you would just have to define a pair of scopes in your Person class:

scope :actors, -> { where('position like ?', '%Actor%') }
scope :directors, -> { where('position like ?', '%Director%') }

You would lose the ability to do person.is_a? with this, but Ruby doesn't really do multiple inheritance in such a way as to allow #is_a? to return true when passed sibling classes anyway. You can also get effectively similar functionality with a simple test method:

def is_actor?
    self.position =~ /Actor/
end
def is_director?
    self.position =~ /Director/
end

EDIT: I haven't done a lot of Rails 4, so my scope syntax MAY not be right, I just glanced at the docs. The principle should be sound, though.

wmjbyatt
  • 686
  • 5
  • 15
0

Thank to all answerers above!

I found solution that most appropriate for me: I've created hmt association Person-ProfessionsPerson-Profession and leave descendants for Person class (Director and Actor).

# models/profession.rb
Profession < ActiveRecord::Base
  has_many :professions_people, dependent: :destroy
  has_many :people, through: :professions_people
end

# models/person.rb
def Person < ActiveRecord::Base
  has_many :professions_people, dependent: :destroy
  has_many :professions, through: :professions_people
end

# models/director.rb
def Director < Person
  include PeopleFromProfession
end

# models/actor.rb
def Actor < Person
  include PeopleFromProfession
end

I've seed 2 professions with column "class_type" (which should not change in app's work) "Actor" and "Director" I've also add concern PeopleFromProfession for share some code:

# models/concerns/actor.rb
module PeopleFromProfession
  extend ActiveSupport::Concern

  included do
    default_scope { includes(:professions).where(professions: {class_type: self.name}) }

    after_create :create_join_table_record
  end

  module ClassMethods
    def model_name
      Person.model_name
    end
  end

private
  def create_join_table_record
    self.professions << Profession.where(class_type: self.class.name).first
  end

end

default_scope is for scoping only people with specific profession, create_join_table_record callback is monkey-patch for create missed join table record.

Class method model_name was overwriting for purposes, that covered here Best practices to handle routes for STI subclasses in rails

If you will find some problems in that approach, please tell me.

Community
  • 1
  • 1
innerwhisper
  • 1
  • 1
  • 2