2
votes

Question:

How can I tell ActiveRecord to not include the namespace of the association class while storing/querying the association type column?

Current state of things:

Consider the following class definitions:

class PageTemplateA < ActiveRecord::Base
  has_many :type_a_pages, :as => :pageable, :class_name => 'TypeAPage', :inverse_of => :pageable
end

##### The following class is implemented through STI.
class TypeAPage < Page
  belongs_to :pageable, :class_name => 'PageTemplateA', :inverse_of => :type_a_page
end

class Page < ActiveRecord::Base
  belongs_to :pageable, :polymorphic => true
end

To summarize:

  • TypeAPage is implemented through STI in the database table, pages.
  • TypeAPage is associated with PageTemplateA through a polymorphic association (pages.pageable_type is PageTemplateA when associated with PageTemplateA)

The change I want to make:

I want to move all the above models into a new namespace, say, PagesEngine, so my definition for PageTemplateA looks like:

module PagesEngine
  class PageTemplateA < ActiveRecord::Base
    has_many :type_a_pages, :as => :pageable, :class_name => 'TypeAPage', :inverse_of => :pageable
  end
end

This works fine, except that ActiveRecord infers the pageable_type for TypeAPage to be PagesEngine::PageTemplateA.

How can I tell ActiveRecord to not include the namespace, and resolve pageable_type to PageTemplateA instead of PagesEngine::PageTemplateA?

2

2 Answers

4
votes

For the model that holds the polymorphic relation, you can set self.store_full_sti_class = false or inherit from an abstract model that sets store_full_sti_class to false.

module PagesEngine
  class Page < Base
    # self.store_full_sti_class = false
    belongs_to :pageable, polymorphic: true
  end

  class TypeAPage < Page
    belongs_to :pageable, 
               class_name: 'PageTemplateA', 
               inverse_of: :type_a_pages
  end
end

Explicitly scope the relation (type_as_pages) with the type column (pageable_type) matching the associated model (PagesEngine::PageTemplateA) but without the namespace.

module PagesEngine
  class PageTemplateA < Base
    has_many :type_a_pages, 
             -> { where(pageable_type: 'PageTemplateA') }, 
             foreign_key: :pageable_id, 
             inverse_of: :pageable
  end
end

Finally override belongs_to method and customise Active Record Belongs To Polymorphic Association.

module PagesEngine
  class Base < ActiveRecord::Base
    self.abstract_class = true
    self.store_full_sti_class = false

    def self.belongs_to(name, scope = nil, options = {})
      super(name, scope, options).tap do |hash|
        reflection = hash[name.to_s]
        def reflection.association_class
          if polymorphic?
            BelongsToPolymorphicAssociation
          else
            super
          end
        end unless store_full_sti_class
      end
    end
  end

  class BelongsToPolymorphicAssociation < ActiveRecord::Associations::BelongsToPolymorphicAssociation
    def klass
      type = owner[reflection.foreign_type]
      type.presence && PagesEngine.const_get(type)
    end
  end
end
-1
votes

Use the self.sti_name to override the type column name for the respective class:

module PagesEngine
  class PageTemplateA < ActiveRecord::Base
    def self.sti_name
      "PageTemplateA"
    end
  end
end

You might find the following blog article useful: https://lorefnon.me/2014/07/27/optimizing-sti-columns.html