Initialize Struct from Hash

I just want to check I've not missed something here. Is there a built-in
way to initialize a Struct from a hash of key/value pairs?

That is, can I shorten the following?

K1 = Struct.new :foo, :bar

module FixStruct
  def set(h)
    h.each { |k,v| self[k] = v }
    self
  end
end

class K1
  include FixStruct
end

k1 = K1.new.set(:bar=>456, :foo=>123)
p k1

(I'm talking about real Struct here, not OpenStruct etc)

Thanks,

Brian.

···

--
Posted via http://www.ruby-forum.com/.

ruby-1.8.7-p334 :001 > K = Struct.new :id,:timestamp
=> K
ruby-1.8.7-p334 :002 > K[3,8]
=> #<struct K id=3, timestamp=8>

Jesus.

···

On Thu, Apr 28, 2011 at 7:00 PM, Brian Candler <b.candler@pobox.com> wrote:

I just want to check I've not missed something here. Is there a built-in
way to initialize a Struct from a hash of key/value pairs?

That is, can I shorten the following?

K1 = Struct.new :foo, :bar

module FixStruct
def set(h)
h.each { |k,v| self[k] = v }
self
end
end

class K1
include FixStruct
end

k1 = K1.new.set(:bar=>456, :foo=>123)
p k1

(I'm talking about real Struct here, not OpenStruct etc)

Thanks for all the feedback, at least I know I hadn't missed something
obvious :slight_smile:

I found an interesting thread here too:
http://www.ruby-forum.com/topic/67759

Cheers,

Brian.

···

--
Posted via http://www.ruby-forum.com/.

Sorry, I just realized this is not what you want.
Although if you know the order of the attributes defined by the
Struct, you can build something upon this, transforming the hash into
an array in the appropriate order. Whether that's cleaner than your
solution is not so clear.

Jesus.

···

2011/4/28 Jesús Gabriel y Galán <jgabrielygalan@gmail.com>:

On Thu, Apr 28, 2011 at 7:00 PM, Brian Candler <b.candler@pobox.com> wrote:

I just want to check I've not missed something here. Is there a built-in
way to initialize a Struct from a hash of key/value pairs?

That is, can I shorten the following?

K1 = Struct.new :foo, :bar

module FixStruct
def set(h)
h.each { |k,v| self[k] = v }
self
end
end

class K1
include FixStruct
end

k1 = K1.new.set(:bar=>456, :foo=>123)
p k1

(I'm talking about real Struct here, not OpenStruct etc)

ruby-1.8.7-p334 :001 > K = Struct.new :id,:timestamp
=> K
ruby-1.8.7-p334 :002 > K[3,8]
=> #<struct K id=3, timestamp=8>

"Jesús Gabriel y Galán" <jgabrielygalan@gmail.com> wrote in post
#995567:

self
(I'm talking about real Struct here, not OpenStruct etc)

ruby-1.8.7-p334 :001 > K = Struct.new :id,:timestamp
=> K
ruby-1.8.7-p334 :002 > K[3,8]
=> #<struct K id=3, timestamp=8>

Jesus.

Is there a hash anywhere in your code? How about this:

K1 = Struct.new :foo, :bar

class MyClass < K1
  def initialize(hash)
    super(*hash.values_at(:foo, :bar) )
  end
end

puts MyClass.new(bar: 456, foo: 123)

(stolen from:
DRY Ruby Initialization with Hash Argument - Stack Overflow)

···

On Thu, Apr 28, 2011 at 7:00 PM, Brian Candler <b.candler@pobox.com> > wrote:

--
Posted via http://www.ruby-forum.com/\.

Tangentially, I wonder if anything like the following (very rough proof of concept) has been used instead of Struct.

class Hash
   def structify!
     keys.each do |key|
       class << self; self; end.class_eval do
         define_method key do
           fetch key
         end
         define_method "#{key}=" do |val|
           store key, val
         end
       end
     end
   end
end

h = {:foo => 1, :bar => 2}
h.structify!
p h.foo # 1
h.foo = 3
p h.foo # 3
p h # {:foo=>3, :bar=>2}
p h.oof # undefined

···

On 04/28/2011 01:24 PM, Brian Candler wrote:

Thanks for all the feedback, at least I know I hadn't missed something
obvious :slight_smile:

I found an interesting thread here too:
An alternative to the class Foo < Struct.new(vars) idiom and - Ruby - Ruby-Forum

Yep, I know. How about this?

ruby-1.8.7-p334 :014 > h = {:id => 3, :timestamp => 6}
=> {:timestamp=>6, :id=>3}
ruby-1.8.7-p334 :043 > K = Struct.new :id, :timestamp do
ruby-1.8.7-p334 :044 > def self.from_hash h
ruby-1.8.7-p334 :045?> self[*h.values_at(*K.members.map {|m| m.to_sym})]
ruby-1.8.7-p334 :046?> end
ruby-1.8.7-p334 :047?> end
=> K
ruby-1.8.7-p334 :048 > K.from_hash h
=> #<struct K id=3, timestamp=6>

Jesus.

···

On Thu, Apr 28, 2011 at 7:16 PM, 7stud -- <bbxx789_05ss@yahoo.com> wrote:

"Jesús Gabriel y Galán" <jgabrielygalan@gmail.com> wrote in post
#995567:

On Thu, Apr 28, 2011 at 7:00 PM, Brian Candler <b.candler@pobox.com> >> wrote:

self
(I'm talking about real Struct here, not OpenStruct etc)

ruby-1.8.7-p334 :001 > K = Struct.new :id,:timestamp
=> K
ruby-1.8.7-p334 :002 > K[3,8]
=> #<struct K id=3, timestamp=8>

Jesus.

Is there a hash anywhere in your code? How about this:

irb(main):004:0> h = {:foo => 1, :bar => 2}
=> {:foo=>1, :bar=>2}
irb(main):005:0> o = OpenStruct.new(h)
=> #<OpenStruct foo=1, bar=2>
irb(main):006:0> o.foo
=> 1
irb(main):007:0> o.bar
=> 2

Well, here we differ

irb(main):008:0> o.oof
=> nil

But the issue with your approach is that it is not dynamic. Keys
added or removed after call to #structify! will not be taken care of.
A more dynamic approach would be

module HashStruct
  def method_missing(s,*a,&b)
    case
    when a.empty? && key?(s)
      self[s]
    when a.size == 1 && /\A(.+)=\z/ =~ s
      self[$1.to_sym] = a.first
    else
      super
    end
  end
end

h.extend HashStruct
h.foo

Kind regards

robert

···

On Thu, Apr 28, 2011 at 10:50 PM, Joel VanderWerf <joelvanderwerf@gmail.com> wrote:

Tangentially, I wonder if anything like the following (very rough proof of
concept) has been used instead of Struct.

class Hash
def structify!
keys.each do |key|
class << self; self; end.class_eval do
define_method key do
fetch key
end
define_method "#{key}=" do |val|
store key, val
end
end
end
end
end

h = {:foo => 1, :bar => 2}
h.structify!
p h.foo # 1
h.foo = 3
p h.foo # 3
p h # {:foo=>3, :bar=>2}
p h.oof # undefined

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

Joel VanderWerf wrote in post #995632:

Tangentially, I wonder if anything like the following (very rough proof
of concept) has been used instead of Struct.

That's neat. However you'd have to make sure you've set a value for
every key of interest, including defaults, before calling "structify!"

How about this variation:

class StructHash < Hash
  def initialize(h = {})
    replace(self.class::DEFAULTS.merge(h))
  end
end

def StructHash(defaults)
  k = Class.new(StructHash)
  k.const_set(:DEFAULTS, defaults)
  defaults.keys.each do |key|
    k.class_eval do
      define_method key do
        fetch key
      end
      define_method "#{key}=" do |val|
        store key, val
      end
    end
  end
  k
end

Foo = StructHash(:foo=>123, :bar=>456)
f = Foo.new(:foo=>0)
p f
p f.foo
p f.bar

I'm usually not a fan of subclassing core types, but I could be
persuaded here.

Perhaps the initialize function should look like this instead:

class StructHash < Hash
  def initialize(h = {})
    replace(self.class::DEFAULTS)
    h.each { |k,v| send("#{k}=", v) }
  end
end

It's not as fast, but it will catch errors if you try to set
non-existent members, and it lets you use symbols and strings
interchangeably.

···

--
Posted via http://www.ruby-forum.com/\.

That's intended: #structify is supposed to turn a hash into something that looks like a Struct, not an OpenStruct.

···

On 04/29/2011 01:06 AM, Robert Klemme wrote:

On Thu, Apr 28, 2011 at 10:50 PM, Joel VanderWerf > <joelvanderwerf@gmail.com> wrote:

Tangentially, I wonder if anything like the following (very rough proof of
concept) has been used instead of Struct.

class Hash
  def structify!
    keys.each do |key|
      class<< self; self; end.class_eval do
        define_method key do
          fetch key
        end
        define_method "#{key}=" do |val|
          store key, val
        end
      end
    end
  end
end

h = {:foo => 1, :bar => 2}
h.structify!
p h.foo # 1
h.foo = 3
p h.foo # 3
p h # {:foo=>3, :bar=>2}
p h.oof # undefined

irb(main):004:0> h = {:foo => 1, :bar => 2}
=> {:foo=>1, :bar=>2}
irb(main):005:0> o = OpenStruct.new(h)
=> #<OpenStruct foo=1, bar=2>
irb(main):006:0> o.foo
=> 1
irb(main):007:0> o.bar
=> 2

Well, here we differ

irb(main):008:0> o.oof
=> nil

But the issue with your approach is that it is not dynamic. Keys
added or removed after call to #structify! will not be taken care of.
A more dynamic approach would be