easyhooks 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +201 -0
- data/README.md +374 -0
- data/lib/easyhooks/action.rb +37 -0
- data/lib/easyhooks/base.rb +22 -0
- data/lib/easyhooks/concerns/helpers.rb +35 -0
- data/lib/easyhooks/concerns/validators.rb +93 -0
- data/lib/easyhooks/hook.rb +8 -0
- data/lib/easyhooks/migration.rb +12 -0
- data/lib/easyhooks/post_processor.rb +101 -0
- data/lib/easyhooks/specification.rb +108 -0
- data/lib/easyhooks/store.rb +51 -0
- data/lib/easyhooks/store_values.rb +34 -0
- data/lib/easyhooks/trigger.rb +22 -0
- data/lib/easyhooks/version.rb +5 -0
- data/lib/easyhooks.rb +109 -0
- data/lib/generators/easyhooks/migration/migration_generator.rb +28 -0
- data/lib/generators/easyhooks/migration/templates/migration.rb +28 -0
- metadata +190 -0
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
|
5
|
+
module Easyhooks
|
6
|
+
module Validators
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
ALLOWED_ON_VALUES = %i[create update destroy].freeze
|
10
|
+
ALLOWED_METHODS = %i[get post put patch delete].freeze
|
11
|
+
ALLOWED_TYPES = %i[default stored].freeze
|
12
|
+
|
13
|
+
included do
|
14
|
+
def validate_type(type)
|
15
|
+
return :default if type.nil?
|
16
|
+
raise TypeError, "Invalid #{self.class} type: #{type}" unless ALLOWED_TYPES.include?(type)
|
17
|
+
type
|
18
|
+
end
|
19
|
+
|
20
|
+
def validate_method(method)
|
21
|
+
return nil if method.nil? && @type == :stored # this will be loaded by the processor
|
22
|
+
return :post unless method.present?
|
23
|
+
method = method.downcase if method.is_a?(String)
|
24
|
+
raise TypeError, "Invalid method '#{method}' for #{self.class} '#{@name}'. Allowed values are: #{ALLOWED_METHODS}" unless ALLOWED_METHODS.include?(method.to_sym)
|
25
|
+
method.to_sym
|
26
|
+
end
|
27
|
+
|
28
|
+
def valid_url?(url)
|
29
|
+
URI.parse(url) rescue false
|
30
|
+
end
|
31
|
+
|
32
|
+
def validate_endpoint(endpoint)
|
33
|
+
return nil if endpoint.nil? && @type == :stored # this will be loaded by the processor
|
34
|
+
raise TypeError, "#{self.class} endpoint can't be nil" unless endpoint.present?
|
35
|
+
raise TypeError, "#{self.class} endpoint is not a valid URL: #{endpoint}" unless valid_url?(endpoint)
|
36
|
+
endpoint
|
37
|
+
end
|
38
|
+
|
39
|
+
def validate_name(name)
|
40
|
+
raise TypeError, "#{self.class} name can't be nil" unless name.present?
|
41
|
+
raise TypeError, "Invalid #{self.class} name '#{name}'. Name can only have alphanumeric characters and underscore" unless name =~ /\A[a-zA-Z0-9_]+\z/
|
42
|
+
name
|
43
|
+
end
|
44
|
+
|
45
|
+
def validate_on(on)
|
46
|
+
return ALLOWED_ON_VALUES if on.nil?
|
47
|
+
on = [on] unless on.is_a?(Array)
|
48
|
+
on.map do |value|
|
49
|
+
raise TypeError, "Invalid attribute 'on' for #{self.class} #{@name}: #{on}. Allowed values are: #{ALLOWED_ON_VALUES}" unless ALLOWED_ON_VALUES.include?(value.to_sym)
|
50
|
+
value
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def validate_only(only)
|
55
|
+
return [] if only.nil?
|
56
|
+
only = [only] unless only.is_a?(Array)
|
57
|
+
only.map(&:to_sym) # convert only array into symbols array
|
58
|
+
end
|
59
|
+
|
60
|
+
def validate_callback(callback, attribute)
|
61
|
+
if callback.nil? || callback.is_a?(Symbol) || callback.respond_to?(:call)
|
62
|
+
callback
|
63
|
+
else
|
64
|
+
raise TypeError, "Invalid attribute '#{attribute}' for #{self.class} #{@name}: #{attribute} must be nil, an instance method name symbol or a callable (eg. a proc or lambda)"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def validate_auth(auth)
|
69
|
+
return nil if auth.nil?
|
70
|
+
# validate if auth header is a string and it must be a Bearer token or a Basic auth to include into a HTTP request
|
71
|
+
raise TypeError, "Invalid attribute 'auth_header' for #{self.class} #{@name}: #{auth} must be nil or a string" unless auth.is_a?(String)
|
72
|
+
raise TypeError, "Invalid attribute 'auth_header' for #{self.class} #{@name}: #{auth} must be a Bearer token or a Basic auth" unless auth =~ /\ABearer\s/ || auth =~ /\ABasic\s/
|
73
|
+
auth
|
74
|
+
end
|
75
|
+
|
76
|
+
def validate_headers(headers)
|
77
|
+
return {} if headers.nil?
|
78
|
+
headers = JSON.parse(headers.gsub(':', '').gsub('=>', ':')) if headers.is_a?(String)
|
79
|
+
raise TypeError, "Invalid attribute 'headers' for #{self.class} #{@name}: #{headers} must be nil or a hash" unless headers.is_a?(Hash)
|
80
|
+
headers
|
81
|
+
end
|
82
|
+
|
83
|
+
def validate_hook(hook)
|
84
|
+
return hook if hook.nil? && @type == :stored
|
85
|
+
hook.method = validate_method(hook.method)
|
86
|
+
hook.endpoint = validate_endpoint(hook.endpoint)
|
87
|
+
hook.auth = validate_auth(hook.auth)
|
88
|
+
hook.headers = validate_headers(hook.headers)
|
89
|
+
hook
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'net/http'
|
3
|
+
|
4
|
+
module Easyhooks
|
5
|
+
class PostProcessor < ActiveJob::Base
|
6
|
+
queue_as :easyhooks
|
7
|
+
|
8
|
+
def perform(klass_name, object_id, payload, action_name, action_trigger)
|
9
|
+
init_data(klass_name, object_id, payload, action_name, action_trigger)
|
10
|
+
begin
|
11
|
+
request = create_http_request
|
12
|
+
make_request(request)
|
13
|
+
trigger_event
|
14
|
+
rescue => e
|
15
|
+
if @action.on_fail.present? && @object.respond_to?(@action.on_fail_callable)
|
16
|
+
@object.send(@action.on_fail_callable, e)
|
17
|
+
else
|
18
|
+
raise e
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def init_data(klass_name, object_id, payload, action_name, action_trigger)
|
26
|
+
@klass_name = klass_name
|
27
|
+
@object = find_object(object_id)
|
28
|
+
@action_trigger = action_trigger
|
29
|
+
@action_name = action_name
|
30
|
+
load_action_and_payload(payload)
|
31
|
+
end
|
32
|
+
|
33
|
+
def find_object(object_id)
|
34
|
+
@klass = @klass_name.constantize
|
35
|
+
@klass.find_by(id: object_id)
|
36
|
+
end
|
37
|
+
|
38
|
+
def json?(value)
|
39
|
+
# check if value is a json string
|
40
|
+
JSON.parse(value)
|
41
|
+
true
|
42
|
+
rescue JSON::ParserError
|
43
|
+
false
|
44
|
+
end
|
45
|
+
|
46
|
+
def enrich_data(data)
|
47
|
+
# if data is a hash or array or a json string
|
48
|
+
default = {
|
49
|
+
object: @klass_name,
|
50
|
+
action: @action.name,
|
51
|
+
trigger: {
|
52
|
+
name: @action.trigger_name,
|
53
|
+
event: @action_trigger.to_s.upcase
|
54
|
+
}
|
55
|
+
}
|
56
|
+
if json?(data)
|
57
|
+
default.merge({ data: JSON.parse(data) }).to_json
|
58
|
+
else
|
59
|
+
default.merge({ data: { id: data }}).to_json
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def load_action_and_payload(payload)
|
64
|
+
@action = @klass.easyhooks_actions[@action_name]
|
65
|
+
@action.load!(@klass_name)
|
66
|
+
@payload = enrich_data(payload)
|
67
|
+
end
|
68
|
+
|
69
|
+
def create_http_request
|
70
|
+
parsed_url = URI.parse(@action.hook.endpoint)
|
71
|
+
host = parsed_url.host
|
72
|
+
port = parsed_url.port
|
73
|
+
raise "Unable to load endpoint: #{@action.hook.endpoint}" unless host.present? && port.present?
|
74
|
+
|
75
|
+
@http = Net::HTTP.new(host, port)
|
76
|
+
@http.use_ssl = true if parsed_url.scheme == 'https'
|
77
|
+
|
78
|
+
Net::HTTP.const_get(@action.hook.method.to_s.capitalize).new(parsed_url.request_uri)
|
79
|
+
end
|
80
|
+
|
81
|
+
def make_request(request)
|
82
|
+
request.body = @payload
|
83
|
+
request.add_field('Content-Type', 'application/json')
|
84
|
+
|
85
|
+
# add headers
|
86
|
+
@action.hook.headers.each do |key, value|
|
87
|
+
request.add_field(key, value)
|
88
|
+
end
|
89
|
+
|
90
|
+
# adds auth (bearer or basic)
|
91
|
+
request.add_field('Authorization', @action.hook.auth) if @action.hook.auth.present?
|
92
|
+
|
93
|
+
@http.verify_mode = OpenSSL::SSL::VERIFY_NONE if Rails.env.test? # disable SSL verification for test env
|
94
|
+
@response = @http.request(request)
|
95
|
+
end
|
96
|
+
|
97
|
+
def trigger_event
|
98
|
+
@object.send(@action.event_callable, @response) if @object.respond_to?(@action.event_callable)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'easyhooks/action'
|
4
|
+
require 'easyhooks/trigger'
|
5
|
+
require 'easyhooks/hook'
|
6
|
+
require 'easyhooks/concerns/helpers'
|
7
|
+
require 'easyhooks/concerns/validators'
|
8
|
+
|
9
|
+
module Easyhooks
|
10
|
+
class Specification
|
11
|
+
include Easyhooks::Helpers
|
12
|
+
include Easyhooks::Validators
|
13
|
+
|
14
|
+
attr_accessor :name, :type, :global_args, :actions, :triggers, :scoped_action, :scoped_trigger
|
15
|
+
|
16
|
+
def initialize(name, type, args = {}, &specification)
|
17
|
+
@name = name
|
18
|
+
@type = type
|
19
|
+
@global_args = args.merge({ type: type })
|
20
|
+
@triggers = {}
|
21
|
+
@actions = {}
|
22
|
+
instance_eval(&specification)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def trigger(name, args = {}, &actions)
|
28
|
+
# merge args with global args keeping args as the priority
|
29
|
+
args = @global_args.merge(args)
|
30
|
+
|
31
|
+
type = args[:type]
|
32
|
+
|
33
|
+
hook_definition = find_trigger_hook(name, type, args)
|
34
|
+
|
35
|
+
# create the trigger
|
36
|
+
new_trigger = Easyhooks::Trigger.new(name, hook_definition, args)
|
37
|
+
@triggers[name] = new_trigger
|
38
|
+
@scoped_trigger = new_trigger
|
39
|
+
instance_eval(&actions) if actions
|
40
|
+
end
|
41
|
+
|
42
|
+
def action(name, args = {}, &event)
|
43
|
+
args = @scoped_trigger.args.merge(args)
|
44
|
+
type = args[:type]
|
45
|
+
|
46
|
+
hook_definition = find_action_hook(name, type, args)
|
47
|
+
|
48
|
+
# create the action
|
49
|
+
new_action = Easyhooks::Action.new(name, @scoped_trigger.name, hook_definition, args, &event)
|
50
|
+
@actions[name] = new_action
|
51
|
+
@scoped_action = new_action
|
52
|
+
@scoped_trigger.add_action(new_action)
|
53
|
+
end
|
54
|
+
|
55
|
+
def find_trigger_hook(name, type, args)
|
56
|
+
hook_definition = Hook.new
|
57
|
+
Hook::ATTRIBUTES.each do |field|
|
58
|
+
value = hook_lookup(:triggers, name, type, args, field) || hook_lookup(:classes, @name, type, args, field)
|
59
|
+
hook_definition.send("#{field}=".to_sym, value)
|
60
|
+
end
|
61
|
+
hook_definition
|
62
|
+
end
|
63
|
+
|
64
|
+
def find_action_hook(name, type, args)
|
65
|
+
hook_definition = Hook.new
|
66
|
+
Hook::ATTRIBUTES.each do |field|
|
67
|
+
value = hook_lookup(:actions, name, type, args, field) || @scoped_trigger.hook.send(field)
|
68
|
+
hook_definition.send("#{field}=".to_sym, value)
|
69
|
+
end
|
70
|
+
hook_definition
|
71
|
+
end
|
72
|
+
|
73
|
+
def hook_lookup(attr_type, attr_name, type, args, field)
|
74
|
+
# stored triggers will load at post processor time
|
75
|
+
return nil if type == :stored
|
76
|
+
|
77
|
+
if args.present?
|
78
|
+
value = args[field]&.to_s
|
79
|
+
return value if value.present?
|
80
|
+
end
|
81
|
+
|
82
|
+
if config_file_exists? && [:method, :endpoint, :auth].include?(field)
|
83
|
+
value = config.dig(Rails.env, attr_type.to_s, attr_name.to_s, field.to_s)&.to_s
|
84
|
+
return value if value.present?
|
85
|
+
end
|
86
|
+
|
87
|
+
if config_file_exists? && field == :headers
|
88
|
+
value = config.dig(Rails.env, attr_type.to_s, attr_name.to_s, field.to_s)&.to_h
|
89
|
+
return value if value.present?
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def config_file_exists?
|
94
|
+
@config_file_exists ||= File.exist?(config_file)
|
95
|
+
end
|
96
|
+
|
97
|
+
def config_file
|
98
|
+
args = 'config', 'easyhooks.yml'
|
99
|
+
args.unshift('test') if Rails.env.test?
|
100
|
+
|
101
|
+
@config_file ||= File.join(Rails.root, args)
|
102
|
+
end
|
103
|
+
|
104
|
+
def config
|
105
|
+
@config ||= YAML.load_file(config_file)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './store_values'
|
4
|
+
|
5
|
+
module Easyhooks
|
6
|
+
class Store < ActiveRecord::Base
|
7
|
+
self.table_name = 'easyhooks_store'
|
8
|
+
|
9
|
+
has_many :values, class_name: 'Easyhooks::StoreValues', dependent: :destroy
|
10
|
+
|
11
|
+
validates_presence_of :name, :method, :endpoint, :context
|
12
|
+
|
13
|
+
def add_headers(headers)
|
14
|
+
headers.each do |key, value|
|
15
|
+
values.create(context: 'headers', key: key, value: value)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_auth(type, auth)
|
20
|
+
values.create(context: 'auth', key: type, value: auth)
|
21
|
+
end
|
22
|
+
|
23
|
+
def headers
|
24
|
+
values.where(context: 'headers').map { |v| [v.key, v.value] }.to_h
|
25
|
+
end
|
26
|
+
|
27
|
+
def auth
|
28
|
+
auth = values.where(context: 'auth').first
|
29
|
+
return nil unless auth.present?
|
30
|
+
|
31
|
+
"#{auth.key} #{auth.value}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# == Schema Information
|
37
|
+
#
|
38
|
+
# Table name: easyhooks_store
|
39
|
+
#
|
40
|
+
# id :integer not null, primary key
|
41
|
+
# context :string not null
|
42
|
+
# name :string not null
|
43
|
+
# method :string not null
|
44
|
+
# endpoint :string not null
|
45
|
+
# created_at :datetime
|
46
|
+
# updated_at :datetime
|
47
|
+
#
|
48
|
+
# Indexes
|
49
|
+
#
|
50
|
+
# index_easyhooks_store_on_name_and_context (name, context) UNIQUE
|
51
|
+
#
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './store'
|
4
|
+
|
5
|
+
module Easyhooks
|
6
|
+
class StoreValues < ActiveRecord::Base
|
7
|
+
self.table_name = 'easyhooks_store_values'
|
8
|
+
|
9
|
+
belongs_to :store, class_name: 'Easyhooks::Store'
|
10
|
+
|
11
|
+
validates_presence_of :key, :value, :context
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# == Schema Information
|
16
|
+
#
|
17
|
+
# Table name: easyhooks_store_values
|
18
|
+
#
|
19
|
+
# id :integer not null, primary key
|
20
|
+
# context :string not null
|
21
|
+
# key :string not null
|
22
|
+
# value :string not null
|
23
|
+
# easyhooks_store_id :integer not null
|
24
|
+
# created_at :datetime
|
25
|
+
# updated_at :datetime
|
26
|
+
#
|
27
|
+
# Indexes
|
28
|
+
#
|
29
|
+
# index_easyhooks_store_values_on_key_and_context (key, context)
|
30
|
+
#
|
31
|
+
# Foreign Keys
|
32
|
+
#
|
33
|
+
# fk_rails_... (easyhooks_store_id => easyhooks_store.id)
|
34
|
+
#
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'easyhooks/base'
|
4
|
+
|
5
|
+
module Easyhooks
|
6
|
+
class Trigger < Easyhooks::Base
|
7
|
+
|
8
|
+
attr_accessor :args, :on, :only, :actions
|
9
|
+
|
10
|
+
def initialize(name, hook, args = {})
|
11
|
+
super(name, args[:type], hook, args[:if], args[:payload], args[:on_fail])
|
12
|
+
@args = args
|
13
|
+
@on = validate_on(args[:on])
|
14
|
+
@only = validate_only(args[:only])
|
15
|
+
@actions = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_action(action)
|
19
|
+
@actions.push(action)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/easyhooks.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'rails'
|
5
|
+
require 'active_support'
|
6
|
+
require 'active_record'
|
7
|
+
require 'active_job'
|
8
|
+
require 'easyhooks/specification'
|
9
|
+
require 'easyhooks/post_processor'
|
10
|
+
|
11
|
+
module Easyhooks
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
attr_reader :easyhooks_spec
|
15
|
+
|
16
|
+
def easyhooks(type = :default, args = {}, &specification)
|
17
|
+
if type.is_a?(Hash)
|
18
|
+
args = type
|
19
|
+
type = :default
|
20
|
+
end
|
21
|
+
# get self.class replacing :: from the module::class name with _
|
22
|
+
# e.g. MyModule::MyClass becomes MyModule_MyClass
|
23
|
+
klass_name = self.name.to_s.gsub('::', '_')
|
24
|
+
assign_easyhooks Specification.new(klass_name, type, args, &specification)
|
25
|
+
end
|
26
|
+
|
27
|
+
def easyhooks_actions
|
28
|
+
@easyhooks_spec.actions
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def assign_easyhooks(specification_object)
|
34
|
+
@easyhooks_spec = specification_object
|
35
|
+
|
36
|
+
@easyhooks_spec.actions.each do |_, action|
|
37
|
+
module_eval do
|
38
|
+
define_method action.event_callable do |response_data|
|
39
|
+
instance_exec(response_data, &action.event) if action.event.present?
|
40
|
+
end
|
41
|
+
|
42
|
+
define_method action.on_fail_callable do |error|
|
43
|
+
send(action.on_fail, error)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
module InstanceMethods
|
51
|
+
extend ActiveSupport::Concern
|
52
|
+
|
53
|
+
included do
|
54
|
+
after_commit :dispatch_triggers
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def triggered_by
|
60
|
+
return :create if self.transaction_include_any_action?([:create])
|
61
|
+
|
62
|
+
return :update if self.transaction_include_any_action?([:update])
|
63
|
+
|
64
|
+
return :destroy if self.transaction_include_any_action?([:destroy])
|
65
|
+
|
66
|
+
:none
|
67
|
+
end
|
68
|
+
|
69
|
+
def perform_trigger_actions(trigger)
|
70
|
+
trigger.actions.each do |action|
|
71
|
+
next unless action.condition_applicable?(self)
|
72
|
+
payload = action.request_payload(self).to_json
|
73
|
+
PostProcessor.perform_later(self.class.name, self.id, payload, action.name, triggered_by)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def execute_trigger(trigger)
|
78
|
+
return unless trigger.condition_applicable?(self)
|
79
|
+
if trigger.only.empty? || triggered_by == :destroy
|
80
|
+
perform_trigger_actions(trigger)
|
81
|
+
else
|
82
|
+
trigger.only.each do |field|
|
83
|
+
perform_trigger_actions(trigger) if self.previous_changes.has_key?(field)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def dispatch_triggers
|
89
|
+
return unless self.class.easyhooks_spec
|
90
|
+
|
91
|
+
self.class.easyhooks_spec.triggers.each do |_, trigger|
|
92
|
+
execute_trigger(trigger) if self.transaction_include_any_action?(trigger.on)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.included(klass)
|
98
|
+
# check if the klass extends from ActiveRecord::Base, if not raise an error
|
99
|
+
unless klass.ancestors.include?(ActiveRecord::Base)
|
100
|
+
raise "Easyhooks can only be included in classes that extend from ActiveRecord::Base"
|
101
|
+
end
|
102
|
+
|
103
|
+
klass.send :include, InstanceMethods
|
104
|
+
|
105
|
+
klass.extend ClassMethods
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
ActiveRecord::Base.send(:include, Easyhooks)
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'rails/generators/migration'
|
3
|
+
|
4
|
+
module Easyhooks
|
5
|
+
class MigrationGenerator < Rails::Generators::Base
|
6
|
+
include Rails::Generators::Migration
|
7
|
+
|
8
|
+
desc 'Generates migration for easyhooks'
|
9
|
+
source_root File.expand_path('../templates', __FILE__)
|
10
|
+
|
11
|
+
def create_migration_file
|
12
|
+
migration_template 'migration.rb','db/migrate/easyhooks_migration.rb'
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.next_migration_number(dirname)
|
16
|
+
if timestamped_migrations?
|
17
|
+
Time.now.utc.strftime('%Y%m%d%H%M%S')
|
18
|
+
else
|
19
|
+
'%.3d' % (current_migration_number(dirname) + 1)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.timestamped_migrations?
|
24
|
+
(ActiveRecord::Base.respond_to?(:timestamped_migrations) && ActiveRecord::Base.timestamped_migrations) ||
|
25
|
+
(ActiveRecord.respond_to?(:timestamped_migrations) && ActiveRecord.timestamped_migrations)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class EasyhooksMigration < ActiveRecord::Migration[6.0]
|
4
|
+
def self.up
|
5
|
+
create_table :easyhooks_store do |t|
|
6
|
+
t.string :context, null: false
|
7
|
+
t.string :name, null: false
|
8
|
+
t.string :method, null: false
|
9
|
+
t.string :endpoint, null: false
|
10
|
+
t.timestamps null: true
|
11
|
+
end
|
12
|
+
add_index :easyhooks_store, %i[name context], unique: true
|
13
|
+
|
14
|
+
create_table :easyhooks_store_values do |t|
|
15
|
+
t.string :context, null: false
|
16
|
+
t.string :key, null: false
|
17
|
+
t.string :value, null: false
|
18
|
+
t.integer :store_id, null: false, references: :easyhooks_store
|
19
|
+
t.timestamps null: true
|
20
|
+
end
|
21
|
+
add_index :easyhooks_store_values, %i[context key]
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.down
|
25
|
+
drop_table :easyhooks_store_values
|
26
|
+
drop_table :easyhooks_store
|
27
|
+
end
|
28
|
+
end
|