provider_kit 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +129 -0
- data/Rakefile +11 -0
- data/lib/generators/provider/USAGE +10 -0
- data/lib/generators/provider/provider_generator.rb +55 -0
- data/lib/generators/provider/templates/capability.rb.tt +11 -0
- data/lib/generators/provider/templates/context.rb.tt +12 -0
- data/lib/generators/provider/templates/provider.rb.tt +13 -0
- data/lib/generators/provider/templates/spec.rb.tt +34 -0
- data/lib/provider_kit/buildable.rb +16 -0
- data/lib/provider_kit/callbacks.rb +33 -0
- data/lib/provider_kit/capability.rb +121 -0
- data/lib/provider_kit/capability_extension.rb +22 -0
- data/lib/provider_kit/capable.rb +101 -0
- data/lib/provider_kit/context.rb +58 -0
- data/lib/provider_kit/encrypted_settings.rb +47 -0
- data/lib/provider_kit/encryptor.rb +32 -0
- data/lib/provider_kit/engine.rb +20 -0
- data/lib/provider_kit/exceptions.rb +12 -0
- data/lib/provider_kit/execution.rb +40 -0
- data/lib/provider_kit/json_client.rb +81 -0
- data/lib/provider_kit/json_request.rb +160 -0
- data/lib/provider_kit/logging.rb +73 -0
- data/lib/provider_kit/provideable.rb +40 -0
- data/lib/provider_kit/provider.rb +62 -0
- data/lib/provider_kit/provider_attribute.rb +99 -0
- data/lib/provider_kit/registerable.rb +52 -0
- data/lib/provider_kit/registration.rb +88 -0
- data/lib/provider_kit/settings.rb +169 -0
- data/lib/provider_kit/version.rb +15 -0
- data/lib/provider_kit.rb +61 -0
- metadata +205 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ba514d53e0d9f91bec62dbe6ff0687b73038ae223a19f266a6a11321fda19b54
|
4
|
+
data.tar.gz: 33ab21762119c6ca00ce6951fde0bedd45f6c9e719c4b175478592df47c7aadf
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e16ff38a23a7cb98796299f1259f009d94fb5ae2b28cf6db850e38b3f58f6a0ab89eec170d25a1b537569842497694ba74fb0fd660d91df609fc3b573c578675
|
7
|
+
data.tar.gz: 00ed090c391619a04d47dc39ca56d42f9bbdef1742bfa4ced62e009624508224b71e7677f4cac17a1c33e9c16b36e544c1c75b6ad07d35d1610b0b92ce80d678
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 John D. Tornow
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
# ProviderKit
|
2
|
+
|
3
|
+
ProviderKit is a simple utility for creating an abstraction layer between various third-party services (usually via API) and your application's code.
|
4
|
+
|
5
|
+
It contains some conventions and utilities for making this easier. Think of it as a way to create simple wrappers on top of third-party APIs, but also a way to make those consistent across 'providers' of the same type.
|
6
|
+
|
7
|
+
For example, say you support payments from multiple third-party services like Stripe or PayPal. In each of those services we'll need to handle customers, subscriptions, and payments. Here's how we could interface with that using ProviderKit:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
# customer 1 uses Stripe
|
11
|
+
user = User.create(provider: :stripe, ...)
|
12
|
+
user.provider.customers.get(id: user.customer_key) # => { id: "cus_Nff" }
|
13
|
+
|
14
|
+
# customer 2 uses PayPal
|
15
|
+
user2 = User.create(provider: :paypal)
|
16
|
+
user.provider.customers.get(id: user.customer_key) # => { id: "abc-123" }
|
17
|
+
```
|
18
|
+
|
19
|
+
Then anywhere in your code where you have business logic that requires a customer the code is consistent, but each individual provider contains its custom code to deal with that provider's details. Here's what the 'get customer' logic could look like inside of a provider's configuration:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
module StripeProvider
|
23
|
+
class Customers
|
24
|
+
def get(id:)
|
25
|
+
stripe_customer = Stripe::Customer.retrieve(id)
|
26
|
+
|
27
|
+
{
|
28
|
+
id: stripe_customer.id,
|
29
|
+
# ... etc
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
```
|
35
|
+
|
36
|
+
This library was originally developed for this exact use case, but we use it for a bunch of different provider types including linking multiple CRM tools, ecommerce providers, and many more.
|
37
|
+
|
38
|
+
## Generator
|
39
|
+
|
40
|
+
Use the generator to create a new provider:
|
41
|
+
|
42
|
+
```bash
|
43
|
+
rails g provider my_provider_name
|
44
|
+
```
|
45
|
+
|
46
|
+
## Capabilities
|
47
|
+
|
48
|
+
Providers have 'capabilities' to determine what types of things they can do. Each provider registers those capabilities within the provider setup itself. For example, the `Customers` class above would likely be registered within the `StripeProvider` like this:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
module StripeProvider
|
52
|
+
class Provider
|
53
|
+
|
54
|
+
include ProviderKit::Capable
|
55
|
+
|
56
|
+
capable_of :subscriptions, with: Customers
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
```
|
61
|
+
|
62
|
+
This registration offers some nice reflection methods, so you can determine at runtime which features a particular provider allows. For example, Stripe has a customers [search endpoint](https://docs.stripe.com/api/customers/search) but PayPal does not. So we can build the search feature into `StripeProvider`:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
module StripeProvider
|
66
|
+
class Customers
|
67
|
+
def search(query:)
|
68
|
+
customers = Stripe::Customer.search(query:)
|
69
|
+
|
70
|
+
customers.map do |customer|
|
71
|
+
{
|
72
|
+
id: customer.id,
|
73
|
+
name: customer.name,
|
74
|
+
# ...
|
75
|
+
}
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
```
|
81
|
+
|
82
|
+
Then we can check it at runtime to make sure the provider is capable of this action:
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
if user.provider.capable_of?(:customers, :search)
|
86
|
+
user.customers.search(query: 'john@example.com')
|
87
|
+
else
|
88
|
+
[]
|
89
|
+
end
|
90
|
+
```
|
91
|
+
|
92
|
+
This is a primitive example but it's a powerful concept!
|
93
|
+
|
94
|
+
## Credentials
|
95
|
+
|
96
|
+
Rails credentials can be used within the context of a provider's capability methods. By convention, store each provider's credentials in this format:
|
97
|
+
|
98
|
+
```yaml
|
99
|
+
# rails credentials:edit
|
100
|
+
|
101
|
+
providers:
|
102
|
+
provider_name_here:
|
103
|
+
secret_key: abc123
|
104
|
+
|
105
|
+
stripe:
|
106
|
+
secret_key: sk_1234
|
107
|
+
```
|
108
|
+
|
109
|
+
Then they can be used anywhere in a provider:
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
module StripeProvider
|
113
|
+
class Customers
|
114
|
+
def get(id:)
|
115
|
+
# you probably should put this in an initializer instead
|
116
|
+
# so it doesn't need to be in every method,
|
117
|
+
# but it does work!
|
118
|
+
Stripe.api_key = credentials.secret_key
|
119
|
+
|
120
|
+
# ...
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
```
|
125
|
+
|
126
|
+
## A Work In Progress
|
127
|
+
|
128
|
+
This gem has been in production since 2019, so it's battle tested, but not very well spec-tested. It's an ongoing work in progress. If you find it helpful, go for it, I'm very open to contributions. But it's presented as-is, and probably won't be very actively maintained other than ensuring it works with new versions of Ruby and Rails.
|
129
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ProviderGenerator < Rails::Generators::NamedBase
|
4
|
+
|
5
|
+
source_root File.expand_path("templates", __dir__)
|
6
|
+
|
7
|
+
argument :attributes, type: :array, default: [], banner: "capabilities"
|
8
|
+
|
9
|
+
class_option :type, type: :string, desc: "Assign a type to this provider"
|
10
|
+
class_option :types, type: :array, desc: "Assign multiple types to this provider"
|
11
|
+
|
12
|
+
def add_provider_initializer
|
13
|
+
if types.any?
|
14
|
+
insert_into_file "config/initializers/providers.rb", %Q(\nProviderKit.register :#{ provider_name }, class_name: "#{ class_name }::Provider", types: [ #{ types.map(&:inspect).join(", ") } ]\n)
|
15
|
+
else
|
16
|
+
insert_into_file "config/initializers/providers.rb", %Q(\nProviderKit.register :#{ provider_name }, class_name: "#{ class_name }::Provider"\n)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def create_provider_files
|
21
|
+
template "context.rb", File.join("app/providers/#{ singular_name }.rb")
|
22
|
+
template "provider.rb", File.join("app/providers/#{ singular_name }/provider.rb")
|
23
|
+
|
24
|
+
capabilities.each do |capability|
|
25
|
+
@capability = capability
|
26
|
+
template "capability.rb", File.join("app/providers/#{ singular_name }/capabilities/#{ capability.underscore }.rb")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def create_spec_file
|
31
|
+
template "spec.rb", File.join("spec/providers/#{ singular_name }_spec.rb")
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
attr_reader :capability
|
37
|
+
|
38
|
+
def capabilities
|
39
|
+
@capabilities ||= attributes_names.map do |c|
|
40
|
+
c.to_s.classify.pluralize
|
41
|
+
end.sort
|
42
|
+
end
|
43
|
+
|
44
|
+
def provider_name
|
45
|
+
singular_name.to_s.gsub("_", "")
|
46
|
+
end
|
47
|
+
|
48
|
+
def types
|
49
|
+
@types ||= begin
|
50
|
+
types = options[:type].presence || options[:types].presence || []
|
51
|
+
types = Array(types).flatten.compact.map(&:to_sym)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module <%= class_name %>
|
4
|
+
class Provider
|
5
|
+
|
6
|
+
include ProviderKit::Capable
|
7
|
+
|
8
|
+
# Use credentials for the Current.account when set
|
9
|
+
# include AccountCredentiable
|
10
|
+
<% capabilities.each do |capability| %>
|
11
|
+
capable_of :<%= capability.underscore %>, with: Capabilities::<%= capability %><% end %>
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "rails_helper"
|
2
|
+
|
3
|
+
RSpec.describe <%= class_name %> do
|
4
|
+
|
5
|
+
describe ".provider" do
|
6
|
+
it "is the provider" do
|
7
|
+
expect(<%= class_name %>.provider).to be_kind_of(<%= class_name %>::Provider)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe ".provider_key" do
|
12
|
+
it "is :<%= provider_name %>" do
|
13
|
+
expect(<%= class_name %>.provider_key).to eq(:<%= provider_name %>)
|
14
|
+
end
|
15
|
+
end<% if types.any? %>
|
16
|
+
|
17
|
+
describe ".provider_types" do<% types.each do |type| %>
|
18
|
+
it "includes <%= type %>" do
|
19
|
+
expect(<%= class_name %>.provider_types).to include(:<%= type %>)
|
20
|
+
expect(ProviderKit.providers(type: :<%= type %>)).to include(<%= class_name %>::Provider)
|
21
|
+
end
|
22
|
+
<% end %>
|
23
|
+
end<% end %><% capabilities.each do |capability| %>
|
24
|
+
|
25
|
+
context ".<%= capability.underscore %>" do
|
26
|
+
it "is a capability" do
|
27
|
+
expect(<%= class_name %>.capable_of?(:<%= capability.underscore %>)).to eq(true)
|
28
|
+
expect(<%= class_name %>.<%= capability.underscore %>).to be_kind_of(ProviderKit::Capability)
|
29
|
+
end
|
30
|
+
|
31
|
+
pending "fill in <%= class_name %>.<%= capability.underscore %> methods here"
|
32
|
+
end<% end %>
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ProviderKit
|
4
|
+
# Designed to add provider utility methods to module
|
5
|
+
module Buildable
|
6
|
+
|
7
|
+
# ProviderKit.with(:stripe).subscriptions.get(id: "123")
|
8
|
+
def with(key_or_record)
|
9
|
+
provider = ProviderKit::Provider.new(key_or_record)
|
10
|
+
return nil unless provider.present?
|
11
|
+
|
12
|
+
provider.provider_instance
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/callbacks"
|
4
|
+
|
5
|
+
module ProviderKit
|
6
|
+
# Add callback behavior to services for logging and lifecycle checks
|
7
|
+
module Callbacks
|
8
|
+
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
include ActiveSupport::Callbacks
|
11
|
+
|
12
|
+
included do
|
13
|
+
define_callbacks :perform
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
|
18
|
+
def after_perform(*filters, &blk)
|
19
|
+
set_callback(:perform, :after, *filters, &blk)
|
20
|
+
end
|
21
|
+
|
22
|
+
def around_perform(*filters, &blk)
|
23
|
+
set_callback(:perform, :around, *filters, &blk)
|
24
|
+
end
|
25
|
+
|
26
|
+
def before_perform(*filters, &blk)
|
27
|
+
set_callback(:perform, :before, *filters, &blk)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ProviderKit
|
4
|
+
class Capability
|
5
|
+
|
6
|
+
attr_reader :key, :provider
|
7
|
+
|
8
|
+
def initialize(key, target_klass, provider: nil, **options)
|
9
|
+
@key = key
|
10
|
+
@target_klass = target_klass.to_s
|
11
|
+
@options = options || {}
|
12
|
+
@provider = provider
|
13
|
+
@context = options.delete(:context)
|
14
|
+
end
|
15
|
+
|
16
|
+
def callable?(method_name)
|
17
|
+
callable_methods.include?(method_name.to_s)
|
18
|
+
end
|
19
|
+
|
20
|
+
def method_missing(method_name, *args, **kwargs)
|
21
|
+
case method_name.to_s
|
22
|
+
when /(.+)\?$/
|
23
|
+
call($1, *args, **kwargs) == true
|
24
|
+
else
|
25
|
+
call(method_name, *args, **kwargs)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def with_context(provider: @provider, **context)
|
30
|
+
Capability.new(
|
31
|
+
key,
|
32
|
+
@target_klass,
|
33
|
+
provider: provider.with_context(**context),
|
34
|
+
**options.merge(context:)
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
attr_reader :context
|
41
|
+
attr_reader :options
|
42
|
+
|
43
|
+
def call(method_name, *args, **kwargs)
|
44
|
+
call_target = target
|
45
|
+
return nil unless call_target
|
46
|
+
return nil unless callable?(method_name)
|
47
|
+
|
48
|
+
raw_response = call_target.public_send(method_name, *args, **kwargs)
|
49
|
+
|
50
|
+
case raw_response
|
51
|
+
when Hash
|
52
|
+
format_response_hash(raw_response)
|
53
|
+
when Array
|
54
|
+
raw_response.map { |h| format_response_hash(h) }
|
55
|
+
else
|
56
|
+
raw_response
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Creates a safe list of methods that can be called on the target
|
61
|
+
# Only those methods that were defined in the class are valid
|
62
|
+
def callable_methods
|
63
|
+
@callable_methods ||= begin
|
64
|
+
blacklist = Object.public_instance_methods
|
65
|
+
whitelist = target.class.public_instance_methods - blacklist
|
66
|
+
|
67
|
+
Set.new(whitelist.map(&:to_s))
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def format_response_hash(raw_response)
|
72
|
+
return raw_response unless raw_response.is_a?(Hash)
|
73
|
+
|
74
|
+
klass = ProviderKit.config.capability_response_format
|
75
|
+
klass = Settings if klass == :object
|
76
|
+
|
77
|
+
if klass.is_a?(Class)
|
78
|
+
klass.new(raw_response)
|
79
|
+
else
|
80
|
+
raw_response
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def prepare_target
|
85
|
+
klass = @target_klass
|
86
|
+
|
87
|
+
if String === klass
|
88
|
+
klass = klass.safe_constantize
|
89
|
+
end
|
90
|
+
|
91
|
+
unless klass.respond_to?(:new)
|
92
|
+
raise ProviderKit::InvalidCapability.new("Invalid capability class: #{ @target_klass }")
|
93
|
+
end
|
94
|
+
|
95
|
+
klass
|
96
|
+
end
|
97
|
+
|
98
|
+
def target_class
|
99
|
+
@target = nil unless Rails.application.config.cache_classes
|
100
|
+
@target ||= prepare_target
|
101
|
+
end
|
102
|
+
|
103
|
+
def target
|
104
|
+
unless target_class.included_modules.include?(CapabilityExtension)
|
105
|
+
target_class.send(:include, CapabilityExtension)
|
106
|
+
end
|
107
|
+
|
108
|
+
instance = if options.present?
|
109
|
+
target_class.new(options)
|
110
|
+
else
|
111
|
+
target_class.new
|
112
|
+
end
|
113
|
+
|
114
|
+
instance.instance_variable_set("@context", context)
|
115
|
+
instance.instance_variable_set("@provider", provider)
|
116
|
+
|
117
|
+
instance
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ProviderKit
|
4
|
+
module CapabilityExtension
|
5
|
+
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
attr_reader :provider
|
11
|
+
attr_reader :context
|
12
|
+
|
13
|
+
def config
|
14
|
+
provider&.config
|
15
|
+
end
|
16
|
+
|
17
|
+
def credentials
|
18
|
+
provider&.credentials
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ProviderKit
|
4
|
+
# Mixin for a model to make it have the provider methods
|
5
|
+
module Capable
|
6
|
+
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
attr_accessor :context
|
11
|
+
|
12
|
+
mattr_accessor :capabilities
|
13
|
+
mattr_accessor :key
|
14
|
+
|
15
|
+
self.capabilities ||= {}
|
16
|
+
|
17
|
+
delegate :capable_of?, :perform_capability, to: self
|
18
|
+
end
|
19
|
+
|
20
|
+
def config
|
21
|
+
registration&.config
|
22
|
+
end
|
23
|
+
|
24
|
+
def credentials
|
25
|
+
registration&.credentials
|
26
|
+
end
|
27
|
+
|
28
|
+
def display_key
|
29
|
+
registration&.display_key
|
30
|
+
end
|
31
|
+
|
32
|
+
def method_missing(method_name, *args, **kwargs)
|
33
|
+
if capable_of?(method_name)
|
34
|
+
if kwargs.present?
|
35
|
+
perform_capability(method_name).with_context(provider: self, **kwargs)
|
36
|
+
else
|
37
|
+
perform_capability(method_name).with_context(provider: self, **(context || {}))
|
38
|
+
end
|
39
|
+
else
|
40
|
+
super
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def provider_key
|
45
|
+
key
|
46
|
+
end
|
47
|
+
|
48
|
+
def with_context(**context)
|
49
|
+
self.context = context
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
class_methods do
|
54
|
+
|
55
|
+
def capable_of(namespace, with:, **options)
|
56
|
+
self.capabilities[namespace.to_sym] = Capability.new(namespace, with, **options)
|
57
|
+
end
|
58
|
+
|
59
|
+
def capable_of?(thing, method_name = nil)
|
60
|
+
return false unless self.capabilities.has_key?(thing.to_sym)
|
61
|
+
return true unless method_name.present?
|
62
|
+
|
63
|
+
capability = capabilities[thing]
|
64
|
+
return false unless capability
|
65
|
+
|
66
|
+
capability.callable?(method_name)
|
67
|
+
end
|
68
|
+
|
69
|
+
def method_missing(method_name, *args, **kwargs)
|
70
|
+
if capable_of?(method_name)
|
71
|
+
perform_capability(method_name, *args, **kwargs)
|
72
|
+
else
|
73
|
+
super
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def perform_capability(name, **kwargs)
|
78
|
+
capabilities[name].with_context(provider:, **kwargs)
|
79
|
+
end
|
80
|
+
|
81
|
+
def provider
|
82
|
+
provider_key = key.presence || module_parent&.provider_key
|
83
|
+
ProviderKit.with(provider_key)
|
84
|
+
end
|
85
|
+
|
86
|
+
def with_context(**context)
|
87
|
+
instance = new()
|
88
|
+
instance.context = context
|
89
|
+
instance
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def registration
|
97
|
+
@registration ||= ProviderKit.registrations[key]
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ProviderKit
|
4
|
+
# Designed to add provider utility methods to module
|
5
|
+
module Context
|
6
|
+
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
class_methods do
|
10
|
+
|
11
|
+
def capabilities
|
12
|
+
provider.capabilities.keys
|
13
|
+
end
|
14
|
+
|
15
|
+
def capable_of?(thing, method_name = nil)
|
16
|
+
provider.capable_of?(thing, method_name)
|
17
|
+
end
|
18
|
+
|
19
|
+
def config
|
20
|
+
provider_registration.config
|
21
|
+
end
|
22
|
+
|
23
|
+
def credentials
|
24
|
+
provider_registration.credentials
|
25
|
+
end
|
26
|
+
|
27
|
+
def method_missing(method_name, **kwargs)
|
28
|
+
if capable_of?(method_name)
|
29
|
+
return provider.perform_capability(method_name, **kwargs)
|
30
|
+
end
|
31
|
+
|
32
|
+
super
|
33
|
+
end
|
34
|
+
|
35
|
+
def provider
|
36
|
+
@provider ||= ProviderKit.with(provider_key)
|
37
|
+
end
|
38
|
+
|
39
|
+
def provider_key
|
40
|
+
@provider_key ||= self.to_s.underscore.gsub(/_provider$/, "").to_sym
|
41
|
+
end
|
42
|
+
|
43
|
+
def provider_registration
|
44
|
+
ProviderKit.registrations[provider_key]
|
45
|
+
end
|
46
|
+
|
47
|
+
def provider_types
|
48
|
+
provider_registration.types
|
49
|
+
end
|
50
|
+
|
51
|
+
def with_context(**kwargs)
|
52
|
+
provider.with_context(**kwargs)
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|