From 43756df81452e57374d51869f2883d4c74130f84 Mon Sep 17 00:00:00 2001 From: Alex Rattray Date: Wed, 3 Jul 2019 20:00:19 -0700 Subject: [PATCH 1/6] Revert "Use Util.convert_to_stripe_object instead of initialize_from" This reverts commit ea736eba1b8f33d8febbf0b1be0957f34f7b04db. --- lib/stripe/resources/issuing/card.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/stripe/resources/issuing/card.rb b/lib/stripe/resources/issuing/card.rb index ba38e5dd8..c24ee6625 100644 --- a/lib/stripe/resources/issuing/card.rb +++ b/lib/stripe/resources/issuing/card.rb @@ -13,7 +13,7 @@ class Card < APIResource def details(params = {}, opts = {}) resp, opts = request(:get, resource_url + "/details", params, opts) - Util.convert_to_stripe_object(resp.data, opts) + initialize_from(resp.data, opts) end end end From bb553526586736d82258023e6a4a182da9968550 Mon Sep 17 00:00:00 2001 From: Alex Rattray Date: Wed, 3 Jul 2019 20:14:53 -0700 Subject: [PATCH 2/6] Fix issuing card details, which must have been broken prior to ea736eb --- lib/stripe/resources/issuing/card.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/stripe/resources/issuing/card.rb b/lib/stripe/resources/issuing/card.rb index c24ee6625..ba38e5dd8 100644 --- a/lib/stripe/resources/issuing/card.rb +++ b/lib/stripe/resources/issuing/card.rb @@ -13,7 +13,7 @@ class Card < APIResource def details(params = {}, opts = {}) resp, opts = request(:get, resource_url + "/details", params, opts) - initialize_from(resp.data, opts) + Util.convert_to_stripe_object(resp.data, opts) end end end From 5e8faa28ed70533a65f3e7f0c23d09e538e7753e Mon Sep 17 00:00:00 2001 From: Alex Rattray Date: Wed, 3 Jul 2019 20:30:33 -0700 Subject: [PATCH 3/6] wip --- lib/stripe/api_resource.rb | 11 +++++++++++ lib/stripe/resources/charge.rb | 8 ++++++-- lib/stripe/resources/issuing/card.rb | 8 ++++++-- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/lib/stripe/api_resource.rb b/lib/stripe/api_resource.rb index a6a50f5ef..133fbd0be 100644 --- a/lib/stripe/api_resource.rb +++ b/lib/stripe/api_resource.rb @@ -103,5 +103,16 @@ def self.retrieve(id, opts = {}) instance.refresh instance end + + protected def make_request_returning_stripe_object(method:, path:, params:, opts: {}) + resp, opts = request(method, path, params, opts) + + # If we're getting back this thing, update in-place; otherwise, instantiate anew. + if Util.object_classes[resp.data[:object]] == self.class + initialize_from(resp.data, opts) + else + Util.convert_to_stripe_object(resp.data, opts) + end + end end end diff --git a/lib/stripe/resources/charge.rb b/lib/stripe/resources/charge.rb index f9ec48d43..1714dc10b 100644 --- a/lib/stripe/resources/charge.rb +++ b/lib/stripe/resources/charge.rb @@ -32,8 +32,12 @@ def refund(params = {}, opts = {}) end def capture(params = {}, opts = {}) - resp, opts = request(:post, capture_url, params, opts) - initialize_from(resp.data, opts) + make_request_returning_stripe_object( + method: :post, + path: capture_url, + params: params, + opts: opts + ) end def update_dispute(params = {}, opts = {}) diff --git a/lib/stripe/resources/issuing/card.rb b/lib/stripe/resources/issuing/card.rb index ba38e5dd8..229c21a1b 100644 --- a/lib/stripe/resources/issuing/card.rb +++ b/lib/stripe/resources/issuing/card.rb @@ -12,8 +12,12 @@ class Card < APIResource custom_method :details, http_verb: :get def details(params = {}, opts = {}) - resp, opts = request(:get, resource_url + "/details", params, opts) - Util.convert_to_stripe_object(resp.data, opts) + make_request_returning_stripe_object( + method: :get, + path: resource_url + "/details", + params: params, + opts: opts + ) end end end From bd833fe57c25cca927d7b14fc83df096cb3a62af Mon Sep 17 00:00:00 2001 From: Alex Rattray Date: Thu, 4 Jul 2019 08:00:33 -0700 Subject: [PATCH 4/6] sketch out test... --- test/stripe/api_resource_test.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/stripe/api_resource_test.rb b/test/stripe/api_resource_test.rb index 7d9c47e7e..0bc33c302 100644 --- a/test/stripe/api_resource_test.rb +++ b/test/stripe/api_resource_test.rb @@ -511,6 +511,12 @@ class NestedTestAPIResource < APIResource end end + context "#make_request_returning_stripe_object" do + should "make requests appropriately" + should "update attributes in-place when it returns the same thing" + should "instantiate a new object of the appropriate class when it is different than the host class" + end + @@fixtures = {} # rubocop:disable Style/ClassVars setup do if @@fixtures.empty? From 69b5e6e1c31bb64a9441952510cbf2f7b56976c2 Mon Sep 17 00:00:00 2001 From: Alex Rattray Date: Tue, 6 Aug 2019 17:23:11 -0700 Subject: [PATCH 5/6] Rename to request_stripe_object, use with codegen --- lib/stripe/api_resource.rb | 6 +-- lib/stripe/resources/account.rb | 8 +++- lib/stripe/resources/charge.rb | 8 +--- lib/stripe/resources/credit_note.rb | 8 +++- lib/stripe/resources/dispute.rb | 8 +++- lib/stripe/resources/invoice.rb | 40 ++++++++++++++----- lib/stripe/resources/issuing/authorization.rb | 16 ++++++-- lib/stripe/resources/issuing/card.rb | 2 +- lib/stripe/resources/order.rb | 16 ++++++-- lib/stripe/resources/payment_intent.rb | 24 ++++++++--- lib/stripe/resources/payment_method.rb | 16 ++++++-- lib/stripe/resources/payout.rb | 8 +++- lib/stripe/resources/review.rb | 8 +++- lib/stripe/resources/setup_intent.rb | 16 ++++++-- lib/stripe/resources/source.rb | 8 +++- lib/stripe/resources/subscription_schedule.rb | 16 ++++++-- lib/stripe/resources/topup.rb | 8 +++- lib/stripe/resources/transfer.rb | 8 +++- lib/stripe/util.rb | 4 ++ test/stripe/api_resource_test.rb | 2 +- 20 files changed, 167 insertions(+), 63 deletions(-) diff --git a/lib/stripe/api_resource.rb b/lib/stripe/api_resource.rb index 133fbd0be..7000537e2 100644 --- a/lib/stripe/api_resource.rb +++ b/lib/stripe/api_resource.rb @@ -104,11 +104,11 @@ def self.retrieve(id, opts = {}) instance end - protected def make_request_returning_stripe_object(method:, path:, params:, opts: {}) + protected def request_stripe_object(method:, path:, params:, opts: {}) resp, opts = request(method, path, params, opts) - # If we're getting back this thing, update in-place; otherwise, instantiate anew. - if Util.object_classes[resp.data[:object]] == self.class + # If we're getting back this thing, update; otherwise, instantiate. + if Util.object_name_matches_class?(resp.data[:object], self.class) initialize_from(resp.data, opts) else Util.convert_to_stripe_object(resp.data, opts) diff --git a/lib/stripe/resources/account.rb b/lib/stripe/resources/account.rb index b28cb0a6f..36db22a5b 100644 --- a/lib/stripe/resources/account.rb +++ b/lib/stripe/resources/account.rb @@ -20,8 +20,12 @@ class Account < APIResource operations: %i[create retrieve update delete list] def reject(params = {}, opts = {}) - resp, opts = request(:post, resource_url + "/reject", params, opts) - initialize_from(resp.data, opts) + request_stripe_object( + method: :post, + path: resource_url + "/reject", + params: params, + opts: opts + ) end save_nested_resource :external_account diff --git a/lib/stripe/resources/charge.rb b/lib/stripe/resources/charge.rb index 1714dc10b..f9ec48d43 100644 --- a/lib/stripe/resources/charge.rb +++ b/lib/stripe/resources/charge.rb @@ -32,12 +32,8 @@ def refund(params = {}, opts = {}) end def capture(params = {}, opts = {}) - make_request_returning_stripe_object( - method: :post, - path: capture_url, - params: params, - opts: opts - ) + resp, opts = request(:post, capture_url, params, opts) + initialize_from(resp.data, opts) end def update_dispute(params = {}, opts = {}) diff --git a/lib/stripe/resources/credit_note.rb b/lib/stripe/resources/credit_note.rb index 0b369f590..3538d81ad 100644 --- a/lib/stripe/resources/credit_note.rb +++ b/lib/stripe/resources/credit_note.rb @@ -11,8 +11,12 @@ class CreditNote < APIResource custom_method :void_credit_note, http_verb: :post, http_path: "void" def void_credit_note(params = {}, opts = {}) - resp, opts = request(:post, resource_url + "/void", params, opts) - initialize_from(resp.data, opts) + request_stripe_object( + method: :post, + path: resource_url + "/void", + params: params, + opts: opts + ) end end end diff --git a/lib/stripe/resources/dispute.rb b/lib/stripe/resources/dispute.rb index 2541a3387..882651a90 100644 --- a/lib/stripe/resources/dispute.rb +++ b/lib/stripe/resources/dispute.rb @@ -10,8 +10,12 @@ class Dispute < APIResource custom_method :close, http_verb: :post def close(params = {}, opts = {}) - resp, opts = request(:post, resource_url + "/close", params, opts) - initialize_from(resp.data, opts) + request_stripe_object( + method: :post, + path: resource_url + "/close", + params: params, + opts: opts + ) end def close_url diff --git a/lib/stripe/resources/invoice.rb b/lib/stripe/resources/invoice.rb index dfee9f9a5..24ffa74e0 100644 --- a/lib/stripe/resources/invoice.rb +++ b/lib/stripe/resources/invoice.rb @@ -16,28 +16,48 @@ class Invoice < APIResource custom_method :void_invoice, http_verb: :post, http_path: "void" def finalize_invoice(params = {}, opts = {}) - resp, opts = request(:post, resource_url + "/finalize", params, opts) - initialize_from(resp.data, opts) + request_stripe_object( + method: :post, + path: resource_url + "/finalize", + params: params, + opts: opts + ) end def mark_uncollectible(params = {}, opts = {}) - resp, opts = request(:post, resource_url + "/mark_uncollectible", params, opts) - initialize_from(resp.data, opts) + request_stripe_object( + method: :post, + path: resource_url + "/mark_uncollectible", + params: params, + opts: opts + ) end def pay(params = {}, opts = {}) - resp, opts = request(:post, resource_url + "/pay", params, opts) - initialize_from(resp.data, opts) + request_stripe_object( + method: :post, + path: resource_url + "/pay", + params: params, + opts: opts + ) end def send_invoice(params = {}, opts = {}) - resp, opts = request(:post, resource_url + "/send", params, opts) - initialize_from(resp.data, opts) + request_stripe_object( + method: :post, + path: resource_url + "/send", + params: params, + opts: opts + ) end def void_invoice(params = {}, opts = {}) - resp, opts = request(:post, resource_url + "/void", params, opts) - initialize_from(resp.data, opts) + request_stripe_object( + method: :post, + path: resource_url + "/void", + params: params, + opts: opts + ) end def self.upcoming(params, opts = {}) diff --git a/lib/stripe/resources/issuing/authorization.rb b/lib/stripe/resources/issuing/authorization.rb index eb13be217..05ac7ef36 100644 --- a/lib/stripe/resources/issuing/authorization.rb +++ b/lib/stripe/resources/issuing/authorization.rb @@ -12,13 +12,21 @@ class Authorization < APIResource custom_method :decline, http_verb: :post def approve(params = {}, opts = {}) - resp, opts = request(:post, resource_url + "/approve", params, opts) - initialize_from(resp.data, opts) + request_stripe_object( + method: :post, + path: resource_url + "/approve", + params: params, + opts: opts + ) end def decline(params = {}, opts = {}) - resp, opts = request(:post, resource_url + "/decline", params, opts) - initialize_from(resp.data, opts) + request_stripe_object( + method: :post, + path: resource_url + "/decline", + params: params, + opts: opts + ) end end end diff --git a/lib/stripe/resources/issuing/card.rb b/lib/stripe/resources/issuing/card.rb index 229c21a1b..32efcd6d4 100644 --- a/lib/stripe/resources/issuing/card.rb +++ b/lib/stripe/resources/issuing/card.rb @@ -12,7 +12,7 @@ class Card < APIResource custom_method :details, http_verb: :get def details(params = {}, opts = {}) - make_request_returning_stripe_object( + request_stripe_object( method: :get, path: resource_url + "/details", params: params, diff --git a/lib/stripe/resources/order.rb b/lib/stripe/resources/order.rb index 19a32c333..8c9220861 100644 --- a/lib/stripe/resources/order.rb +++ b/lib/stripe/resources/order.rb @@ -12,13 +12,21 @@ class Order < APIResource custom_method :return_order, http_verb: :post, http_path: "returns" def pay(params = {}, opts = {}) - resp, opts = request(:post, resource_url + "/pay", params, opts) - Util.convert_to_stripe_object(resp.data, opts) + request_stripe_object( + method: :post, + path: resource_url + "/pay", + params: params, + opts: opts + ) end def return_order(params = {}, opts = {}) - resp, opts = request(:post, resource_url + "/returns", params, opts) - Util.convert_to_stripe_object(resp.data, opts) + request_stripe_object( + method: :post, + path: resource_url + "/returns", + params: params, + opts: opts + ) end private def pay_url diff --git a/lib/stripe/resources/payment_intent.rb b/lib/stripe/resources/payment_intent.rb index 5bbf8fbeb..b9e46bd12 100644 --- a/lib/stripe/resources/payment_intent.rb +++ b/lib/stripe/resources/payment_intent.rb @@ -13,18 +13,30 @@ class PaymentIntent < APIResource custom_method :confirm, http_verb: :post def cancel(params = {}, opts = {}) - resp, opts = request(:post, resource_url + "/cancel", params, opts) - initialize_from(resp.data, opts) + request_stripe_object( + method: :post, + path: resource_url + "/cancel", + params: params, + opts: opts + ) end def capture(params = {}, opts = {}) - resp, opts = request(:post, resource_url + "/capture", params, opts) - initialize_from(resp.data, opts) + request_stripe_object( + method: :post, + path: resource_url + "/capture", + params: params, + opts: opts + ) end def confirm(params = {}, opts = {}) - resp, opts = request(:post, resource_url + "/confirm", params, opts) - initialize_from(resp.data, opts) + request_stripe_object( + method: :post, + path: resource_url + "/confirm", + params: params, + opts: opts + ) end end end diff --git a/lib/stripe/resources/payment_method.rb b/lib/stripe/resources/payment_method.rb index 4dd3fad15..d366a6f34 100644 --- a/lib/stripe/resources/payment_method.rb +++ b/lib/stripe/resources/payment_method.rb @@ -12,13 +12,21 @@ class PaymentMethod < APIResource custom_method :detach, http_verb: :post def attach(params = {}, opts = {}) - resp, opts = request(:post, resource_url + "/attach", params, opts) - initialize_from(resp.data, opts) + request_stripe_object( + method: :post, + path: resource_url + "/attach", + params: params, + opts: opts + ) end def detach(params = {}, opts = {}) - resp, opts = request(:post, resource_url + "/detach", params, opts) - initialize_from(resp.data, opts) + request_stripe_object( + method: :post, + path: resource_url + "/detach", + params: params, + opts: opts + ) end end end diff --git a/lib/stripe/resources/payout.rb b/lib/stripe/resources/payout.rb index d9e1d4800..79ec44d66 100644 --- a/lib/stripe/resources/payout.rb +++ b/lib/stripe/resources/payout.rb @@ -11,8 +11,12 @@ class Payout < APIResource custom_method :cancel, http_verb: :post def cancel(params = {}, opts = {}) - resp, opts = request(:post, resource_url + "/cancel", params, opts) - initialize_from(resp.data, opts) + request_stripe_object( + method: :post, + path: resource_url + "/cancel", + params: params, + opts: opts + ) end def cancel_url diff --git a/lib/stripe/resources/review.rb b/lib/stripe/resources/review.rb index 8eb0e62b6..701ef2d0e 100644 --- a/lib/stripe/resources/review.rb +++ b/lib/stripe/resources/review.rb @@ -9,8 +9,12 @@ class Review < APIResource custom_method :approve, http_verb: :post def approve(params = {}, opts = {}) - resp, opts = request(:post, resource_url + "/approve", params, opts) - initialize_from(resp.data, opts) + request_stripe_object( + method: :post, + path: resource_url + "/approve", + params: params, + opts: opts + ) end end end diff --git a/lib/stripe/resources/setup_intent.rb b/lib/stripe/resources/setup_intent.rb index 3b3815fdb..296bad55c 100644 --- a/lib/stripe/resources/setup_intent.rb +++ b/lib/stripe/resources/setup_intent.rb @@ -12,13 +12,21 @@ class SetupIntent < APIResource custom_method :confirm, http_verb: :post def cancel(params = {}, opts = {}) - resp, opts = request(:post, resource_url + "/cancel", params, opts) - initialize_from(resp.data, opts) + request_stripe_object( + method: :post, + path: resource_url + "/cancel", + params: params, + opts: opts + ) end def confirm(params = {}, opts = {}) - resp, opts = request(:post, resource_url + "/confirm", params, opts) - initialize_from(resp.data, opts) + request_stripe_object( + method: :post, + path: resource_url + "/confirm", + params: params, + opts: opts + ) end end end diff --git a/lib/stripe/resources/source.rb b/lib/stripe/resources/source.rb index ec897b87e..dc0a61995 100644 --- a/lib/stripe/resources/source.rb +++ b/lib/stripe/resources/source.rb @@ -10,8 +10,12 @@ class Source < APIResource custom_method :verify, http_verb: :post def verify(params = {}, opts = {}) - resp, opts = request(:post, resource_url + "/verify", params, opts) - initialize_from(resp.data, opts) + request_stripe_object( + method: :post, + path: resource_url + "/verify", + params: params, + opts: opts + ) end def detach(params = {}, opts = {}) diff --git a/lib/stripe/resources/subscription_schedule.rb b/lib/stripe/resources/subscription_schedule.rb index 2c24e784d..122c47553 100644 --- a/lib/stripe/resources/subscription_schedule.rb +++ b/lib/stripe/resources/subscription_schedule.rb @@ -15,13 +15,21 @@ class SubscriptionSchedule < APIResource nested_resource_class_methods :revision, operations: %i[retrieve list] def cancel(params = {}, opts = {}) - resp, opts = request(:post, resource_url + "/cancel", params, opts) - initialize_from(resp.data, opts) + request_stripe_object( + method: :post, + path: resource_url + "/cancel", + params: params, + opts: opts + ) end def release(params = {}, opts = {}) - resp, opts = request(:post, resource_url + "/release", params, opts) - initialize_from(resp.data, opts) + request_stripe_object( + method: :post, + path: resource_url + "/release", + params: params, + opts: opts + ) end def revisions(params = {}, opts = {}) diff --git a/lib/stripe/resources/topup.rb b/lib/stripe/resources/topup.rb index 5468a9c89..de59727f5 100644 --- a/lib/stripe/resources/topup.rb +++ b/lib/stripe/resources/topup.rb @@ -11,8 +11,12 @@ class Topup < APIResource custom_method :cancel, http_verb: :post def cancel(params = {}, opts = {}) - resp, opts = request(:post, resource_url + "/cancel", params, opts) - initialize_from(resp.data, opts) + request_stripe_object( + method: :post, + path: resource_url + "/cancel", + params: params, + opts: opts + ) end end end diff --git a/lib/stripe/resources/transfer.rb b/lib/stripe/resources/transfer.rb index 3918a5cd4..bff728231 100644 --- a/lib/stripe/resources/transfer.rb +++ b/lib/stripe/resources/transfer.rb @@ -15,8 +15,12 @@ class Transfer < APIResource operations: %i[create retrieve update list] def cancel(params = {}, opts = {}) - resp, opts = request(:post, resource_url + "/cancel", params, opts) - initialize_from(resp.data, opts) + request_stripe_object( + method: :post, + path: resource_url + "/cancel", + params: params, + opts: opts + ) end def cancel_url diff --git a/lib/stripe/util.rb b/lib/stripe/util.rb index e4036b8f1..6201a8913 100644 --- a/lib/stripe/util.rb +++ b/lib/stripe/util.rb @@ -43,6 +43,10 @@ def self.object_classes @object_classes ||= Stripe::ObjectTypes.object_names_to_classes end + def self.object_name_matches_class?(object_name, klass) + Util.object_classes[object_name] == klass + end + # Converts a hash of fields or an array of hashes into a +StripeObject+ or # array of +StripeObject+s. These new objects will be created as a concrete # type as dictated by their `object` field (e.g. an `object` value of diff --git a/test/stripe/api_resource_test.rb b/test/stripe/api_resource_test.rb index 0bc33c302..c118cf8b4 100644 --- a/test/stripe/api_resource_test.rb +++ b/test/stripe/api_resource_test.rb @@ -511,7 +511,7 @@ class NestedTestAPIResource < APIResource end end - context "#make_request_returning_stripe_object" do + context "#request_stripe_object" do should "make requests appropriately" should "update attributes in-place when it returns the same thing" should "instantiate a new object of the appropriate class when it is different than the host class" From 17d689a9a802f97d4cc524102657a84109cdb24c Mon Sep 17 00:00:00 2001 From: Alex Rattray Date: Tue, 6 Aug 2019 18:19:41 -0700 Subject: [PATCH 6/6] Flesh out tests --- test/stripe/api_resource_test.rb | 77 ++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/test/stripe/api_resource_test.rb b/test/stripe/api_resource_test.rb index c118cf8b4..13c0e06ba 100644 --- a/test/stripe/api_resource_test.rb +++ b/test/stripe/api_resource_test.rb @@ -512,9 +512,80 @@ class NestedTestAPIResource < APIResource end context "#request_stripe_object" do - should "make requests appropriately" - should "update attributes in-place when it returns the same thing" - should "instantiate a new object of the appropriate class when it is different than the host class" + class HelloTestAPIResource < APIResource + OBJECT_NAME = "hello".freeze + def say_hello(params = {}, opts = {}) + request_stripe_object( + method: :post, + path: resource_url + "/say", + params: params, + opts: opts + ) + end + end + + setup do + Util.instance_variable_set( + :@object_classes, + Stripe::ObjectTypes.object_names_to_classes.merge( + "hello" => HelloTestAPIResource + ) + ) + end + teardown do + Util.class.instance_variable_set(:@object_classes, Stripe::ObjectTypes.object_names_to_classes) + end + + should "make requests appropriately" do + stub_request(:post, "#{Stripe.api_base}/v1/hellos/hi_123/say") + .with(body: { foo: "bar" }, headers: { "Stripe-Account" => "acct_hi" }) + .to_return(body: JSON.generate("object" => "hello")) + + hello = HelloTestAPIResource.new(id: "hi_123") + hello.say_hello({ foo: "bar" }, stripe_account: "acct_hi") + end + + should "update attributes in-place when it returns the same thing" do + stub_request(:post, "#{Stripe.api_base}/v1/hellos/hi_123/say") + .to_return(body: JSON.generate("object" => "hello", "additional" => "attribute")) + + hello = HelloTestAPIResource.new(id: "hi_123") + hello.unsaved = "a value" + new_hello = hello.say_hello + + # Doesn't matter if you use the return variable or the instance. + assert_equal(hello, new_hello) + + # It updates new attributes in-place. + assert_equal("attribute", hello.additional) + + # It removes unsaved attributes, but at least lets you know about them. + e = assert_raises(NoMethodError) { hello.unsaved } + assert_match("The 'unsaved' attribute was set in the past", e.message) + end + + should "instantiate a new object of the appropriate class when it is different than the host class" do + stub_request(:post, "#{Stripe.api_base}/v1/hellos/hi_123/say") + .to_return(body: JSON.generate("object" => "goodbye", "additional" => "attribute")) + + hello = HelloTestAPIResource.new(id: "hi_123") + hello.unsaved = "a value" + new_goodbye = hello.say_hello + + # The returned value and the instance are different objects. + refute_equal(new_goodbye, hello) + + # The returned value has stuff from the server. + assert_equal("attribute", new_goodbye.additional) + assert_equal("goodbye", new_goodbye.object) + + # You instance doesn't have stuff from the server. + e = assert_raises(NoMethodError) { hello.additional } + refute_match(/was set in the past/, e.message) + + # The instance preserves unset attributes on the original instance (not sure this is good behavior?) + assert_equal("a value", hello.unsaved) + end end @@fixtures = {} # rubocop:disable Style/ClassVars