実行時に定義されたクラスにインスタンス変数を追加し、後でクラスの外部からその値を取得および設定するにはどうすればよいですか?
クラスを最初に定義したソース コードを変更する代わりに、実行時にクラス インスタンスを変更できるメタプログラミング ソリューションを探しています。解決策のいくつかは、クラス定義でインスタンス変数を宣言する方法を説明していますが、それは私が求めているものではありません。
実行時に定義されたクラスにインスタンス変数を追加し、後でクラスの外部からその値を取得および設定するにはどうすればよいですか?
クラスを最初に定義したソース コードを変更する代わりに、実行時にクラス インスタンスを変更できるメタプログラミング ソリューションを探しています。解決策のいくつかは、クラス定義でインスタンス変数を宣言する方法を説明していますが、それは私が求めているものではありません。
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\">
属性アクセサーを使用できます。
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>
ご覧のとおり、singleton メソッドは、attr_accessor を使用して定義されたものsetit
と同じフィールド を設定します... したがって、singleton メソッドは、この質問に対する完全に有効なアプローチです。@b
「class MyObject」の使用がオープン クラスの使用である場合は、initialize メソッドを再定義していることに注意してください。
Ruby では、オーバーロードのようなものはありません...オーバーライドまたは再定義のみです...つまり、特定のメソッドのインスタンスは 1 つしか存在できないため、再定義すると再定義されます...そして初期化しますmethod も同じです (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!
他のソリューションも完全に機能しますが、ここでは、define_method を使用した例を示します。オープン クラスを使用しないことに熱心な場合は、配列クラスの「var」変数を定義しますが、同等であることに注意してください。オープンクラスを使用するには...利点は、未知のクラスに対してそれを実行できることです(したがって、特定のクラスを開くのではなく、任意のオブジェクトのクラスです)...また、define_methodはメソッド内で機能しますが、内部でクラスを開くことはできません方法。
array = []
array.class.send(:define_method, :var) { @var }
array.class.send(:define_method, :var=) { |value| @var = value }
そして、これがその使用例です... array2、異なる配列にもメソッドがあることに注意してください。
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
Mike Stone の回答はすでにかなり包括的ですが、少し詳細を追加したいと思います。
インスタンスが作成された後でも、いつでもクラスを変更して、必要な結果を得ることができます。コンソールで試すことができます:
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
編集に応じて、読み取り専用:
編集:クラスを最初に定義したソースコードを変更する代わりに、実行時にクラスインスタンスを変更できるメタプログラミングソリューションを探していることを明確にする必要があるようです。いくつかの解決策は、クラス定義でインスタンス変数を宣言する方法を説明していますが、それは私が求めていることではありません。混乱させて申し訳ありません。
いつでもクラスを開くことができる「オープンクラス」の概念をよく理解していないと思います。例えば:
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ファイルに分散させることができます。Moduleオブジェクトの「define_method」メソッドを使用して、クラスインスタンスに新しいメソッドを定義できますが、これはオープンクラスを使用するのと同じです。
Rubyの「クラスを開く」とは、任意の時点で任意のクラスを再定義できることを意味します。つまり、新しいメソッドを追加したり、既存のメソッドを再定義したり、本当に必要なものを何でも再定義できます。「オープンクラス」ソリューションは本当にあなたが探しているもののようです...
以前の回答はすべて、コードを書いているときに微調整したいクラスの名前が何であるかを知っていることを前提としているようです。まあ、それは常に正しいとは限りません (少なくとも、私にとってはそうではありません)。いくつかの変数を付与したい (たとえば、メタデータなどを保持するために) 一連のクラスを繰り返し処理している可能性があります。その場合、このようなものが仕事をします、
# 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"
しばらく前に、このための gem を書きました。これは「Flexible」と呼ばれ、rubygems 経由では入手できませんが、昨日まで github 経由で入手可能でした。私には無用なので削除しました。
できるよ
class Foo
include Flexible
end
f = Foo.new
f.bar = 1
エラーなしでそれで。そのため、その場でオブジェクトからインスタンス変数を設定および取得できます。興味があれば... ソースコードを github に再度アップロードできます。有効にするには、いくつかの変更が必要です
f.bar?
#=> true
インスタンス変数「bar」が定義されているかどうかをオブジェクトに問い合わせる方法として、それ以外は実行中です。
よろしく、musicmatze