Ruby - Using class_eval to define methods

2019-01-30 15:40发布

I'm doing the SaaS Stanford class, trying to do Part 5 of this assignment

I'm having a really hard time grasping this concept, this is what I've attempted to do:

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

I'm probably doing all sorts of things wrong, read The Book Of Ruby chapter on metaprogramming and I still don't get it, can someone help me comprehend this?

3条回答
别忘想泡老子
2楼-- · 2019-01-30 16:29

This was fun!!!

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"

Note that the only reason you need to use strings with class_eval is so that you can refer to the value of attr_name when defining the custom setter. Otherwise one would normally pass a block to class_eval.

查看更多
3楼-- · 2019-01-30 16:30

With regard to what you've done you're actually on the cusp of the solution. It's just that #{attr_name}_history doesn't exist in your code. You will need to create an instance variable and set it to nil if it doesn't exist. What you have already should handle pushing into the array if it does exist.

There are several ways to do that. One way is if defined? @#{attr_name}_history DoStuffHere

查看更多
趁早两清
4楼-- · 2019-01-30 16:37

You must notice that #{attr_name}_history is a instance variable, so use @ before, like @foo in the class below

def #{attr_name}=value, #{attr_name}= is the method name, value is parameter, same as def func parameter

def #{attr_name}=value
  (!defined? @#{attr_name}_history) ? @#{attr_name}_history = [nil, value] : @#{attr_name}_history << value
end
查看更多
登录 后发表回答