Rails元编程:如何在运行时添加实例方法?

4
我在Rails中定义了自己的AR类,其中包括用户字段0-9的动态创建实例方法。这些用户字段不会直接存储在数据库中,因为它们很少被使用,所以它们将被序列化在一起。以下是最佳实践吗?还有其他替代方案吗?
启动添加方法的代码应该从哪里调用?
class Info < ActiveRecord::Base

end

# called from an init file to add the instance methods
parts = []
(0..9).each do |i|
   parts.push "def user_field_#{i}"     # def user_field_0
   parts.push   "get_user_fields && @user_fields[#{i}]"
   parts.push "end"
end

Info.class_eval parts.join
2个回答

11

一种不错的方法,特别是如果你可能有超过0..9个用户字段,就是使用method_missing

class Info
  USER_FIELD_METHOD = /^user_field_(\n+)$/
  def method_missing(method, *arg)
    return super unless method =~ USER_FIELD_METHOD
    i = Regexp.last_match[1].to_i
    get_user_fields && @user_fields[i]
  end

  # Useful in 1.9.2, or with backports gem:
  def respond_to_missing?(method, private)  
    super || method =~ USER_FIELD_METHOD
  end
end        

如果您更喜欢定义方法:

10.times do |i|
  Info.class_eval do
    define_method :"user_field_#{i}" do
      get_user_fields && @user_fields[i]
    end
  end
end

谢谢。为什么在你的第二个例子中,[i]会在方法定义时被评估而不是在方法执行时被评估? - Larry K
[i]会按预期工作,因为方法定义是一个块,在Ruby中,块是闭包。希望有所帮助。 - Marc-André Lafortune

4

使用method_missing非常难以维护且不必要。另一种替代方案是使用define_method,但会导致性能较差的代码。以下这行代码就足够了:

class Info
end

Info.class_eval 10.times.inject("") {|s,i| s += <<END}
  def user_field_#{i}
    puts "in user_field_#{i}"
  end
END

puts Info.new.user_field_4

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接