2010-02-17 3 views
8

Ho una struttura piccola ma in crescita per building .net systems with ruby/rake, su cui sto lavorando da un po 'di tempo. In questa base di codice, ho la seguente:Come posso convertire questo codice in meta-programmazione, quindi posso smettere di duplicarlo?

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 

il modello che questo codice segue viene ripetuto circa 20 volte nel quadro. La differenza in ogni versione è il nome della classe che viene creata/chiamata (anziché AssemblyInfoTask, potrebbe essere MSBuildTask o NUnitTask) e il contenuto del metodo execute. Ogni attività ha la propria implementazione del metodo di esecuzione.

Correggendo costantemente bug in questo modello di codice e devo ripetere la correzione 20 volte, ogni volta che ho bisogno di una correzione.

So che è possibile eseguire alcune metamodifiche magiche e collegare questo codice per ognuna delle mie attività da una singola posizione ... ma sto facendo davvero fatica a farlo funzionare.

la mia idea è che voglio essere in grado di chiamare qualcosa di simile:

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 

e questo sarebbe cablare tutto quello che mi serve.

Ho bisogno di aiuto! suggerimenti, suggerimenti, qualcuno disposto ad affrontare questo ... come posso evitare di dover ripetere questo schema di codice più e più volte?

Aggiornamento: È possibile ottenere il codice sorgente completo qui: http://github.com/derickbailey/Albacore/ il codice fornito è /lib/rake/assemblyinfotask.rb

+0

+1 per l'uso di Github, molto bello poter navigare su tutta la fonte. – Xorlev

+0

Perché non metti tutti gli errori di classe nel file del modulo? L'approccio di metaprogrammo quindi ti salva solo 1 linea di codice reale (non "di fine"), mentre a costo di una maggiore offuscazione. A meno che non sia necessario definire dinamicamente le attività, utilizzerei semplicemente l'approccio vanilla. – klochner

+0

. . . a meno che, naturalmente, questo non sia un esercizio di apprendimento piuttosto che un progetto finito. – klochner

risposta

4

Ok, ecco qualche metaprogrammazione che farà ciò che si vuole (in ruby18 o 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 

Gli strumenti chiave nella casella metaprogrammers strumento sono:

  • class<<self;self;end - a ge t al metaclasse per qualsiasi oggetto, in modo da poter definire i metodi su tale oggetto
  • define_method - in modo da poter definire i metodi usando attuali variabili locali

utili sono anche

  • const_set, const_get: consentire per impostare/ottenere costanti
  • class_eval: consente di definire i metodi utilizzando def come se si fosse in una regione class <Classname> ... end
+1

Se si desidera la capacità di auto-capitalizzare ecc., È possibile trovare tale funzionalità in ActiveSupport. – yfeldblum

+0

sto usando Ruby 1.8.6 ... penso che questa soluzione sia per 1.9+ –

+0

ActiveSupport può solo autocapitalizzare quando si usano i delimitatori di sottolineatura, che Derick non sembra volere. – rampion

1

Qualcosa di simile, testato su rubino 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' 

L'elemento centrale è il metodo "create_task", esso:

  • Crea nuova classe
  • aggiunge eseguire metodo
  • Nomi classe e lo espone