Reputation: 6847
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?
Upvotes: 7
Views: 3814
Reputation: 106027
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
Upvotes: 8
Reputation: 18803
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>
Upvotes: 8
Reputation: 15171
The best way to implement this probably won't involve Forwardable
, and will depend on your specific use case. Here is an example of one way you could do this without eval:
class C
class << self
attr_accessor :hash
def def_hash_delegate(key)
define_method(key) do
C.hash[key]
end
end
end
@hash = { some_value: 42, other_value: 31415 }
def_hash_delegate :some_value
def_hash_delegate :other_value
def_hash_delegate :value_3
end
c = C.new
puts c.some_value
puts c.other_value
C.hash[:value_3] = 3948
puts c.value_3
Upvotes: 3