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のインスタンスメソッドを定義し、メソッド名は次のようなキーです。

 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 に答える