Frage

Für Benutzerkontrolle und sauberer Code möchte ich eine Klasse schreiben, die so verwendet werden kann:

Encoder::Theora.encode do
  infile = "path/to/infile"
  outfile = "path/to/outfile"
  passes = 2
  # ... more params
end

Die Herausforderung besteht nun darin, diese Parameter in meiner Encode -Methode verfügbar zu haben.

module Encoder
  class Theora
    def self.encode(&proc)
      proc.call
      # do some fancy encoding stuff here
      # using the parameters from the proc
    end
  end
end

Dieser Ansatz funktioniert nicht. Wenn der Proc aufgerufen wird, werden die Variablen im Kontext der Theora -Klasse nicht bewertet. Normalerweise möchte ich Method_miss verwenden, um jeden Parameter in eine Klassenvariable der Klassentheora zu setzen, aber ich finde nicht den richtigen Weg für einen Eintrag.

Kann mich jemand in die richtige Richtung weisen?

War es hilfreich?

Lösung

Es kann nicht so gemacht werden, wie Sie es geschrieben haben, Afaik. Der Körper des Proc hat seinen eigenen Bereich, und Variablen, die innerhalb dieses Umfangs erzeugt werden, sind außerhalb dessen nicht sichtbar.

Der idiomatische Ansatz besteht darin, ein Konfigurationsobjekt zu erstellen und in den Block zu übergeben, der die Arbeiten mit Methoden oder Attributen dieses Objekts beschreibt. Dann werden diese Einstellungen bei der Arbeit gelesen. Dies ist der Ansatz von create_table beispielsweise in ActivereCord -Migrationen.

So können Sie so etwas tun:

module Encoder
  class Theora
    Config = Struct.new(:infile, :outfile, :passes)

    def self.encode(&proc)
      config = Config.new
      proc.call(config)
      # use the config settings here
      fp = File.open(config.infile)       # for example
      # ...
    end
  end
end

# then use the method like this:
Encoder::Theora.encode do |config|
  config.infile = "path/to/infile"
  config.outfile = "path/to/outfile"
  config.passes = 2
  # ...
end

Andere Tipps

Ich bin mir nicht sicher, ob es möglich ist, die DSL dazu zu bringen, die Aufgabe zu verwenden. Ich denke, der Ruby -Dolmetscher wird das immer annehmen infile in infile = 'path/to/something' ist eine lokale Variable in diesem Kontext (aber self.infile = 'path/to/something' kann zur Arbeit gemacht werden). Wenn Sie jedoch ohne dieses bestimmte Detail leben können, können Sie Ihre DSL wie folgt implementieren:

module Encoder
  class Theora
    def self.encode(&block)
      instance = new
      instance.instance_eval(&block)
      instance
    end

    def infile(path=nil)
      @infile = path if path
      @infile
    end
  end
end

Und benutze es so:

Encoder::Theora.encode do
  infile 'path/somewhere'
end

(Implementieren Sie die anderen Eigenschaften ähnlich).

Als ich damit herumspiele, kam ich nach Folgendes an, was ich nicht unbedingt empfehle und was nicht ganz zur erforderlichen Syntax passt, aber es ermöglicht, die Zuordnung (irgendwie) zu verwenden. So lesen Sie im Geiste der Vollständigkeit:

module Encoder
  class Theora
    def self.encode(&proc)
      infile = nil
      outfile = nil
      yield binding
    end
  end
end

Encoder::Theora.encode do |b|
  b.eval <<-ruby
    infile = "path/to/infile"
    outfile = "path/to/outfile"
  ruby
end

Ich glaube Binding.Val funktioniert nur in Ruby 1.9. Es scheint auch, dass die lokalen Variablen vor dem Nachgeben deklariert werden müssen, oder es wird nicht funktionieren - weiß jemand warum?

Ok, zuerst muss ich sagen, dass PMDBOIs Antwort sehr elegant und mit ziemlicher Sicherheit der richtige ist.

Trotzdem möchten Sie eine super geschnittene DSL mögen

Encoder::Theora.encode do
  infile "path/to/infile"
  outfile "path/to/outfile"
  passes 2
end

Sie können so etwas Hässliches tun:

require 'blockenspiel'
module Encoder
  class Theora
    # this replaces pmdboi's elegant Struct
    class Config
      include Blockenspiel::DSL
      def method_missing(method_id, *args, &blk)
        if args.length == 1
          instance_variable_set :"@#{method_id}", args[0]
        else
          instance_variable_get :"@#{method_id}"
        end
      end
    end

    def self.encode(&blk)
      config = Config.new
      Blockenspiel.invoke blk, config
      # now you can do things like
      puts config.infile
      puts config.outfile
      puts config.passes
    end
  end
end
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top