-
Notifications
You must be signed in to change notification settings - Fork 552
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add API resource instance methods to StripeClient
This change introduces a proof-of-concept to add convenience methods to access API resources through a StripeClient for per-client configuration. This first iteration only allows for the `api_key` to be configured but can be extended to allow other options such as `stripe_version`, which should solve #872. The primary workhorse for this feature is a new module called `Stripe::ClientAPIOperations` that defines instance methods on `StripeClient` when it is included. A `ClientProxy` is used to send any method calls to an API resource with the instantiated client injected. There are a few noteworthy aspects of this approach: - Many resources are namespaced, which introduces a unique challenge when it comes to method chaining calls (e.g. client.issuing.authorizations). In order to handle those cases, we create a `ClientProxy` object for the root namespace (e.g., "issuing") and define all resource methods (e.g. "authorizations") at once to avoid re-defining the proxy object when there are multiple resources per namespace. - Sigma deviates from other namespaced API resources and does not have an `OBJECT_NAME` separated by a period. We account for that nuance directly. - `method_missing` is substantially slower than direct calls. Therefore, methods are defined where possible but `method_missing` is still used at the last step when delegating resource methods to the actual resource.
- Loading branch information
1 parent
0620436
commit 4a7ca18
Showing
17 changed files
with
577 additions
and
82 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
# frozen_string_literal: true | ||
|
||
module Stripe | ||
# Define instance methods on the including class (i.e. StripeClient) | ||
# to access API resources. | ||
module ClientAPIOperations | ||
# Proxy object to inject the client into API resources. When included, | ||
# all resources are defined as singleton methods on the client in the | ||
# plural form (e.g. Stripe::Account => client.accounts). | ||
class ClientProxy | ||
def initialize(client:, resource: nil) | ||
@client = client | ||
@resource = resource | ||
end | ||
|
||
attr_reader :client | ||
|
||
def with_client(client) | ||
@client = client | ||
self | ||
end | ||
|
||
# Used to either send a method to the API resource or the nested | ||
# ClientProxy when a resource is namespaced. Since the method signature | ||
# differs when operating on a collection versus a singular resource, it's | ||
# required to perform introspection on the parameters to respect any | ||
# passed in options or overrides. | ||
def method_missing(method, *args) | ||
super unless @resource | ||
opts_pos = @resource.method(method).parameters.index(%i[opt opts]) | ||
args[opts_pos] = { client: @client }.merge(args[opts_pos] || {}) | ||
|
||
@resource.public_send(method, *args) || super | ||
end | ||
|
||
def respond_to_missing?(symbol, include_private = false) | ||
super unless @resource | ||
@resource.respond_to?(symbol) || super | ||
end | ||
end | ||
|
||
def self.included(base) | ||
base.class_eval do | ||
# Sigma, unlike other namespaced API objects, is not separated by a | ||
# period so we modify the object name to follow the expected convention. | ||
api_resources = Stripe::Util.api_object_classes | ||
sigma_class = api_resources.delete("scheduled_query_run") | ||
api_resources["sigma.scheduled_query_run"] = sigma_class | ||
|
||
# Group namespaces that have mutiple resourses | ||
grouped_resources = api_resources.group_by do |key, _| | ||
key.include?(".") ? key.split(".").first : key | ||
end | ||
|
||
grouped_resources.each do |resource_namespace, resources| | ||
# Namespace resource names are separated with a period by convention. | ||
if resources[0][0].include?(".") | ||
|
||
# Defines the methods required for chaining calls for resources that | ||
# are namespaced. A proxy object is created so that all resource | ||
# methods can be defined at once. | ||
# | ||
# NOTE: At some point, a smarter pluralization scheme may be | ||
# necessary for resource names with complex pluralization rules. | ||
proxy = ClientProxy.new(client: nil) | ||
resources.each do |resource_name, resource_class| | ||
method_name = resource_name.split(".").last | ||
proxy.define_singleton_method("#{method_name}s") do | ||
ClientProxy.new(client: proxy.client, resource: resource_class) | ||
end | ||
end | ||
|
||
# Defines the first method for resources that are namespaced. By | ||
# convention these methods are singular. A proxy object is returned | ||
# so that the client can be injected along the method chain. | ||
define_method(resource_namespace) do | ||
proxy.with_client(self) | ||
end | ||
else | ||
# Defines plural methods for non-namespaced resources | ||
define_method("#{resource_namespace}s".to_sym) do | ||
ClientProxy.new(client: self, resource: resources[0][1]) | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.