active_webhook 1.0.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/.env.sample +1 -0
- data/.gitignore +63 -0
- data/.rspec +4 -0
- data/.rubocop.yml +86 -0
- data/.todo +48 -0
- data/.travis.yml +14 -0
- data/CHANGELOG.md +5 -0
- data/DEV_README.md +199 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +483 -0
- data/Rakefile +23 -0
- data/active_webhook.gemspec +75 -0
- data/config/environment.rb +33 -0
- data/lib/active_webhook.rb +125 -0
- data/lib/active_webhook/adapter.rb +117 -0
- data/lib/active_webhook/callbacks.rb +53 -0
- data/lib/active_webhook/configuration.rb +105 -0
- data/lib/active_webhook/delivery/base_adapter.rb +96 -0
- data/lib/active_webhook/delivery/configuration.rb +16 -0
- data/lib/active_webhook/delivery/faraday_adapter.rb +19 -0
- data/lib/active_webhook/delivery/net_http_adapter.rb +28 -0
- data/lib/active_webhook/error_log.rb +7 -0
- data/lib/active_webhook/formatting/base_adapter.rb +109 -0
- data/lib/active_webhook/formatting/configuration.rb +18 -0
- data/lib/active_webhook/formatting/json_adapter.rb +19 -0
- data/lib/active_webhook/formatting/url_encoded_adapter.rb +28 -0
- data/lib/active_webhook/hook.rb +9 -0
- data/lib/active_webhook/logger.rb +21 -0
- data/lib/active_webhook/models/configuration.rb +18 -0
- data/lib/active_webhook/models/error_log_additions.rb +15 -0
- data/lib/active_webhook/models/subscription_additions.rb +72 -0
- data/lib/active_webhook/models/topic_additions.rb +70 -0
- data/lib/active_webhook/queueing/active_job_adapter.rb +43 -0
- data/lib/active_webhook/queueing/base_adapter.rb +67 -0
- data/lib/active_webhook/queueing/configuration.rb +15 -0
- data/lib/active_webhook/queueing/delayed_job_adapter.rb +28 -0
- data/lib/active_webhook/queueing/sidekiq_adapter.rb +43 -0
- data/lib/active_webhook/queueing/syncronous_adapter.rb +14 -0
- data/lib/active_webhook/subscription.rb +7 -0
- data/lib/active_webhook/topic.rb +7 -0
- data/lib/active_webhook/verification/base_adapter.rb +31 -0
- data/lib/active_webhook/verification/configuration.rb +13 -0
- data/lib/active_webhook/verification/hmac_sha256_adapter.rb +20 -0
- data/lib/active_webhook/verification/unsigned_adapter.rb +11 -0
- data/lib/active_webhook/version.rb +5 -0
- data/lib/generators/install_generator.rb +20 -0
- data/lib/generators/migrations_generator.rb +24 -0
- data/lib/generators/templates/20210618023338_create_active_webhook_tables.rb +31 -0
- data/lib/generators/templates/active_webhook_config.rb +87 -0
- metadata +447 -0
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
|
3
|
+
def initialize_rake_environment
|
4
|
+
return unless require_relative "config/environment"
|
5
|
+
rescue LoadError
|
6
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
7
|
+
end
|
8
|
+
|
9
|
+
Bundler::GemHelper.install_tasks
|
10
|
+
|
11
|
+
APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
|
12
|
+
load 'rails/tasks/engine.rake'
|
13
|
+
|
14
|
+
task default: :spec
|
15
|
+
|
16
|
+
require "rspec/core/rake_task"
|
17
|
+
RSpec::Core::RakeTask.new(:spec)
|
18
|
+
|
19
|
+
require "rubocop/rake_task"
|
20
|
+
RuboCop::RakeTask.new do |task|
|
21
|
+
task.requires << "rubocop-performance"
|
22
|
+
task.requires << "rubocop-rspec"
|
23
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path("lib", __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require "active_webhook/version"
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = "active_webhook"
|
9
|
+
spec.version = ActiveWebhook::VERSION
|
10
|
+
spec.authors = ["Jay Crouch"]
|
11
|
+
spec.email = ["i.jaycrouch@gmail.com"]
|
12
|
+
|
13
|
+
spec.summary = "Simple, efficient, and extensible webhooks for Ruby."
|
14
|
+
spec.description = "Simple, efficient, and extensible webhooks for Ruby, including: Rate Limits, Cryptographic " \
|
15
|
+
"Signatures, Asynchronous Delivery, Buffered Delivery, Versioning."
|
16
|
+
spec.homepage = "https://github.com/amazing-jay/active_webhook"
|
17
|
+
spec.license = "MIT"
|
18
|
+
|
19
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
20
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
21
|
+
if spec.respond_to?(:metadata)
|
22
|
+
# spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
23
|
+
|
24
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
25
|
+
spec.metadata["source_code_uri"] = "https://github.com/amazing-jay/active_webhook"
|
26
|
+
spec.metadata["changelog_uri"] = "https://github.com/amazing-jay/active_webhook/master/tree/CHANGELOG.md."
|
27
|
+
else
|
28
|
+
raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
29
|
+
end
|
30
|
+
|
31
|
+
# Specify which files should be added to the gem when it is released.
|
32
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
33
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
34
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|bin)/}) }
|
35
|
+
end
|
36
|
+
|
37
|
+
spec.bindir = "exe"
|
38
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
39
|
+
spec.require_paths = ["lib"]
|
40
|
+
spec.required_ruby_version = ">= 2.5"
|
41
|
+
|
42
|
+
spec.add_dependency "activerecord", ">= 5.0.0"
|
43
|
+
spec.add_dependency "memoist"
|
44
|
+
|
45
|
+
spec.add_development_dependency "awesome_print", "~> 1.9.2"
|
46
|
+
spec.add_development_dependency "bundler", "~> 1.17"
|
47
|
+
spec.add_development_dependency "database_cleaner", "~> 2.0.1"
|
48
|
+
spec.add_development_dependency "delayed_job_active_record", "~> 4.1.6"
|
49
|
+
spec.add_development_dependency "dotenv", "~> 2.5"
|
50
|
+
spec.add_development_dependency "factory_bot", "~> 6.2.0"
|
51
|
+
spec.add_development_dependency "faker", "~> 2.18"
|
52
|
+
spec.add_development_dependency "faraday", "~> 1.4.2"
|
53
|
+
spec.add_development_dependency "listen", "~> 3.5.1"
|
54
|
+
spec.add_development_dependency "pry-byebug", "~> 3.9"
|
55
|
+
|
56
|
+
# must come before those below
|
57
|
+
if ENV['TEST_RAILS_VERSION'].nil?
|
58
|
+
spec.add_development_dependency 'rails', '~> 6.1.3.2'
|
59
|
+
else
|
60
|
+
spec.add_development_dependency 'rails', ENV['TEST_RAILS_VERSION'].to_s
|
61
|
+
end
|
62
|
+
|
63
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
64
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
65
|
+
spec.add_development_dependency "rspec-rails", "~> 5.0.1"
|
66
|
+
spec.add_development_dependency "rubocop", "~> 0.60"
|
67
|
+
spec.add_development_dependency "rubocop-performance", "~> 1.5"
|
68
|
+
spec.add_development_dependency "rubocop-rspec", "~> 1.37"
|
69
|
+
spec.add_development_dependency "codecov", "~> 0.5.2"
|
70
|
+
spec.add_development_dependency "simplecov", "~> 0.16"
|
71
|
+
spec.add_development_dependency "sqlite3", "~> 1.4.2"
|
72
|
+
spec.add_development_dependency "sidekiq", "~> 6.2.1"
|
73
|
+
spec.add_development_dependency "rspec-sidekiq", "~> 3.1.0"
|
74
|
+
spec.add_development_dependency "webmock", "~> 3.13"
|
75
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dotenv/load"
|
4
|
+
|
5
|
+
ENV["RACK_ENV"] ||= ENV["RAILS_ENV"] ||= "development"
|
6
|
+
|
7
|
+
require "bundler/setup"
|
8
|
+
|
9
|
+
if RUBY_ENGINE == "jruby"
|
10
|
+
# Workaround for issue in I18n/JRuby combo.
|
11
|
+
# See https://github.com/jruby/jruby/issues/6547 and
|
12
|
+
# https://github.com/ruby-i18n/i18n/issues/555
|
13
|
+
require "i18n/backend"
|
14
|
+
require "i18n/backend/simple"
|
15
|
+
end
|
16
|
+
|
17
|
+
environments = [ENV["RAILS_ENV"].to_sym]
|
18
|
+
environments << :development if environments.last == :test
|
19
|
+
|
20
|
+
Bundler.require(*environments)
|
21
|
+
|
22
|
+
SimpleCov.start if ENV['RAILS_ENV'].to_s == "test"
|
23
|
+
SimpleCov.formatter = SimpleCov::Formatter::Codecov if ENV["CI"] == "true"
|
24
|
+
|
25
|
+
require "active_webhook"
|
26
|
+
|
27
|
+
db_config = File.read([__dir__.delete_suffix('/config'), "spec/dummy/config/database.yml"].join("/"))
|
28
|
+
db_config = ERB.new(db_config).result
|
29
|
+
db_config = YAML.safe_load(db_config, [], [], true)
|
30
|
+
DB_CONFIG = db_config[ENV["RAILS_ENV"]]
|
31
|
+
ActiveRecord::Base.establish_connection(DB_CONFIG)
|
32
|
+
|
33
|
+
FactoryBot.find_definitions if %w(test development).include? ENV["RAILS_ENV"]
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_record"
|
4
|
+
require "memoist"
|
5
|
+
|
6
|
+
require "active_webhook/adapter"
|
7
|
+
require "active_webhook/delivery/base_adapter"
|
8
|
+
require "active_webhook/formatting/base_adapter"
|
9
|
+
require "active_webhook/queueing/base_adapter"
|
10
|
+
require "active_webhook/verification/base_adapter"
|
11
|
+
|
12
|
+
require "active_webhook/models/error_log_additions"
|
13
|
+
require "active_webhook/models/subscription_additions"
|
14
|
+
require "active_webhook/models/topic_additions"
|
15
|
+
|
16
|
+
require "active_webhook/callbacks"
|
17
|
+
require "active_webhook/hook"
|
18
|
+
require "active_webhook/error_log"
|
19
|
+
require "active_webhook/logger"
|
20
|
+
require "active_webhook/subscription"
|
21
|
+
require "active_webhook/topic"
|
22
|
+
require "active_webhook/version"
|
23
|
+
|
24
|
+
module ActiveWebhook
|
25
|
+
class InvalidAdapterError < StandardError; end
|
26
|
+
|
27
|
+
IDENTIFIER = "Active Webhook v#{VERSION}"
|
28
|
+
|
29
|
+
# IDENTIFIER must be defined first
|
30
|
+
require "active_webhook/configuration"
|
31
|
+
|
32
|
+
class << self
|
33
|
+
attr_writer :enabled
|
34
|
+
|
35
|
+
def configuration
|
36
|
+
@configuration ||= Configuration.new
|
37
|
+
end
|
38
|
+
|
39
|
+
def configure
|
40
|
+
yield(configuration)
|
41
|
+
|
42
|
+
configuration.after_configure
|
43
|
+
end
|
44
|
+
|
45
|
+
def logger
|
46
|
+
defined?(Rails) ? Rails.logger : (@logger ||= Logger.new($stdout))
|
47
|
+
end
|
48
|
+
|
49
|
+
def subscription_model
|
50
|
+
configuration.models.subscription
|
51
|
+
end
|
52
|
+
|
53
|
+
def topic_model
|
54
|
+
configuration.models.topic
|
55
|
+
end
|
56
|
+
|
57
|
+
def origin
|
58
|
+
return @@origin if defined? @@origin
|
59
|
+
|
60
|
+
@@origin = (Rails.application.config.action_mailer.default_url_options[:host] if defined?(Rails))
|
61
|
+
rescue StandardError
|
62
|
+
@@origin = ""
|
63
|
+
end
|
64
|
+
|
65
|
+
# TODO: change the next 4 methods to use memoized thread safe class var rather than configuration.enabled
|
66
|
+
def enabled?
|
67
|
+
configuration.enabled
|
68
|
+
end
|
69
|
+
|
70
|
+
def disabled?
|
71
|
+
!enabled?
|
72
|
+
end
|
73
|
+
|
74
|
+
def enable
|
75
|
+
state = enabled?
|
76
|
+
configuration.enabled = true
|
77
|
+
value = yield
|
78
|
+
ensure
|
79
|
+
configuration.enabled = state
|
80
|
+
value
|
81
|
+
end
|
82
|
+
|
83
|
+
def disable
|
84
|
+
state = enabled?
|
85
|
+
configuration.enabled = false
|
86
|
+
value = yield
|
87
|
+
ensure
|
88
|
+
configuration.enabled = state
|
89
|
+
value
|
90
|
+
end
|
91
|
+
|
92
|
+
Configuration::ADAPTERS.each do |type|
|
93
|
+
define_method "#{type}_adapter" do
|
94
|
+
fetch_adapter type, configuration.send(type).send("adapter")
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def trigger(key:, version: nil, **context)
|
99
|
+
queueing_adapter.call(key: key, version: version, **context) if enabled?
|
100
|
+
true
|
101
|
+
end
|
102
|
+
|
103
|
+
protected
|
104
|
+
|
105
|
+
def fetch_adapter(type, adapter)
|
106
|
+
if adapter.is_a?(Symbol) || adapter.is_a?(String)
|
107
|
+
adapter = begin
|
108
|
+
@adapters ||= {}
|
109
|
+
@adapters[type] ||= {}
|
110
|
+
@adapters[type][adapter.to_sym] = begin
|
111
|
+
path = "active_webhook/#{type}/#{adapter}_adapter"
|
112
|
+
require path
|
113
|
+
const_name = path.camelize
|
114
|
+
["http", "sha", "hmac", "json", "url"].each { |acronym| const_name.gsub!(acronym.camelize, acronym.upcase) }
|
115
|
+
const_name.constantize
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
raise InvalidAdapterError unless adapter.respond_to?(:call)
|
121
|
+
|
122
|
+
adapter
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveWebhook
|
4
|
+
class Adapter
|
5
|
+
extend Memoist
|
6
|
+
# TODO: memoize everything in all adapters
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def attributes
|
10
|
+
@attributes ||= if self == ActiveWebhook::Adapter
|
11
|
+
[]
|
12
|
+
else
|
13
|
+
ancestors.each_with_object([]) do |ancestor, attrs|
|
14
|
+
break attrs += ancestor.attributes if ancestor != self && ancestor.respond_to?(:attributes)
|
15
|
+
|
16
|
+
attrs
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# # byebug
|
21
|
+
# # puts ['start', self, @attributes].join(', ')
|
22
|
+
# @attributes ||= ancestors.each_with_object([]) do |ancestor, attrs|
|
23
|
+
# # break attrs if ancestor == ActiveWebhook::Adapter
|
24
|
+
|
25
|
+
# if ancestor != self
|
26
|
+
# if ancestor.respond_to?(:attributes)
|
27
|
+
# break attrs += ancestor.attributes
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
# attrs
|
31
|
+
|
32
|
+
# # puts ['searching', ancestor, attrs].join(', ')
|
33
|
+
|
34
|
+
# # byebug
|
35
|
+
# # break attrs if ancestor == ActiveWebhook::Adapter
|
36
|
+
# # break attrs if ancestor != self && ancestor.respond_to?(:attributes))
|
37
|
+
# # byebug
|
38
|
+
# # attrs
|
39
|
+
# end
|
40
|
+
# puts ['finished', self, @attributes].join(' ,')
|
41
|
+
# # byebug
|
42
|
+
# # @attributes ||= []
|
43
|
+
# @attributes
|
44
|
+
end
|
45
|
+
|
46
|
+
def attribute(*attrs)
|
47
|
+
# Module.new.tap do |m| # Using anonymous modules so that super can be used to extend accessor methods
|
48
|
+
# include m
|
49
|
+
|
50
|
+
attrs = attrs.map(&:to_sym)
|
51
|
+
(attrs - attributes).each do |attr_name|
|
52
|
+
attributes << attr_name.to_sym
|
53
|
+
# m.attr_accessor attr_name
|
54
|
+
attr_accessor attr_name
|
55
|
+
# end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# def inherited(subclass)
|
60
|
+
# byebug
|
61
|
+
# # super
|
62
|
+
# subclass.instance_variable_set(:@attributes, ancestors.each_with_object([]) do |ancestor, attrs|
|
63
|
+
# unless ancestor == self || !ancestor.respond_to?(:attributes)
|
64
|
+
# break attrs += ancestor.attributes
|
65
|
+
# end
|
66
|
+
|
67
|
+
# attrs
|
68
|
+
# end)
|
69
|
+
# # byebug
|
70
|
+
# # x = 1
|
71
|
+
# end
|
72
|
+
|
73
|
+
def call(*args, **kwargs, &block)
|
74
|
+
new(*args, **kwargs).call(&block)
|
75
|
+
end
|
76
|
+
|
77
|
+
def component_name
|
78
|
+
raise NotImplementedError, ".component_name must be implemented."
|
79
|
+
end
|
80
|
+
|
81
|
+
def configuration
|
82
|
+
ActiveWebhook.configuration
|
83
|
+
end
|
84
|
+
|
85
|
+
def component_configuration
|
86
|
+
configuration.send(component_name)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
attribute :context
|
91
|
+
|
92
|
+
def initialize(**kwargs)
|
93
|
+
self.class.attributes.each do |attr_name|
|
94
|
+
send("#{attr_name}=", kwargs[attr_name]) unless attr_name == :context
|
95
|
+
end
|
96
|
+
self.context = kwargs #.symbolize_keys! #with_indifferent_access
|
97
|
+
end
|
98
|
+
|
99
|
+
def call
|
100
|
+
raise NotImplementedError, "#call must be implemented."
|
101
|
+
end
|
102
|
+
|
103
|
+
def attributes
|
104
|
+
self.class.attributes.each_with_object({}) do |attr_name, h|
|
105
|
+
h[attr_name] = send(attr_name)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def configuration
|
110
|
+
self.class.configuration
|
111
|
+
end
|
112
|
+
|
113
|
+
def component_configuration
|
114
|
+
self.class.component_configuration
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveWebhook
|
4
|
+
module Callbacks
|
5
|
+
class InvalidCallbackError < StandardError; end
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
SUPPORTED_CALLBACKS = %i(created updated deleted).freeze
|
9
|
+
|
10
|
+
class_methods do
|
11
|
+
def trigger_webhooks(version: nil, only: nil, except: [], **_options)
|
12
|
+
callbacks = if only.nil?
|
13
|
+
SUPPORTED_CALLBACKS
|
14
|
+
else
|
15
|
+
Array.wrap(only).map(&:to_sym)
|
16
|
+
end - Array.wrap(except).map(&:to_sym)
|
17
|
+
|
18
|
+
callbacks.each do |callback|
|
19
|
+
unless SUPPORTED_CALLBACKS.include? callback
|
20
|
+
raise InvalidCallbackError, "Invalid callback: #{callback}. Must be one of #{SUPPORTED_CALLBACKS}."
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
after_commit :trigger_created_webhook, on: :create if callbacks.include?(:created)
|
25
|
+
|
26
|
+
after_commit :trigger_updated_webhook, on: :update if callbacks.include?(:updated)
|
27
|
+
|
28
|
+
after_commit :trigger_deleted_webhook, on: :destroy if callbacks.include?(:deleted)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def trigger_created_webhook
|
33
|
+
trigger_webhook(:created)
|
34
|
+
end
|
35
|
+
|
36
|
+
def trigger_updated_webhook
|
37
|
+
trigger_webhook(:updated) unless previous_changes.empty?
|
38
|
+
end
|
39
|
+
|
40
|
+
def trigger_deleted_webhook
|
41
|
+
trigger_webhook(:deleted)
|
42
|
+
end
|
43
|
+
|
44
|
+
def trigger_webhook(key, version: nil, type: 'resource', **context)
|
45
|
+
key = [self.class.name.underscore, key].join("/") unless key.is_a?(String)
|
46
|
+
context[:resource_id] ||= id
|
47
|
+
context[:resource_type] ||= self.class.name
|
48
|
+
|
49
|
+
ActiveWebhook.trigger(key: key, version: version, type: type, **context)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
ActiveRecord::Base.include ActiveWebhook::Callbacks
|
53
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveWebhook
|
4
|
+
class Configuration
|
5
|
+
class InvalidOptionError < StandardError
|
6
|
+
def initialize(option, value, values)
|
7
|
+
@option = option
|
8
|
+
@values = values
|
9
|
+
msg = "Invalid option for #{option}: #{value}. Must be one of #{values}."
|
10
|
+
super(msg)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module Base
|
15
|
+
extend ActiveSupport::Concern
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
(self.class.instance_variable_get(:@components) || []).each do |component_name|
|
19
|
+
component = "#{self.class.name.deconstantize}::#{component_name.to_s.camelize}::Configuration"
|
20
|
+
component = component.constantize.new
|
21
|
+
instance_variable_set "@#{component_name}", component
|
22
|
+
end
|
23
|
+
|
24
|
+
(self.class.instance_variable_get(:@options) || []).each do |option, option_definition|
|
25
|
+
send "#{option}=", option_definition[:default]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class_methods do
|
30
|
+
protected
|
31
|
+
|
32
|
+
# TODO: consider changing so that all options accept a proc
|
33
|
+
# q: does the proc run every time the option is asked? seems inefficent
|
34
|
+
def define_option(option, values: [], default: nil, allow_nil: false, allow_proc: false, prefixes: nil)
|
35
|
+
attr_reader option
|
36
|
+
|
37
|
+
default = values&.first if default.nil? && !allow_nil
|
38
|
+
prefixes ||= name.deconstantize.underscore.delete_prefix("active_webhook").split("/")
|
39
|
+
prefixes.shift if prefixes.first.blank?
|
40
|
+
|
41
|
+
@options ||= {}
|
42
|
+
@options[option] = {
|
43
|
+
values: values,
|
44
|
+
default: default,
|
45
|
+
allow_proc: allow_proc,
|
46
|
+
prefixes: prefixes
|
47
|
+
}
|
48
|
+
|
49
|
+
const_set option.to_s.pluralize.upcase, values
|
50
|
+
|
51
|
+
define_method "#{option}=" do |value|
|
52
|
+
unless (allow_proc && value.respond_to?(:call)) ||
|
53
|
+
(allow_nil && value.nil?) ||
|
54
|
+
values.empty? ||
|
55
|
+
values.include?(value)
|
56
|
+
raise Configuration::InvalidOptionError.new (prefixes + [option]).compact.join("."), value,
|
57
|
+
values
|
58
|
+
end
|
59
|
+
|
60
|
+
instance_variable_set "@#{option}", value
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def define_component(component_name)
|
65
|
+
@components ||= []
|
66
|
+
@components << component_name
|
67
|
+
require "#{name.deconstantize.to_s.underscore}/#{component_name}/configuration"
|
68
|
+
attr_reader component_name
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
include Base
|
74
|
+
|
75
|
+
define_component :models
|
76
|
+
ADAPTERS = %i[delivery formatting queueing verification].freeze
|
77
|
+
ADAPTERS.each { |component| define_component component }
|
78
|
+
|
79
|
+
define_option :origin
|
80
|
+
define_option :enabled, values: [true, false]
|
81
|
+
|
82
|
+
def origin=(value)
|
83
|
+
if (@origin = value).nil?
|
84
|
+
ActiveWebhook.remove_class_variable(:@@origin) if ActiveWebhook.class_variable_defined?(:@@origin)
|
85
|
+
else
|
86
|
+
ActiveWebhook.class_variable_set(:@@origin, value)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def after_configure
|
91
|
+
# reset logger,
|
92
|
+
# ActiveWebhook.class_variable_set(:@logger, nil)
|
93
|
+
# cause all adapter files specified to be loaded
|
94
|
+
ADAPTERS.each { |type| ActiveWebhook.send "#{type}_adapter" }
|
95
|
+
|
96
|
+
# (re)set relationships for all models
|
97
|
+
models.error_log.belongs_to :subscription, class_name: models.subscription.name, foreign_key: :subscription_id
|
98
|
+
models.topic.has_many :subscriptions, class_name: models.subscription.name, foreign_key: :topic_id
|
99
|
+
models.subscription.belongs_to :topic, class_name: models.topic.name, foreign_key: :topic_id
|
100
|
+
models.subscription.has_many :error_logs, class_name: models.error_log.name, foreign_key: :subscription_id
|
101
|
+
|
102
|
+
self
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|