Skip to content
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 #921

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion lib/stripe.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
require "stripe/util"
require "stripe/connection_manager"
require "stripe/multipart_encoder"
require "stripe/stripe_client"
require "stripe/stripe_object"
require "stripe/stripe_response"
require "stripe/list_object"
Expand All @@ -40,10 +39,15 @@
require "stripe/singleton_api_resource"
require "stripe/webhook"
require "stripe/stripe_configuration"
require "stripe/client_api_operations"

# Named API resources
require "stripe/resources"

# StripeClient requires API Resources to be loaded
# due to dynamic methods defined by ClientAPIOperations
require "stripe/stripe_client"

# OAuth
require "stripe/oauth"

Expand All @@ -62,6 +66,8 @@ module Stripe
class << self
extend Forwardable

attr_reader :configuration

# User configurable options
def_delegators :@configuration, :api_key, :api_key=
def_delegators :@configuration, :api_version, :api_version=
Expand Down
1 change: 1 addition & 0 deletions lib/stripe/api_operations/list.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ def list(filters = {}, opts = {})
resp, opts = execute_resource_request(:get, resource_url, filters, opts)
obj = ListObject.construct_from(resp.data, opts)

filters ||= {}
# set filters so that we can fetch the same limit, expansions, and
# predicates when accessing the next and previous pages
obj.filters = filters.dup
Expand Down
91 changes: 91 additions & 0 deletions lib/stripe/client_api_operations.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# 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])
normalized_opts = Stripe::Util.normalize_opts(args[opts_pos] || {})

args[opts_pos] = { client: @client }.merge(normalized_opts)

@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
joeltaylor marked this conversation as resolved.
Show resolved Hide resolved
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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

100% open to other naming for all of these concepts.

17 changes: 9 additions & 8 deletions lib/stripe/connection_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ class ConnectionManager
# garbage collected or not.
attr_reader :last_used

def initialize
def initialize(config = Stripe.configuration)
@config = config
@active_connections = {}
@last_used = Util.monotonic_time

Expand Down Expand Up @@ -117,17 +118,17 @@ def execute_request(method, uri, body: nil, headers: nil, query: nil)
# reused Go's default for `DefaultTransport`.
connection.keep_alive_timeout = 30

connection.open_timeout = Stripe.open_timeout
connection.read_timeout = Stripe.read_timeout
if connection.respond_to?(:write_timeout=)
connection.write_timeout = Stripe.write_timeout
connection.write_timeout = @config.write_timeout
end
connection.open_timeout = @config.open_timeout
connection.read_timeout = @config.read_timeout

connection.use_ssl = uri.scheme == "https"

if Stripe.verify_ssl_certs
if @config.verify_ssl_certs
connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
connection.cert_store = Stripe.ca_store
connection.cert_store = @config.ca_store
else
connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
warn_ssl_verify_none
Expand All @@ -141,10 +142,10 @@ def execute_request(method, uri, body: nil, headers: nil, query: nil)
# out those pieces to make passing them into a new connection a little less
# ugly.
private def proxy_parts
if Stripe.proxy.nil?
if @config.proxy.nil?
[nil, nil, nil, nil]
else
u = URI.parse(Stripe.proxy)
u = URI.parse(@config.proxy)
[u.host, u.port, u.user, u.password]
end
end
Expand Down
8 changes: 5 additions & 3 deletions lib/stripe/oauth.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ module OAuthOperations

def self.execute_resource_request(method, url, params, opts)
opts = Util.normalize_opts(opts)
opts[:client] ||= StripeClient.active_client
opts[:api_base] ||= Stripe.connect_base
opts[:client] ||= params[:client] || StripeClient.active_client
opts[:api_base] ||= opts[:client].config.connect_base

params.delete(:client)
super(method, url, params, opts)
end
end
Expand All @@ -29,7 +30,8 @@ def self.get_client_id(params = {})
end

def self.authorize_url(params = {}, opts = {})
base = opts[:connect_base] || Stripe.connect_base
client = params[:client] || StripeClient.active_client
base = opts[:connect_base] || client.config.connect_base

path = "/oauth/authorize"
path = "/express" + path if opts[:express]
Expand Down
7 changes: 4 additions & 3 deletions lib/stripe/object_types.rb
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
{
Account::OBJECT_NAME => Account,
AccountLink::OBJECT_NAME => AccountLink,
AlipayAccount::OBJECT_NAME => AlipayAccount,
Expand Down Expand Up @@ -97,5 +99,4 @@ def self.object_names_to_classes
end
end
end

# rubocop:enable Metrics/MethodLength
1 change: 1 addition & 0 deletions lib/stripe/resources/account.rb
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ def deauthorize(client_id = nil, opts = {})
client_id: client_id,
stripe_user_id: id,
}
opts = @opts.merge(Util.normalize_opts(opts))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#deauthorize doesn't have access to @opts, which may contain the client option we need to override global configuration.

OAuth.deauthorize(params, opts)
end

Expand Down
3 changes: 2 additions & 1 deletion lib/stripe/resources/file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ def self.create(params = {}, opts = {})
end
end

config = opts[:client]&.config || Stripe.configuration
opts = {
api_base: Stripe.uploads_base,
api_base: config.uploads_base,
content_type: MultipartEncoder::MULTIPART_FORM_DATA,
}.merge(Util.normalize_opts(opts))
super
Expand Down
Loading