2012-01-14 58 views
0

我已经定义了一个模块来扩展ActiveRecord。Class_eval不能在每个块内工作

在我的情况下,我必须生成实例方法,其中带有作为参数给出的符号给compound_datetime类方法。当class_evaleach区块之外被调用,但不在其内时调用它;在后一种情况下,我得到一个未定义的方法错误。

有谁知道我在做什么错?

module DateTimeComposer 
    mattr_accessor :attrs 
    @@attrs = [] 

    module ActiveRecordExtensions 
    module ClassMethods 
     def compound_datetime(*attrs) 
     DateTimeComposer::attrs = attrs 
     include ActiveRecordExtensions::InstanceMethods 
     end 
    end 

    module InstanceMethods 
     def datetime_compounds 
     DateTimeComposer::attrs 
     end 

     def self.define_compounds(attrs) 
     attrs.each do |attr| 
      class_eval <<-METHODS 
      def #{attr.to_s}_to() 
       puts 'tes' 
      end 
      METHODS 
     end 
     end 

     define_compounds(DateTimeComposer::attrs) 
    end 
    end 
end 


class Account < ActiveRecord::Base 
    compound_datetime :sales_at, :published_at 
end 

当我尝试访问方法:

Account.new.sales_at_to 

我得到一个MethodError: undefined method sales_at_to for #<Account:0x007fd7910235a8>

+0

你能放这里的错误日志? – megas 2012-01-14 07:12:54

+0

你能不能展示一些代码实际上如何使用这个模块? – rdvdijk 2012-01-14 11:48:01

+0

现在比较好? – 2012-01-14 14:34:03

回答

3

您在InstanceMethods模块定义的末尾调用define_compounds(DateTimeComposer::attrs)。在代码中的那一点,attrs仍然是一个空阵列,selfInstanceMethods模块。

这意味着没有方法将被定义,即使他们是,他们将被绑定到InstanceMethods的元类,使他们类方法是模块,而不是你Account类的实例方法

这是因为方法调用InstanceMethods模块定义中,因为它们是由Ruby解释器看到,当你调用include ActiveRecordExtensions::InstanceMethods进行评估。暗示这是it is possible to run arbitrary code in the most unusual of places, such as within a class definition

为了解决这个问题,你可以使用由红宝石,每当一个模块包含在另一个被称为提供included callback

module InstanceMethods 
    # mod is the Class or Module that included this module. 
    def included(mod) 
    DateTimeComposer::attrs.each do |attr| 
     mod.instance_eval <<-METHODS 
     def #{attr.to_s}_to 
      puts 'tes' 
     end 
     METHODS 
    end 
    end 
end 

作为一个附加的建议,你应该能够达到同样的效果通过简单定义调用compound_datetime时的方法,从而消除对全局类变量的依赖。

但是,如果你必须访问被宣布为化合物日期时间,你应该使用类的实例变量,这是唯一的每个类和层次结构不共享领域:

module ClassMethods 
    def compound_datetime(*attrs) 
    @datetime_compounds = attrs 
    attrs.each do |attr| 
     instance_eval <<-METHODS 
     def #{attr.to_s}_to 
      puts 'tes' 
     end 
     METHODS 
    end 
    end 

    def datetime_compounds; @datetime_compounds; end; 
end 

class Account < ActiveRecord::Base 
    compound_datetime :sales_at, :published_at 
end 

class AnotherModel < ActiveRecord::Base 
    compound_datetime :attr1, :attr2 
end 

Account.datetime_compounds 
=> [ :sales_at, :published_at ] 

AnotherModel.datetime_compounds 
=> [ :attr1, :attr2 ]