Подтвердить что ты не робот

Как работает метод делегирования рельсов?

Прочитав ответ jvans ниже и посмотрев на исходный код еще раз, я получаю его сейчас:). И в случае, если кому-то все еще интересно, как работают делегаты рельсов. Все рельсы делают это, создавая новый метод с (module_eval) в файле/классе, из которого вы запускали метод делегата.

Итак, например:

  class A
    delegate :hello, :to => :b
  end

  class B
    def hello
     p hello
    end
  end

В момент, когда делегат называется rails, будет создан метод hello с (* args, & block) в классе A (технически в файле, в котором написан класс A), и в этом методе все рельсы используют ": to" (который должен быть объектом или классом, который уже определен в классе A), и назначить его локальной переменной _, а затем просто вызывает метод для этого объекта или класса, проходящий в параметрах.

Итак, чтобы делегат работал без создания исключения... с нашим предыдущим примером. Экземпляр A должен иметь переменную экземпляра, ссылающуюся на экземпляр класса B.

  class A
    attr_accessor :b

    def b
      @b ||= B.new
    end

    delegate :hello, :to => :b
  end

  class B
    def hello
     p hello
    end
  end

Это не вопрос о том, как использовать метод делегата в rails, о котором я уже знаю. Мне интересно, как именно "делегировать" делегаты методы: D. В Rails 4 делегат исходного кода определен в основном классе Ruby Module, что делает его доступным как метод класса во всех приложениях rails.

На самом деле мой первый вопрос: каким будет класс Ruby Module? Я имею в виду, что у каждого класса Ruby есть предки > Object > Kernel > BasicObject, и любой модуль в ruby ​​имеет одинаковые предки. Итак, как именно Ruby добавляет методы ко всем классам/модулям ruby, когда кто-то снова открывает класс модуля?

Мой второй вопрос: я понимаю, что метод делегата в rails использует module_eval для фактической делегирования, но я действительно не понимаю, как работает module_eval.

def delegate(*methods)
 options = methods.pop
 unless options.is_a?(Hash) && to = options[:to]
  raise ArgumentError, 'Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter).'
end

prefix, allow_nil = options.values_at(:prefix, :allow_nil)

if prefix == true && to =~ /^[^a-z_]/
  raise ArgumentError, 'Can only automatically set the delegation prefix when delegating to a method.'
end

method_prefix = \
  if prefix
    "#{prefix == true ? to : prefix}_"
  else
    ''
  end

file, line = caller.first.split(':', 2)
line = line.to_i

to = to.to_s
to = 'self.class' if to == 'class'

methods.each do |method|
  # Attribute writer methods only accept one argument. Makes sure []=
  # methods still accept two arguments.
  definition = (method =~ /[^\]]=$/) ? 'arg' : '*args, &block'

  # The following generated methods call the target exactly once, storing
  # the returned value in a dummy variable.
  #
  # Reason is twofold: On one hand doing less calls is in general better.
  # On the other hand it could be that the target has side-effects,
  # whereas conceptually, from the user point of view, the delegator should
  # be doing one call.
  if allow_nil
    module_eval(<<-EOS, file, line - 3)
      def #{method_prefix}#{method}(#{definition})        # def customer_name(*args, &block)
        _ = #{to}                                         #   _ = client
        if !_.nil? || nil.respond_to?(:#{method})         #   if !_.nil? || nil.respond_to?(:name)
          _.#{method}(#{definition})                      #     _.name(*args, &block)
        end                                               #   end
      end                                                 # end
    EOS
  else
    exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")

    module_eval(<<-EOS, file, line - 2)
      def #{method_prefix}#{method}(#{definition})                                          # def customer_name(*args, &block)
        _ = #{to}                                                                           #   _ = client
        _.#{method}(#{definition})                                                          #   _.name(*args, &block)
      rescue NoMethodError => e                                                             # rescue NoMethodError => e
        if _.nil? && e.name == :#{method}                                                   #   if _.nil? && e.name == :name
          #{exception}                                                                      #     # add helpful message to the exception
        else                                                                                #   else
          raise                                                                             #     raise
        end                                                                                 #   end
      end                                                                                   # end
    EOS
  end
end

конец

4b9b3361

Ответ 1

Ruby не открывает класс модуля здесь. В рубине класс Module и класс Class почти идентичны.

    Class.instance_methods - Module.instance_methods #=> [:allocate, :new, :superclass]

Основное отличие состоит в том, что вы не можете "новый" модуль. Модуль - это рубиновая версия множественного наследования, поэтому когда вы делаете:

 module A
 end
 module B
 end

 class C
   include A
   include B
 end

за кулисами ruby ​​фактически создает нечто, называемое анонимным классом. поэтому приведенное выше фактически эквивалентно:

 class A
 end
 class B < A
 end
 class C < B
 end

module_eval здесь немного обманчив. Ничто из кода, на которое вы смотрите, не имеет дело с модулями. class_eval и module_eval - это одно и то же, и они просто снова открывают класс, на который они вызваны, поэтому, если вы хотите добавить методы в класс C, который вы можете сделать:

 C.class_eval do 
    def my_new_method
    end
  end

или

 C.module_eval do 
    def my_new_method
    end
  end

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

  class C
   end
  class C
     def my_new_method
     end 
  end

поэтому, когда они вызывают module_eval в источнике выше, они просто открывают текущий класс, который он называется, и динамически определяют методы, которые вы делегируете

Я думаю, что это лучше ответит на ваш вопрос:

 Class.ancestors #=> [Module, Object, PP::ObjectMixin, Kernel, BasicObject]

поскольку все в ruby ​​- это класс, цепочка поиска метода будет проходить через все эти объекты, пока не найдет то, что ищет. По модулю reoping вы добавляете поведение ко всему. Цепочка предков здесь немного обманчива, так как BasicObject.class # = > Класс и модуль находятся в иерархии поиска класса, даже BasicObject наследует поведение от модуля repening. Преимущество повторного открытия модуля здесь по классу состоит в том, что вы можете теперь вызывать этот метод как внутри модуля, так и внутри класса! Очень круто, узнал что-то здесь сам.

Ответ 2

Прочитав ответ jvans ниже и посмотрев на исходный код еще раз, я получаю его сейчас:). И в случае, если кому-то все еще интересно, как работают делегаты рельсов. Все рельсы делают это, создавая новый метод с (module_eval) в файле/классе, из которого вы запускали метод делегата.

Итак, например:

  class A
    delegate :hello, :to => :b
  end

  class B
    def hello
     p hello
    end
  end

В момент, когда делегат называется rails, будет создан метод hello с (* args, & block) в классе A (технически в файле, в котором написан класс A), и в этом методе все рельсы используют ": to" (который должен быть объектом или классом, который уже определен в классе A), и назначить его локальной переменной _, а затем просто вызывает метод для этого объекта или класса, проходящий в параметрах.

Итак, чтобы делегат работал без создания исключения... с нашим предыдущим примером. Экземпляр A должен иметь переменную экземпляра, ссылающуюся на экземпляр класса B.

  class A
    attr_accessor :b

    def b
      @b ||= B.new
    end

    delegate :hello, :to => :b
  end

  class B
    def hello
     p hello
    end
  end