custom_active_record_observer 0.2.0 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: b13cc7f8b0aceb704bd1d8e1e1fad0835574c65d
4
- data.tar.gz: 219db492b79a927aaecc9afe16d5d4d8fc6270e2
2
+ SHA256:
3
+ metadata.gz: 6644843d956acabec36f996af2a3add3756ee78aaeb5cf1b9286bbf1fb3edfc3
4
+ data.tar.gz: 6f4f323b250aac555eb294762811fe0c1d52c0f0288d03a41657489c2b804022
5
5
  SHA512:
6
- metadata.gz: 3d0ad899787e4a3e447cc3255b373875f541d731698bd200f48688d7645132e6bb83593657daa2b1a0929c563a5eb0ec69470ef9c8bc9655864fced1f03d26d4
7
- data.tar.gz: ef7562bcbce78b0b2a0cc29ceb64ba14d8222c31621d761bb63e26c0152a27862bdb415e6fd019f429e82fa0a1217c6a22bfbabbb218de13a1ecd1610865082a
6
+ metadata.gz: 6cc3163d3cf8ba51021ce31681945d301413ee30aff9387014c86c8f522d1cca62331f9ae7d25f910b6020aa06a72ab29956e6302133a48ec1be064df5d48efe
7
+ data.tar.gz: 30bbcc652985d669ef52e620a9b0e55d136d7feabdffd214b83db4ce1678b33589fe93830dee145d082f8944ec434dc428e7296e597e2fd885511d4927871a34
data/README.md CHANGED
@@ -1,25 +1,99 @@
1
+ [gem]: https://rubygems.org/gems/custom_active_record_observer
2
+ [travis]: https://travis-ci.org/OnApp/custom_active_record_observer
3
+
1
4
  # CustomActiveRecordObserver
2
5
 
3
- `custom_active_record_observer` is a DSL to track create/update/destroy operations based on AR callbacks.
6
+ [![Gem Version](https://badge.fury.io/rb/custom_active_record_observer.svg)][gem]
7
+ [![Build Status](https://travis-ci.org/OnApp/custom_active_record_observer.svg?branch=master)][travis]
8
+
9
+ A DSL to track create/update/destroy operations based on AR callbacks.
4
10
 
5
- Its a simple DSL similar to the code below:
11
+ ### Installation
12
+ Add the following code to your `Gemfile`
6
13
 
14
+ ```ruby
15
+ # Gemfile
16
+ gem 'custom_active_record_observer'
7
17
  ```
8
- CustomActiveRecordObserver.observe :EngagedUser, :CompanyOwner, handler: CustomerEventPublisher.new do
9
- on_create do |customer|
10
- Events::CustomerSignedUp.new(data: {customer_id: customer.id}
18
+ and run `bundle install`
19
+
20
+ Then add the observation rules (in `config/initializers/events_observation.rb`)
21
+ ```ruby
22
+ # config/initializers/events_observation.rb
23
+ CustomActiveRecordObserver.observe 'User' do
24
+ on_create do |user|
25
+ # do something
11
26
  end
27
+ end
28
+ ```
29
+
30
+ In this expample we track model `User`. If a new user is created, AR `:after_commit` callback is launched and the block in `on_create` executes.
12
31
 
13
- on_destroy do |customer|
14
- Events::CustomerSignedOut.new(data: {…})
32
+ We can track by real classes (which is not always possible, since that code is better to be placed in `config/initializers`). But passing strings or symbols is also allowed:
33
+ ```ruby
34
+ CustomActiveRecordObserver.observe User, :EngagedUser, 'CompanyOwner' { ... }
35
+ ```
36
+ Here we track three classes.
37
+
38
+ ### Usage
39
+ How do we use `custom_active_record_observer` in the wild? For instance, in this example we publish `UserRegisteredEvent` on user creation. (using `rails_event_store` gem). This is a pretty useful trade-off if there are no operations or aggregates introduced in the system architecture.
40
+
41
+ ```ruby
42
+ CustomActiveRecordObserver.observe 'User' do
43
+ on_create do |user|
44
+ Rails.application.config.event_store.publish_event(
45
+ UserRegisteredEvent.strict(data: { user_id: user.id }),
46
+ stream_name: "users-#{user.id}"
47
+ )
15
48
  end
49
+ end
50
+ ```
51
+
52
+ #### Usage with `handler`
53
+ `handler` might be passed into the observation. It is a simple object/class which has metho `call`. The result of `do..end` block is passed into that handler.
16
54
 
17
- on_change :group_id do |customer|
18
- Events::CustomerGroupChanged.new(data: {…})
55
+ ```ruby
56
+ handler = ->(event) do
57
+ Rails.application.config.event_store.publish_event(
58
+ event,
59
+ stream_name: "users-#{ event.data.fetch(:user_id) }"
60
+ )
61
+ end
62
+
63
+ CustomActiveRecordObserver.observe 'User', handler: handler do
64
+ on_create do |user|
65
+ UserRegisteredEvent.strict(data: { user_id: user.id })
66
+ end
67
+
68
+ on_add :user_group_id do |user|
69
+ UserAssignedToUserGroup.strict(data: {
70
+ user_id: user.id,
71
+ user_group_id: user.user_group_id
72
+ })
73
+ end
74
+
75
+ on_change :user_group_id do |user|
76
+ UserAssignedToAnotherUserGroupEvent.strict(data: {
77
+ user_id: user.id,
78
+ user_group_id: user.user_group_id
79
+ })
80
+ end
81
+
82
+ on_remove :user_group_id do |user|
83
+ UserUnassignedFromUserGroupEvent.strict(data: {
84
+ user_id: user.id,
85
+ user_group_id: user.previous_changes.fetch(:user_group_id).first
86
+ })
87
+ end
88
+
89
+ on_destroy do |user|
90
+ UserTerminatedEvent.strict(data: { user_id: user.id })
19
91
  end
20
-
21
92
  end
22
93
  ```
23
94
 
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.
95
+ ### Licence
96
+ See `LICENSE` file.
97
+
98
+ ## Contributing
99
+ Bug reports and pull requests are welcome on GitHub at https://github.com/OnApp/custom_active_record_observer.
@@ -6,6 +6,42 @@ module CustomActiveRecordObserver
6
6
  mattr_accessor :schema
7
7
  @@schema = Schema.new
8
8
 
9
+ # @param class_names [Symbols] - list of class names
10
+ # may be an object of Class, Symbol, or String
11
+ # (e.g. :User, 'User' or User)
12
+ # @example Usage without handler
13
+ # CustomActiveRecordObserver.observe 'User' do
14
+ # on_create do |user|
15
+ # Rails.application.config.event_store.publish_event(
16
+ # UserRegisteredEvent.strict(data: { user_id: user.id }),
17
+ # stream_name: "users-#{user.id}"
18
+ # )
19
+ # end
20
+ #
21
+ # on_destroy do |user|
22
+ # Rails.application.config.event_store.publish_event(
23
+ # UserTerminatedEvent.strict(data: { user_id: user.id }),
24
+ # stream_name: "users-#{user.id}"
25
+ # )
26
+ # end
27
+ # end
28
+ #
29
+ # @example Usage with handler
30
+ # handler = ->(event) {
31
+ # Rails.application.config.event_store.publish_event(
32
+ # event,
33
+ # stream_name: "users-#{event.data[:user_id]}"
34
+ # )
35
+ # }
36
+ # CustomActiveRecordObserver.observe 'User', handler: handler do
37
+ # on_create do |user|
38
+ # UserRegisteredEvent.strict(data: { user_id: user.id })
39
+ # end
40
+ #
41
+ # on_destroy do |user|
42
+ # UserTerminatedEvent.strict(data: { user_id: user.id })
43
+ # end
44
+ # end
9
45
  def self.observe(*class_names, handler: proc {}, &block)
10
46
  class_names.each do |class_name|
11
47
  DSL.new(block).actions_and_rules.each do |(action, rule)|
@@ -1,4 +1,6 @@
1
1
  require_relative 'models_extender'
2
+ require_relative 'observable/base'
3
+ require_relative 'observable/paranoid'
2
4
  require_relative 'observable'
3
5
  require_relative 'engine'
4
6
  require_relative 'rules/all'
@@ -5,7 +5,7 @@ module CustomActiveRecordObserver
5
5
  method_names.each do |name|
6
6
  define_method name do |*args|
7
7
  @_active_record_observer_changes ||= {}
8
- @_active_record_observer_changes.merge!(changes)
8
+ @_active_record_observer_changes.merge!(previous_changes)
9
9
 
10
10
  super(*args)
11
11
  end
@@ -1,21 +1,50 @@
1
+ # @title CustomActiveRecordObserver::DSL
1
2
  module CustomActiveRecordObserver
3
+ # A main methods that are supposed to be used within CustomActiveRecordObserver
4
+ #
5
+ # @author OnApp Ltd.
2
6
  class DSL
7
+ attr_reader :actions_and_rules
8
+
9
+ # @attr_reader [Array] actions_and_rules the resulting list of applied rules
3
10
  def initialize(block)
4
- instance_exec(&block)
5
- end
11
+ @actions_and_rules = []
6
12
 
7
- def actions_and_rules
8
- @actions_and_rules ||= []
13
+ instance_exec(&block)
9
14
  end
10
15
 
16
+ # Executes the code inside a block after the record is *created*
11
17
  def on_create(&block)
12
18
  store(:create, Rules::CreateRule.new(block))
13
19
  end
14
20
 
21
+ # Executes the code inside a block after the record is *destroyed*
15
22
  def on_destroy(&block)
16
23
  store(:destroy, Rules::DestroyRule.new(block))
17
24
  end
18
25
 
26
+ # @param attributes [Symbols] - list of attribute names (e.g. :user_id, :user_group_id ...)
27
+ # @param values [Array(2)] - array of two values (before and after)
28
+ # Launches code inside a block if an object changes any of attributes according
29
+ # to the pattern which is described in the brackets (value before -> value after)
30
+ #
31
+ # This uses `===` method to compare values.
32
+ # @example Add hook on update any of attributes according to the pattern
33
+ # on_update :address, :phone_number, [CustomActiveRecordObserver::NotNill,
34
+ # CustomActiveRecordObserver::NotNill] do |user|
35
+ # # notify system that user contact info was changed
36
+ # end
37
+ #
38
+ # Will be launched only if user_group_id was changed from `nil` to anything but nil.
39
+ # in the array [] there are two values that are compared with the real changes
40
+ # to detect if they maches.
41
+ # @example Track nil -> not nill changes of user_group_id attribute
42
+ # on_update :user_group_id, [nil, CustomActiveRecordObserver::NotNill] do |user|
43
+ # # here we know that some user_group was assigned to user
44
+ # end
45
+ # @example Other examples of usage:
46
+ # on_update :user_group_id, [1, 2] { |user| ... }
47
+ # on_update :user_group_id, [1, Integer] { |user| ... }
19
48
  def on_update(*attributes, &block)
20
49
  pattern = attributes.pop if attributes.last.is_a?(Array)
21
50
 
@@ -24,14 +53,32 @@ module CustomActiveRecordObserver
24
53
  end
25
54
  end
26
55
 
56
+ # Executes the code inside a block when the specific attributes are *updated*
57
+ # from <tt>nil</tt> to some <tt>not nil</tt> value
58
+ # @example
59
+ # on_add :user_group_id do |user|
60
+ # # here we know that user_group_id was nil, but now it is not
61
+ # end
27
62
  def on_add(*attributes, &block)
28
63
  on_update(*attributes.push([nil, NotNil]), &block)
29
64
  end
30
65
 
66
+ # Executes the code inside a block when the specific attributes are *updated*
67
+ # from some <tt>not nil</tt> value to <tt>nil</tt>
68
+ # @example
69
+ # on_remove :user_group_id do |user|
70
+ # # here we know that user_group_id is equal to nil now
71
+ # end
31
72
  def on_remove(*attributes, &block)
32
73
  on_update(*attributes.push([NotNil, nil]), &block)
33
74
  end
34
75
 
76
+ # Executes the code inside a block when the specific attributes are *updated*
77
+ # from some <tt>not nil</tt> to another <tt>not nil</tt>
78
+ # @example
79
+ # on_change :user_group_id do |user|
80
+ # # user was assigned to another user group
81
+ # end
35
82
  def on_change(*attributes, &block)
36
83
  on_update(*attributes.push([NotNil, NotNil]), &block)
37
84
  end
@@ -1,5 +1,9 @@
1
+ # @title CustomActiveRecordObserver::NotNil
1
2
  module CustomActiveRecordObserver
3
+ # A module to represent any of values that are not equal to +nil+
2
4
  module NotNil
5
+ # @param other [Any]
6
+ # @return [Boolean]
3
7
  def self.===(other)
4
8
  super || !other.nil?
5
9
  end
@@ -3,9 +3,15 @@ module CustomActiveRecordObserver
3
3
  extend ActiveSupport::Concern
4
4
 
5
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
6
+ if defined?(CollectiveIdea::Acts::NestedSet) && self < CollectiveIdea::Acts::NestedSet::Model
7
+ prepend CustomActiveRecordObserver::ChangesTracker[:set_depth!]
8
+ end
9
+
10
+ if try(:paranoid?)
11
+ include Paranoid
12
+ else
13
+ include Base
14
+ end
9
15
  end
10
16
  end
11
17
  end
@@ -0,0 +1,16 @@
1
+ module CustomActiveRecordObserver
2
+ module Observable
3
+ # The base module which is included to all classes that are observed.
4
+ # It adds AR after commit callbacks to class instances and handles
5
+ # the results with <tt>CustomActiveRecordObserver::Handler</tt>
6
+ module Base
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ after_commit -> { CustomActiveRecordObserver::Handler.(self, :create, previous_changes) }, on: :create
11
+ after_commit -> { CustomActiveRecordObserver::Handler.(self, :update, previous_changes) }, on: :update
12
+ after_commit -> { CustomActiveRecordObserver::Handler.(self, :destroy, previous_changes) }, on: :destroy
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module CustomActiveRecordObserver
2
+ module Observable
3
+ module Paranoid
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ after_commit -> { CustomActiveRecordObserver::Handler.(self, :create, previous_changes) },
8
+ on: :create
9
+ after_commit -> { CustomActiveRecordObserver::Handler.(self, :update, previous_changes) },
10
+ on: :update
11
+ after_destroy -> { CustomActiveRecordObserver::Handler.(self, :destroy, previous_changes) },
12
+ if: -> { deleted_at }
13
+ end
14
+ end
15
+ end
16
+ end
@@ -20,6 +20,10 @@ module CustomActiveRecordObserver
20
20
  end
21
21
 
22
22
  def add_rule(class_name, action, rules, handler)
23
+ unless class_name.is_a?(Symbol)
24
+ class_name = class_name.to_s.to_sym
25
+ end
26
+
23
27
  schema[class_name] ||= {}
24
28
  schema[class_name][action] ||= []
25
29
 
@@ -1,3 +1,3 @@
1
1
  module CustomActiveRecordObserver
2
- VERSION = '0.2.0'.freeze
2
+ VERSION = '0.5.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: custom_active_record_observer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - OnApp Ltd.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-11-22 00:00:00.000000000 Z
11
+ date: 2020-08-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,40 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '3.2'
20
- - - "<"
21
- - !ruby/object:Gem::Version
22
- version: '6'
19
+ version: '5.2'
23
20
  type: :runtime
24
21
  prerelease: false
25
22
  version_requirements: !ruby/object:Gem::Requirement
26
23
  requirements:
27
24
  - - ">="
28
25
  - !ruby/object:Gem::Version
29
- version: '3.2'
30
- - - "<"
31
- - !ruby/object:Gem::Version
32
- version: '6'
26
+ version: '5.2'
33
27
  - !ruby/object:Gem::Dependency
34
28
  name: railties
35
29
  requirement: !ruby/object:Gem::Requirement
36
30
  requirements:
37
31
  - - ">="
38
32
  - !ruby/object:Gem::Version
39
- version: '3.2'
40
- - - "<"
41
- - !ruby/object:Gem::Version
42
- version: '6'
33
+ version: '5.2'
43
34
  type: :runtime
44
35
  prerelease: false
45
36
  version_requirements: !ruby/object:Gem::Requirement
46
37
  requirements:
47
38
  - - ">="
48
39
  - !ruby/object:Gem::Version
49
- version: '3.2'
50
- - - "<"
51
- - !ruby/object:Gem::Version
52
- version: '6'
40
+ version: '5.2'
53
41
  - !ruby/object:Gem::Dependency
54
42
  name: rspec
55
43
  requirement: !ruby/object:Gem::Requirement
@@ -78,6 +66,34 @@ dependencies:
78
66
  - - ">="
79
67
  - !ruby/object:Gem::Version
80
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: appraisal
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
81
97
  description: |2
82
98
  This gem delivers a simple DSL based on the after_commit callback
83
99
  which can be used as an alternative to hooks from inside the model
@@ -98,6 +114,8 @@ files:
98
114
  - lib/custom_active_record_observer/models_extender.rb
99
115
  - lib/custom_active_record_observer/not_nil.rb
100
116
  - lib/custom_active_record_observer/observable.rb
117
+ - lib/custom_active_record_observer/observable/base.rb
118
+ - lib/custom_active_record_observer/observable/paranoid.rb
101
119
  - lib/custom_active_record_observer/rules/all.rb
102
120
  - lib/custom_active_record_observer/rules/base_rule.rb
103
121
  - lib/custom_active_record_observer/rules/create_rule.rb
@@ -117,7 +135,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
117
135
  requirements:
118
136
  - - ">="
119
137
  - !ruby/object:Gem::Version
120
- version: 2.2.0
138
+ version: 2.5.6
121
139
  required_rubygems_version: !ruby/object:Gem::Requirement
122
140
  requirements:
123
141
  - - ">="
@@ -125,7 +143,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
125
143
  version: '0'
126
144
  requirements: []
127
145
  rubyforge_project:
128
- rubygems_version: 2.5.2
146
+ rubygems_version: 2.7.6.2
129
147
  signing_key:
130
148
  specification_version: 4
131
149
  summary: A small handy library to track create/update/destroy actions on active_record