From 99cee85775bc5656b942bf4e1b0568a8f40addcd Mon Sep 17 00:00:00 2001 From: Ufuk Kayserilioglu Date: Thu, 22 Dec 2022 02:27:38 +0200 Subject: Add copy with changes functionality for Data objects (#6766) Implements [Feature #19000] This commit adds copy with changes functionality for `Data` objects using a new method `Data#with`. Since Data objects are immutable, the only way to change them is by creating a copy. This PR adds a `with` method for `Data` class instances that optionally takes keyword arguments. If the `with` method is called with no arguments, the behaviour is the same as the `Kernel#dup` method, i.e. a new shallow copy is created with no field values changed. However, if keyword arguments are supplied to the `with` method, then the copy is created with the specified field values changed. For example: ```ruby Point = Data.define(:x, :y) point = Point.new(x: 1, y: 2) point.with(x: 3) # => # ``` Passing positional arguments to `with` or passing keyword arguments to it that do not correspond to any of the members of the Data class will raise an `ArgumentError`. Co-authored-by: Alan Wu --- test/ruby/test_data.rb | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) (limited to 'test/ruby') diff --git a/test/ruby/test_data.rb b/test/ruby/test_data.rb index 4d28da6061..3cafb365ed 100644 --- a/test/ruby/test_data.rb +++ b/test/ruby/test_data.rb @@ -158,6 +158,65 @@ class TestData < Test::Unit::TestCase assert_not_operator(o1, :eql?, o3) end + def test_with + klass = Data.define(:foo, :bar) + source = klass.new(foo: 1, bar: 2) + + # Simple + test = source.with + assert_equal(source.object_id, test.object_id) + + # Changes + test = source.with(foo: 10) + + assert_equal(1, source.foo) + assert_equal(2, source.bar) + assert_equal(source, klass.new(foo: 1, bar: 2)) + + assert_equal(10, test.foo) + assert_equal(2, test.bar) + assert_equal(test, klass.new(foo: 10, bar: 2)) + + test = source.with(foo: 10, bar: 20) + + assert_equal(1, source.foo) + assert_equal(2, source.bar) + assert_equal(source, klass.new(foo: 1, bar: 2)) + + assert_equal(10, test.foo) + assert_equal(20, test.bar) + assert_equal(test, klass.new(foo: 10, bar: 20)) + + # Keyword splat + changes = { foo: 10, bar: 20 } + test = source.with(**changes) + + assert_equal(1, source.foo) + assert_equal(2, source.bar) + assert_equal(source, klass.new(foo: 1, bar: 2)) + + assert_equal(10, test.foo) + assert_equal(20, test.bar) + assert_equal(test, klass.new(foo: 10, bar: 20)) + + # Wrong protocol + assert_raise_with_message(ArgumentError, "wrong number of arguments (given 1, expected 0)") do + source.with(10) + end + assert_raise_with_message(ArgumentError, "unknown keywords: :baz, :quux") do + source.with(foo: 1, bar: 2, baz: 3, quux: 4) + end + assert_raise_with_message(ArgumentError, "wrong number of arguments (given 1, expected 0)") do + source.with(1, bar: 2) + end + assert_raise_with_message(ArgumentError, "wrong number of arguments (given 2, expected 0)") do + source.with(1, 2) + end + assert_raise_with_message(ArgumentError, "wrong number of arguments (given 1, expected 0)") do + source.with({ bar: 2 }) + end + end + def test_memberless klass = Data.define -- cgit v1.2.3