stripe-ruby/lib/stripe/api_resource.rb
Brandur c796958516 Generalize saving nested resources
Since #433, saving API resources nested under other API resources has
not been the default. Instead, any instances where this should occur
have been special cased with specific method implementations that would
set the `#save_with_parent` flag when a field is written.

This ended up causing some problems because as seen in #457, because
places that we need to do this aren't well vetted, some were forgotten.

This makes implementation of new fields that need this behavior simpler
by implementing a `.save_nested_resource` metraprogramming method on the
`APIResource` class. This can be called as necessary by any concrete API
resource implementations.

We replace existing implementatinos and also add one to `Subscription`,
which had previously been suffering from a similar problem where its
`#source` had not received a special case.
2016-08-30 11:52:16 -07:00

69 lines
2.4 KiB
Ruby

module Stripe
class APIResource < StripeObject
include Stripe::APIOperations::Request
# A flag that can be set a behavior that will cause this resource to be
# encoded and sent up along with an update of its parent resource. This is
# usually not desirable because resources are updated individually on their
# own endpoints, but there are certain cases, replacing a customer's source
# for example, where this is allowed.
attr_accessor :save_with_parent
def self.class_name
self.name.split('::')[-1]
end
def self.resource_url
if self == APIResource
raise NotImplementedError.new('APIResource is an abstract class. You should perform actions on its subclasses (Charge, Customer, etc.)')
end
"/v1/#{CGI.escape(class_name.downcase)}s"
end
# A metaprogramming call that specifies that a field of a resource can be
# its own type of API resource (say a nested card under an account for
# example), and if that resource is set, it should be transmitted to the
# API on a create or update. Doing so is not the default behavior because
# API resources should normally be persisted on their own RESTful
# endpoints.
def self.save_nested_resource(name)
define_method(:"#{name}=") do |value|
super(value)
# The parent setter will perform certain useful operations like
# converting to an APIResource if appropriate. Refresh our argument
# value to whatever it mutated to.
value = send(name)
# Note that the value may be subresource, but could also be a scalar
# (like a tokenized card's ID for example), so we check the type before
# setting #save_with_parent here.
if value.is_a?(APIResource)
value.save_with_parent = true
end
value
end
end
def resource_url
unless id = self['id']
raise InvalidRequestError.new("Could not determine which URL to request: #{self.class} instance has invalid ID: #{id.inspect}", 'id')
end
"#{self.class.resource_url}/#{CGI.escape(id)}"
end
def refresh
response, opts = request(:get, resource_url, @retrieve_params)
initialize_from(response, opts)
end
def self.retrieve(id, opts={})
opts = Util.normalize_opts(opts)
instance = self.new(id, opts)
instance.refresh
instance
end
end
end