-
Notifications
You must be signed in to change notification settings - Fork 555
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add API resource instance methods to StripeClient #954
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
# 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. | ||
def method_missing(method, *args) | ||
super unless @resource.respond_to?(method) | ||
|
||
update_args_with_client!(method, args) | ||
|
||
@resource.public_send(method, *args) | ||
end | ||
|
||
def respond_to_missing?(symbol, include_private = false) | ||
super unless @resource | ||
@resource.respond_to?(symbol) || super | ||
end | ||
|
||
# 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. | ||
# | ||
# Two noteworthy caveats: | ||
# 1) Does not merge into methods that use `_opts` as that means | ||
# the param is unused. | ||
# 2) Preserves incorrect options (e.g. passing nil) so that APIResource | ||
# can handle errors. | ||
def update_args_with_client!(method, args) | ||
opts_pos = @resource.method(method).parameters.index(%i[opt opts]) | ||
|
||
return unless opts_pos | ||
|
||
opts = opts_pos >= args.length ? {} : args[opts_pos] | ||
|
||
normalized_opts = Stripe::Util.normalize_opts(opts) | ||
args[opts_pos] = { client: @client }.merge(normalized_opts) | ||
end | ||
Comment on lines
+47
to
+56
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The primary regressions that I found during the first PR all had to do with nuances when it came time to merge the options. I was able to identify the scenarios and lock them in under test: https://github.com/stripe/stripe-ruby/pull/954/files#diff-50b82fc5b424d8d4c21c2ddd6c67485e40fbc76fdf5cc1e4320949a4ad20fb62R56 |
||
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 | ||
|
||
# Update `invoiceitems` to match snake case convention | ||
invoice_item_class = api_resources.delete("invoiceitem") | ||
api_resources["invoice_item"] = invoice_item_class | ||
Comment on lines
+67
to
+69
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @brandur-stripe Here's the update to turn |
||
|
||
# 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. | ||
proxy = ClientProxy.new(client: nil) | ||
resources.each do |resource_name, resource_class| | ||
method_name = resource_name.split(".").last | ||
proxy.define_singleton_method(method_name) 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.to_sym) do | ||
ClientProxy.new(client: self, resource: resources[0][1]) | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,17 @@ | ||
# frozen_string_literal: true | ||
|
||
# rubocop:disable Metrics/MethodLength | ||
|
||
module Stripe | ||
module ObjectTypes | ||
def self.object_names_to_classes | ||
{ | ||
# data structures | ||
ListObject::OBJECT_NAME => ListObject, | ||
}.merge(api_object_names_to_classes) | ||
end | ||
|
||
# business objects | ||
def self.api_object_names_to_classes | ||
{ | ||
Comment on lines
+10
to
+14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OOC, why is this change necessary? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ob-stripe it was done so I could get a list of API resources to dynamically define the methods here: https://github.com/stripe/stripe-ruby/pull/954/files#diff-992f231b0add61aeb46ab11fb68529936809a99a74ed9f1c1755a4b01f57fb58R63 Certainly open to other suggestions! |
||
Account::OBJECT_NAME => Account, | ||
AccountLink::OBJECT_NAME => AccountLink, | ||
AlipayAccount::OBJECT_NAME => AlipayAccount, | ||
|
@@ -98,5 +100,4 @@ def self.object_names_to_classes | |
end | ||
end | ||
end | ||
|
||
# rubocop:enable Metrics/MethodLength |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's possible for filters to be
nil
due to how we delegate methods to the API resources.