Wie funktioniert die Rails-Delegate-Methode?

Nachdem ich die Antwort von jvans unten gelesen und mir den Quellcode noch ein paar Mal angesehen habe, verstehe ich ihn jetzt :). Und falls sich noch jemand fragt, wie genau die Rails-Delegierten funktionieren. Alle Rails erstellen eine neue Methode mit (module_eval) in der Datei / Klasse, aus der Sie die Delegate-Methode ausgeführt haben.

Also zum Beispiel:

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

  class B
    def hello
     p hello
    end
  end

An dem Punkt, an dem der Delegat Rails heißt, wird eine Hello-Methode mit (* args, & block) in Klasse A erstellt (technisch in der Datei, in der Klasse A geschrieben ist). In dieser Methode verwenden alle Rails den Wert ": to" (Dies sollte ein Objekt oder eine Klasse sein, die bereits in der Klasse A definiert ist), und weisen Sie es einer lokalen Variablen _ zu. Rufen Sie dann einfach die Methode für dieses Objekt oder diese Klasse auf, und übergeben Sie die Parameter.

Damit der Delegierte arbeiten kann, ohne eine Ausnahme auszulösen ... mit unserem vorherigen Beispiel. Eine Instanz von A muss bereits eine Instanzvariable haben, die auf eine Instanz der Klasse B verweist.

  class A
    attr_accessor :b

    def b
      @b ||= B.new
    end

    delegate :hello, :to => :b
  end

  class B
    def hello
     p hello
    end
  end

Dies ist keine Frage zur "Verwendung der Delegate-Methode in Schienen", die ich bereits kenne. Ich frage mich, wie genau "delegieren" Methoden delegiert: D. In Rails 4 ist der Quellcode delegate in der Ruby Module-Kernklasse definiert, wodurch er als Klassenmethode in allen Rails-Apps verfügbar ist.

Eigentlich wäre meine erste Frage, wie Rubys Modulklasse enthalten ist. Ich meine, jede Ruby-Klasse hat Vorfahren von> Object> Kernel> BasicObject und jedes Modul in Ruby hat dieselben Vorfahren. Wie genau fügt Ruby allen Ruby-Klassen / Modulen Methoden hinzu, wenn jemand die Modul-Klasse erneut öffnet?

Meine zweite Frage ist .. Ich verstehe, dass die Delegate-Methode in Rails module_eval verwendet, um die eigentliche Delegation durchzuführen, aber ich verstehe nicht wirklich, wie module_eval funktioniert.

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

Ende

Antworten auf die Frage(2)

Ihre Antwort auf die Frage