From 121991c37f92d6388f1b1ba776070926584a5fab Mon Sep 17 00:00:00 2001 From: tsymbalenkovlad Date: Wed, 29 Apr 2026 10:52:30 +0300 Subject: [PATCH 1/8] Support block in CSV::Row#to_h --- lib/csv/row.rb | 9 +++++++-- test/csv/test_row.rb | 3 +++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/csv/row.rb b/lib/csv/row.rb index 86323f7d..b42031d9 100644 --- a/lib/csv/row.rb +++ b/lib/csv/row.rb @@ -652,8 +652,13 @@ def ==(other) # row.to_h # => {"Name"=>"Foo"} def to_h hash = {} - each do |key, _value| - hash[key] = self[key] unless hash.key?(key) + each do |key, value| + new_key, new_value = if block_given? + yield(key, value) + else + [key, value] + end + hash[new_key] = new_value unless hash.key?(new_key) end hash end diff --git a/test/csv/test_row.rb b/test/csv/test_row.rb index b7179450..33b70b62 100644 --- a/test/csv/test_row.rb +++ b/test/csv/test_row.rb @@ -339,6 +339,9 @@ def test_to_hash assert_predicate(string_key, :frozen?) assert_same(string_key, @row.headers[h]) end + row2 = CSV::Row.new(%w{A B C}, [1, 2, 3]) + hash2 = row2.to_hash { |k, v| [k, v ** 2] } + assert_equal({"A" => 1, "B" => 4, "C" => 9}, hash2) end def test_to_csv From 070786982f1989086108b95654022cd6c7fdf488 Mon Sep 17 00:00:00 2001 From: tsymbalenkovlad Date: Thu, 30 Apr 2026 12:35:54 +0300 Subject: [PATCH 2/8] Simplify block_given? case based on @kou feedback --- lib/csv/row.rb | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/csv/row.rb b/lib/csv/row.rb index b42031d9..1befc7a1 100644 --- a/lib/csv/row.rb +++ b/lib/csv/row.rb @@ -653,12 +653,9 @@ def ==(other) def to_h hash = {} each do |key, value| - new_key, new_value = if block_given? - yield(key, value) - else - [key, value] - end - hash[new_key] = new_value unless hash.key?(new_key) + key, value = yield(key, value) if block_given? + + hash[key] = value unless hash.key?(key) end hash end From a11c20f89e33e3750b76131d6b33f7f3c4e98037 Mon Sep 17 00:00:00 2001 From: tsymbalenkovlad Date: Thu, 30 Apr 2026 12:36:13 +0300 Subject: [PATCH 3/8] Separate block test case --- test/csv/test_row.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/csv/test_row.rb b/test/csv/test_row.rb index 33b70b62..53b0fbc0 100644 --- a/test/csv/test_row.rb +++ b/test/csv/test_row.rb @@ -339,9 +339,12 @@ def test_to_hash assert_predicate(string_key, :frozen?) assert_same(string_key, @row.headers[h]) end - row2 = CSV::Row.new(%w{A B C}, [1, 2, 3]) - hash2 = row2.to_hash { |k, v| [k, v ** 2] } - assert_equal({"A" => 1, "B" => 4, "C" => 9}, hash2) + end + + def test_to_hash_with_block + row = CSV::Row.new(%w{A B C}, [1, 2, 3]) + hash = row.to_hash { |k, v| [k, v ** 2] } + assert_equal({"A" => 1, "B" => 4, "C" => 9}, hash) end def test_to_csv From 7dbaa45a0ab5d43c617d66385e1a005268ef3980 Mon Sep 17 00:00:00 2001 From: tsymbalenkovlad Date: Thu, 30 Apr 2026 13:11:31 +0300 Subject: [PATCH 4/8] Get value from self --- lib/csv/row.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/csv/row.rb b/lib/csv/row.rb index 1befc7a1..e0e61e44 100644 --- a/lib/csv/row.rb +++ b/lib/csv/row.rb @@ -652,7 +652,8 @@ def ==(other) # row.to_h # => {"Name"=>"Foo"} def to_h hash = {} - each do |key, value| + each do |key, _value| + value = self[key] key, value = yield(key, value) if block_given? hash[key] = value unless hash.key?(key) From 8257d8e0d93588a35934d159b31fccd089b35bbd Mon Sep 17 00:00:00 2001 From: Vlad Date: Thu, 30 Apr 2026 14:44:08 +0300 Subject: [PATCH 5/8] Fix formatting Co-authored-by: Sutou Kouhei --- test/csv/test_row.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/csv/test_row.rb b/test/csv/test_row.rb index 53b0fbc0..2df0b6e3 100644 --- a/test/csv/test_row.rb +++ b/test/csv/test_row.rb @@ -342,7 +342,7 @@ def test_to_hash end def test_to_hash_with_block - row = CSV::Row.new(%w{A B C}, [1, 2, 3]) + row = CSV::Row.new(%w{A B C}, [1, 2, 3]) hash = row.to_hash { |k, v| [k, v ** 2] } assert_equal({"A" => 1, "B" => 4, "C" => 9}, hash) end From a01ef69b256fd29c6b33cf045c863b9da99f6f55 Mon Sep 17 00:00:00 2001 From: tsymbalenkovlad Date: Thu, 30 Apr 2026 14:59:43 +0300 Subject: [PATCH 6/8] Improve condition and test, add documentation --- lib/csv/row.rb | 10 +++++++++- test/csv/test_row.rb | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/csv/row.rb b/lib/csv/row.rb index e0e61e44..d29ba66a 100644 --- a/lib/csv/row.rb +++ b/lib/csv/row.rb @@ -650,13 +650,21 @@ def ==(other) # table = CSV.parse(source, headers: true) # row = table[0] # row.to_h # => {"Name"=>"Foo"} + # + # If a block is given, will call it with (key, value) arguments and use result as a hash entry: + # source = "Name,Value\nfoo,1\nbar,2\nbaz,3\n" + # table = CSV.parse(source, headers: true) + # row = table[0] + # row.to_h { |key, value| [key, value.to_i * 2] } # => {"Name"=>"foo", "Value"=>2} def to_h hash = {} each do |key, _value| + next if hash.key?(key) + value = self[key] key, value = yield(key, value) if block_given? - hash[key] = value unless hash.key?(key) + hash[key] = value end hash end diff --git a/test/csv/test_row.rb b/test/csv/test_row.rb index 53b0fbc0..2d725978 100644 --- a/test/csv/test_row.rb +++ b/test/csv/test_row.rb @@ -342,8 +342,8 @@ def test_to_hash end def test_to_hash_with_block - row = CSV::Row.new(%w{A B C}, [1, 2, 3]) - hash = row.to_hash { |k, v| [k, v ** 2] } + row = CSV::Row.new(%w{A A B C}, [1, 2, 2, 3]) + hash = row.to_hash { |k, v| [k, v**2] } assert_equal({"A" => 1, "B" => 4, "C" => 9}, hash) end From d2a095cbba07b1ee0f0976f9f5e277a895bcbd78 Mon Sep 17 00:00:00 2001 From: tsymbalenkovlad Date: Thu, 30 Apr 2026 15:20:34 +0300 Subject: [PATCH 7/8] add call-seq doc --- lib/csv/row.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/csv/row.rb b/lib/csv/row.rb index d29ba66a..7e561200 100644 --- a/lib/csv/row.rb +++ b/lib/csv/row.rb @@ -637,6 +637,7 @@ def ==(other) # :call-seq: # row.to_h -> hash + # row.to_h {|key, value| ... } -> hash # # Returns the new \Hash formed by adding each header-value pair in +self+ # as a key-value pair in the \Hash. From fa0d288436a9f5d4a99fac0c643a15bf509848ff Mon Sep 17 00:00:00 2001 From: tsymbalenkovlad Date: Fri, 1 May 2026 10:35:44 +0300 Subject: [PATCH 8/8] Handle repeated headers in block, validate block result type --- lib/csv/row.rb | 18 +++++++++++++----- test/csv/test_row.rb | 16 ++++++++++++++-- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/lib/csv/row.rb b/lib/csv/row.rb index 7e561200..f711bf10 100644 --- a/lib/csv/row.rb +++ b/lib/csv/row.rb @@ -659,14 +659,22 @@ def ==(other) # row.to_h { |key, value| [key, value.to_i * 2] } # => {"Name"=>"foo", "Value"=>2} def to_h hash = {} - each do |key, _value| - next if hash.key?(key) - value = self[key] - key, value = yield(key, value) if block_given? + if block_given? + each do |key, _value| + result = yield(key, self[key]) + raise TypeError, "wrong element type #{result.class} (expected array)" unless result.is_a?(Array) + raise ArgumentError, "wrong array length (expected 2, was #{result.size})" unless result.size == 2 - hash[key] = value + key, value = result + hash[key] = value unless hash.key?(key) + end + else + each do |key, _value| + hash[key] = self[key] unless hash.key?(key) + end end + hash end alias_method :to_hash, :to_h diff --git a/test/csv/test_row.rb b/test/csv/test_row.rb index 20cd81fb..260b1dd3 100644 --- a/test/csv/test_row.rb +++ b/test/csv/test_row.rb @@ -343,8 +343,20 @@ def test_to_hash def test_to_hash_with_block row = CSV::Row.new(%w{A A B C}, [1, 2, 2, 3]) - hash = row.to_hash { |k, v| [k, v**2] } - assert_equal({"A" => 1, "B" => 4, "C" => 9}, hash) + new_keys_map = {"A" => "A", "B" => "B", "C" => "B"} + hash = row.to_hash { |k, v| [new_keys_map[k], v**2] } + assert_equal({"A" => 1, "B" => 4}, hash) + hash.keys.each_with_index do |string_key, h| + assert_predicate(string_key, :frozen?) + end + + assert_raise TypeError do + row.to_hash { "foo" } + end + + assert_raise ArgumentError do + row.to_hash { [1] } + end end def test_to_csv