Adding an instance variable to a class in Ruby

前端 未结 8 578
太阳男子
太阳男子 2021-01-31 02:39

How can I add an instance variable to a defined class at runtime, and later get and set its value from outside of the class?

I\'m looking for a metaprogramming so

相关标签:
8条回答
  • 2021-01-31 03:21

    Mike Stone's answer is already quite comprehensive, but I'd like to add a little detail.

    You can modify your class at any moment, even after some instance have been created, and get the results you desire. You can try it out in your console:

    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
    
    0 讨论(0)
  • 2021-01-31 03:23

    You can use attribute accessors:

    class Array
      attr_accessor :var
    end
    

    Now you can access it via:

    array = []
    array.var = 123
    puts array.var
    

    Note that you can also use attr_reader or attr_writer to define just getters or setters or you can define them manually as such:

    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
    

    You can also use singleton methods if you just want it defined on a single instance:

    array = []
    
    def array.var
      @var
    end
    
    def array.var=(value)
      @var = value
    end
    
    array.var = 123
    puts array.var
    

    FYI, in response to the comment on this answer, the singleton method works fine, and the following is proof:

    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> 
    

    As you can see, the singleton method setit will set the same field, @b, as the one defined using the attr_accessor... so a singleton method is a perfectly valid approach to this question.

    0 讨论(0)
  • 2021-01-31 03:35

    It looks like all of the previous answers assume that you know what the name of the class that you want to tweak is when you are writing your code. Well, that isn't always true (at least, not for me). I might be iterating over a pile of classes that I want to bestow some variable on (say, to hold some metadata or something). In that case something like this will do the job,

    # 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 讨论(0)
  • 2021-01-31 03:37

    @Readonly

    If your usage of "class MyObject" is a usage of an open class, then please note you are redefining the initialize method.

    In Ruby, there is no such thing as overloading... only overriding, or redefinition... in other words there can only be 1 instance of any given method, so if you redefine it, it is redefined... and the initialize method is no different (even though it is what the new method of Class objects use).

    Thus, never redefine an existing method without aliasing it first... at least if you want access to the original definition. And redefining the initialize method of an unknown class may be quite risky.

    At any rate, I think I have a much simpler solution for you, which uses the actual metaclass to define singleton methods:

    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
    

    You can use both the metaclass and open classes to get even trickier and do something like:

    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" })
    

    The above is basically exposing the metaclass via the "metaclass" method, then using it in define_attributes to dynamically define a bunch of attributes with attr_accessor, and then invoking the attribute setter afterwards with the associated value in the hash.

    With Ruby you can get creative and do the same thing many different ways ;-)


    FYI, in case you didn't know, using the metaclass as I have done means you are only acting on the given instance of the object. Thus, invoking define_attributes will only define those attributes for that particular instance.

    Example:

    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!
    
    0 讨论(0)
  • 2021-01-31 03:38

    I wrote a gem for this some time ago. It's called "Flexible" and not available via rubygems, but was available via github until yesterday. I deleted it because it was useless for me.

    You can do

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

    with it without getting any error. So you can set and get instance variables from an object on the fly. If you are interessted... I could upload the source code to github again. It needs some modification to enable

    f.bar?
    #=> true
    

    as method for asking the object if a instance variable "bar" is defined or not, but anything else is running.

    Kind regards, musicmatze

    0 讨论(0)
  • 2021-01-31 03:39

    Readonly, in response to your edit:

    Edit: It looks like I need to clarify that I'm looking for a metaprogramming solution that allows me to modify the class instance at runtime instead of modifying the source code that originally defined the class. A few of the solutions explain how to declare instance variables in the class definitions, but that is not what I am asking about. Sorry for the confusion.

    I think you don't quite understand the concept of "open classes", which means you can open up a class at any time. For example:

    class A
      def hello
        print "hello "
      end
    end
    
    class A
      def world
        puts "world!"
      end
    end
    
    a = A.new
    a.hello
    a.world
    

    The above is perfectly valid Ruby code, and the 2 class definitions can be spread across multiple Ruby files. You could use the "define_method" method in the Module object to define a new method on a class instance, but it is equivalent to using open classes.

    "Open classes" in Ruby means you can redefine ANY class at ANY point in time... which means add new methods, redefine existing methods, or whatever you want really. It sounds like the "open class" solution really is what you are looking for...

    0 讨论(0)
提交回复
热议问题