Search code examples
rubyinstantiationopenstruct

How to modify OpenStruct in the instantiated class


I have the following code where I instantiate an object that returns an OpenStruct result.

require 'ostruct'

class TestModel
  attr_accessor :result

  def initializer
  end

  def result
    OpenStruct.new(successful?: false)
  end

  def successful!
    result.send('successful?', true)
  end
end

I want the class to work so that I could modify any attributes of my result on the fly.

test = TestModel.new

test.result
=> #<OpenStruct successful?=false>

test.result.successful!
=> #<OpenStruct successful?=true>

This syntax comes from the official OpenStruct page, and it works just on its own but not inside an instantiated class - https://ruby-doc.org/stdlib-2.5.3/libdoc/ostruct/rdoc/OpenStruct.html

result.send('successful?', true)

I've also tried to use lambda but to no avail

  def result
    OpenStruct.new(successful?: false, :successful! => lamdba {self.uccessful? = true})
  end

Any ideas? I really want to know how to do that.


Solution

  • OpenStruct requires you to use Object#send or Hash-like keys to make use of predicate symbols. The documentation says:

    Hash keys with spaces or characters that could normally not be used for method calls (e.g. ()[]*) will not be immediately available on the OpenStruct object as a method for retrieval or assignment, but can still be reached through the Object#send method or using [].

    In addition, it's unclear why you want to define @result as writable, or why you would override the getter method so that TestModel#result always returns false. That's probably causing at least part of your problem.

    Instead, I'd rewrite the code as follows:

    require 'ostruct'
    
    class TestModel 
      attr_reader :result
    
      def initialize
        @result = OpenStruct.new :successful? => nil
      end
    
      def unsuccessful
        @result.send "successful?=", false
      end
        
      def successful!
        @result.send "successful?=", true
      end
    end
    
    test_model = TestModel.new
    #=> #<TestModel:0x00007faf5c1b9528 @result=#<OpenStruct successful?=nil>>
    
    test_model.result
    #=> nil
    
    test_model.successful!
    #=> true
    
    test_model.result
    #=> #<OpenStruct successful?=true>
    
    test_model.unsuccessful
    #=> false
    
    test_model.result
    #=> #<OpenStruct successful?=false>
    

    You could certainly initialize the struct member as false rather than nil if you prefer, but I think this is semantically clearer. Your mileage in that regard may vary.