custom_active_record_observer 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +25 -0
- data/Rakefile +20 -0
- data/lib/custom_active_record_observer.rb +16 -0
- data/lib/custom_active_record_observer/all.rb +9 -0
- data/lib/custom_active_record_observer/changes_tracker.rb +16 -0
- data/lib/custom_active_record_observer/dsl.rb +45 -0
- data/lib/custom_active_record_observer/engine.rb +11 -0
- data/lib/custom_active_record_observer/handler.rb +17 -0
- data/lib/custom_active_record_observer/models_extender.rb +9 -0
- data/lib/custom_active_record_observer/not_nil.rb +7 -0
- data/lib/custom_active_record_observer/observable.rb +11 -0
- data/lib/custom_active_record_observer/rules/all.rb +3 -0
- data/lib/custom_active_record_observer/rules/base_rule.rb +19 -0
- data/lib/custom_active_record_observer/rules/create_rule.rb +10 -0
- data/lib/custom_active_record_observer/rules/destroy_rule.rb +10 -0
- data/lib/custom_active_record_observer/rules/update_rule.rb +32 -0
- data/lib/custom_active_record_observer/schema.rb +44 -0
- data/lib/custom_active_record_observer/version.rb +3 -0
- metadata +158 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: de2f53459d0571d2542de638f2f10e80637d20ff
|
4
|
+
data.tar.gz: f1a58b6f3014fbbbfb96eee376fa1a8de82c7110
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 91aaa0ca81ac659ef16b3f58169e3069ecae1d549ffe5b959391b87c34fc5b17234aeaf6b29c308cf0d60e23a6ce3ad52acdefebd56b5643210d5313426d161c
|
7
|
+
data.tar.gz: 7deb22d765fffe58c49902a163604823e88e22172fd5b8e0bdd63dcce2fe7b53960dd7e3a622a24c0cca60449c03154b089938812562e7885788d57dd5866f96
|
data/README.md
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# CustomActiveRecordObserver
|
2
|
+
|
3
|
+
`custom_active_record_observer` is a DSL to track create/update/destroy operations based on AR callbacks.
|
4
|
+
|
5
|
+
Its a simple DSL similar to the code below:
|
6
|
+
|
7
|
+
```
|
8
|
+
CustomActiveRecordObserver.observe :EngagedUser, :CompanyOwner, handler: CustomerEventPublisher.new do
|
9
|
+
on_create do |customer|
|
10
|
+
Events::CustomerSignedUp.new(data: {customer_id: customer.id}
|
11
|
+
end
|
12
|
+
|
13
|
+
on_destroy do |customer|
|
14
|
+
Events::CustomerSignedOut.new(data: {…})
|
15
|
+
end
|
16
|
+
|
17
|
+
on_change :group_id do |customer|
|
18
|
+
Events::CustomerGroupChanged.new(data: {…})
|
19
|
+
end
|
20
|
+
…
|
21
|
+
end
|
22
|
+
```
|
23
|
+
|
24
|
+
which you can put into a single initializer file instead of scattering it around and having it in each AR model.
|
25
|
+
That file is easier to remove in future. It incapsulates all the “inaccurate" dependencies.
|
data/Rakefile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
|
8
|
+
begin
|
9
|
+
require 'rdoc/task'
|
10
|
+
rescue LoadError
|
11
|
+
require 'rdoc/rdoc'
|
12
|
+
require 'rake/rdoctask'
|
13
|
+
RDoc::Task = Rake::RDocTask
|
14
|
+
end
|
15
|
+
|
16
|
+
APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
|
17
|
+
load 'rails/tasks/engine.rake'
|
18
|
+
|
19
|
+
Bundler::GemHelper.install_tasks
|
20
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'active_support/dependencies'
|
2
|
+
|
3
|
+
module CustomActiveRecordObserver
|
4
|
+
require_relative 'custom_active_record_observer/all'
|
5
|
+
|
6
|
+
mattr_accessor :schema
|
7
|
+
@@schema = Schema.new
|
8
|
+
|
9
|
+
def self.observe(*class_names, handler:, &block)
|
10
|
+
class_names.each do |class_name|
|
11
|
+
DSL.new(block).actions_and_rules.each do |(action, rule)|
|
12
|
+
schema.add_rule(class_name, action, rule, handler)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require_relative 'models_extender'
|
2
|
+
require_relative 'observable'
|
3
|
+
require_relative 'engine'
|
4
|
+
require_relative 'rules/all'
|
5
|
+
require_relative 'not_nil'
|
6
|
+
require_relative 'handler'
|
7
|
+
require_relative 'dsl'
|
8
|
+
require_relative 'schema'
|
9
|
+
require_relative 'changes_tracker'
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module CustomActiveRecordObserver
|
2
|
+
module ChangesTracker
|
3
|
+
def self.[](*method_names)
|
4
|
+
Module.new do
|
5
|
+
method_names.each do |name|
|
6
|
+
define_method name do |*args|
|
7
|
+
@_active_record_observer_changes ||= {}
|
8
|
+
@_active_record_observer_changes.merge!(changes)
|
9
|
+
|
10
|
+
super(*args)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module CustomActiveRecordObserver
|
2
|
+
class DSL
|
3
|
+
def initialize(block)
|
4
|
+
instance_exec(&block)
|
5
|
+
end
|
6
|
+
|
7
|
+
def actions_and_rules
|
8
|
+
@actions_and_rules ||= []
|
9
|
+
end
|
10
|
+
|
11
|
+
def on_create(&block)
|
12
|
+
store(:create, Rules::CreateRule.new(block))
|
13
|
+
end
|
14
|
+
|
15
|
+
def on_destroy(&block)
|
16
|
+
store(:destroy, Rules::DestroyRule.new(block))
|
17
|
+
end
|
18
|
+
|
19
|
+
def on_update(*attributes, &block)
|
20
|
+
pattern = attributes.pop if attributes.last.is_a?(Array)
|
21
|
+
|
22
|
+
attributes.each do |attribute|
|
23
|
+
store(:update, Rules::UpdateRule.new(block, attribute: attribute, pattern: pattern))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def on_add(*attributes, &block)
|
28
|
+
on_update(*attributes.push([nil, NotNil]), &block)
|
29
|
+
end
|
30
|
+
|
31
|
+
def on_remove(*attributes, &block)
|
32
|
+
on_update(*attributes.push([NotNil, nil]), &block)
|
33
|
+
end
|
34
|
+
|
35
|
+
def on_change(*attributes, &block)
|
36
|
+
on_update(*attributes.push([NotNil, NotNil]), &block)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def store(action, rule)
|
42
|
+
actions_and_rules << [action, rule]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module CustomActiveRecordObserver
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace CustomActiveRecordObserver
|
4
|
+
|
5
|
+
config.to_prepare do
|
6
|
+
CustomActiveRecordObserver.schema.freeze
|
7
|
+
|
8
|
+
CustomActiveRecordObserver::ModelsExtender.(CustomActiveRecordObserver.schema)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module CustomActiveRecordObserver
|
2
|
+
module Handler
|
3
|
+
def self.call(target, action, _changes, schema: CustomActiveRecordObserver.schema)
|
4
|
+
# Some AR libraries reload AR object in the callbacks.
|
5
|
+
# Hence, ActiveModel::Dirty model changes hash becomes empty.
|
6
|
+
# @_active_record_observer_changes is needed to that changes not be missed
|
7
|
+
changes =
|
8
|
+
(target.instance_variable_get(:@_active_record_observer_changes) || {}).merge(_changes)
|
9
|
+
|
10
|
+
ActiveRecord::Base.transaction do
|
11
|
+
schema.
|
12
|
+
get_rules(target.class.name, action, changes).
|
13
|
+
each { |rule, handler| handler.(rule.(target)) }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module CustomActiveRecordObserver
|
2
|
+
module Observable
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
after_commit -> { CustomActiveRecordObserver::Handler.(self, :create, previous_changes) }, on: :create
|
7
|
+
after_commit -> { CustomActiveRecordObserver::Handler.(self, :update, previous_changes) }, on: :update
|
8
|
+
after_commit -> { CustomActiveRecordObserver::Handler.(self, :destroy, previous_changes) }, on: :destroy
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module CustomActiveRecordObserver
|
2
|
+
module Rules
|
3
|
+
class BaseRule
|
4
|
+
attr_reader :block
|
5
|
+
|
6
|
+
def initialize(block)
|
7
|
+
@block = block
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(target)
|
11
|
+
block.call(target)
|
12
|
+
end
|
13
|
+
|
14
|
+
def allowed?(changes)
|
15
|
+
raise NotImplementedError
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module CustomActiveRecordObserver
|
2
|
+
module Rules
|
3
|
+
class UpdateRule < BaseRule
|
4
|
+
attr_reader :attribute, :pattern
|
5
|
+
|
6
|
+
def initialize(block, attribute: nil, pattern: nil)
|
7
|
+
super(block)
|
8
|
+
|
9
|
+
if pattern.is_a?(Array) && pattern.size != 2
|
10
|
+
raise ArgumentError, "#{ pattern } is expected to be an array of two elements"
|
11
|
+
end
|
12
|
+
|
13
|
+
@attribute = attribute
|
14
|
+
@pattern = pattern
|
15
|
+
end
|
16
|
+
|
17
|
+
def allowed?(changes)
|
18
|
+
attribute &&
|
19
|
+
changes.has_key?(attribute) &&
|
20
|
+
matches_pattern?(*changes.fetch(attribute))
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def matches_pattern?(value_was, value)
|
26
|
+
return true unless pattern # no pattern => allowed for all
|
27
|
+
|
28
|
+
pattern.first === value_was && pattern.last === value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module CustomActiveRecordObserver
|
2
|
+
class Schema
|
3
|
+
attr_reader :schema
|
4
|
+
|
5
|
+
delegate :fetch, to: :schema
|
6
|
+
|
7
|
+
# {
|
8
|
+
# 'User' =>
|
9
|
+
# {
|
10
|
+
# update: [[rule_1, handler], [rule_2, handler]],
|
11
|
+
# create: [[rule_3, handler]]
|
12
|
+
# }
|
13
|
+
# 'Article' =>
|
14
|
+
# {
|
15
|
+
# destroy: [[rule5, handler]],
|
16
|
+
# }
|
17
|
+
# }
|
18
|
+
def initialize(schema = {})
|
19
|
+
@schema = schema
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_rule(class_name, action, rules, handler)
|
23
|
+
schema[class_name] ||= {}
|
24
|
+
schema[class_name][action] ||= []
|
25
|
+
|
26
|
+
Array(rules).each do |rule|
|
27
|
+
schema[class_name][action] << [rule, handler]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def get_rules(class_name, action, raw_changes = {})
|
32
|
+
changes = raw_changes.symbolize_keys
|
33
|
+
|
34
|
+
schema.fetch(class_name.to_sym, {}).
|
35
|
+
fetch(action, []).select do |(rule, handler)|
|
36
|
+
rule.allowed?(changes)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def classes
|
41
|
+
schema.keys.map { |name| name.to_s.constantize }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
metadata
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: custom_active_record_observer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- OnApp Ltd.
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-11-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.2'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '6'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '3.2'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '6'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: railties
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '3.2'
|
40
|
+
- - "<"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '6'
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '3.2'
|
50
|
+
- - "<"
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '6'
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: rspec
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
type: :development
|
61
|
+
prerelease: false
|
62
|
+
version_requirements: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: sqlite3
|
69
|
+
requirement: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
type: :development
|
75
|
+
prerelease: false
|
76
|
+
version_requirements: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: fuubar
|
83
|
+
requirement: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
type: :development
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
name: pry
|
97
|
+
requirement: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
description:
|
110
|
+
email:
|
111
|
+
- support@onapp.com
|
112
|
+
executables: []
|
113
|
+
extensions: []
|
114
|
+
extra_rdoc_files: []
|
115
|
+
files:
|
116
|
+
- README.md
|
117
|
+
- Rakefile
|
118
|
+
- lib/custom_active_record_observer.rb
|
119
|
+
- lib/custom_active_record_observer/all.rb
|
120
|
+
- lib/custom_active_record_observer/changes_tracker.rb
|
121
|
+
- lib/custom_active_record_observer/dsl.rb
|
122
|
+
- lib/custom_active_record_observer/engine.rb
|
123
|
+
- lib/custom_active_record_observer/handler.rb
|
124
|
+
- lib/custom_active_record_observer/models_extender.rb
|
125
|
+
- lib/custom_active_record_observer/not_nil.rb
|
126
|
+
- lib/custom_active_record_observer/observable.rb
|
127
|
+
- lib/custom_active_record_observer/rules/all.rb
|
128
|
+
- lib/custom_active_record_observer/rules/base_rule.rb
|
129
|
+
- lib/custom_active_record_observer/rules/create_rule.rb
|
130
|
+
- lib/custom_active_record_observer/rules/destroy_rule.rb
|
131
|
+
- lib/custom_active_record_observer/rules/update_rule.rb
|
132
|
+
- lib/custom_active_record_observer/schema.rb
|
133
|
+
- lib/custom_active_record_observer/version.rb
|
134
|
+
homepage:
|
135
|
+
licenses:
|
136
|
+
- Apache 2.0
|
137
|
+
metadata: {}
|
138
|
+
post_install_message:
|
139
|
+
rdoc_options: []
|
140
|
+
require_paths:
|
141
|
+
- lib
|
142
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - ">="
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: '0'
|
147
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - ">="
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '0'
|
152
|
+
requirements: []
|
153
|
+
rubyforge_project:
|
154
|
+
rubygems_version: 2.5.2
|
155
|
+
signing_key:
|
156
|
+
specification_version: 4
|
157
|
+
summary: CustomActiveRecordObserver
|
158
|
+
test_files: []
|