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 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,9 @@
1
+ module CustomActiveRecordObserver
2
+ module ModelsExtender
3
+ def self.call(schema)
4
+ schema.classes.map(&:base_class).uniq.each do |base_class|
5
+ base_class.include(CustomActiveRecordObserver::Observable)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ module CustomActiveRecordObserver
2
+ module NotNil
3
+ def self.===(other)
4
+ super || !other.nil?
5
+ end
6
+ end
7
+ 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,3 @@
1
+ require_relative 'base_rule'
2
+
3
+ Dir[File.expand_path('*.rb', __dir__)].each { |f| require f }
@@ -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,10 @@
1
+ module CustomActiveRecordObserver
2
+ module Rules
3
+ class CreateRule < BaseRule
4
+ def allowed?(changes)
5
+ true
6
+ end
7
+ end
8
+ end
9
+ end
10
+
@@ -0,0 +1,10 @@
1
+ module CustomActiveRecordObserver
2
+ module Rules
3
+ class DestroyRule < BaseRule
4
+ def allowed?(changes)
5
+ true
6
+ end
7
+ end
8
+ end
9
+ end
10
+
@@ -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
@@ -0,0 +1,3 @@
1
+ module CustomActiveRecordObserver
2
+ VERSION = '0.1.0'.freeze
3
+ 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: []