Ruby-utilisation de la classe eval pour définir des méthodes

Je fais la classe Saas Stanford, en essayant de faire la partie 5 de cette affectation

J'ai vraiment du mal à saisir ce concept, c'est ce que j'ai essayé de faire:

class Class
  def attr_accessor_with_history(attr_name)
    attr_name = attr_name.to_s
    attr_reader attr_name
    attr_reader attr_name + '_history'
    class_eval %Q'{def #{attr_name}(a);#{attr_name}_history.push(a) ; end;}'
  end
end

Je fais probablement toutes sortes de choses mal, lisez le chapitre du Livre de Ruby sur la métaprogrammation et je ne comprends toujours pas, quelqu'un peut-il m'aider à comprendre cela?

22
demandé sur 8vius 2012-03-05 06:52:34

3 réponses

C'était amusant!!!

class Class
    def attr_accessor_with_history(attr_name)
        attr_name = attr_name.to_s # make sure it's a string
        attr_reader attr_name
        attr_reader attr_name+"_history"
        class_eval %Q"
            def #{attr_name}=(value)
                if !defined? @#{attr_name}_history
                    @#{attr_name}_history = [@#{attr_name}]
                end
                @#{attr_name} = value
                @#{attr_name}_history << value
            end
        "
    end
end

class Foo
    attr_accessor_with_history :bar
end

class Foo2
    attr_accessor_with_history :bar
    def initialize()
        @bar = 'init'
    end
end

f = Foo.new
f.bar = 1
f.bar = nil
f.bar = '2'
f.bar = [1,nil,'2',:three]
f.bar = :three
puts "First bar:", f.bar.inspect, f.bar_history.inspect
puts "Correct?", f.bar_history == [f.class.new.bar, 1, nil, '2', [1,nil,'2',:three], :three] ? "yes" : "no"
old_bar_history = f.bar_history.inspect

f2 = Foo2.new
f2.bar = 'baz'
f2.bar = f2
puts "\nSecond bar:", f2.bar.inspect, f2.bar_history.inspect
puts "Correct?", f2.bar_history == [f2.class.new.bar, 'baz', f2] ? "yes" : "no"

puts "\nIs the old f.bar intact?", f.bar_history.inspect == old_bar_history ? "yes" : "no"

Notez que la seule raison pour laquelle vous devez utiliser des chaînes avec class_eval est que vous pouvez vous référer à la valeur de attr_name lors de la définition du setter personnalisé. Sinon, on passerait normalement un bloc à class_eval.

41
répondu Irfy 2012-03-05 03:51:26

En ce qui concerne ce que vous avez fait, vous êtes en fait sur le point de la solution. C'est juste que #{attr_name}_history n'existe pas dans votre code. Vous devrez créer une variable d'instance et la définir sur nil si elle n'existe pas. Ce que vous avez déjà devrait gérer pousser dans le tableau s'il existe.

Il y a plusieurs façons de le faire. Une façon est if defined? @#{attr_name}_history DoStuffHere

6
répondu Angel Marquez 2012-03-05 03:57:06

Vous devez remarquer que #{attr_name}_history est une variable d'instance, utilisez donc @ before, comme @foo dans la classe ci-dessous

def #{attr_name}=value, #{attr_name}= est le nom de la méthode, value est le paramètre, même en tant que def func parameter

def #{attr_name}=value
  (!defined? @#{attr_name}_history) ? @#{attr_name}_history = [nil, value] : @#{attr_name}_history << value
end
0
répondu duykhoa 2013-07-30 14:51:46