Search code examples
rubyoopinner-classesfluentd

In Ruby, how can inner class access variables/methods defined in outer class?


Example:

require "fluent/plugin/output"

module Fluent::Plugin
    class Outer < Fluent::Plugin::Output
        @outer_var = "Hello from outer variable"

        class Inner
            def initialize()
                puts "@outer_var", @outer_var # nil
                log.info "Hello from inner initialize" # undefined local variable or method log
                outer_method() # undefined method `outer_method' for #<Outer::Inner:0x00007fd2100452b0> (NoMethodError)
            end
        end # Inner

        def initialize()
            Inner.new()
            log.info "Hello from outer initialize" # works
        end

        def outer_method
            puts "Hello from outer method"
        end

    end # Outer
end # Fluent::Plugin

How can I make the outer variables and methods accessible to the inner class? Example here shows how inner/outer classes are defined but not how outer class may be accessed. I understand that the concept of inner/outer classes don't exists in Ruby, as per this answer. What is the best way use the Inner and Outer class while still being part of the output module.


Solution

  • In Ruby, how can inner class access variables/methods defined in outer class?

    Ruby does not have a concept of "inner" or "outer" classes. It sounds like you are coming from a language like BETA, Scala, or Newspeak, but Ruby is not BETA, Scala, or Newspeak. As a general rule, any programming language works exactly how the specification for the programming language says it works, not how the specification for some other programming language says it works. BETA, Scala, and Newspeak have nested classes, but Ruby has not.

    In Ruby, you can lexically nest a class definition, but that does not create a nested class. If you nest the definition of a class inside the definition of another class, that does not create any sort of relationship whatsoever between those two classes. None. You cannot "access variables/methods" of one class from the other class because there is no relationship between those two classes you could follow in order to get at those variables/methods.

    The only thing a nested class definition does, is namespace the constant defined by the inner class definition to the outer class. That's it. There is no relationship between the two classes, the only relationship is between the constant and the class.

    It is just not possible.

    The way to create relationships between classes in Ruby is inheritance, not nesting (because there is no nesting). The way to create relationships between objects in Ruby is association, aggregation, or composition, not nesting.

    Ruby is flexible enough to implement something that behaves like a "proper" BETA-style inner class. Remember, an inner class is "nested" inside an object instance of the outer class. We can emulate that using an instance variable:

    class Outer
      attr_reader :Inner
    
      def outer_method
        __callee__
      end
    
      private
    
      attr_writer :Inner
    
      def initialize
        self.Inner = Class.new(self.class) do
          def inner_method
            [__callee__, outer_method]
          end
        end
      end
    end
    
    outer = Outer.new
    inner = outer.Inner.new
    p inner.inner_method
    

    Please note that I made the inner class inherit from the outer class: that is the way to do behavior sharing in Ruby. You need to have some form of inheritance or association relationship. The nesting does not create a relationship.