Получение переменной 'instance' из родительского метакласса - PullRequest
0 голосов
/ 07 января 2019

С помощью следующего кода, каким образом я могу получить доступ к @arr из Child?

class Parent
    class << self
        def create_singleton_variable
            @arr = [1,2,3]
        end

        def arr
            @arr
        end
    end
end

class Child < Parent
    def get
        puts self.arr
    end
    def self.get 
        puts self.arr
    end
end


p "class method call #{Child.get}"
#=> ➜ ruby child.rb    
#=> "class method call "

c = Child.new
p "instance call #{c.get}"
#=> ➜ ruby child.rb 
#=> Traceback (most recent call last):
#=>        1: from child.rb:24:in `<main>'
#=> child.rb:15:in `get': undefined method `arr' for #<Child:0x00007fe0eb02e7d8> (NoMethodError)

Я пробовал и много других способов, но не чувствую необходимости публиковать их здесь.

отредактируйте вопрос, поскольку, похоже, мне нужно немного больше контекста:

Я пытаюсь добавить модуль в структуру Thor. Я хочу затем получить доступ к этот бит кода

module ThorExtensions
  module Thor
    module CompletionGeneration
      def self.prepended(base)
        base.singleton_class.prepend(ClassMethods)
      end

      module ClassMethods
        def completion
          puts "Start Completion"
          p self
          p self.superclass
          p self.class.superclass.subcommands
          puts "End Completion"
        end
      end
    end
  end
end

результат в

Start Completion
Debug
Thor
bundler: failed to load command: exe/pt (exe/pt)
NoMethodError: undefined method `subcommands' for Module:Class
  /Users/tyler.thrailkill/Documents/code/backend/pt-cli/lib/thor_extensions/completion_generation.rb:13:in `completion'
  /Users/tyler.thrailkill/Documents/code/backend/pt-cli/lib/debug/debug.rb:24:in `<class:Debug>'
  /Users/tyler.thrailkill/Documents/code/backend/pt-cli/lib/debug/debug.rb:4:in `<top (required)>'
  /Users/tyler.thrailkill/Documents/code/backend/pt-cli/lib/pt.rb:5:in `require'
  /Users/tyler.thrailkill/Documents/code/backend/pt-cli/lib/pt.rb:5:in `<top (required)>'
  exe/pt:13:in `require'
  exe/pt:13:in `<top (required)>'

что, конечно, не то, что я хочу. Похоже, что, возможно, моя проблема с предваряющей?

Редактировать 2

Кажется, я проделал ужасную работу по объяснению моей проблемы с предварянием. Вот полностью рабочий пример, показывающий мою проблему. Я полагаю, что это связано с тем, что при добавлении чего-либо к классу по существу создается другой класс в стеке вызовов, который вызывается первым. Я надеюсь, что на самом деле я все еще могу получить доступ к этому методу как-то.

class Parent
  class << self
    def create_singleton_variable
      @arr = [1,2,3]
      puts "arr is initialized #{@arr}"
    end
    # ... lots of code here. 
    def arr
      puts "arr is #{@arr.inspect}"
      @arr
    end
  end
end

module CompletionGeneration
  def self.prepended(base)
    base.singleton_class.prepend(ClassMethods)
  end

  module ClassMethods
    def completion 
      puts "self.superclass.arr == #{self.superclass.arr.inspect}" # unable to access superclass arr
      puts "self.class.superclass.arr == #{self.class.superclass.arr}" # likewise, unable to access Child's metaclass superclass 
    rescue Exception => e
      # do nothing, this is just so you can see arr is actually initialized in the context of the Child
      p e
    end
  end
end

Parent.prepend CompletionGeneration

class Child < Parent
  create_singleton_variable
  completion
  arr
end

Child.new

приводит к выводу

➜ ruby child.rb
arr is initialized [1, 2, 3]
arr is nil
self.superclass.arr == nil
#<NoMethodError: undefined method `arr' for Module:Class>
arr is [1, 2, 3]

Этот код должен быть просто скопирован и вставлен как есть.

1 Ответ

0 голосов
/ 07 января 2019

Вот ваш код, слегка измененный.

class Parent
  def self.create_singleton_variable
    @arr = [1,2,3]
  end 
  def self.arr
    puts "self = #{self} in the getter for @arr"
    @arr
  end
end

class Child < Parent
  def get
    puts self.arr
  end
  def self.get 
    puts self.arr
  end
end

Я написал Parent более обычным способом. За исключением добавления оператора puts, оно эквивалентно тому, которое содержится в вопросе.

Во-первых, главный удар: Ядро # ставит - все возвращается nil. Вам необходимо удалить puts из обоих методов:

class Child < Parent
  def get
    self.arr
  end
  def self.get 
    self.arr
  end
end

Parent.create_singleton_variable
  #=> [1, 2, 3] 
Child.get.nil?
self = Child in the getter for @arr
  #=> true

Мы видим, что в получателе arr, вызванном Child классовым методом get, self равно Child, поэтому метод ищет переменную экземпляра класса @arr из Child не из Parent. Поскольку никакая переменная экземпляра не была инициализирована, возвращается nil.

Вам нужно следующее.

class Parent
  class << self
    def create_singleton_variable
      @arr = [1,2,3]
    end
    def arr
      puts "self = #{self} in the getter for @arr"
      @arr
    end
  end
end

class Child < Parent
  def get
    self.class.superclass.arr
  end
  def self.get 
    superclass.arr
  end
end

Принципиальное отличие от приведенного в вопросе заключается в том, что Класс # суперкласс меняет область действия (т. Е. self) на Parent.

Видим, желаемый результат получен.

Child.get
self = Parent in the getter for @arr
  #=> [1, 2, 3] 

Child.new.class.superclass.arr
self = Parent in the getter for @arr
  #=> [1, 2, 3] 

Распространенным заблуждением является то, что метод класса Child, определенный def self.get; self.arr; end, вызывает получатель Parent::arr и, следовательно, возвращает значение переменной экземпляра Parent @arr. Это Child::arr, который вызывается, однако, этот метод был унаследован от Parent, и это переменная экземпляра класса Child @arr, которая извлекается, тонкое, но важное различие.

Редактировать 2

Первое наблюдение состоит в том, что Parent можно записать более обычным (и полностью эквивалентным) способом.

class Parent
  def self.create_singleton_variable
    @arr = [1,2,3]
    puts "arr is initialized #{@arr}"
  end

  def self.arr
    puts "arr is #{@arr.inspect}"
    @arr
  end
end

Независимо от того, как написано, self будет равняться Parent, если какой-либо метод класса задействован для родителя. Поэтому первый создаст переменные экземпляра класса @arr.

Parent.methods(false)
  #=> [:create_singleton_variable, :arr] 
Parent.instance_variables
  #=> []
Parent.ancestors
  #=> [Parent, Object, Kernel, BasicObject] 

Теперь давайте создадим переменную класса для Parent.

Parent.create_singleton_variable
  # arr is initialized [1, 2, 3]
Parent.instance_variables
  #=> [:@arr]

Теперь позвольте мне изменить значение @arr.

Parent.instance_variable_set(:@arr, ['dog', 'cat'])
  #=> ["dog", "cat"]
Parent.arr
  # arr is ["dog", "cat"]
  #=> ["dog", "cat"] 

Затем создайте класс Child, но еще не добавляйте модуль в начале.

class Child < Parent
  create_singleton_variable
  arr
end
arr is initialized [1, 2, 3]
arr is [1, 2, 3]

Child.ancestors
  #=> [Child, Parent, Object, Kernel, BasicObject]
Child.instance_variables
  #=> [:@arr] 
Child.instance_variable_get(:@arr)
  #=> [1, 2, 3] 

Сюрпризов нет. Затем загрузите модуль.

module CompletionGeneration
  def self.prepended(base)
    base.singleton_class.prepend(ClassMethods)
  end

  module ClassMethods
    def completion
      puts "self=#{self}"
      puts "superclass=#{superclass}"
      puts "self.class=#{self.class}"
      puts "self.class.superclass == #{self.class.superclass}" 
      puts "superclass.arr == #{superclass.arr.inspect}"
      puts "self.class.superclass.arr == #{self.class.superclass.arr}" 
      rescue Exception => e
        # do nothing, this is just so you can see arr is actually
        # initialized in the context of the Child
        puts "Exception => e=#{e}"
    end
  end
end

(Примечание self. не требуется в "superclass.arr == #{superclass.arr.inspect}") Теперь добавьте этот модуль к Parent.

Parent.prepend CompletionGeneration

Parent.ancestors
  #=> [CompletionGeneration, Parent, Object, Kernel, BasicObject] 
Parent.methods.include?(:completion)
  #=> true 
Child.ancestors
  #=> [Child, CompletionGeneration, Parent, Object, Kernel, BasicObject] 
Child.methods.include?(:completion)
  #=> true 

Метод модулей обратного вызова CompletionGeneration::prepended запускается с base, равным Parent, в результате чего синглтон-класс Parent предшествует ClassMethods, тем самым добавляя метод класса Parent::completion. Поскольку у Parent ранее не было метода с таким именем, использование prepend или include имело бы такой же эффект. Кроме того, вместо Parent.singleton_class.include ClassMethods можно было бы использовать обратный вызов included(base) и выполнить Parent.extend ClassMethods. Возможно, prepend используется здесь для общего случая, когда Parent может иметь метод класса с таким именем. 1

Теперь выполните следующее.

Child.completion
self=Child
superclass=Parent
self.class=Class
self.class.superclass == Module
arr is ["dog", "cat"]
superclass.arr == ["dog", "cat"]
Exception => e=undefined method `arr' for Module:Class

Исключение возникло, когда

puts "self.class.superclass.arr == #{self.class.superclass.arr}"

казнили. Как это составляет

puts "self.class.superclass.arr == #{Module.arr}"

но, конечно, Module не имеет модульного метода arr.

1 С учетом Child.ancestors, добавление Parent к модулю приводит к тому, что дочерние элементы Parent только include (вместо prepend) модуля; то есть, если у ребенка уже есть метод completion до добавления, этот метод не будет заменен методом модуля с тем же именем.

...