2010-02-22 9 views
3

J'utilise Blockenspiel pour créer un DSL avec Ruby. Il fonctionne très bien et résout beaucoup de mes problèmes, mais j'ai rencontré le problème suivant qui n'est pas strictement lié à Blockenspiel.Est-il possible d'évaluer un Ruby DSL dans un contexte non global?

Supposons que j'ai un DSL qui ressemble à ceci:

dish do 
    name = 'Pizza' 
    ingredients = ... 
    nutrition_facts = ... 
end 

dish do 
    name = 'Doner' 
    ingredients = ... 
    nutrition_facts = ... 
end 

Maintenant, j'ai un compilateur de menu qui prend les plats et les compile en un menu. Le compilateur devrait maintenant être capable de compiler plusieurs fichiers de menu, afin qu'il ait installé et effacer un contexte global. Cela devrait de préférence se produire en parallèle.

Je trouve que sinatra utilise des variables de classe, mais cela a pour conséquence qu'il ne peut faire un traitement séquentiel et que vous devez effacer les variables de classe lorsque vous voulez compiler un nouveau menu . Une alternative serait d'utiliser des variables globales.

Je préférerais évaluer les méthodes DSL dans le cadre d'un objet , de sorte qu'il n'y a pas de contexte global et je pouvais compiler les menus en parallèle, mais la dernière fois que j'ai essayé, je rencontré quelques problèmes lorsque déclarer des méthodes (helper-) dans le fichier de menu.

Quels sont les procédés possibles? Quelle est la manière recommandée de faire ceci?

Répondre

2

Quel nombre de bibliothèques que j'ai vu faire est tirer parti de instance_eval pour ce genre de chose.

Tant que la performance n'est pas un énorme problème, vous pouvez faire des choses comme:

class Menu 
    def initialize file 
    instance_eval File.read(file),file,1 
    end 

    def dish &block 
    Dish.new &block 
    end 
    #.... 
end 

class Dish 
    def name(n=nil) 
    @name = n if n 
    @name 
    end 
    def ingredients(igrd=nil) 
    @ingredients= igrd if igrd 
    @ingredients 
    end 
end 
#.... 

Menu.new 'menus/pizza_joint'

menus/pizza_joint

dish do 
    name 'Cheese Pizza' 
    ingredients ['Cheese','Dough','Sauce'] 
end 

Il existe en fait des bibliothèques DSL qui ajoutent des accesseurs comme #name et #ingredients pour que vous n'ayez pas à les construire par la main. Par exemple: dslify

+0

Puis-je également déclarer de nouvelles fonctions et les utiliser dans un menu pendant son évaluation? –

+0

Oui. 'instance_eval', agissant comme un bloc, vous permet de définir des classes, des modules et des méthodes à l'intérieur. – BaroqueBobcat

2

Il existe essentiellement deux façons d'archiver ce que vous voulez.

Option a: Vous cédez un objet avec régleurs méthodes:

Dish = Struct.new(:name, :ingredients, :nutrition_facts) 
def dish 
    d = Dish.new 
    yield d 
    d 
end 

dish do |d| 
    d.name = 'Pizza' 
    d.ingredients = ... 
    d.nutrition_facts = ... 
end 

Option b: vous utilisez des variables d'instance et instance_eval

class Dish 
    attr_accessor :name, :ingredients, :nutrition_facts 
end 
def dish(&blk) 
    d = Dish.new 
    d.instance_eval(&blk) 
    d 
end 

dish do 
    @name = 'Doner' 
    @ingredients = ... 
    @nutrition_facts = ... 
end 

Dans les deux cas, la méthode de plat renvoie une instance de Plat sur lequel vous pouvez appeler par exemple name pour accéder au nom défini dans le bloc (et plusieurs appels à plat renverront des objets indépendants). Notez qu'avec instance_eval, l'utilisateur pourra également appeler des méthodes privées de la classe Dish dans le bloc et que les noms de variable d'orthographe ne provoqueront pas d'erreur.

+0

Si vous remplacez '@foo =' par 'self.foo =', vous n'avez pas à vous soucier des fautes d'orthographe. –

+0

@jleedev: Bon point. – sepp2k

+0

Mais j'ai encore besoin d'un menu global, que je voulais éviter. –