Search code examples
rubymetaprogrammingopenstruct

catch-all getter method on openstruct?


If I have an OpenStruct:

require 'ostruct'
open_struct = OpenStruct.new

I can overwrite [] which works in some cases

open_struct.define_singleton_method(:[]) do |*args|
  puts args.map(&:class)
  puts args
end

open_struct.a = 1
open_struct[:a]
# => Symbol
#    a

But this [] method is not called when using the dot-method syntax:

open_struct.a
# => 1

I am trying to make a class which inherits from OpenStruct and works more like a Javascript object (basically I'm trying to remove the necessity to run call on a proc that's stored as a value)


Solution

  • First of all - OpenStruct already functions very much like JavaScript (given that #[] is a synonym of #call):

    JS:

    foo = {}
    foo.bar = function() { console.log("Hello, world!"); };
    foo.bar();
    // => Hello, world!
    

    Ruby:

    foo = OpenStruct.new
    foo.bar = proc { puts "Hello, world!" }
    foo.bar[]
    # => Hello, world!
    

    If you mean function more like Ruby... you can override new_ostruct_member:

    require 'ostruct'
    
    class AutoCallableOpenStruct < OpenStruct
      protected def new_ostruct_member(name)
        name = name.to_sym
        unless respond_to?(name)
          define_singleton_method(name) {
            val = @table[name]
            if Proc === val && val.arity == 0
              val.call
            else
              val
            end
          }
          define_singleton_method("#{name}=") { |x| modifiable[name] = x }
        end
        name
      end
    end
    
    a = AutoCallableOpenStruct.new
    a.name = "max"
    a.helloworld = proc { puts "Hello, world!" }
    a.hello = proc { |name| puts "Hello, #{name}!" }
    
    a.name              # non-Proc, retrieve
    # => max
    a.helloworld        # nullary proc, autocall
    # => Hello, world!
    a.hello[a.name]     # non-nullary Proc, retrieve (#[] invokes)
    # => Hello, max!
    

    Just be aware that OpenStruct in Ruby slows down your program, and shouldn't be used if you can avoid it.