custom_active_record_observer 0.3.0 → 0.3.1

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
2
  SHA1:
3
- metadata.gz: d517d3fde840c8135daaf6eb3c5399ae908feca7
4
- data.tar.gz: 65d5a41cf511bef2dc452a9f47e6a52740fba8c3
3
+ metadata.gz: 4af7438aff8a12f47958c45d93a0fa4da7efbcba
4
+ data.tar.gz: 00116ca29ecc84e69dac009c5313a265e0697578
5
5
  SHA512:
6
- metadata.gz: 58dbf2b12928f3b643a402317b305c2388b997dab1caf1ca2084fbd4b6235389f9ebbc4d81e3852763b5a33749aa0aa88c4ed5ca70a09b334370d8beb3bc4daa
7
- data.tar.gz: 480d372c5311324ba4fb3c65c1a29d439921b59188095906c8aa4d354d74ac02275bfc9522bc977955973253dfc0287bcbdc34d0fafe944ae2f2a5a0ee8e1f78
6
+ metadata.gz: d0718789103b5aaac520e90497227f4484877317f3351b37dba4104a2b45839847d502b818682829fca542e19dfcc5b1e94f9723cb8dd454e2f918f4f0d4679e
7
+ data.tar.gz: 5ad0514aa41b98a010e30f8c714f30cd0f01c12b5a520de5c76f3c6f390b43d440eb132d96685b51e81ae0bf31f9e963668a7e7bb6e9f09e9849447f9ef5d393
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,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
@@ -1,5 +1,8 @@
1
1
  module CustomActiveRecordObserver
2
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>
3
6
  module Base
4
7
  extend ActiveSupport::Concern
5
8
 
@@ -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.3.0'.freeze
2
+ VERSION = '0.3.1'.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.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - OnApp Ltd.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-02-15 00:00:00.000000000 Z
11
+ date: 2018-03-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord