voltage 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 +7 -0
- data/.github/CODEOWNERS +4 -0
- data/.github/FUNDING.yml +4 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +41 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +23 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +38 -0
- data/.github/dependabot.yml +15 -0
- data/.github/workflows/ruby-tests.yml +53 -0
- data/.gitignore +17 -0
- data/.rubocop.yml +12 -0
- data/CHANGELOG.md +16 -0
- data/CODE_OF_CONDUCT.md +75 -0
- data/CONTRIBUTING.md +80 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +299 -0
- data/Rakefile +15 -0
- data/examples/activerecord_model.rb +43 -0
- data/examples/arguments.rb +19 -0
- data/examples/block_context.rb +23 -0
- data/examples/blocks.rb +18 -0
- data/examples/call.rb +23 -0
- data/examples/chain.rb +58 -0
- data/examples/listener.rb +30 -0
- data/lib/voltage/extensions/active_record.rb +60 -0
- data/lib/voltage/extensions/call.rb +25 -0
- data/lib/voltage/listener.rb +27 -0
- data/lib/voltage/mock.rb +51 -0
- data/lib/voltage/version.rb +5 -0
- data/lib/voltage.rb +49 -0
- data/voltage.gemspec +35 -0
- metadata +187 -0
data/README.md
ADDED
@@ -0,0 +1,299 @@
|
|
1
|
+
# Voltage
|
2
|
+
|
3
|
+
[](https://github.com/fnando/voltage/actions/workflows/ruby-tests.yml)
|
4
|
+
[](https://rubygems.org/gems/voltage)
|
5
|
+
[](https://rubygems.org/gems/voltage)
|
6
|
+
|
7
|
+
A simple observer implementation on POROs (Plain Old Ruby Object) and
|
8
|
+
ActiveRecord objects.
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add this line to your application's Gemfile:
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
gem "voltage"
|
16
|
+
```
|
17
|
+
|
18
|
+
And then execute:
|
19
|
+
|
20
|
+
$ bundle
|
21
|
+
|
22
|
+
Or install it yourself as:
|
23
|
+
|
24
|
+
$ gem install voltage
|
25
|
+
|
26
|
+
## Usage
|
27
|
+
|
28
|
+
You can use Voltage with PORO (Plain Old Ruby Object) and ActiveRecord.
|
29
|
+
|
30
|
+
### Plain Ruby
|
31
|
+
|
32
|
+
All you have to do is including the `Voltage` module. Then you can add listeners
|
33
|
+
and trigger events.
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
class Status
|
37
|
+
include Voltage
|
38
|
+
|
39
|
+
def ready!
|
40
|
+
emit(:ready)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
status = Status.new
|
45
|
+
status.before(:ready) { puts "Before the ready event!" }
|
46
|
+
status.on(:ready) { puts "I'm ready!" }
|
47
|
+
status.after(:ready) { puts "After the ready event!" }
|
48
|
+
status.ready!
|
49
|
+
#=> Before the ready event!
|
50
|
+
#=> I'm ready!
|
51
|
+
#=> After the ready event!
|
52
|
+
```
|
53
|
+
|
54
|
+
You can also pass objects that implement methods like `before_*`, `on_*` and
|
55
|
+
`after_*`.
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
class MyListener
|
59
|
+
def before_ready
|
60
|
+
puts "Before the ready event!"
|
61
|
+
end
|
62
|
+
|
63
|
+
def on_ready
|
64
|
+
puts "I'm ready!"
|
65
|
+
end
|
66
|
+
|
67
|
+
def after_ready
|
68
|
+
puts "After the ready event!"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
Status.new
|
73
|
+
.add_listener(MyListener.new)
|
74
|
+
.ready!
|
75
|
+
#=> Before the ready event!
|
76
|
+
#=> I'm ready!
|
77
|
+
#=> After the ready event!
|
78
|
+
```
|
79
|
+
|
80
|
+
Executed blocks don't switch context. You always have to emit the object you're
|
81
|
+
interested in. The follow example uses `emit(:output, self)` to send the
|
82
|
+
`Contact` instance to all listeners.
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
class Contact
|
86
|
+
include Voltage
|
87
|
+
|
88
|
+
attr_reader :name, :email
|
89
|
+
|
90
|
+
def initialize(name, email)
|
91
|
+
@name, @email = name, email
|
92
|
+
end
|
93
|
+
|
94
|
+
def output!
|
95
|
+
emit(:output, self)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
contact = Contact.new('John Doe', 'john@example.org')
|
100
|
+
contact.on(:output) {|contact| puts contact.name, contact.email }
|
101
|
+
contact.output!
|
102
|
+
#=> John Doe
|
103
|
+
#=> john@example.org
|
104
|
+
```
|
105
|
+
|
106
|
+
You can provide arguments while emitting a voltage:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
class Arguments
|
110
|
+
include Voltage
|
111
|
+
end
|
112
|
+
|
113
|
+
class MyListener
|
114
|
+
def on_args(a, b)
|
115
|
+
puts a, b
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
Arguments.new
|
120
|
+
.on(:args) {|a, b| puts a, b }
|
121
|
+
.add_listener(MyListener.new)
|
122
|
+
.emit(:args, 1, 2)
|
123
|
+
```
|
124
|
+
|
125
|
+
### ActiveRecord
|
126
|
+
|
127
|
+
You can use Voltage with ActiveRecord, which will give you some default events
|
128
|
+
like `:create`, `:update`, `:remove` and `:validation`.
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
class Thing < ActiveRecord::Base
|
132
|
+
include Voltage.active_record
|
133
|
+
|
134
|
+
validates_presence_of :name
|
135
|
+
end
|
136
|
+
|
137
|
+
thing = Thing.new(:name => "Stuff")
|
138
|
+
thing.on(:create) {|thing| puts thing.updated_at, thing.name }
|
139
|
+
thing.on(:update) {|thing| puts thing.updated_at, thing.name }
|
140
|
+
thing.on(:remove) {|thing| puts thing.destroyed? }
|
141
|
+
thing.on(:validation) {|thing| p thing.errors.full_messages }
|
142
|
+
|
143
|
+
thing.save!
|
144
|
+
#=> 2013-01-26 10:32:39 -0200
|
145
|
+
#=> Stuff
|
146
|
+
|
147
|
+
thing.update_attributes(:name => "Updated stuff")
|
148
|
+
#=> 2013-01-26 10:33:11 -0200
|
149
|
+
#=> Updated stuff
|
150
|
+
|
151
|
+
thing.update_attributes(:name => nil)
|
152
|
+
#=> ["Name can't be blank"]
|
153
|
+
|
154
|
+
thing.destroy
|
155
|
+
#=> true
|
156
|
+
```
|
157
|
+
|
158
|
+
These are the available events:
|
159
|
+
|
160
|
+
- `before(:create)`: triggered before creating the record (record is valid).
|
161
|
+
- `on(:create)`: triggered after `before(:create)` event.
|
162
|
+
- `after(:create)`: triggered after the `on(:create)` event.
|
163
|
+
- `before(:update)`: triggered before updating the record (record is valid).
|
164
|
+
- `on(:update)`: triggered when the `before(:update)` event.
|
165
|
+
- `after(:update)`: triggered after the `on(:update)` event.
|
166
|
+
- `before(:remove)`: triggered before removing the record.
|
167
|
+
- `on(:remove)`: triggered after the `before(:remove)`.
|
168
|
+
- `after(:remove)`: triggered after the `on(:remove)` event.
|
169
|
+
- `before(:validation)`: triggered before validating record.
|
170
|
+
- `on(:validation)`: triggered when record is invalid.
|
171
|
+
- `after(:validation)`: triggered after validating record.
|
172
|
+
|
173
|
+
### Inside Rails
|
174
|
+
|
175
|
+
Although there's no special code for Rails, here's just an example of how you
|
176
|
+
can use it:
|
177
|
+
|
178
|
+
```ruby
|
179
|
+
class UsersController < ApplicationController
|
180
|
+
def create
|
181
|
+
@user = User.new(user_params)
|
182
|
+
|
183
|
+
Signup.new(@user)
|
184
|
+
.on(:success) { redirect_to login_path, notice: 'Welcome to MyApp!' }
|
185
|
+
.on(:failure) { render :new }
|
186
|
+
.call
|
187
|
+
end
|
188
|
+
end
|
189
|
+
```
|
190
|
+
|
191
|
+
If you're using plain ActiveRecord, just do something like the following:
|
192
|
+
|
193
|
+
```ruby
|
194
|
+
class UsersController < ApplicationController
|
195
|
+
def create
|
196
|
+
@user = User.new(user_params)
|
197
|
+
@user
|
198
|
+
.on(:create) { redirect_to login_path, notice: 'Welcome to MyApp!' }
|
199
|
+
.on(:validation) { render :new }
|
200
|
+
.save
|
201
|
+
end
|
202
|
+
end
|
203
|
+
```
|
204
|
+
|
205
|
+
### Voltage::Call
|
206
|
+
|
207
|
+
You can include `Voltage.call` instead, so you can have a common interface for
|
208
|
+
your observable object. This will add the `.call()` method to the target class,
|
209
|
+
which will delegate attributes to the observable's `initialize` method and call
|
210
|
+
its `call` method.
|
211
|
+
|
212
|
+
```ruby
|
213
|
+
class Contact
|
214
|
+
include Voltage.call
|
215
|
+
|
216
|
+
attr_reader :name, :email
|
217
|
+
|
218
|
+
def initialize(name, email)
|
219
|
+
@name, @email = name, email
|
220
|
+
end
|
221
|
+
|
222
|
+
def call
|
223
|
+
emit(:output, self)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
Contact.call('John', 'john@example.com') do |o|
|
228
|
+
o.on(:output) {|contact| puts contact }
|
229
|
+
end
|
230
|
+
```
|
231
|
+
|
232
|
+
Notice that you don't have to explicit call the instance's `call` method;
|
233
|
+
`Contact.call` will initialize the object with all the provided parameters and
|
234
|
+
call `Contact#call` after the block has been executed.
|
235
|
+
|
236
|
+
### Testing
|
237
|
+
|
238
|
+
`Voltage::Mock` can be helpful for most test situations where you don't want to
|
239
|
+
bring other mock libraries.
|
240
|
+
|
241
|
+
```ruby
|
242
|
+
require "voltage/mock"
|
243
|
+
|
244
|
+
class SomeTest < Minitest::Test
|
245
|
+
def test_some_test
|
246
|
+
mock = Voltage::Mock.new
|
247
|
+
|
248
|
+
# Using listener
|
249
|
+
sum = Sum.new
|
250
|
+
sum.add_listener(mock)
|
251
|
+
|
252
|
+
# Calling `mock.on(event_name)` is required because
|
253
|
+
# the handler doesn't receive the event name, just the
|
254
|
+
# arguments.
|
255
|
+
sum = Sum.new
|
256
|
+
sum.on(:result, &mock.on(:result))
|
257
|
+
|
258
|
+
# Using with Voltage.call
|
259
|
+
Sum.call(1, 2, &mock)
|
260
|
+
|
261
|
+
assert mock.received?(:result)
|
262
|
+
assert mock.received?(:result, times: 1)
|
263
|
+
assert mock.received?(:result, with: [3])
|
264
|
+
assert mock.received?(:result, with: ->(result) { result == 3 } )
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
```
|
269
|
+
|
270
|
+
## Contributing
|
271
|
+
|
272
|
+
1. Fork it
|
273
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
274
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
275
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
276
|
+
5. Create new Pull Request
|
277
|
+
|
278
|
+
## License
|
279
|
+
|
280
|
+
Copyright (c) 2013 Nando Vieira
|
281
|
+
|
282
|
+
MIT License
|
283
|
+
|
284
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
285
|
+
this software and associated documentation files (the "Software"), to deal in
|
286
|
+
the Software without restriction, including without limitation the rights to
|
287
|
+
use, copy, modify, merge, publish, distribute, sub-license, and/or sell copies
|
288
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
289
|
+
so, subject to the following conditions:
|
290
|
+
|
291
|
+
The above copyright notice and this permission notice shall be included in all
|
292
|
+
copies or substantial portions of the Software.
|
293
|
+
|
294
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
295
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
296
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
297
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
298
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
299
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rake/testtask"
|
5
|
+
require "rubocop/rake_task"
|
6
|
+
|
7
|
+
Rake::TestTask.new(:test) do |t|
|
8
|
+
t.libs << "test"
|
9
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
10
|
+
t.warning = false
|
11
|
+
end
|
12
|
+
|
13
|
+
RuboCop::RakeTask.new
|
14
|
+
|
15
|
+
task default: %i[test rubocop]
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
4
|
+
require "voltage"
|
5
|
+
require "active_record"
|
6
|
+
|
7
|
+
ActiveRecord::Base.establish_connection(
|
8
|
+
adapter: "sqlite3",
|
9
|
+
database: ":memory:"
|
10
|
+
)
|
11
|
+
|
12
|
+
ActiveRecord::Schema.define(version: 0) do
|
13
|
+
create_table :things do |t|
|
14
|
+
t.string :name
|
15
|
+
t.timestamps null: false
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class Thing < ActiveRecord::Base
|
20
|
+
include Voltage::ActiveRecord
|
21
|
+
|
22
|
+
validates_presence_of :name
|
23
|
+
end
|
24
|
+
|
25
|
+
thing = Thing.new(name: "Stuff")
|
26
|
+
thing.on(:create) {|model| puts model.updated_at, model.name }
|
27
|
+
thing.on(:update) {|model| puts model.updated_at, model.name }
|
28
|
+
thing.on(:remove) {|model| puts model.destroyed? }
|
29
|
+
thing.on(:validation) {|model| p model.errors.full_messages }
|
30
|
+
|
31
|
+
thing.save!
|
32
|
+
#=> 2013-01-26 10:32:39 -0200
|
33
|
+
#=> Stuff
|
34
|
+
|
35
|
+
thing.update_attributes(name: "Updated stuff")
|
36
|
+
#=> 2013-01-26 10:33:11 -0200
|
37
|
+
#=> Updated stuff
|
38
|
+
|
39
|
+
thing.update_attributes(name: nil)
|
40
|
+
#=> ["Name can"t be blank"]
|
41
|
+
|
42
|
+
thing.destroy
|
43
|
+
#=> true
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
4
|
+
require "voltage"
|
5
|
+
|
6
|
+
class Arguments
|
7
|
+
include Voltage
|
8
|
+
end
|
9
|
+
|
10
|
+
class MyListener
|
11
|
+
def on_args(a, b)
|
12
|
+
puts a, b
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
args = Arguments.new
|
17
|
+
args.on(:args) {|a, b| puts a, b }
|
18
|
+
args.listeners << MyListener.new
|
19
|
+
args.emit(:args, 1, 2)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
4
|
+
require "voltage"
|
5
|
+
|
6
|
+
class Contact
|
7
|
+
include Voltage
|
8
|
+
|
9
|
+
attr_reader :name, :email
|
10
|
+
|
11
|
+
def initialize(name, email)
|
12
|
+
@name = name
|
13
|
+
@email = email
|
14
|
+
end
|
15
|
+
|
16
|
+
def output!
|
17
|
+
emit(:output, self)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
contact = Contact.new("John Doe", "john@example.org")
|
22
|
+
contact.on(:output) {|c| puts c.name, c.email }
|
23
|
+
contact.output!
|
data/examples/blocks.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
4
|
+
require "voltage"
|
5
|
+
|
6
|
+
class Status
|
7
|
+
include Voltage
|
8
|
+
|
9
|
+
def ready!
|
10
|
+
emit(:ready)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
status = Status.new
|
15
|
+
status.before(:ready) { puts "Before the ready event!" }
|
16
|
+
status.on(:ready) { puts "I'm ready!" }
|
17
|
+
status.after(:ready) { puts "After the ready event!" }
|
18
|
+
status.ready!
|
data/examples/call.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
4
|
+
require "voltage"
|
5
|
+
|
6
|
+
class Contact
|
7
|
+
include Voltage.call
|
8
|
+
|
9
|
+
attr_reader :name, :email
|
10
|
+
|
11
|
+
def initialize(name, email)
|
12
|
+
@name = name
|
13
|
+
@email = email
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
emit(:output, self)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
Contact.call("John", "john@example.com") do |o|
|
22
|
+
o.on(:output) {|contact| puts contact.name }
|
23
|
+
end
|
data/examples/chain.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
4
|
+
require "voltage"
|
5
|
+
require "active_record"
|
6
|
+
|
7
|
+
ActiveRecord::Base.establish_connection(
|
8
|
+
adapter: "sqlite3",
|
9
|
+
database: ":memory:"
|
10
|
+
)
|
11
|
+
|
12
|
+
ActiveRecord::Schema.define(version: 0) do
|
13
|
+
create_table :things do |t|
|
14
|
+
t.string :name
|
15
|
+
t.timestamps null: false
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class Thing < ActiveRecord::Base
|
20
|
+
include Voltage.active_record
|
21
|
+
|
22
|
+
validates_presence_of :name
|
23
|
+
end
|
24
|
+
|
25
|
+
class MyListener
|
26
|
+
%i[validation update create remove].each do |type|
|
27
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
28
|
+
def before_#{type}(thing); puts __method__; end
|
29
|
+
def on_#{type}(thing); puts __method__; end
|
30
|
+
def after_#{type}(thing); puts __method__; end
|
31
|
+
RUBY
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
puts "\n=== Creating valid record"
|
36
|
+
thing = Thing.new(name: "Stuff")
|
37
|
+
thing.listeners << MyListener.new
|
38
|
+
thing.save
|
39
|
+
|
40
|
+
puts "\n=== Creating invalid record"
|
41
|
+
thing = Thing.new(name: nil)
|
42
|
+
thing.listeners << MyListener.new
|
43
|
+
thing.save
|
44
|
+
|
45
|
+
puts "\n=== Updating valid record"
|
46
|
+
thing = Thing.create(name: "Stuff")
|
47
|
+
thing.listeners << MyListener.new
|
48
|
+
thing.update_attributes(name: "Updated stuff")
|
49
|
+
|
50
|
+
puts "\n=== Updating invalid record"
|
51
|
+
thing = Thing.create!(name: "Stuff")
|
52
|
+
thing.listeners << MyListener.new
|
53
|
+
thing.update_attributes(name: nil)
|
54
|
+
|
55
|
+
puts "\n=== Removing record"
|
56
|
+
thing = Thing.create(name: "Stuff")
|
57
|
+
thing.listeners << MyListener.new
|
58
|
+
thing.destroy
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
4
|
+
require "voltage"
|
5
|
+
|
6
|
+
class Status
|
7
|
+
include Voltage
|
8
|
+
|
9
|
+
def ready!
|
10
|
+
emit(:ready)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class MyListener
|
15
|
+
def before_ready
|
16
|
+
puts "Before the ready event!"
|
17
|
+
end
|
18
|
+
|
19
|
+
def on_ready
|
20
|
+
puts "I'm ready!"
|
21
|
+
end
|
22
|
+
|
23
|
+
def after_ready
|
24
|
+
puts "After the ready event!"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
status = Status.new
|
29
|
+
status.listeners << MyListener.new
|
30
|
+
status.ready!
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Voltage
|
4
|
+
def self.active_record
|
5
|
+
Extensions::ActiveRecord
|
6
|
+
end
|
7
|
+
|
8
|
+
module Extensions
|
9
|
+
module ActiveRecord
|
10
|
+
def self.included(base)
|
11
|
+
base.class_eval do
|
12
|
+
include Voltage
|
13
|
+
|
14
|
+
around_create :around_create_signal
|
15
|
+
around_save :around_save_signal
|
16
|
+
around_destroy :around_destroy_signal
|
17
|
+
before_validation :before_validation_signal
|
18
|
+
after_validation :after_validation_signal
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private def around_create_signal
|
23
|
+
emit_signal(:before, :create, self)
|
24
|
+
yield
|
25
|
+
return unless persisted?
|
26
|
+
|
27
|
+
emit_signal(:on, :create, self)
|
28
|
+
emit_signal(:after, :create, self)
|
29
|
+
end
|
30
|
+
|
31
|
+
private def around_save_signal
|
32
|
+
if new_record?
|
33
|
+
yield
|
34
|
+
return
|
35
|
+
end
|
36
|
+
|
37
|
+
emit_signal(:before, :update, self)
|
38
|
+
yield
|
39
|
+
emit_signal(:on, :update, self)
|
40
|
+
emit_signal(:after, :update, self)
|
41
|
+
end
|
42
|
+
|
43
|
+
private def around_destroy_signal
|
44
|
+
emit_signal(:before, :remove, self)
|
45
|
+
yield
|
46
|
+
emit_signal(:on, :remove, self)
|
47
|
+
emit_signal(:after, :remove, self)
|
48
|
+
end
|
49
|
+
|
50
|
+
private def before_validation_signal
|
51
|
+
emit_signal(:before, :validation, self)
|
52
|
+
end
|
53
|
+
|
54
|
+
private def after_validation_signal
|
55
|
+
emit_signal(:on, :validation, self) if errors.any?
|
56
|
+
emit_signal(:after, :validation, self)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Voltage
|
4
|
+
def self.call
|
5
|
+
Extensions::Call
|
6
|
+
end
|
7
|
+
|
8
|
+
module Extensions
|
9
|
+
module Call
|
10
|
+
def self.included(target)
|
11
|
+
target.include(Voltage)
|
12
|
+
target.extend(ClassMethods)
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
def call(*args, **kwargs)
|
17
|
+
new(*args, **kwargs).tap do |instance|
|
18
|
+
yield(instance) if block_given?
|
19
|
+
instance.call
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Voltage
|
4
|
+
class Listener
|
5
|
+
def initialize(context, type, event, &block)
|
6
|
+
@context = context
|
7
|
+
@type = type
|
8
|
+
@event = event
|
9
|
+
@block = block
|
10
|
+
@event_method = :"#{@type}_#{@event}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def method_missing(method_name, *args)
|
14
|
+
return super unless respond_to_missing?(method_name, false)
|
15
|
+
|
16
|
+
@block.call(*args)
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
"<#{self.class} event: #{@event_method}>"
|
21
|
+
end
|
22
|
+
|
23
|
+
def respond_to_missing?(method_name, _include_private)
|
24
|
+
method_name == @event_method
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/voltage/mock.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Voltage
|
4
|
+
class Mock
|
5
|
+
def calls
|
6
|
+
@calls ||= []
|
7
|
+
end
|
8
|
+
|
9
|
+
def method_missing(name, *args)
|
10
|
+
return super unless respond_to_missing?(name)
|
11
|
+
|
12
|
+
calls << {event: name.to_s.gsub(/^on_/, "").to_sym, args: args}
|
13
|
+
end
|
14
|
+
|
15
|
+
def respond_to_missing?(name, _include_all = false)
|
16
|
+
name =~ /^on_/
|
17
|
+
end
|
18
|
+
|
19
|
+
def received?(event, options = {})
|
20
|
+
received_event?(event, options[:times] || -1) &&
|
21
|
+
received_with?(event, options[:with])
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_proc
|
25
|
+
proc {|action| action.add_listener(self) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def on(event)
|
29
|
+
proc {|*args| calls << {event: event, args: args} }
|
30
|
+
end
|
31
|
+
|
32
|
+
private def received_event?(event, count)
|
33
|
+
received_calls = calls.count {|call| call[:event] == event }
|
34
|
+
|
35
|
+
return received_calls.nonzero? if count == -1
|
36
|
+
|
37
|
+
received_calls == count
|
38
|
+
end
|
39
|
+
|
40
|
+
private def received_with?(event, args)
|
41
|
+
return true unless args
|
42
|
+
|
43
|
+
calls.any? do |call|
|
44
|
+
next unless call[:event] == event
|
45
|
+
next args.call(call[:args]) if args.is_a?(Proc)
|
46
|
+
|
47
|
+
args == call[:args]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|