13

This:

[{a: 1, b: 2}, {a: 3, b: 4}].each do |a:, b:| p a end

Raises the following warning in Ruby 2.7

warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call

I understand that each is passing a hash to the block, and the block now accepts |a:, b:| as named arguments but, is there any way to correctly destructure the hash in this context?

Daniel
  • 4,051
  • 2
  • 28
  • 46
  • 1
    I don't have an answer, but did find two relevant discussions: [1](https://bugs.ruby-lang.org/issues/8895) [2](https://bugs.ruby-lang.org/issues/11048) – Tom Lord Jun 04 '20 at 08:50
  • Thanks! On the other hand, those discussions are pre-2.7 deprecations, so I think that they refer to a different issue. – Daniel Jun 04 '20 at 09:04
  • 1
    Check this https://discuss.rubyonrails.org/t/new-2-7-3-0-keyword-argument-pain-point/74980. – Sebastián Palma Jun 04 '20 at 09:10
  • 1
    It looks like this behavior was not ever explicitly intended behavior and therefore it is not guaranteed to be backwards compatible. Jeremy Evans' comment here – which Matz ends up agreeing with – explains the logic behind it. https://bugs.ruby-lang.org/issues/14183#note-114 – Eli Sadoff Jun 04 '20 at 15:19

3 Answers3

4

I'm uncertain, but I think perhaps the idea is to use pattern matching for hash destructuring? For example:

{a: 1, b: 2}.tap do |args|
  args in {a: a, b: b} # !!!
  p a
end

Currently by default however, this will display a warning (which can be disabled):

Pattern matching is experimental, and the behavior may change in future versions of Ruby!

Tom Lord
  • 27,404
  • 4
  • 50
  • 77
  • That's really interesting! On the other hand, I didn't have a proper look at pattern matching yet, but I think that this would not be the real purpose of this feature. – Daniel Jun 04 '20 at 09:25
  • It works though: `[{a: 1, b: 2}, {a: 3, b: 4}].each { |args| args in {a: a, b: b}; p a }`. But I am just changing one warning for another. :P – Daniel Jun 04 '20 at 09:25
  • 1
    [You can disable warnings](https://dev.to/andrewmcodes/hiding-ruby-2-7-deprecation-warnings-in-rails-6-2mil) via: `RUBYOPT='-W:no-deprecated -W:no-experimental'`. Perhaps you could decide it's OK to ignore experimental warnings, but not deprecations? Not ideal, I know, but I thought it's an interesting suggestion to share. – Tom Lord Jun 04 '20 at 09:53
  • 1
    You could also of course go for a more verbose approach if the goal is purely to avoid warnings - i.e. `a = args.fetch(:a); b = args.fetch(:b)`. – Tom Lord Jun 04 '20 at 09:56
3

In Ruby 3 you can use the rightward assignment operator =>:

{a: 1, b: 2}.tap do |args|
  args => { a:, b: }
  p a
end
David Moles
  • 48,006
  • 27
  • 136
  • 235
1

If you already know that you have two keys in each Hash as per your example, why not this?

[{a: 1, b: 2}, {a: 3, b: 4}].each do |h|
  a, b = h.values
  p a
end
iGian
  • 11,023
  • 3
  • 21
  • 36
  • 6
    The danger of this solution is that it depends on the order of the elements in the hash. For instance, `[{a: 1, b: 2}, {b: 3, a: 4}]` would yield a wrong result. – Daniel Jun 04 '20 at 09:29
  • Oops! You are correct, I missed this case. But you can assign `a = h[:a]`. Don't you? – iGian Jun 04 '20 at 09:35