1

我定义了以下内容:

#!/usr/bin/env ruby

class Something
  def self._attr_accessor key, value, type
    (class << self; self; end).send( :attr_accessor, key.to_sym)
    instance_variable_set "@#{key}", value
  end
end

class Client < Something
  _attr_accessor 'foo_bar', 'json', String
end

my_something = Client.new
puts my_something.foo_bar

但我收到以下错误:

/test_inheritance.rb:18:in `<class:Client>': undefined method `foo_bar' for Client:Class (NoMethodError)
    from ./test_inheritance.rb:14:in `<main>'

我正在做的一些元编程工作:

#!/usr/bin/env ruby
class Something
  def self._attr_accessor key, value, type
    (class << self; self; end).send( :attr_accessor, key.to_sym)
    instance_variable_set "@#{key}", value
  end
end

class Client < Something
  _attr_accessor 'foo_bar', 'json', String

  puts self.foo_bar
end

my_something = Client.new

#puts my_something.foo_bar

因为它输出正确的结果。但是如何定义 _attr_accessor 方法以便我能够公开访问它?

4

2 回答 2

1

尝试将您的方法替换为:

class Something

  def self._attr_accessor key, value, type
    method_sym      = key.to_sym
    insance_variable = "@#{key}"

    (class << self; self; end).send( :attr_accessor, method_sym)
    instance_variable_set insance_variable, value

    attr_accessor method_sym

    define_method(method_sym) do 
        self.instance_variable_get(insance_variable) or self.class.send(method_sym) 
    end

  end

 end

 define_method(method_sym) do 
    self.instance_variable_get(insance_variable) or self.class.send(method_sym) 
 end

上面代码中,define_method是为Someting定义一个实例方法,方法名是key,如

 attr_accessor "foo_bar", "json", String

那么define_method生成的代码是:

 def foo_bar
    if @foo_bar
       @foo_bar
    else
      self.class.foo_bar
    end        
 end

另外,ActiveSupport 有 attr_accessor_with_default方法,好像也是这个功能。请参考它的代码:

class Module
# Declare an attribute accessor with an initial default return value.t>:
#
#   class Person
#     attr_accessor_with_default :age, 25
#   end
#
#   person = Person.new
#   person.age # => 25
#
# To give attribute <tt>:element_name</tt> a dynamic default value, evaluated
# in scope of self:
#
#   attr_accessor_with_default(:element_name) { name.underscore }
#
def attr_accessor_with_default(sym, default = Proc.new)
  define_method(sym, block_given? ? default : Proc.new { default })
  module_eval(<<-EVAL, __FILE__, __LINE__ + 1)
    def #{sym}=(value)
      class << self; attr_accessor :#{sym} end
      @#{sym} = value
    end
  EVAL
end
end
于 2012-04-11T03:44:52.993 回答
1

format对于一个人,我认为您正在绊倒一个保留方法的事实,Class并且它与您的attr_accessor尝试相冲突。

其次,有更好的方法来做到这一点。我为我正在处理的项目制作了一个相当强大的“访问器”实用程序类。它允许您定义类级别的默认值并仍然覆盖实例定义。

实现如下所示:

module OptionAccessor
  # Given a list of names, this declares an option accessor which works like
  # a combination of cattr_accessor and attr_accessor, except that defaults
  # defined for a class will propagate down to the instances and subclasses,
  # but these defaults can be over-ridden in subclasses and instances
  # without interference. Optional hash at end of list can be used to set:
  #  * :default => Assigns a default value which is otherwise nil
  #  * :boolean => If true, creates an additional name? method and will
  #                convert all assigned values to a boolean true/false.
  def option_accessor(*args)
    option_reader(*args)
    option_writer(*args)
  end

  # Given a list of names, this declares an option reader which works like
  # a combination of cattr_reader and attr_reader, except that defaults
  # defined for a class will propagate down to the instances and subclasses,
  # but these defaults can be over-ridden in subclasses and instances
  # without interference. Optional hash at end of list can be used to set:
  #  * :default => Assigns a default value which is otherwise nil
  #  * :boolean => If true, creates an additional name? method and will
  #                convert all assigned values to a boolean true/false.
  def option_reader(*names)
    names = [ names ].flatten.compact
    options = names.last.is_a?(Hash) ? names.pop : { }

    names.each do |name|
      iv = :"@#{name}"

      (class << self; self; end).class_eval do
        if (options[:boolean])
          define_method(:"#{name}?") do
            iv_value = instance_variable_get(iv)

            !!(iv_value.nil? ? (self.superclass.respond_to?(name) ? self.superclass.send(name) : nil) : iv_value)
          end
        end

        define_method(name) do
          iv_value = instance_variable_get(iv)

          iv_value.nil? ? (self.superclass.respond_to?(name) ? self.superclass.send(name) : nil) : iv_value
        end
      end

      define_method(name) do
        iv_value = instance_variable_get(iv)

        iv_value.nil? ? self.class.send(name) : iv_value
      end

      if (options[:boolean])
        define_method(:"#{name}?") do
          iv_value = instance_variable_get(iv)

          !!(iv_value.nil? ? self.class.send(name) : iv_value)
        end
      end

      instance_variable_set(iv, options[:default])
    end
  end

  # Given a list of names, this declares an option writer which works like
  # a combination of cattr_writer and attr_writer, except that defaults
  # defined for a class will propagate down to the instances and subclasses,
  # but these defaults can be over-ridden in subclasses and instances
  # without interference. Options can be specified:
  #  * :boolean => If true, converts all supplied values to true or false
  #                unless nil, in which case nil is preserved.
  def option_writer(*names)
    names = [ names ].flatten.compact
    options = names.last.is_a?(Hash) ? names.pop : { }

    names.each do |name|
      iv = :"@#{name}"

      (class << self; self; end).class_eval do
        if (options[:boolean])
          define_method(:"#{name}=") do |value|
            instance_variable_set(iv, value.nil? ? nil : !!value)
          end
        else
          define_method(:"#{name}=") do |value|
            instance_variable_set(iv, value)
          end
        end
      end

      if (options[:boolean])
        define_method(:"#{name}=") do |value|
          instance_variable_set(iv, value.nil? ? nil : !!value)
        end
      else
        define_method(:"#{name}=") do |value|
          instance_variable_set(iv, value)
        end
      end
    end
  end
end
于 2012-04-11T03:45:20.660 回答