Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using def_delegate with a hash

I know how Forwardable#def_delegate works with methods on objects, but is there a similar way to forward methods names to hash keys. Like:

hash = { some_value: 42, other_value: 31415 }
def_delegate :hash, :some_value, :other_value

Calling object.some_value should return 42

PS: def and class eval is a way, but is there a nicer way?

like image 213
Calin Avatar asked Aug 04 '15 14:08

Calin


2 Answers

This is a good job for OpenStruct, which basically wraps a Hash in an object.

2.2.1 :001 > require 'ostruct'
 => true
2.2.1 :002 > s = OpenStruct.new(a: 1, b: 2)
 => #<OpenStruct a=1, b=2>
2.2.1 :003 > s.a
 => 1
2.2.1 :004 > s.c = 3
 => 3

If you want to be strict about the methods available, Struct lets you create little, dynamic classes.

2.2.1 :001 > hash = {a: 1, b: 2}
 => {:a=>1, :b=>2}
2.2.1 :002 > struct = Struct.new(*hash.keys)
 => #<Class:0x007fd104b32888>
2.2.1 :003 > instance = struct.new(*hash.values)
 => #<struct a=1, b=2>
2.2.1 :004 > instance.a = 3
 => 3
2.2.1 :005 > instance.c
NoMethodError: undefined method `c' for #<struct a=3, b=2>
like image 177
Kristján Avatar answered Oct 05 '22 04:10

Kristján


Not directly, no. One option is to use OpenStruct from Ruby's standard library.

require "ostruct"

class Foo
  extend Forwardable
  delegate :@data, :some_value, :other_value

  def initialize(hash)
    @data = OpenStruct.new(hash)
  end
end

hash = { some_value: 42, other_value: 31415 }
foo = Foo.new(hash)
foo.some_value # => 42

A simpler option is to just delegate the :[] method, but it's not as pretty:

class Foo
  extend Forwardable
  delegate :@data, :[]

  def initialize(hash)
    @data = hash
  end
end

hash = { some_value: 42, other_value: 31415 }
foo = Foo.new(hash)
foo[:some_value] # => 42

Barring that, there's always define_method:

[ :some_value, :other_value ].each do |meth|
  define_method(meth) { @data[meth] }
end

Or method_missing:

def method_missing(meth, *args, &block)
  return @data[meth] if @data.key?(meth)
  super
end

def respond_to_missing?(meth, *args)
  @data.key?(meth) || super
end
like image 36
Jordan Running Avatar answered Oct 05 '22 05:10

Jordan Running