私有方法,继承和元编程?

时间:2012-04-11 03:22:37

标签: ruby metaprogramming

我有以下定义:

#!/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方法,以便我能够公开访问它?

2 个答案:

答案 0 :(得分: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定义一个实例方法,方法名是关键的,比如

 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

答案 1 :(得分:1)

对于其中一个,我认为你正在绊倒formatClass上的保留方法并且与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