diff --git a/ChangeLog b/ChangeLog index 6f5590e..ac85f49 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +Fri Sep 2 17:27:31 2016 Matthew Mongeau + + * struct.c (rb_struct_update): Add Struct#merge and Struct#merge! + for updating structs + Fri Sep 2 16:06:59 2016 Nobuyoshi Nakada * internal.h (MEMO_V1_SET, MEMO_V2_SET): fix typos. use the macro diff --git a/struct.c b/struct.c index 150ca20..9a33789 100644 --- a/struct.c +++ b/struct.c @@ -894,6 +894,59 @@ rb_struct_aref(VALUE s, VALUE idx) return RSTRUCT_GET(s, i); } +static int +value_update(VALUE key, VALUE value, VALUE s) +{ + rb_struct_aset(s, key, value); + return ST_CONTINUE; +} + +/* + * call-seq: + * struct.merge!(hash) -> struct + * + * Updates the values of the struct using the keys and values of the hash + * + * Point = Struct.new(:x, :y) + * hash = { "x" => 5 } + * point = Point.new(1, 2) + * point.merge!(hash) + * + * point.x #=> 5 + */ + +VALUE +rb_struct_update(VALUE self, VALUE hash) +{ + VALUE converted_hash = rb_convert_type(hash, T_HASH, "Hash", "to_hash"); + + rb_hash_foreach(converted_hash, value_update, self); + + return self; +} + +/* + * call-seq: + * struct.merge(hash) -> new_struct + * + * Returns a new struct with updated values using the keys and values of + * the hash + * + * Point = Struct.new(:x, :y) + * p = Point.new(1, 2) + * p2 = p.merge(y: 20) + * + * p.y #=> 2 + * p2.y #=> 20 + */ + +VALUE +rb_struct_merge(VALUE self, VALUE hash) +{ + VALUE dup = rb_obj_dup(self); + return rb_struct_update(dup, hash); +} + /* * call-seq: * struct[member] = obj -> obj @@ -1215,6 +1268,9 @@ InitVM_Struct(void) rb_define_method(rb_cStruct, "members", rb_struct_members_m, 0); rb_define_method(rb_cStruct, "dig", rb_struct_dig, -1); + + rb_define_method(rb_cStruct, "merge", rb_struct_merge, 1); + rb_define_method(rb_cStruct, "merge!", rb_struct_update, 1); } #undef rb_intern diff --git a/test/ruby/test_struct.rb b/test/ruby/test_struct.rb index 9047e81..2ed323f 100644 --- a/test/ruby/test_struct.rb +++ b/test/ruby/test_struct.rb @@ -367,6 +367,35 @@ def test_dig assert_nil(o.dig(:b, 0)) end + def test_merge! + klass = @Struct.new(:a, :b) + o = klass.new(1, 2) + o.merge!(a: 10) + assert_equal(o.a, 10) + assert_equal(o.b, 2) + + o.merge!("b" => 5) + assert_equal(o.b, 5) + + assert_raise(TypeError) { + o.merge(0) + } + end + + def test_merge + klass = @Struct.new(:a, :b) + o = klass.new(1, 2) + p = o.merge(a: 10) + assert_equal(p.a, 10) + assert_equal(p.b, 2) + assert_equal(o.a, 1) + assert_equal(o.b, 2) + + assert_raise(TypeError) { + o.merge(0) + } + end + def test_new_dupilicate bug12291 = '[ruby-core:74971] [Bug #12291]' assert_raise_with_message(ArgumentError, /duplicate member/, bug12291) {