定義されたクラスにインスタンス変数を追加するにはどうすればよいですか? ランタイム、後でクラスの外部からその値を取得および設定しますか?
最初にクラスを定義したソースコードを変更する代わりに、実行時にクラスインスタンスを変更できるメタプログラミングソリューションを探しています。いくつかの解決策は、クラス定義でインスタンス変数を宣言する方法を説明していますが、それは私が求めていることではありません。
属性アクセサーを使用できます。
class Array
attr_accessor :var
end
これで、次の方法でアクセスできます。
array = []
array.var = 123
puts array.var
attr_reader
またはattr_writer
を使用してゲッターまたはセッターのみを定義することも、手動で定義することもできます。
class Array
attr_reader :getter_only_method
attr_writer :setter_only_method
# Manual definitions equivalent to using attr_reader/writer/accessor
def var
@var
end
def var=(value)
@var = value
end
end
単一のインスタンスで定義するだけの場合は、シングルトンメソッドを使用することもできます。
array = []
def array.var
@var
end
def array.var=(value)
@var = value
end
array.var = 123
puts array.var
参考までに、この回答に関するコメントへの回答として、シングルトンメソッドは適切に機能し、以下が証明されます。
irb(main):001:0> class A
irb(main):002:1> attr_accessor :b
irb(main):003:1> end
=> nil
irb(main):004:0> a = A.new
=> #<A:0x7fbb4b0efe58>
irb(main):005:0> a.b = 1
=> 1
irb(main):006:0> a.b
=> 1
irb(main):007:0> def a.setit=(value)
irb(main):008:1> @b = value
irb(main):009:1> end
=> nil
irb(main):010:0> a.setit = 2
=> 2
irb(main):011:0> a.b
=> 2
irb(main):012:0>
ご覧のとおり、シングルトンメソッドsetit
は、attr_accessorを使用して定義したフィールドと同じフィールド@b
を設定します。したがって、シングルトンメソッドは、この質問に対して完全に有効なアプローチです。
Rubyはこのためのメソッドinstance_variable_get
およびinstance_variable_set
。 ( ドキュメント )
次のように新しいインスタンス変数を作成して割り当てることができます。
>> foo = Object.new
=> #<Object:0x2aaaaaacc400>
>> foo.instance_variable_set(:@bar, "baz")
=> "baz"
>> foo.inspect
=> #<Object:0x2aaaaaacc400 @bar=\"baz\">
@ 読み取り専用
「クラスMyObject」の使用がオープンクラスの使用である場合は、initializeメソッドを再定義していることに注意してください。
Rubyでは、オーバーロードなどのことはありません...オーバーライドまたは再定義のみ...つまり、特定のメソッドのインスタンスは1つしか存在できないため、再定義すると、再定義されます...そして初期化メソッドは同じです(たとえClassオブジェクトの新しいメソッドが使用するものでも)。
したがって、少なくとも元の定義にアクセスしたい場合は、最初にエイリアスを設定せずに既存のメソッドを再定義しないでください。また、不明なクラスの初期化メソッドを再定義することは非常に危険な場合があります。
とにかく、実際のメタクラスを使用してシングルトンメソッドを定義する、もっと簡単な解決策があると思います。
m = MyObject.new
metaclass = class << m; self; end
metaclass.send :attr_accessor, :first, :second
m.first = "first"
m.second = "second"
puts m.first, m.second
メタクラスとオープンクラスの両方を使用して、さらに複雑にして次のようなことを行うことができます。
class MyObject
def metaclass
class << self
self
end
end
def define_attributes(hash)
hash.each_pair { |key, value|
metaclass.send :attr_accessor, key
send "#{key}=".to_sym, value
}
end
end
m = MyObject.new
m.define_attributes({ :first => "first", :second => "second" })
上記は基本的に「メタクラス」メソッドを介してメタクラスを公開し、次にそれをdefine_attributesで使用してattr_accessorで一連の属性を動的に定義し、その後ハッシュの関連する値で属性セッターを呼び出します。
Rubyを使用すると、クリエイティブになり、同じことをさまざまな方法で行うことができます;-)
参考までに、ご存じない場合は、私が行ったようにメタクラスを使用するということは、オブジェクトの特定のインスタンスのみを操作するということです。したがって、define_attributesを呼び出すと、その特定のインスタンスの属性のみが定義されます。
例:
m1 = MyObject.new
m2 = MyObject.new
m1.define_attributes({:a => 123, :b => 321})
m2.define_attributes({:c => "abc", :d => "zxy"})
puts m1.a, m1.b, m2.c, m2.d # this will work
m1.c = 5 # this will fail because c= is not defined on m1!
m2.a = 5 # this will fail because a= is not defined on m2!
マイク・ストーンの答え はすでにかなり包括的ですが、少し詳細を追加したいと思います。
インスタンスが作成された後でも、いつでもクラスを変更して、希望する結果を得ることができます。あなたはあなたのコンソールでそれを試すことができます:
s1 = 'string 1'
s2 = 'string 2'
class String
attr_accessor :my_var
end
s1.my_var = 'comment #1'
s2.my_var = 'comment 2'
puts s1.my_var, s2.my_var
他のソリューションも完全に機能しますが、ここでは、define_methodを使用した例を示します。オープンクラスを使用しない場合は、配列クラスの「var」変数を定義しますが、同等であることに注意してください。オープンクラスを使用する...利点は、不明なクラスに対して実行できることです(したがって、特定のクラスを開くのではなく、任意のオブジェクトのクラス)...また、define_methodはメソッド内で機能しますが、方法。
array = []
array.class.send(:define_method, :var) { @var }
array.class.send(:define_method, :var=) { |value| @var = value }
そして、これがその使用例です... array2、a [〜#〜] different [〜#〜]配列にもメソッドがあるので、これが必要なものでない場合は、おそらく、別の投稿で説明したシングルトンメソッドが必要です。
irb(main):001:0> array = []
=> []
irb(main):002:0> array.class.send(:define_method, :var) { @var }
=> #<Proc:0x00007f289ccb62b0@(irb):2>
irb(main):003:0> array.class.send(:define_method, :var=) { |value| @var = value }
=> #<Proc:0x00007f289cc9fa88@(irb):3>
irb(main):004:0> array.var = 123
=> 123
irb(main):005:0> array.var
=> 123
irb(main):006:0> array2 = []
=> []
irb(main):007:0> array2.var = 321
=> 321
irb(main):008:0> array2.var
=> 321
irb(main):009:0> array.var
=> 123
しばらく前にこのための宝石を書きました。これは「フレキシブル」と呼ばれ、rubygemsでは利用できませんが、昨日までgithubで利用できました。役に立たなかったので削除しました。
できるよ
class Foo
include Flexible
end
f = Foo.new
f.bar = 1
エラーなしでそれと。したがって、その場でオブジェクトからインスタンス変数を設定および取得できます。あなたが興味を持っているなら...私はソースコードを再びgithubにアップロードすることができます。有効にするためにいくつかの変更が必要です
f.bar?
#=> true
インスタンス変数「bar」が定義されているかどうかをオブジェクトに問い合わせるメソッドとして。
敬具、musicmatze
これまでのすべての回答は、コードを記述しているときにTweakするクラスの名前がわかっていることを前提としているようです。まあ、それはいつも正しいとは限りません(少なくとも、私にとっては)。 (たとえば、メタデータなどを保持するために)いくつかの変数を与えたいクラスの山を繰り返し処理している可能性があります。その場合、このようなことがうまくいきます。
# example classes that we want to Tweak
class Foo;end
class Bar;end
klasses = [Foo, Bar]
# iterating over a collection of klasses
klasses.each do |klass|
# #class_eval gets it done
klass.class_eval do
attr_accessor :baz
end
end
# it works
f = Foo.new
f.baz # => nil
f.baz = 'it works' # => "it works"
b = Bar.new
b.baz # => nil
b.baz = 'it still works' # => "it still works"
読み取り専用、編集への応答:
編集:クラスを最初に定義したソースコードを変更するのではなく、実行時にクラスインスタンスを変更できるメタプログラミングソリューションを探していることを明確にする必要があるようです。いくつかの解決策は、クラス定義でインスタンス変数を宣言する方法を説明していますが、それは私が求めていることではありません。混乱させて申し訳ありません。
「オープンクラス」の概念はよく理解していないと思います。つまり、いつでもクラスを開くことができます。例えば:
class A
def hello
print "hello "
end
end
class A
def world
puts "world!"
end
end
a = A.new
a.hello
a.world
上記は完全に有効なRubyコードであり、2つのクラス定義は複数のRubyファイルに分散できます。モジュールで「define_method」メソッドを使用できます。クラスインスタンスで新しいメソッドを定義するオブジェクトですが、オープンクラスを使用することと同じです。
Rubyの「オープンクラス」は、任意の時点で任意のクラスを再定義できることを意味します...これは、新しいメソッドの追加、既存のメソッドの再定義、または実際に必要なものを意味します。「オープンクラスのソリューションは本当にあなたが探しているものです...