我有以下定义:
#!/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方法,以便我能够公开访问它?
答案 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)
对于其中一个,我认为你正在绊倒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