web-dev-qa-db-ja.com

Ruby-class_evalを使用してメソッドを定義する

SaaSスタンフォードクラス、 この割り当て のパート5を実行しようとしています

私はこの概念を理解するのに本当に苦労しています、これは私がやろうとしたことです:

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

私はおそらくあらゆる種類の間違ったことをしているでしょう。メタプログラミングに関するTheBook Of Rubyの章を読んでも、まだ理解できません。誰かがこれを理解するのを手伝ってくれませんか?

22
8vius

楽しかったです!!!

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"

Class_evalで文字列を使用する必要がある唯一の理由は、カスタムセッターを定義するときにattr_nameの-​​valueを参照できるようにするためです。それ以外の場合は、通常、ブロックをclass_evalに渡します。

41
Irfy

あなたがしたことに関して、あなたは実際に解決策の最前線にいます。それだけです#{attr_name}_historyはコードに存在しません。インスタンス変数を作成し、存在しない場合はnilに設定する必要があります。配列が存在する場合は、すでに配列へのプッシュを処理する必要があります。

これを行うにはいくつかの方法があります。 1つの方法はif defined? @#{attr_name}_history DoStuffHere

6
Angel Marquez

#{attr_name}_historyはインスタンス変数であることに注意する必要があるため、以下のクラスの@fooのように、前に@を使用してください。

def #{attr_name}=value#{attr_name}=はメソッド名、valueはパラメーター、def func parameterと同じ

def #{attr_name}=value
  (!defined? @#{attr_name}_history) ? @#{attr_name}_history = [nil, value] : @#{attr_name}_history << value
end
0
duykhoa