web-dev-qa-db-ja.com

Rubyのクラスにインスタンス変数を追加する

定義されたクラスにインスタンス変数を追加するにはどうすればよいですか? ランタイム、後でクラスの外部からその値を取得および設定しますか?

最初にクラスを定義したソースコードを変更する代わりに、実行時にクラスインスタンスを変更できるメタプログラミングソリューションを探しています。いくつかの解決策は、クラス定義でインスタンス変数を宣言する方法を説明していますが、それは私が求めていることではありません。

37
Readonly

属性アクセサーを使用できます。

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を設定します。したがって、シングルトンメソッドは、この質問に対して完全に有効なアプローチです。

15
Mike Stone

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\">
65
Gordon Wilson

@ 読み取り専用

「クラス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!
15
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
2
webmat

他のソリューションも完全に機能しますが、ここでは、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
2
Mike Stone

しばらく前にこのための宝石を書きました。これは「フレキシブル」と呼ばれ、rubygemsでは利用できませんが、昨日までgithubで利用できました。役に立たなかったので削除しました。

できるよ

class Foo
    include Flexible
end
f = Foo.new
f.bar = 1

エラーなしでそれと。したがって、その場でオブジェクトからインスタンス変数を設定および取得できます。あなたが興味を持っているなら...私はソースコードを再びgithubにアップロードすることができます。有効にするためにいくつかの変更が必要です

f.bar?
#=> true

インスタンス変数「bar」が定義されているかどうかをオブジェクトに問い合わせるメソッドとして。

敬具、musicmatze

0
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"
0
Huliax

読み取り専用、編集への応答:

編集:クラスを最初に定義したソースコードを変更するのではなく、実行時にクラスインスタンスを変更できるメタプログラミングソリューションを探していることを明確にする必要があるようです。いくつかの解決策は、クラス定義でインスタンス変数を宣言する方法を説明していますが、それは私が求めていることではありません。混乱させて申し訳ありません。

「オープンクラス」の概念はよく理解していないと思います。つまり、いつでもクラスを開くことができます。例えば:

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の「オープンクラス」は、任意の時点で任意のクラスを再定義できることを意味します...これは、新しいメソッドの追加、既存のメソッドの再定義、または実際に必要なものを意味します。「オープンクラスのソリューションは本当にあなたが探しているものです...

0
Mike Stone