0

Have an array of single pair hashes, like this:

arguments = [{:name=>"ABCD"},{:title=>"Awesome"},{:number=>4}]

I need to loop through and pull each one off as a key and value. Right now, I'm doing this:

def methodname(*arguments, &block)
  arguments.each do |arg|
    arg.each do |key, value|
      # use my key and value
    end
  end
  # use the &block here in awesome ways
end

Ick. Gotta be a better way, so I'm asking if someone knows it. I've searched and can't seem to find this particular question on StackOverflow, but let me know if it's out there.

EDIT: Added context to the code example.

Jamon Holmgren
  • 23,738
  • 6
  • 59
  • 75
  • That… actually doesn't look that bad to me. You're iterating over an array, which takes a line, and you're iterating over each key-value pair in the hash, which takes a line. That's about as clean and simple as it gets. – Matchu Aug 27 '12 at 23:50
  • 2
    I suspect he wants to avoid "iterating over each key-value pair" since there is only ever going to be *one* key-value pair. – Jörg W Mittag Aug 27 '12 at 23:51
  • @JörgWMittag: ahh, good call. Gotcha. – Matchu Aug 27 '12 at 23:52

3 Answers3

2

If arguments are expected to be unique...

arguments.inject({},:merge).each { ... }

FWIW - if you can change this data structure, it might be a good idea to do so. This data is better represented in a single hash.

You can also define your method differently and avoid the problem altogether.

irb(main):011:0> def foo(kwargs={},&block)
irb(main):012:1>    pp kwargs
irb(main):013:1> end
irb(main):025:0> foo(:biz=>1,:baz=>2)
{:biz=>1, :baz=>2}
=> nil
dfb
  • 13,133
  • 2
  • 31
  • 52
  • I agree with changing the data structure. It's what I am getting with this: def methodname(*arguments, &block) # stuff end Excuse the idiotic formatting...you get the point. – Jamon Holmgren Aug 28 '12 at 00:01
  • 1
    BTW: you can write `arguments.inject({}, :merge)` instead. Or just `arguments.inject(:merge)` if you are sure that there will always be at least one condition. – Jörg W Mittag Aug 28 '12 at 00:03
  • @dfb I get syntax error, unexpected tPOW, expecting ')'. Note this is RubyMotion...that probably complicates things a bit. – Jamon Holmgren Aug 28 '12 at 00:05
  • @JamonHolmgren - My mistake. You can do `methodname(kwargs={},&block)` and then pass in key-value pairs. kwargs will contain the input as a hash – dfb Aug 28 '12 at 00:10
  • I'd have to call it with methodname { name: "ABCD", title: "yo" } do ? I'd prefer not to have to pass in a hash. – Jamon Holmgren Aug 28 '12 at 00:12
  • @JamonHolmgren - not sure what you're asking, you don't need to pass in a hash explictly, but see my edits – dfb Aug 28 '12 at 00:14
  • @dfb Didn't realize I could do that. Much, much better! Thank you. – Jamon Holmgren Aug 28 '12 at 00:16
  • @dfb can you edit your answer to show the redefined method? That's exactly what I was looking for. – Jamon Holmgren Aug 28 '12 at 00:18
2

Since this isn't a common idiom, you're not gonna find a really clean solution out there. Regardless, here's a decent shot at it:

arguments.map(&:first).each do |key, value|
    # use key and value for something
end

This solution takes advantage of the fact that Hash, as an Enumerable, has a first method that returns the first result it would yield on iteration, so {:foo => :bar}.first == [:foo, :bar]. Map that through all the hashes and you're good to go.

Matchu
  • 83,922
  • 18
  • 153
  • 160
1
arguments.each do |arg|
  (key, value), = *arg
  # use my key and value
end

I guess whether or not you consider this to be "a better way" mostly depends on whether your teammates understand how assignment works in Ruby.

Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • Thanks, this looks reasonable. Looking through the rest of the answers for the most elegant solution. – Jamon Holmgren Aug 28 '12 at 00:05
  • BTW: that trailing comma is easy to miss, maybe introducing a dummy variable would be more readable: `(key, value), _ = *arg`. I'm on the fence about this. – Jörg W Mittag Aug 28 '12 at 00:08