eventish 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
+ 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: []