2012-04-11 92 views
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>' 

metaprograming的一点我做的工作:

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

回答

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 
+0

这就是我正在寻找的,你有任何示例用例? – rudolph9 2012-04-11 04:03:26

+0

另外,将metod从'format'改为'foobar'当我在类外部调用时遇到和未声明的问题,并且类似于我最初描述的那样,它在类中调用时指示私有方法声明时工作正常。如果你能向我解释为什么该方法被宣布为私人,我将非常感激! – rudolph9 2012-04-11 04:08:08

+1

一个例子就是将'option_accessor:foo'放在一个类中。然后你可以调用MyClass.foo或MyClass.new.foo等,即使通过MyClass.foo ='foo''分配。 – tadman 2012-04-11 15:59:49

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是定义成才一个实例方法,方法名是关键,如

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 
+0

这摆脱了我的错误,但我仍无法访问,就好像使用attr_accessor定义的方法。我的整个目标是引导先前存在的方法,我可以为其定义默认方法。 – rudolph9 2012-04-11 16:25:16

+0

另外我真的不知道'defin_method'块中发生了什么,你能向我解释吗? – rudolph9 2012-04-11 16:26:28