2012-01-14 61 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 ]