Как использовать переменные класса из методов класса и экземпляра, которые смешиваются через модуль - PullRequest
1 голос
/ 02 июня 2011

Я хочу иметь возможность сделать параметр, передаваемый моему методу класса (проверяемым), доступным для методов экземпляра. Я смешиваю методы класса и экземпляра, используя модуль.

Очевидный выбор - использовать переменную класса, но я получаю ошибку при попытке доступа к ней:

неинициализированная переменная класса @@ auditable_only_once в Auditable

class Document
  include Auditable
  auditable :only_once => true
end

# The mixin
module Auditable
  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def auditable(options = {})

      options[:only_once] ||= false

      class_eval do
        # SET THE OPTION HERE!!
        @@auditable_only_once = options[:only_once]
      end
      end
    end

    private

    def audit(action)
      # AND READ IT BACK LATER HERE
      return if @@auditable_only_once && self.audit_item
      AuditItem.create(:auditable => self, :tag => "#{self.class.to_s}_#{action}".downcase, :user => self.student)
    end    
  end

Я сократил часть кода, чтобы его было легче читать, полный код здесь: https://gist.github.com/1004399 (РЕДАКТИРОВАТЬ: Gist теперь включает решение)

1 Ответ

0 голосов
/ 02 июня 2011

Использование @@ переменных экземпляра класса нерегулярно, а число случаев, когда они строго необходимы, чрезвычайно редко. Большую часть времени они просто вызывают проблемы или путаницу. Как правило, вы можете использовать обычные переменные экземпляра в контексте класса без проблем.

Что вы можете сделать, это использовать другой шаблон для такого рода вещей. Если у вас есть mattr_accessor, предоставляемый ActiveSupport, вы можете использовать его вместо этой переменной, или вы всегда можете написать свой собственный эквивалент в компоненте ClassMethods.

Один из подходов, которые я использовал, состоит в том, чтобы разбить ваше расширение на два модуля: хук и реализацию. Хук добавляет только методы к базовому классу, которые могут использоваться для добавления остальных методов, если это необходимо, но в противном случае не загрязняет пространство имен:

module ClassExtender
  def self.included(base)
    base.send(:extend, self)
  end

  def engage(options = { })
    extend ClassExtenderMethods::ClassMethods
    include ClassExtenderMethods::InstanceMethods

    self.class_extender_options.merge!(options)
  end
end

Этот метод engage можно вызывать как угодно, как в вашем примере это auditable.

Затем вы создаете контейнерный модуль для методов класса и экземпляра, которые расширение добавляет, когда оно выполняется:

module ClassExtenderMethods
  module ClassMethods
    def class_extender_options
      @class_extender_options ||= {
        :default_false => false
      }
    end
  end

  module InstanceMethods
    def instance_method_example
      :example
    end
  end
end

В этом случае существует простой метод class_extender_options, который можно использовать для запроса или изменения параметров для определенного класса. Это избавляет от необходимости использовать переменную экземпляра напрямую. Также добавлен пример метода экземпляра.

Вы можете определить простой пример:

class Foo
  include ClassExtender

  engage(:true => true)
end

Затем проверьте, что он работает правильно:

Foo.class_extender_options
# => {:default_false=>false, :true=>true}

foo = Foo.new
foo.instance_method_example
# => :example
...