active_webhook 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|