2010-02-17 5 views
8

J'ai un cadre petit mais croissant pour building .net systems with ruby/rake, sur lequel je travaille depuis longtemps. Dans cette base de code, je donne les résultats suivants:Comment puis-je convertir ce code en méta-programmation, donc je peux arrêter de le dupliquer?

require 'rake/tasklib' 

def assemblyinfo(name=:assemblyinfo, *args, &block) 
    Albacore::AssemblyInfoTask.new(name, *args, &block) 
end 

module Albacore 
    class AssemblyInfoTask < Albacore::AlbacoreTask 
    def execute(name) 
     asm = AssemblyInfo.new 
     asm.load_config_by_task_name(name) 
     call_task_block(asm) 
     asm.write 
     fail if asm.failed 
    end 
    end 
end 

le motif que ce code suit est répété environ 20 fois dans le cadre. La différence dans chaque version est le nom de la classe en cours de création/d'appel (au lieu de AssemblyInfoTask, il peut s'agir de MSBuildTask ou de NUnitTask), et le contenu de la méthode execute. Chaque tâche a sa propre implémentation de méthode d'exécution.

Je suis constamment fixer des bugs dans ce modèle de code et je dois répéter le correctif 20 fois, chaque fois que je besoin d'une solution.

Je sais qu'il est possible de faire de la magie de méta-programmation et câbler ce code pour chacun de mes tâches à partir d'un seul endroit ... mais je vais avoir une période très difficile à le faire fonctionner.

mon idée est que je veux être en mesure d'appeler quelque chose comme ceci:

create_task :assemblyinfo do |name| 
    asm = AssemblyInfo.new 
    asm.load_config_by_task_name(name) 
    call_task_block(asm) 
    asm.write 
    fail if asm.failed 
end 

et ce câblais tout ce que je besoin.

J'ai besoin d'aide! conseils, suggestions, quelqu'un qui veut s'attaquer à ce problème ... comment puis-je éviter de devoir répéter ce modèle de code encore et encore?

Mise à jour: Vous pouvez obtenir le code source complet ici: http://github.com/derickbailey/Albacore/ le code fourni est /lib/rake/assemblyinfotask.rb

+0

+1 pour l'utilisation de Github, très agréable de pouvoir parcourir toute la source. – Xorlev

+0

Pourquoi ne placez-vous pas tous les arguments de la classe dans le fichier du module? L'approche de la métaprogrammation ne vous permet alors d'économiser qu'une seule ligne de code réelle (non "end"), tout en se faisant au détriment de l'obscurcissement supplémentaire. À moins que vous ayez besoin de définir dynamiquement les tâches, j'utiliserais simplement l'approche vanille. – klochner

+0

. . . à moins bien sûr que ce soit destiné à être plus un exercice d'apprentissage qu'un projet fini. – klochner

Répondre

4

Ok, voici quelques metaprogramming qui va faire ce que vous voulez (en ruby18 ou ruby19)

def create_task(taskname, &execute_body) 
    taskclass = :"#{taskname}Task" 
    taskmethod = taskname.to_s.downcase.to_sym 
    # open up the metaclass for main 
    (class << self; self; end).class_eval do 
    # can't pass a default to a block parameter in ruby18 
    define_method(taskmethod) do |*args, &block| 
     # set default name if none given 
     args << taskmethod if args.empty? 
     Albacore.const_get(taskclass).new(*args, &block) 
    end 
    end 
    Albacore.const_set(taskclass, Class.new(Albacore::AlbacoreTask) do 
    define_method(:execute, &execute_body) 
    end) 
end 

create_task :AssemblyInfo do |name| 
    asm = AssemblyInfo.new 
    asm.load_config_by_task_name(name) 
    call_task_block(asm) 
    asm.write 
    fail if asm.failed 
end 

les outils clés dans la boîte à outils metaprogrammers sont:

  • class<<self;self;end - ge t au métaclasse pour tout objet, de sorte que vous pouvez définir des méthodes sur cet objet
  • define_method - de sorte que vous pouvez définir des méthodes utilisant des variables locales actuelles

sont également utiles

  • const_set, const_get: permettre vous permet de définir/obtenir des constantes
  • class_eval: vous permet de définir des méthodes en utilisant def comme si vous étiez dans une région class <Classname> ... end
+1

Si vous voulez la possibilité de capitaliser automatiquement, vous pouvez trouver cette fonctionnalité dans ActiveSupport. – yfeldblum

+0

J'utilise Ruby 1.8.6 ... Je pense que cette solution est pour 1.9+ –

+0

ActiveSupport ne peut s'autocapitaliser que lorsque vous utilisez des délimiteurs de soulignement, ce que Derick ne semblait pas vouloir. – rampion

1

Quelque chose comme ça, testé sur ruby ​​1.8.6:

class String 
    def camelize 
    self.split(/[^a-z0-9]/i).map{|w| w.capitalize}.join 
    end 
end 

class AlbacoreTask; end 

def create_task(name, &block) 
    klass = Class.new AlbacoreTask 
    klass.send :define_method, :execute, &block 
    Object.const_set "#{name.to_s.camelize}Task", klass 
end 

create_task :test do |name| 
    puts "test: #{name}" 
end 

testing = TestTask.new 
testing.execute 'me' 

La pièce principale est la méthode "create_task", il:

  • Crée nouvelle classe
  • ajoute méthode execute
  • noms de la classe et expose
Questions connexes