Class_eval не работает внутри каждого блока - PullRequest
0 голосов
/ 14 января 2012

Я определил модуль для расширения ActiveRecord.

В моем случае мне нужно генерировать методы экземпляра с символами, указанными в качестве аргументов для метода класса compound_datetime.Он работает, когда class_eval вызывается вне блока each, но не внутри него;в последнем случае я получаю неопределенную ошибку метода.

Кто-нибудь знает, что я делаю неправильно?

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>.

1 Ответ

3 голосов
/ 14 января 2012

Вы звоните define_compounds(DateTimeComposer::attrs) в конце определения модуля InstanceMethods.На этом этапе в коде attrs все еще является пустым массивом, а self является модулем InstanceMethods.

Это означает, что никакие методы не будут определены, и даже если бы они были, они были быпривязан к метаклассу InstanceMethods, что делает их методами класса этого модуля, а не методами экземпляра вашего Account класса.

Это происходит потому, что вызовы методов внутри модуля InstanceMethodsОпределение оценивается так, как его видит интерпретатор ruby, , а не , когда вы звоните include ActiveRecordExtensions::InstanceMethods.Следствием этого является то, что можно запускать произвольный код в самых необычных местах, например, в пределах определения класса .

. Для решения проблемы вы можете использовать included обратный вызов , предоставляемый ruby, который вызывается всякий раз, когда модуль включен в другой:

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, устраняя тем самым зависимость от глобальной переменной класса attrs.

Однако, если вы должны иметь доступ к полям, которые были объявлены как составные 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 ]
...