0
ruby 2.5

I have the following code:

test = {'primer' => 'grey'}
layers = ["tan","burgundy"]
fillers = ["blue","yellow"]
layers.each do |l|
    fillers.each do |f|
      test[l] = {} if !test.respond_to?(l)
      test[l][f] = {} if !test[l].respond_to?(f)
    end
end

When I run it in irb, I get the following:

{"primer"=>"grey", "tan"=>{"yellow"=>{}}, "burgundy"=>{"yellow"=>{}}}

I am expecting:

{"primer"=>"grey", "tan"=>{"blue"=>{},"yellow"=>{}}, "burgundy"=>{"blue"=>{},"yellow"=>{}}}

Why does the first respond_to produce the key, when the second one, replaces the previous key?

What am I missing?

Tom Lord
  • 27,404
  • 4
  • 50
  • 77
EastsideDev
  • 6,257
  • 9
  • 59
  • 116

2 Answers2

4

The expression

test.respond_to?(l)

does not make sense. l is a string, and respond_to? returns true if the receiver has a method of the name represented by this string. Since the receiver is a Hash and a Hash has no methods Hash#tan and Hash#burgundy, the test will always fail.

Maybe you want to do a test.has_key?(l) instead....

user1934428
  • 19,864
  • 7
  • 42
  • 87
  • My question was why is it responding to the first respond_to but not the next one – EastsideDev Nov 13 '19 at 08:30
  • **All** the `respond_to` will return false, hence the `if` clauses will **always** be true. The end result is the same as if you would have written no `if` clause at all. That's why you get your result. You can easily verify it if you place a `p test` after each assignment to the `test` Hash. – user1934428 Nov 13 '19 at 08:39
  • @EastsideDeveloper : With other words: In effect, you execute `layers.each { |l| fillers.each { |f| test[l] = {}; test[l][f] = {} }}`, which with your input data results in `{"primer"=>"grey", "tan"=>{"yellow"=>{}}, "burgundy"=>{"yellow"=>{}}}`. – user1934428 Nov 13 '19 at 08:44
  • Yes, I can see now how that happened – EastsideDev Nov 13 '19 at 08:45
1

Let's say you have the hash {a: 1}, having a key :a doesn't make the hash object respond to :a. hash.respond_to?(:a) would still return false. You want to check if a key exists, this can be done using has_key?/key?.

layers.each do |l|
  fillers.each do |f|
    test[l] = {} unless test.has_key?(l)
    test[l][f] = {} unless test[l].has_key?(f)
  end
end

However since you set the values to a hash, which is a truthy value. You could also use ||= which only assigns a value if the current value is falsy. test[:non_existing_key] will result in nil (unless a default is set).

Meaning the above can be replaced with:

layers.each do |l|
  fillers.each do |f|
    test[l] ||= {}
    test[l][f] ||= {}
  end
end

You could simplify this whole statement with the use of product which combines the two loops for you.

layers.product(fillers) do |layer, filler|
  test[layer] ||= {}
  test[layer][filler] ||= {}
end
3limin4t0r
  • 19,353
  • 2
  • 31
  • 52