eventish 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: '08c9cf6785fc35a3174bf04df8a3f46011a10f1dc465ed24903d3924c293927a'
4
+ data.tar.gz: 53efbd04a12d69149c594f375b3b79da0cda0a9ed6a339e09d88ff4a9d4d830b
5
+ SHA512:
6
+ metadata.gz: 3aabb2135effcd4234379ef10beda4cb8cd2a5489b6b7318e7bc9cc84de6ef84daa5972b35496636dc3d439f7829cf2ef3ef27786c7512339a24b87404b4bce8
7
+ data.tar.gz: 63197732c22986bfc7f9d647808350886b71121cc9ccd3125ba35fa3f6eb3db2cc152b36c4f0fbd347224c46b9be50381a18e595014088341879769590593535
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2022 Mattia Roccoberton
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,158 @@
1
+ # Eventish
2
+
3
+ Yet another opinionated events library which proposes a simple API to handle... events 🎉
4
+
5
+ The main features:
6
+ - composable: just require the components that you need;
7
+ - with adapters: support _ActiveSupport::Notifications_;
8
+ - with async events: support _ActiveJob_;
9
+ - with callbacks: support _ActiveRecord_.
10
+
11
+ ## Install
12
+
13
+ - Add to your Gemfile: `gem 'eventish'` (and execute `bundle`)
14
+ - Create an initializer - _config/initializers/eventish.rb_:
15
+
16
+ ```rb
17
+ require 'eventish/adapters/active_support'
18
+
19
+ Eventish.setup do |config|
20
+ config.adapter = Eventish::Adapters::ActiveSupport
21
+ end
22
+
23
+ Rails.configuration.to_prepare do
24
+ # NOTE: required to load the event descendants when eager_load is off
25
+ unless Rails.configuration.eager_load
26
+ events = Rails.root.join('app/events/**/*.rb').to_s
27
+ Dir[events].sort.each { |event| require event }
28
+ end
29
+ end
30
+
31
+ Rails.configuration.after_initialize do
32
+ Eventish::SimpleEvent.subscribe_all # NOTE: events will be available after this point
33
+
34
+ Eventish.adapter.publish('app_loaded') # just a test event
35
+ end
36
+ ```
37
+
38
+ - Create some events - _app/events/main/app_loaded_event.rb_:
39
+
40
+ ```rb
41
+ module Main
42
+ class AppLoadedEvent < Eventish::SimpleEvent
43
+ def call(_none, _options = {})
44
+ puts '> App loaded event'
45
+ end
46
+ end
47
+ end
48
+ ```
49
+
50
+ For a complete example please take a look at the [dummy app](spec/dummy) in the specs.
51
+
52
+ ### Adatpers
53
+
54
+ Only _ActiveSupport_ is supported for now.
55
+
56
+ ```rb
57
+ # initializer setup
58
+ require 'eventish/adapters/active_support'
59
+
60
+ Eventish.setup do |config|
61
+ config.adapter = Eventish::Adapters::ActiveSupport
62
+ end
63
+ ```
64
+
65
+ ### Simple events
66
+
67
+ Generic events not related to a specific component.
68
+
69
+ ```rb
70
+ # initializer setup
71
+ Rails.configuration.after_initialize do
72
+ # Subscribe all Eventish::SimpleEvent descendants using the configured adapter
73
+ # The descendants event classes must be loaded before this point - see eager_load notes in the Install section
74
+ Eventish::SimpleEvent.subscribe_all
75
+ end
76
+ ```
77
+
78
+ Sample event - _app/events/main/test_event.rb_:
79
+
80
+ ```rb
81
+ module Main
82
+ class TestEvent < Eventish::SimpleEvent
83
+ def call(_none, _options = {})
84
+ puts '> A test event'
85
+ end
86
+
87
+ class << self
88
+ def event_name
89
+ # this is optional, if not set the event name will be inferred from the class name
90
+ # in this sample it would be "test" - TestEvent => underscore => remove _event suffix
91
+ 'some_event'
92
+ end
93
+ end
94
+ end
95
+ end
96
+ ```
97
+
98
+ Publish the event: `Eventish.adapter.publish('some_event')`
99
+
100
+ ### Async events
101
+
102
+ Events executed in a background process. Only _ActiveJob_ is supported for now.
103
+
104
+ ```rb
105
+ # initializer setup
106
+ require 'eventish/active_job_event'
107
+
108
+ Rails.configuration.after_initialize do
109
+ Eventish::ActiveJobEvent.subscribe_all
110
+ end
111
+ ```
112
+
113
+ Sample event - _app/events/notifications/user_after_save_commit_event.rb_:
114
+
115
+ ```rb
116
+ module Notifications
117
+ class UserAfterCommitEvent < Eventish::ActiveJobEvent
118
+ def call(user, _options = {})
119
+ Rails.logger.info ">>> User ##{user.id} after commit notification"
120
+ end
121
+ end
122
+ end
123
+ ```
124
+
125
+ ### Callbacks
126
+
127
+ Wrapper for callbacks. Only _ActiveRecord_ is supported for now.
128
+ This is just a glue component to have callbacks with a nice syntax.
129
+
130
+ ```rb
131
+ # initializer setup
132
+ require 'eventish/callback'
133
+ ```
134
+
135
+ Sample model usage:
136
+
137
+ ```rb
138
+ class User < ActiveRecord::Base
139
+ before_validation ::Eventish::Callback
140
+ after_save_commit ::Eventish::Callback
141
+ end
142
+ ```
143
+
144
+ For events definition see Simple or Async events.
145
+
146
+ ## Do you like it? Star it!
147
+
148
+ If you use this component just star it. A developer is more motivated to improve a project when there is some interest.
149
+
150
+ Or consider offering me a coffee, it's a small thing but it is greatly appreciated: [about me](https://www.blocknot.es/about-me).
151
+
152
+ ## Contributors
153
+
154
+ - [Mattia Roccoberton](https://www.blocknot.es): author
155
+
156
+ ## License
157
+
158
+ The gem is available as open-source under the terms of the [MIT](LICENSE.txt).
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Eventish
4
+ class ActiveJobEvent < ::ActiveJob::Base
5
+ def call(_target, _args, &_block)
6
+ raise NotImplementedError
7
+ end
8
+
9
+ def perform(target, args)
10
+ call(target, args)
11
+ end
12
+
13
+ class << self
14
+ include Eventish::EventApi
15
+
16
+ def trigger(target, args, &_block)
17
+ perform_later(target, args)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Eventish
4
+ class Adapters
5
+ class ActiveSupport
6
+ class << self
7
+ def publish(event_name, target = nil, options: {})
8
+ ::ActiveSupport::Notifications.instrument(event_name, target: target, event_options: options)
9
+ end
10
+
11
+ def subscribe(event_name, handler)
12
+ ::ActiveSupport::Notifications.subscribe(event_name) do |name, start, finish, id, payload|
13
+ args = { event: name, id: id, start: start, finish: finish }
14
+ handler.trigger(payload[:target], args, &payload[:block])
15
+ end
16
+ end
17
+
18
+ def unsubscribe(event_name)
19
+ ::ActiveSupport::Notifications.unsubscribe(event_name)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Eventish
4
+ class Callback
5
+ class << self
6
+ def callback_event(target, &block)
7
+ event = "#{Eventish.underscore(target.class.to_s)}_#{__callee__}"
8
+ Eventish.adapter.publish(event, target, &block)
9
+ end
10
+
11
+ def callback_commit_event(target, &block)
12
+ event = "#{Eventish.underscore(target.class.to_s)}_after_commit"
13
+ Eventish.adapter.publish(event, target, &block)
14
+ end
15
+
16
+ alias_method :after_initialize, :callback_event
17
+ alias_method :after_find, :callback_event
18
+
19
+ alias_method :before_validation, :callback_event
20
+ alias_method :after_validation, :callback_event
21
+
22
+ alias_method :before_create, :callback_event
23
+ alias_method :around_create, :callback_event
24
+ alias_method :after_create, :callback_event
25
+
26
+ alias_method :before_update, :callback_event
27
+ alias_method :around_update, :callback_event
28
+ alias_method :after_update, :callback_event
29
+
30
+ alias_method :before_save, :callback_event
31
+ alias_method :around_save, :callback_event
32
+ alias_method :after_save, :callback_event
33
+
34
+ alias_method :before_destroy, :callback_event
35
+ alias_method :around_destroy, :callback_event
36
+ alias_method :after_destroy, :callback_event
37
+
38
+ alias_method :after_commit, :callback_commit_event
39
+ alias_method :after_save_commit, :callback_commit_event # => after_commit
40
+ alias_method :after_create_commit, :callback_commit_event # => after_commit
41
+ alias_method :after_update_commit, :callback_commit_event # => after_commit
42
+ alias_method :after_destroy_commit, :callback_commit_event # => after_commit
43
+ alias_method :after_rollback, :callback_event
44
+
45
+ alias_method :after_touch, :callback_event
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Eventish
4
+ module EventApi
5
+ def <=>(other)
6
+ other&.priority <=> priority
7
+ end
8
+
9
+ def event_name
10
+ # If event_name is not set, infer the event from the class name
11
+ @event_name ||= Eventish.underscore(to_s).delete_suffix('_event')
12
+ end
13
+
14
+ def priority
15
+ 0
16
+ end
17
+
18
+ def subscribe
19
+ Eventish.adapter.subscribe(event_name, self)
20
+ end
21
+
22
+ def subscribe_all
23
+ # iterate the descendants
24
+ ObjectSpace.each_object(singleton_class).sort.each(&:subscribe)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Eventish
4
+ class SimpleEvent
5
+ def call(_target, _args, &_block)
6
+ raise NotImplementedError
7
+ end
8
+
9
+ class << self
10
+ include EventApi
11
+
12
+ def trigger(target, args, &block)
13
+ new.call(target, args, &block)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Eventish # :nodoc:
4
+ VERSION = '0.1.0'
5
+ end
data/lib/eventish.rb ADDED
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'eventish/event_api'
4
+ require_relative 'eventish/simple_event'
5
+
6
+ module Eventish
7
+ OPTIONS = %i[adapter].freeze
8
+
9
+ MissingAdapterError = Class.new(StandardError)
10
+
11
+ module_function
12
+
13
+ def adapter
14
+ config.adapter
15
+ end
16
+
17
+ def config
18
+ @options ||= Struct.new(*OPTIONS).new # rubocop:disable Naming/MemoizedInstanceVariableName
19
+ end
20
+
21
+ def setup
22
+ @options ||= Struct.new(*OPTIONS).new
23
+ yield(@options) if block_given?
24
+ raise MissingAdapterError, 'Please specify an event adapter' unless @options.adapter
25
+
26
+ @options
27
+ end
28
+
29
+ def underscore(camel_cased_word)
30
+ return camel_cased_word.to_s unless /[A-Z-]|::/.match?(camel_cased_word)
31
+
32
+ word = camel_cased_word.to_s.gsub(/\A.*::/, '')
33
+ word.gsub!(/([A-Z]+)(?=[A-Z][a-z])|([a-z\d])(?=[A-Z])/) { ($1 || $2) << "_" }
34
+ word.tr!("-", "_")
35
+ word.downcase!
36
+ word
37
+ end
38
+ end
metadata ADDED
@@ -0,0 +1,54 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: eventish
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Mattia Roccoberton
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-05-17 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A simple and composable event library
14
+ email: mat@blocknot.es
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - LICENSE.txt
20
+ - README.md
21
+ - lib/eventish.rb
22
+ - lib/eventish/active_job_event.rb
23
+ - lib/eventish/adapters/active_support.rb
24
+ - lib/eventish/callback.rb
25
+ - lib/eventish/event_api.rb
26
+ - lib/eventish/simple_event.rb
27
+ - lib/eventish/version.rb
28
+ homepage: https://github.com/blocknotes/eventish
29
+ licenses:
30
+ - MIT
31
+ metadata:
32
+ homepage_uri: https://github.com/blocknotes/eventish
33
+ source_code_uri: https://github.com/blocknotes/eventish
34
+ rubygems_mfa_required: 'true'
35
+ post_install_message:
36
+ rdoc_options: []
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 2.6.0
44
+ required_rubygems_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ requirements: []
50
+ rubygems_version: 3.1.6
51
+ signing_key:
52
+ specification_version: 4
53
+ summary: Yet another events library
54
+ test_files: []