Question

Pour plus de commodité d'utilisateur et un code plus propre, je voudrais écrire une classe qui peut être utilisé comme ceci:

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

Le défi est maintenant, d'avoir que les paramètres disponibles dans ma méthode encode.

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

Cette approche ne fonctionne pas. Lorsque le Proc est appelé, les variables ne sont pas évaluées dans le contexte de la classe Theora. En général, je voudrais utiliser method_missing pour mettre tous les paramètres dans une variable de classe de classe Theora, mais je ne trouve pas la bonne voie pour une entrée.

point Quelqu'un peut-moi dans la bonne direction?

Était-ce utile?

La solution

Il ne peut se faire de la façon dont vous l'avez écrit, autant que je sache. Le corps du proc a sa propre portée, et les variables qui sont créées dans ce cadre ne sont pas visibles à l'extérieur il.

L'approche idiomatiques est de créer un objet de configuration et de passer dans le bloc, qui décrit le travail à faire en utilisant des méthodes ou des attributs de cet objet. Ensuite, ces paramètres sont lus lors de faire le travail. Telle est l'approche adoptée par create_table des migrations ActiveRecord, par exemple.

Vous pouvez faire quelque chose comme ceci:

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

Autres conseils

Je ne suis pas sûr qu'il est possible d'obtenir le DSL à l'affectation d'utilisation, je pense que l'interprète Ruby assumera toujours infile dans infile = 'path/to/something' est une variable locale dans ce contexte (mais self.infile = 'path/to/something' peut être au travail). Cependant, si vous pouvez vivre sans ce détail, vous pouvez mettre en œuvre votre DSL comme ceci:

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

et l'utiliser comme ceci:

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

(mettre en œuvre les autres propriétés Similairement).

En jouant avec ce je suis arrivé à ce qui suit, que je ne recommande pas nécessairement, et qui ne correspond pas tout à fait la syntaxe requise, mais qui ne vous permet d'utiliser l'affectation (en quelque sorte). Alors peruse dans l'esprit d'exhaustivité:

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

Je crois que Binding.eval ne fonctionne que dans Ruby 1.9. Aussi, il semble les variables locales doivent être déclarées avant de céder ou il ne fonctionnera pas - quelqu'un sait pourquoi

OK, je dois d'abord dire que la réponse de pmdboi est très élégante et presque certainement le droit.

Pourtant, juste au cas où vous voulez une super DSL coupée vers le bas comme

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

Vous pouvez faire quelque chose de laid comme ceci:

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
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top