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
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ProviderKit
|
4
|
+
# Serializable object to read/write from account settings fields (encrypted)
|
5
|
+
class EncryptedSettings < Settings
|
6
|
+
|
7
|
+
def to_value
|
8
|
+
encrypt_data_for_storage
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.dump(data)
|
12
|
+
case data
|
13
|
+
when self
|
14
|
+
data.to_value
|
15
|
+
else
|
16
|
+
new(data).to_value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.load(data)
|
21
|
+
new(data.presence)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def build_data(data)
|
27
|
+
if Hash === data
|
28
|
+
data
|
29
|
+
elsif decrypted = decrypt_data(data)
|
30
|
+
JSON.parse(decrypted)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def decrypt_data(encrypted_data)
|
35
|
+
if encrypted_data.present?
|
36
|
+
ProviderKit::Encryptor.shared.decrypt(encrypted_data)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def encrypt_data_for_storage
|
41
|
+
if data.present?
|
42
|
+
ProviderKit::Encryptor.shared.encrypt(data.to_json)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ProviderKit
|
4
|
+
# Wrapper around ActiveSupport::MessageEncryptor with a shared credential key
|
5
|
+
class Encryptor
|
6
|
+
|
7
|
+
def decrypt(encrypted_value, purpose: :provider)
|
8
|
+
crypt.decrypt_and_verify(encrypted_value, purpose:)
|
9
|
+
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
10
|
+
nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def encrypt(raw_value, purpose: :provider)
|
14
|
+
crypt.encrypt_and_sign(raw_value, purpose:)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.shared
|
18
|
+
@shared ||= new
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def crypt
|
24
|
+
@crypt ||= ActiveSupport::MessageEncryptor.new(key)
|
25
|
+
end
|
26
|
+
|
27
|
+
def key
|
28
|
+
ProviderKit.config.credentials_key.byteslice(0, 32)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support"
|
4
|
+
|
5
|
+
module ProviderKit
|
6
|
+
class Engine < ::Rails::Engine
|
7
|
+
|
8
|
+
isolate_namespace ProviderKit
|
9
|
+
|
10
|
+
engine_name "provider_kit"
|
11
|
+
|
12
|
+
initializer "provider_kit.load_credentials_key" do |app|
|
13
|
+
ProviderKit.configure do |config|
|
14
|
+
credentials_key = ENV["PROVIDERKIT_KEY"].presence || Rails.application.credentials.secret_key_base.presence || Rails.application.credentials.secret_key_base
|
15
|
+
config.credentials_key ||= credentials_key
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/rescuable"
|
4
|
+
|
5
|
+
module ProviderKit
|
6
|
+
# Handles the execution of Tasks
|
7
|
+
#
|
8
|
+
# Includes callbacks for the perform method, so the following callbacks are available:
|
9
|
+
#
|
10
|
+
# * before_perform
|
11
|
+
# * around_perform
|
12
|
+
# * after_perform
|
13
|
+
#
|
14
|
+
# Also included is Rescuable, so an individual task can be rescued like a job:
|
15
|
+
#
|
16
|
+
# rescue_from InvalidProviderContext do
|
17
|
+
# logger.warn "Provider context was not found for this task"
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
module Execution
|
21
|
+
|
22
|
+
extend ActiveSupport::Concern
|
23
|
+
include ActiveSupport::Rescuable
|
24
|
+
|
25
|
+
def perform_now
|
26
|
+
run_callbacks :perform do
|
27
|
+
perform
|
28
|
+
end
|
29
|
+
rescue => exception
|
30
|
+
tag_logger(self.class.name, process_id) do
|
31
|
+
rescue_with_handler(exception) || raise
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def perform
|
36
|
+
fail NotImplementedError
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ProviderKit
|
4
|
+
# Generic interface for working with JSON-based APIs.
|
5
|
+
#
|
6
|
+
# This class is just a simple wrapper around net/http so we don't need
|
7
|
+
# to use a third-party networking library for basic stuff.
|
8
|
+
class JsonClient
|
9
|
+
|
10
|
+
DEFAULT_HEADERS = {
|
11
|
+
"Accept" => "application/json",
|
12
|
+
"User-Agent" => "ProviderKitBot/1.0 (+https://example.com)"
|
13
|
+
}
|
14
|
+
|
15
|
+
attr_reader :base_url
|
16
|
+
attr_reader :default_headers
|
17
|
+
attr_reader :default_params
|
18
|
+
attr_reader :default_mode
|
19
|
+
|
20
|
+
def initialize(base_url, **options)
|
21
|
+
@base_url = base_url
|
22
|
+
@default_headers = options[:headers].presence || DEFAULT_HEADERS
|
23
|
+
@default_params = options[:params].presence || {}
|
24
|
+
@default_mode = options[:mode].presence || :json
|
25
|
+
end
|
26
|
+
|
27
|
+
%w( get post patch put delete ).each do |http_method|
|
28
|
+
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
29
|
+
def #{ http_method }(path, params: {}, headers: {}, mode: nil)
|
30
|
+
req = request(path, method: :#{ http_method }, params: params, headers: headers, mode: mode)
|
31
|
+
req.json
|
32
|
+
end
|
33
|
+
CODE
|
34
|
+
end
|
35
|
+
|
36
|
+
def request(path, method: :get, params: {}, headers: {}, mode: nil)
|
37
|
+
path = "/#{ path }".gsub(%r{//}, "/")
|
38
|
+
url = "#{ base_url }#{ path }"
|
39
|
+
headers = default_headers.merge(headers)
|
40
|
+
params = default_params.merge(params)
|
41
|
+
mode = mode.presence || default_mode
|
42
|
+
|
43
|
+
JsonRequest.new(url, method:, params:, headers:, mode:)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
# Provide a generic log subscriber for basic request details.
|
49
|
+
#
|
50
|
+
# A more detailed log subscriber could be configured to log the request/response data too if needed later
|
51
|
+
class LogSubscriber < ActiveSupport::LogSubscriber
|
52
|
+
|
53
|
+
def perform_start(event)
|
54
|
+
info do
|
55
|
+
request = event.payload[:request]
|
56
|
+
|
57
|
+
"Performing json request (Request ID: #{ request.process_id }) #{ request.method.to_s.upcase } #{ request.url }"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def perform(event)
|
62
|
+
request = event.payload[:request]
|
63
|
+
ex = event.payload[:exception_object]
|
64
|
+
|
65
|
+
if ex
|
66
|
+
error do
|
67
|
+
"Error performing json request (Request ID: #{ request.process_id }) in #{ event.duration.round(2) }ms: #{ ex.class } (#{ ex.message }):\n" + Array(ex.backtrace).join("\n")
|
68
|
+
end
|
69
|
+
else
|
70
|
+
info do
|
71
|
+
"Performed json request (Request ID: #{ request.process_id }) in #{ event.duration.round(2) }ms (#{ request.method.to_s.upcase } #{ request.url })"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
ProviderKit::JsonClient::LogSubscriber.attach_to :provider_kit_json_client
|
@@ -0,0 +1,160 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ProviderKit
|
4
|
+
# Generic interface for working with http request/response and json
|
5
|
+
class JsonRequest
|
6
|
+
|
7
|
+
include ProviderKit::Callbacks
|
8
|
+
include ProviderKit::Execution
|
9
|
+
|
10
|
+
attr_reader :url
|
11
|
+
attr_reader :method
|
12
|
+
attr_reader :params
|
13
|
+
attr_reader :headers
|
14
|
+
attr_reader :mode
|
15
|
+
attr_reader :body
|
16
|
+
attr_reader :process_id
|
17
|
+
|
18
|
+
attr_reader :http_request
|
19
|
+
attr_reader :http_response
|
20
|
+
|
21
|
+
around_perform do |request, block, _|
|
22
|
+
tag_logger(request.process_id) do
|
23
|
+
payload = { request: }
|
24
|
+
ActiveSupport::Notifications.instrument("perform_start.provider_kit_json_client", payload.dup)
|
25
|
+
ActiveSupport::Notifications.instrument("perform.provider_kit_json_client", payload) do
|
26
|
+
block.call
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(url, method: :get, params: {}, headers: {}, mode: :json)
|
32
|
+
@url = url
|
33
|
+
@method = method
|
34
|
+
@params = params
|
35
|
+
@headers = headers
|
36
|
+
@mode = mode
|
37
|
+
@process_id = SecureRandom.uuid
|
38
|
+
|
39
|
+
prepare_request
|
40
|
+
perform_now
|
41
|
+
end
|
42
|
+
|
43
|
+
def json
|
44
|
+
JSON.parse(body)
|
45
|
+
rescue
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
|
49
|
+
def success?
|
50
|
+
(200..299).include?(status)
|
51
|
+
end
|
52
|
+
|
53
|
+
def status
|
54
|
+
http_response.code.to_i
|
55
|
+
end
|
56
|
+
|
57
|
+
def uri
|
58
|
+
@uri ||= URI(url)
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def http
|
64
|
+
@http ||= begin
|
65
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
66
|
+
|
67
|
+
if uri.port == 443
|
68
|
+
http.use_ssl = true
|
69
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
70
|
+
end
|
71
|
+
|
72
|
+
http
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def logger
|
77
|
+
Rails.logger
|
78
|
+
end
|
79
|
+
|
80
|
+
def tag_logger(*tags)
|
81
|
+
tags.unshift("JsonRequest")
|
82
|
+
|
83
|
+
logger.tagged(*tags) { yield }
|
84
|
+
end
|
85
|
+
|
86
|
+
# Thanks
|
87
|
+
# https://github.com/rest-client/rest-client/blob/master/lib/restclient/request.rb#L600
|
88
|
+
def parse_body!
|
89
|
+
return unless http_response.present?
|
90
|
+
|
91
|
+
body = http_response.body
|
92
|
+
|
93
|
+
content_encoding = http_response["content-encoding"]
|
94
|
+
|
95
|
+
@body = if (!body) || body.empty?
|
96
|
+
body
|
97
|
+
elsif content_encoding == "gzip"
|
98
|
+
Zlib::GzipReader.new(StringIO.new(body)).read
|
99
|
+
elsif content_encoding == "deflate"
|
100
|
+
begin
|
101
|
+
Zlib::Inflate.new.inflate(body)
|
102
|
+
rescue Zlib::DataError
|
103
|
+
Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(body)
|
104
|
+
end
|
105
|
+
else
|
106
|
+
body
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def perform
|
111
|
+
return @http_response if @http_response
|
112
|
+
|
113
|
+
@http_response = http.request(http_request)
|
114
|
+
parse_body!
|
115
|
+
http_response
|
116
|
+
end
|
117
|
+
|
118
|
+
def prepare_request
|
119
|
+
case method
|
120
|
+
when :get
|
121
|
+
uri.query = URI.encode_www_form(params)
|
122
|
+
@http_request = Net::HTTP::Get.new(uri)
|
123
|
+
when :post
|
124
|
+
@http_request = Net::HTTP::Post.new(uri)
|
125
|
+
set_request_body!
|
126
|
+
when :delete
|
127
|
+
@http_request = Net::HTTP::Delete.new(uri)
|
128
|
+
set_request_body!
|
129
|
+
when :patch
|
130
|
+
@http_request = Net::HTTP::Patch.new(uri)
|
131
|
+
set_request_body!
|
132
|
+
when :put
|
133
|
+
@http_request = Net::HTTP::Put.new(uri)
|
134
|
+
set_request_body!
|
135
|
+
end
|
136
|
+
|
137
|
+
return nil unless http_request
|
138
|
+
|
139
|
+
headers.each do |key, value|
|
140
|
+
http_request[key] = value
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def set_request_body!
|
145
|
+
return unless params.present?
|
146
|
+
|
147
|
+
if use_json_body?
|
148
|
+
http_request.body = params.to_json
|
149
|
+
http_request["Content-Type"] = "application/json"
|
150
|
+
else
|
151
|
+
http_request.set_form_data(params.stringify_keys)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def use_json_body?
|
156
|
+
mode == :json
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ProviderKit
|
4
|
+
# Utility class to log warnings and failures found during data imports
|
5
|
+
module Logging
|
6
|
+
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
cattr_accessor :logger, default: ActiveSupport::TaggedLogging.new(Rails.logger)
|
11
|
+
|
12
|
+
around_perform do |task, block, _|
|
13
|
+
tag_logger(task.class.name, task.process_id) do
|
14
|
+
payload = {
|
15
|
+
provider: task.provider&.key.presence || "unknown provider",
|
16
|
+
task:
|
17
|
+
}
|
18
|
+
|
19
|
+
ActiveSupport::Notifications.instrument("perform_start.provider_kit_tasks", payload.dup)
|
20
|
+
|
21
|
+
ActiveSupport::Notifications.instrument("perform.provider_kit_tasks", payload) do
|
22
|
+
block.call
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def logger
|
31
|
+
Rails.logger
|
32
|
+
end
|
33
|
+
|
34
|
+
def tag_logger(*tags)
|
35
|
+
tags.unshift(provider.key)
|
36
|
+
tags.unshift("Provider")
|
37
|
+
|
38
|
+
logger.tagged(*tags) { yield }
|
39
|
+
end
|
40
|
+
|
41
|
+
class LogSubscriber < ActiveSupport::LogSubscriber
|
42
|
+
|
43
|
+
def perform_start(event)
|
44
|
+
info do
|
45
|
+
task = event.payload[:task]
|
46
|
+
provider = event.payload[:provider]
|
47
|
+
|
48
|
+
"Performing #{ task.class.name } (Process ID: #{ task.process_id }) for #{ provider }"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def perform(event)
|
53
|
+
task = event.payload[:task]
|
54
|
+
provider = event.payload[:provider]
|
55
|
+
ex = event.payload[:exception_object]
|
56
|
+
|
57
|
+
if ex
|
58
|
+
error do
|
59
|
+
"Error performing #{ task.class.name } (Process ID: #{ task.process_id }) for #{ provider } in #{ event.duration.round(2) }ms: #{ ex.class } (#{ ex.message }):\n" + Array(ex.backtrace).join("\n")
|
60
|
+
end
|
61
|
+
else
|
62
|
+
info do
|
63
|
+
"Performed #{ task.class.name } (Process ID: #{ task.process_id }) for #{ provider } in #{ event.duration.round(2) }ms"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
ProviderKit::Logging::LogSubscriber.attach_to :provider_kit_tasks
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ProviderKit
|
4
|
+
# Shortcut for passing an object into a provider
|
5
|
+
module Provideable
|
6
|
+
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
validate :validate_provider_presence
|
11
|
+
end
|
12
|
+
|
13
|
+
def provider
|
14
|
+
if provider_key.present?
|
15
|
+
@provider ||= ProviderKit::ProviderAttribute.new(provider_key, record: self)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def provider=(value)
|
20
|
+
@provider = nil
|
21
|
+
|
22
|
+
write_attribute(:provider, value.to_s.presence)
|
23
|
+
end
|
24
|
+
|
25
|
+
def provider_key
|
26
|
+
read_attribute(:provider).to_s.presence
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def validate_provider_presence
|
32
|
+
if provider.present? && provider_key != "default"
|
33
|
+
return
|
34
|
+
end
|
35
|
+
|
36
|
+
errors.add :provider, :blank
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ProviderKit
|
4
|
+
# Given an object, try and find its registered data provider
|
5
|
+
#
|
6
|
+
# Or directly pass a provider key
|
7
|
+
class Provider
|
8
|
+
|
9
|
+
attr_reader :record
|
10
|
+
|
11
|
+
def initialize(record)
|
12
|
+
@record = record
|
13
|
+
end
|
14
|
+
|
15
|
+
def key
|
16
|
+
record_provider.presence || record_source.presence
|
17
|
+
end
|
18
|
+
|
19
|
+
def present?
|
20
|
+
provider.present?
|
21
|
+
end
|
22
|
+
|
23
|
+
def provider
|
24
|
+
return nil unless registration
|
25
|
+
|
26
|
+
registration.klass
|
27
|
+
end
|
28
|
+
|
29
|
+
def provider_instance
|
30
|
+
return nil unless provider
|
31
|
+
|
32
|
+
instance = provider.new
|
33
|
+
|
34
|
+
if instance.respond_to?(:context=) && record != key
|
35
|
+
instance.context = record
|
36
|
+
end
|
37
|
+
|
38
|
+
instance
|
39
|
+
end
|
40
|
+
|
41
|
+
def registration
|
42
|
+
if key.present?
|
43
|
+
ProviderKit.registrations[key.to_sym]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def record_provider
|
50
|
+
if record.respond_to?(:provider)
|
51
|
+
record.provider
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def record_source
|
56
|
+
if Symbol === record
|
57
|
+
record
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ProviderKit
|
4
|
+
# serializer for a provider attribute turned into a Provider instance
|
5
|
+
class ProviderAttribute
|
6
|
+
|
7
|
+
include Buildable
|
8
|
+
|
9
|
+
attr_reader :key
|
10
|
+
attr_reader :record
|
11
|
+
attr_reader :context
|
12
|
+
|
13
|
+
def initialize(key, record: nil, **context)
|
14
|
+
@key = (key.to_s.presence || "default").to_sym
|
15
|
+
@record = record
|
16
|
+
@context = context.reverse_merge(default_context)
|
17
|
+
end
|
18
|
+
|
19
|
+
def ==(other_key)
|
20
|
+
to_s == other_key.to_s
|
21
|
+
end
|
22
|
+
|
23
|
+
def inspect
|
24
|
+
@key.inspect
|
25
|
+
end
|
26
|
+
|
27
|
+
def method_missing(method_name, *args)
|
28
|
+
return nil unless provider.present?
|
29
|
+
|
30
|
+
if provider.respond_to?(method_name)
|
31
|
+
return provider.public_send(method_name, *args)
|
32
|
+
end
|
33
|
+
|
34
|
+
# self.amazon_selling_partner? ==> self.key == :amazon_selling_partner
|
35
|
+
if match = method_name.to_s.match(/^(?<attribute>[a-z0-9_]+)\?$/)
|
36
|
+
return match[:attribute].to_clean_sym == key
|
37
|
+
end
|
38
|
+
|
39
|
+
provider.with_context(**context).public_send(method_name, *args)
|
40
|
+
end
|
41
|
+
|
42
|
+
def label
|
43
|
+
name
|
44
|
+
end
|
45
|
+
|
46
|
+
def name
|
47
|
+
registration&.name.presence || key.to_s.titleize
|
48
|
+
end
|
49
|
+
|
50
|
+
def options
|
51
|
+
registration&.options.presence || {}
|
52
|
+
end
|
53
|
+
|
54
|
+
def present?
|
55
|
+
provider.present?
|
56
|
+
end
|
57
|
+
|
58
|
+
def registration
|
59
|
+
if present?
|
60
|
+
ProviderKit.registrations[key.to_sym]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_s
|
65
|
+
key.to_s
|
66
|
+
end
|
67
|
+
|
68
|
+
def with_context(**context)
|
69
|
+
@context = context
|
70
|
+
self
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.dump(instance)
|
74
|
+
case instance
|
75
|
+
when self
|
76
|
+
instance.to_s
|
77
|
+
else
|
78
|
+
new(instance).to_s
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.load(str)
|
83
|
+
new(str)
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def default_context
|
89
|
+
_context = {}
|
90
|
+
_context[record.model_name.param_key.to_sym] = record if record.present?
|
91
|
+
_context
|
92
|
+
end
|
93
|
+
|
94
|
+
def provider
|
95
|
+
@provider ||= with(key)
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|