How to dynamically alter inheritance in Ruby

后端 未结 7 1349
粉色の甜心
粉色の甜心 2020-12-06 11:30

I would like to dynamically specify the parent class for a class in Ruby. Consider this code:

class Agent
  def self.hook_up(calling_class, desired_parent_c         


        
相关标签:
7条回答
  • 2020-12-06 11:45

    Perhaps you are looking for this

    Child = Class.new Parent do
      def foo
        "foo"
      end
    end
    
    Child.ancestors   # => [Child, Parent, Object, Kernel]
    Child.new.bar     # => "bar"
    Child.new.foo     # => "foo"
    

    Since parent is an argument to Class.new, you can swap it out with other classes.

    I've used this technique before when writing certain kinds of tests. But I have difficulty thinking of many good excuses to do such a thing.


    I suspect what you really want is a module.

    class Agent
      def self.hook_up(calling_class, desired_parent_class)
        calling_class.send :include , desired_parent_class
      end
    end
    
    module Parent
      def bar
        "bar"
      end
    end
    
    class Child
      def foo
        "foo"
      end
    
      Agent.hook_up(self, Parent)
    end
    
    Child.ancestors   # => [Child, Parent, Object, Kernel]
    Child.new.bar     # => "bar"
    Child.new.foo     # => "foo"
    

    Though, of course, there is no need for the Agent at all

    module Parent
      def bar
        "bar"
      end
    end
    
    class Child
      def foo
        "foo"
      end
    
      include Parent
    end
    
    Child.ancestors   # => [Child, Parent, Object, Kernel]
    Child.new.bar     # => "bar"
    Child.new.foo     # => "foo"
    
    0 讨论(0)
  • 2020-12-06 11:46

    Ruby's SimpleDelegator class (in the delegate library) may help, provided that it's sufficient that the object quack like the base class, rather than actually be an instance of the base class.

    require 'delegate'
    
    class Agent < SimpleDelegator
      def baz
        puts "baz"
      end
    end
    
    class BarParent
      def bar
        puts "bar"
      end
    end
    
    class FooParent
      def foo
        puts "foo"
      end
    end
    
    agent = Agent.new(FooParent.new)
    agent.baz    # => baz
    agent.foo    # => foo
    agent.__setobj__(BarParent.new)
    agent.baz    # => baz
    agent.bar    # => bar
    
    0 讨论(0)
  • 2020-12-06 12:01

    Ruby 1.9 only: (1.8 is similar, but use RCLASS(self)->super instead)

    require 'inline'
    class Class
        inline do |builder|
    
            builder.c %{            
                VALUE set_super(VALUE sup) {
                    RCLASS(self)->ptr->super = sup;
                    return sup;
                }
            }
    
            builder.c %{
                VALUE get_super() {
                    return RCLASS(self)->ptr->super;
                }
            }
    
        end
    
    
    J = Class.new
    J.set_super(Class.new)
    
    0 讨论(0)
  • 2020-12-06 12:05

    I know this question is pretty old and already has some good answers. However I still miss a certain solution.

    If your intention is not to dynamically assign the superclass, but rather create a hook to execute some code on inheritance (XY Problem). There is a build-in way to do this.

    inherited(subclass)

    Callback invoked whenever a subclass of the current class is created.

    Example:

    class Foo
      def self.inherited(subclass)
        puts "New subclass: #{subclass}"
      end
    end
    
    class Bar < Foo
    end
    
    class Baz < Bar
    end
    

    produces:

    New subclass: Bar
    New subclass: Baz
    

    See: Class#inherited

    If your intention is to dynamically create classes, I'd recommend looking at the answer of Joshua Cheek.

    0 讨论(0)
  • 2020-12-06 12:09

    Look at this

      class MyClass < inherit_orm("Adapter")
      end
    

    And the class selector:

      def inherit_orm(model="Activity", orm=nil)
        orm = Config.orm || orm
        require "orm/#{orm.to_s}"
        "ORM::#{orm.to_s.classify}::#{model}".constantize
      end
    

    So, when instance MyClass it will be inherit from a dynamic class depending of orm and model. Be sure to define both in a module. It work fine in public_activity gem ( selector example ).

    I hope to help.. Bye!

    0 讨论(0)
  • 2020-12-06 12:10

    As pointed out already, you should probably look into modules or dynamically create classes. However, you can use evil-ruby to change the superclass. There even is a fork for Ruby 1.9 available. This does only work for MRI. Should be easy to build on Rubinius (clearing methods caches would be the main issue), no clue about JRuby. Here is the code:

    require 'evil'
    
    class Agent
      def self.hook_up(calling_class, desired_parent_class)
        calling_class.superclass = desired_parent_class
      end
    end
    
    class Parent
      def bar
        puts "bar"
      end
    end
    
    class Child
      def foo
        puts "foo"
      end
    
      Agent.hook_up(self, Parent)
    end
    
    Child.new.bar
    
    0 讨论(0)
提交回复
热议问题