Search code examples
ruby-on-railsactiverecordmonkeypatching

Rails 5.2 Monkey Patch ActiveRecord:Inheritance ClassMethods


Im trying to Monkey Patch the follwing ActiveRecord Model, But im having trouble getting it to stick. Im trying to prepend my module so that it overrides the Rails implementation of a private method with the ClassMethods object.

Can anyone point out what i am missing?

module Extensions
  module ActiveRecord
    module Rails5
      # Inheritance functionality
      module Inheritance
        # Inheritance ClassMethods
        module ClassMethods
          def find_sti_class(type_name)
            type_name = base_class.type_for_attribute(inheritance_column).cast(type_name)
            begin
              if store_full_sti_class
                ActiveSupport::Dependencies.constantize(type_name)
              else
                compute_type(type_name)
              end
            rescue NameError
              raise SubclassNotFound,
                "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " \
                "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " \
                "Please rename this column if you didn't intend it to be used for storing the inheritance class " \
                "or overwrite #{name}.inheritance_column to use another column for that information."
            end
          end
        end
      end
    end
  end
end

ActiveRecord::Inheritance::ClassMethods.prepend(Extensions::ActiveRecord::Rails5::Inheritance::ClassMethods)

Solution

  • I would like to start out by saying I abhor this as a solution and should you upgrade to > 6.0 this will no longer work.

    module InheritanceSTIOverride 
      def find_sti_class(type_name) 
         begin 
           super
         rescue ActiveRecord::SubclassNotFound => e 
           if e.message =~ /Invalid single-table inheritance type: (\w+)/
             # might need to use Object.const_get($1)
             ActiveSupport::Dependencies.constantize($1)
           else 
             raise 
           end
         end 
      end 
    end 
    
    ActiveRecord::Base.extend(InheritanceSTIOverride)
    

    Rather than rewrite the entire method we can leverage the fact that the condition you are trying to avoid responds in a very specific manner and references the constant you are trying to return.

    We rescue this error and interrogate the response to obtain the subclass name and then we turn it into a constant.

    Sidenote: prepend inserts a module into the hierarchy chain in front of the object being prepended to; however this can only supply instance methods. Since you need to override a class instance method we use extend instead. extend will add the module into the extended object's singleton class hierarchy chain just above the object being extended. Since ActiveRecord::Base is a compositional object built up by including numerous other modules, which then use the included hook to extend the object as well, our extension will sit above ActiveRecord::Base but below ActiveRecord::Inheritance, which is why this works.