signal 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.
- data/.gitignore +17 -0
- data/.rspec +1 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +22 -0
- data/README.md +167 -0
- data/Rakefile +1 -0
- data/examples/activerecord_model.rb +41 -0
- data/examples/arguments.rb +17 -0
- data/examples/block_context.rb +20 -0
- data/examples/blocks.rb +16 -0
- data/examples/chain.rb +56 -0
- data/examples/listener.rb +28 -0
- data/lib/signal/active_record.rb +55 -0
- data/lib/signal/listener.rb +24 -0
- data/lib/signal/version.rb +3 -0
- data/lib/signal.rb +42 -0
- data/signal.gemspec +21 -0
- data/spec/activerecord_spec.rb +132 -0
- data/spec/signal_spec.rb +85 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/support/callable.rb +10 -0
- data/spec/support/observable.rb +3 -0
- data/spec/support/user.rb +4 -0
- metadata +124 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Nando Vieira
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
# Signal
|
2
|
+
|
3
|
+
A simple observer implementation on POROs (Plain Old Ruby Object) and ActiveRecord objects.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem "signal"
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install signal
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
You can use Signal with PORO (Plain Old Ruby Object) and ActiveRecord.
|
22
|
+
|
23
|
+
### Plain Ruby
|
24
|
+
|
25
|
+
All you have to do is including the `Signal` module. Then you can add listeners and trigger events.
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
class Status
|
29
|
+
include Signal
|
30
|
+
|
31
|
+
def ready!
|
32
|
+
emit(:ready)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
status = Status.new
|
37
|
+
status.before(:ready) { puts "Before the ready event!" }
|
38
|
+
status.on(:ready) { puts "I'm ready!" }
|
39
|
+
status.after(:ready) { puts "After the ready event!" }
|
40
|
+
status.ready!
|
41
|
+
#=> Before the ready event!
|
42
|
+
#=> I'm ready!
|
43
|
+
#=> After the ready event!
|
44
|
+
```
|
45
|
+
|
46
|
+
You can also pass objects that implement methods like `before_*`, `on_*` and `after_*`.
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
class MyListener
|
50
|
+
def before_ready
|
51
|
+
puts "Before the ready event!"
|
52
|
+
end
|
53
|
+
|
54
|
+
def on_ready
|
55
|
+
puts "I'm ready!"
|
56
|
+
end
|
57
|
+
|
58
|
+
def after_ready
|
59
|
+
puts "After the ready event!"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
status = Status.new
|
64
|
+
status.listeners << MyListener.new
|
65
|
+
status.ready!
|
66
|
+
#=> Before the ready event!
|
67
|
+
#=> I'm ready!
|
68
|
+
#=> After the ready event!
|
69
|
+
```
|
70
|
+
|
71
|
+
Blocks are executed in the context of the observable object.
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
class Contact
|
75
|
+
include Signal
|
76
|
+
|
77
|
+
attr_reader :name, :email
|
78
|
+
|
79
|
+
def initialize(name, email)
|
80
|
+
@name, @email = name, email
|
81
|
+
end
|
82
|
+
|
83
|
+
def output!
|
84
|
+
emit(:output)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
contact = Contact.new("John Doe", "john@example.org")
|
89
|
+
contact.on(:output) { puts name, email }
|
90
|
+
contact.output!
|
91
|
+
#=> John Doe
|
92
|
+
#=> john@example.org
|
93
|
+
```
|
94
|
+
|
95
|
+
You can provide arguments while emitting a signal:
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
class Arguments
|
99
|
+
include Signal
|
100
|
+
end
|
101
|
+
|
102
|
+
class MyListener
|
103
|
+
def on_args(a, b)
|
104
|
+
puts a, b
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
args = Arguments.new
|
109
|
+
args.on(:args) {|a, b| puts a, b }
|
110
|
+
args.listeners << MyListener.new
|
111
|
+
args.emit(:args, 1, 2)
|
112
|
+
```
|
113
|
+
|
114
|
+
### ActiveRecord
|
115
|
+
|
116
|
+
You can use Signal with ActiveRecord, which will give you some default events like `:create`, `:update`, `:remove` and `:validation`.
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
class Thing < ActiveRecord::Base
|
120
|
+
include Signal::ActiveRecord
|
121
|
+
|
122
|
+
validates_presence_of :name
|
123
|
+
end
|
124
|
+
|
125
|
+
thing = Thing.new(:name => "Stuff")
|
126
|
+
thing.on(:create) { puts updated_at, name }
|
127
|
+
thing.on(:update) { puts updated_at, name }
|
128
|
+
thing.on(:remove) { puts destroyed? }
|
129
|
+
thing.on(:validation) { p errors.full_messages }
|
130
|
+
|
131
|
+
thing.save!
|
132
|
+
#=> 2013-01-26 10:32:39 -0200
|
133
|
+
#=> Stuff
|
134
|
+
|
135
|
+
thing.update_attributes(:name => "Updated stuff")
|
136
|
+
#=> 2013-01-26 10:33:11 -0200
|
137
|
+
#=> Updated stuff
|
138
|
+
|
139
|
+
thing.update_attributes(:name => nil)
|
140
|
+
#=> ["Name can't be blank"]
|
141
|
+
|
142
|
+
thing.destroy
|
143
|
+
#=> true
|
144
|
+
```
|
145
|
+
|
146
|
+
These are the available events:
|
147
|
+
|
148
|
+
* `before(:create)`: triggered before creating the record (record is valid).
|
149
|
+
* `on(:create)`: triggered after `before(:create)` event.
|
150
|
+
* `after(:create)`: triggered after the `on(:create)` event.
|
151
|
+
* `before(:update)`: triggered before updating the record (record is valid).
|
152
|
+
* `on(:update)`: triggered when the `before(:update)` event.
|
153
|
+
* `after(:update)`: triggered after the `on(:update)` event.
|
154
|
+
* `before(:remove)`: triggered before removing the record.
|
155
|
+
* `on(:remove)`: triggered after the `before(:remove)`.
|
156
|
+
* `after(:remove)`: triggered after the `on(:remove)` event.
|
157
|
+
* `before(:validation)`: triggered before validating record.
|
158
|
+
* `on(:validation)`: triggered when record is invalid.
|
159
|
+
* `after(:validation)`: triggered after validating record.
|
160
|
+
|
161
|
+
## Contributing
|
162
|
+
|
163
|
+
1. Fork it
|
164
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
165
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
166
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
167
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,41 @@
|
|
1
|
+
$:.unshift File.expand_path("../../lib", __FILE__)
|
2
|
+
require "signal"
|
3
|
+
require "active_record"
|
4
|
+
|
5
|
+
ActiveRecord::Base.establish_connection({
|
6
|
+
:adapter => "sqlite3",
|
7
|
+
:database => ":memory:"
|
8
|
+
})
|
9
|
+
|
10
|
+
ActiveRecord::Schema.define(:version => 0) do
|
11
|
+
create_table :things do |t|
|
12
|
+
t.string :name
|
13
|
+
t.timestamps
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Thing < ActiveRecord::Base
|
18
|
+
include Signal::ActiveRecord
|
19
|
+
|
20
|
+
validates_presence_of :name
|
21
|
+
end
|
22
|
+
|
23
|
+
thing = Thing.new(:name => "Stuff")
|
24
|
+
thing.on(:create) { puts updated_at, name }
|
25
|
+
thing.on(:update) { puts updated_at, name }
|
26
|
+
thing.on(:remove) { puts destroyed? }
|
27
|
+
thing.on(:validation) { p errors.full_messages }
|
28
|
+
|
29
|
+
thing.save!
|
30
|
+
#=> 2013-01-26 10:32:39 -0200
|
31
|
+
#=> Stuff
|
32
|
+
|
33
|
+
thing.update_attributes(:name => "Updated stuff")
|
34
|
+
#=> 2013-01-26 10:33:11 -0200
|
35
|
+
#=> Updated stuff
|
36
|
+
|
37
|
+
thing.update_attributes(:name => nil)
|
38
|
+
#=> ["Name can't be blank"]
|
39
|
+
|
40
|
+
thing.destroy
|
41
|
+
#=> true
|
@@ -0,0 +1,17 @@
|
|
1
|
+
$:.unshift File.expand_path("../../lib", __FILE__)
|
2
|
+
require "signal"
|
3
|
+
|
4
|
+
class Arguments
|
5
|
+
include Signal
|
6
|
+
end
|
7
|
+
|
8
|
+
class MyListener
|
9
|
+
def on_args(a, b)
|
10
|
+
puts a, b
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
args = Arguments.new
|
15
|
+
args.on(:args) {|a, b| puts a, b }
|
16
|
+
args.listeners << MyListener.new
|
17
|
+
args.emit(:args, 1, 2)
|
@@ -0,0 +1,20 @@
|
|
1
|
+
$:.unshift File.expand_path("../../lib", __FILE__)
|
2
|
+
require "signal"
|
3
|
+
|
4
|
+
class Contact
|
5
|
+
include Signal
|
6
|
+
|
7
|
+
attr_reader :name, :email
|
8
|
+
|
9
|
+
def initialize(name, email)
|
10
|
+
@name, @email = name, email
|
11
|
+
end
|
12
|
+
|
13
|
+
def output!
|
14
|
+
emit(:output)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
contact = Contact.new("John Doe", "john@example.org")
|
19
|
+
contact.on(:output) { puts name, email }
|
20
|
+
contact.output!
|
data/examples/blocks.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
$:.unshift File.expand_path("../../lib", __FILE__)
|
2
|
+
require "signal"
|
3
|
+
|
4
|
+
class Status
|
5
|
+
include Signal
|
6
|
+
|
7
|
+
def ready!
|
8
|
+
emit(:ready)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
status = Status.new
|
13
|
+
status.before(:ready) { puts "Before the ready event!" }
|
14
|
+
status.on(:ready) { puts "I'm ready!" }
|
15
|
+
status.after(:ready) { puts "After the ready event!" }
|
16
|
+
status.ready!
|
data/examples/chain.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
$:.unshift File.expand_path("../../lib", __FILE__)
|
2
|
+
require "signal"
|
3
|
+
require "active_record"
|
4
|
+
|
5
|
+
ActiveRecord::Base.establish_connection({
|
6
|
+
:adapter => "sqlite3",
|
7
|
+
:database => ":memory:"
|
8
|
+
})
|
9
|
+
|
10
|
+
ActiveRecord::Schema.define(:version => 0) do
|
11
|
+
create_table :things do |t|
|
12
|
+
t.string :name
|
13
|
+
t.timestamps
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Thing < ActiveRecord::Base
|
18
|
+
include Signal::ActiveRecord
|
19
|
+
|
20
|
+
validates_presence_of :name
|
21
|
+
end
|
22
|
+
|
23
|
+
class MyListener
|
24
|
+
[:validation, :update, :create, :remove].each do |type|
|
25
|
+
class_eval <<-RUBY
|
26
|
+
def before_#{type}; puts __method__; end
|
27
|
+
def on_#{type}; puts __method__; end
|
28
|
+
def after_#{type}; puts __method__; end
|
29
|
+
RUBY
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
puts "\n=== Creating valid record"
|
34
|
+
thing = Thing.new(:name => "Stuff")
|
35
|
+
thing.listeners << MyListener.new
|
36
|
+
thing.save
|
37
|
+
|
38
|
+
puts "\n=== Creating invalid record"
|
39
|
+
thing = Thing.new(:name => nil)
|
40
|
+
thing.listeners << MyListener.new
|
41
|
+
thing.save
|
42
|
+
|
43
|
+
puts "\n=== Updating valid record"
|
44
|
+
thing = Thing.create(:name => "Stuff")
|
45
|
+
thing.listeners << MyListener.new
|
46
|
+
thing.update_attributes(:name => "Updated stuff")
|
47
|
+
|
48
|
+
puts "\n=== Updating invalid record"
|
49
|
+
thing = Thing.create!(:name => "Stuff")
|
50
|
+
thing.listeners << MyListener.new
|
51
|
+
thing.update_attributes(:name => nil)
|
52
|
+
|
53
|
+
puts "\n=== Removing record"
|
54
|
+
thing = Thing.create(:name => "Stuff")
|
55
|
+
thing.listeners << MyListener.new
|
56
|
+
thing.destroy
|
@@ -0,0 +1,28 @@
|
|
1
|
+
$:.unshift File.expand_path("../../lib", __FILE__)
|
2
|
+
require "signal"
|
3
|
+
|
4
|
+
class Status
|
5
|
+
include Signal
|
6
|
+
|
7
|
+
def ready!
|
8
|
+
emit(:ready)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class MyListener
|
13
|
+
def before_ready
|
14
|
+
puts "Before the ready event!"
|
15
|
+
end
|
16
|
+
|
17
|
+
def on_ready
|
18
|
+
puts "I'm ready!"
|
19
|
+
end
|
20
|
+
|
21
|
+
def after_ready
|
22
|
+
puts "After the ready event!"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
status = Status.new
|
27
|
+
status.listeners << MyListener.new
|
28
|
+
status.ready!
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Signal
|
2
|
+
module ActiveRecord
|
3
|
+
def self.included(base)
|
4
|
+
base.class_eval do
|
5
|
+
include Signal
|
6
|
+
include InstanceMethods
|
7
|
+
|
8
|
+
around_create :around_create_signal
|
9
|
+
around_save :around_save_signal
|
10
|
+
around_destroy :around_destroy_signal
|
11
|
+
before_validation :before_validation_signal
|
12
|
+
after_validation :after_validation_signal
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module InstanceMethods
|
17
|
+
private
|
18
|
+
def around_create_signal
|
19
|
+
emit_signal(:before, :create)
|
20
|
+
yield
|
21
|
+
return unless persisted?
|
22
|
+
emit_signal(:on, :create)
|
23
|
+
emit_signal(:after, :create)
|
24
|
+
end
|
25
|
+
|
26
|
+
def around_save_signal
|
27
|
+
if new_record?
|
28
|
+
yield
|
29
|
+
return
|
30
|
+
end
|
31
|
+
|
32
|
+
emit_signal(:before, :update)
|
33
|
+
yield
|
34
|
+
emit_signal(:on, :update)
|
35
|
+
emit_signal(:after, :update)
|
36
|
+
end
|
37
|
+
|
38
|
+
def around_destroy_signal
|
39
|
+
emit_signal(:before, :remove)
|
40
|
+
yield
|
41
|
+
emit_signal(:on, :remove)
|
42
|
+
emit_signal(:after, :remove)
|
43
|
+
end
|
44
|
+
|
45
|
+
def before_validation_signal
|
46
|
+
emit_signal(:before, :validation)
|
47
|
+
end
|
48
|
+
|
49
|
+
def after_validation_signal
|
50
|
+
emit_signal(:on, :validation) if errors.any?
|
51
|
+
emit_signal(:after, :validation)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Signal
|
2
|
+
class Listener
|
3
|
+
def initialize(context, type, event, &block)
|
4
|
+
@context = context
|
5
|
+
@type = type
|
6
|
+
@event = event
|
7
|
+
@block = block
|
8
|
+
@event_method = :"#{@type}_#{@event}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def method_missing(method, *args)
|
12
|
+
return super unless method == @event_method
|
13
|
+
@context.instance_exec(*args, &@block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
"<#{self.class} event: #{@event_method}>"
|
18
|
+
end
|
19
|
+
|
20
|
+
def respond_to_missing?(method_name, include_private)
|
21
|
+
method_name == @event_method
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/signal.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require "signal/active_record"
|
2
|
+
require "signal/listener"
|
3
|
+
require "signal/version"
|
4
|
+
|
5
|
+
module Signal
|
6
|
+
def on(event, &block)
|
7
|
+
listeners << Listener.new(self, __method__, event, &block)
|
8
|
+
self
|
9
|
+
end
|
10
|
+
|
11
|
+
def before(event, &block)
|
12
|
+
listeners << Listener.new(self, __method__, event, &block)
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def after(event, &block)
|
17
|
+
listeners << Listener.new(self, __method__, event, &block)
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def emit(event, *args)
|
22
|
+
emit_signal(:before, event, *args)
|
23
|
+
emit_signal(:on, event, *args)
|
24
|
+
emit_signal(:after, event, *args)
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def listeners
|
29
|
+
@listeners ||= []
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
def emit_signal(type, event, *args)
|
34
|
+
listeners.each do |listener|
|
35
|
+
method_name = "#{type}_#{event}"
|
36
|
+
|
37
|
+
if listener.respond_to?(method_name)
|
38
|
+
listener.public_send(method_name, *args)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/signal.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require "./lib/signal/version"
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.name = "signal"
|
6
|
+
gem.version = Signal::VERSION
|
7
|
+
gem.authors = ["Nando Vieira"]
|
8
|
+
gem.email = ["fnando.vieira@gmail.com"]
|
9
|
+
gem.description = "A simple observer implementation for POROs (Plain Old Ruby Object) and ActiveRecord objects."
|
10
|
+
gem.summary = gem.description
|
11
|
+
gem.homepage = "http://github.com/fnando/signal"
|
12
|
+
|
13
|
+
gem.files = `git ls-files`.split($/)
|
14
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
15
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
16
|
+
gem.require_paths = ["lib"]
|
17
|
+
|
18
|
+
gem.add_development_dependency "activerecord"
|
19
|
+
gem.add_development_dependency "sqlite3"
|
20
|
+
gem.add_development_dependency "rspec"
|
21
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Signal::ActiveRecord do
|
4
|
+
let(:callable) { Callable.new }
|
5
|
+
let(:user) { User.new(:username => "johndoe") }
|
6
|
+
|
7
|
+
context "create event" do
|
8
|
+
it "triggers before event" do
|
9
|
+
user.before(:create, &callable)
|
10
|
+
callable.should_receive(:called).with(user)
|
11
|
+
user.save!
|
12
|
+
end
|
13
|
+
|
14
|
+
it "triggers event" do
|
15
|
+
user.on(:create, &callable)
|
16
|
+
callable.should_receive(:called).with(user)
|
17
|
+
user.save!
|
18
|
+
end
|
19
|
+
|
20
|
+
it "triggers after event" do
|
21
|
+
user.after(:create, &callable)
|
22
|
+
callable.should_receive(:called).with(user)
|
23
|
+
user.save!
|
24
|
+
end
|
25
|
+
|
26
|
+
it "doesn't trigger on/after events when record is invalid" do
|
27
|
+
user = User.new
|
28
|
+
|
29
|
+
on_callable = Callable.new
|
30
|
+
after_callable = Callable.new
|
31
|
+
|
32
|
+
user
|
33
|
+
.on(:create, &on_callable)
|
34
|
+
.after(:create, &after_callable)
|
35
|
+
|
36
|
+
on_callable.should_not_receive(:called)
|
37
|
+
after_callable.should_not_receive(:called)
|
38
|
+
|
39
|
+
user.save
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "validation event" do
|
44
|
+
it "triggers before event" do
|
45
|
+
user.before(:validation, &callable)
|
46
|
+
callable.should_receive(:called).with(user)
|
47
|
+
user.save!
|
48
|
+
end
|
49
|
+
|
50
|
+
it "triggers after event" do
|
51
|
+
user.after(:validation, &callable)
|
52
|
+
callable.should_receive(:called).with(user)
|
53
|
+
user.save!
|
54
|
+
end
|
55
|
+
|
56
|
+
it "triggers validation event when record is invalid" do
|
57
|
+
user.username = nil
|
58
|
+
user.on(:validation, &callable)
|
59
|
+
callable.should_receive(:called).with(user)
|
60
|
+
user.save
|
61
|
+
end
|
62
|
+
|
63
|
+
it "skips validation event when record is valid" do
|
64
|
+
user.on(:validation, &callable)
|
65
|
+
callable.should_not_receive(:called)
|
66
|
+
user.save!
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context "update event" do
|
71
|
+
let(:user) { User.create!(:username => "johndoe") }
|
72
|
+
|
73
|
+
it "triggers before event" do
|
74
|
+
user.before(:update, &callable)
|
75
|
+
callable.should_receive(:called).with(user)
|
76
|
+
user.update_attributes!(:username => "johnd")
|
77
|
+
end
|
78
|
+
|
79
|
+
it "triggers on event" do
|
80
|
+
user.on(:update, &callable)
|
81
|
+
callable.should_receive(:called).with(user)
|
82
|
+
user.update_attributes!(:username => "johnd")
|
83
|
+
end
|
84
|
+
|
85
|
+
it "triggers after event" do
|
86
|
+
user.after(:update, &callable)
|
87
|
+
callable.should_receive(:called).with(user)
|
88
|
+
user.update_attributes!(:username => "johnd")
|
89
|
+
end
|
90
|
+
|
91
|
+
it "doesn't trigger on/after events when record is invalid" do
|
92
|
+
user.username = nil
|
93
|
+
|
94
|
+
on_callable = Callable.new
|
95
|
+
after_callable = Callable.new
|
96
|
+
|
97
|
+
user
|
98
|
+
.on(:update, &on_callable)
|
99
|
+
.after(:update, &after_callable)
|
100
|
+
|
101
|
+
on_callable.should_not_receive(:called)
|
102
|
+
after_callable.should_not_receive(:called)
|
103
|
+
|
104
|
+
user.save
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context "remove event" do
|
109
|
+
let(:user) { User.create!(:username => "johndoe") }
|
110
|
+
|
111
|
+
it "triggers before event" do
|
112
|
+
user.before(:remove, &callable)
|
113
|
+
callable.should_receive(:called).with(user)
|
114
|
+
|
115
|
+
user.destroy
|
116
|
+
end
|
117
|
+
|
118
|
+
it "triggers on event" do
|
119
|
+
user.on(:remove, &callable)
|
120
|
+
callable.should_receive(:called).with(user)
|
121
|
+
|
122
|
+
user.destroy
|
123
|
+
end
|
124
|
+
|
125
|
+
it "triggers after event" do
|
126
|
+
user.after(:remove, &callable)
|
127
|
+
callable.should_receive(:called).with(user)
|
128
|
+
|
129
|
+
user.destroy
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
data/spec/signal_spec.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Signal do
|
4
|
+
let(:observable) { Observable.new }
|
5
|
+
let(:callable) { Callable.new }
|
6
|
+
|
7
|
+
context "using blocks" do
|
8
|
+
it "triggers event" do
|
9
|
+
observable.on(:ready, &callable)
|
10
|
+
callable.should_receive(:called).with(observable)
|
11
|
+
|
12
|
+
observable.emit(:ready)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "triggers event with arguments" do
|
16
|
+
observable.on(:ready, &callable)
|
17
|
+
callable.should_receive(:called).with(observable, 1, 2, 3)
|
18
|
+
|
19
|
+
observable.emit(:ready, 1, 2, 3)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "triggers before event" do
|
23
|
+
observable.before(:ready, &callable)
|
24
|
+
callable.should_receive(:called).with(observable)
|
25
|
+
|
26
|
+
observable.emit(:ready)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "triggers before event with arguments" do
|
30
|
+
observable.before(:ready, &callable)
|
31
|
+
callable.should_receive(:called).with(observable, 1, 2, 3)
|
32
|
+
|
33
|
+
observable.emit(:ready, 1, 2, 3)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "triggers after event" do
|
37
|
+
observable.after(:ready, &callable)
|
38
|
+
callable.should_receive(:called).with(observable)
|
39
|
+
|
40
|
+
observable.emit(:ready)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "triggers after event with arguments" do
|
44
|
+
observable.after(:ready, &callable)
|
45
|
+
callable.should_receive(:called).with(observable, 1, 2, 3)
|
46
|
+
|
47
|
+
observable.emit(:ready, 1, 2, 3)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "chains events" do
|
51
|
+
before_callable = Callable.new
|
52
|
+
on_callable = Callable.new
|
53
|
+
after_callable = Callable.new
|
54
|
+
|
55
|
+
observable
|
56
|
+
.before(:ready, &before_callable)
|
57
|
+
.on(:ready, &on_callable)
|
58
|
+
.after(:ready, &after_callable)
|
59
|
+
|
60
|
+
before_callable.should_receive(:called).with(observable).ordered
|
61
|
+
on_callable.should_receive(:called).with(observable).ordered
|
62
|
+
after_callable.should_receive(:called).with(observable).ordered
|
63
|
+
|
64
|
+
observable.emit(:ready)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context "using listeners" do
|
69
|
+
it "triggers event for listener" do
|
70
|
+
callable.respond_to(:on_ready)
|
71
|
+
observable.listeners << callable
|
72
|
+
callable.should_receive(:called).with(callable)
|
73
|
+
|
74
|
+
observable.emit(:ready)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "triggers event for listener with arguments" do
|
78
|
+
callable.respond_to(:on_ready)
|
79
|
+
observable.listeners << callable
|
80
|
+
callable.should_receive(:called).with(callable, 1, 2, 3)
|
81
|
+
|
82
|
+
observable.emit(:ready, 1, 2, 3)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require "bundler"
|
2
|
+
Bundler.setup
|
3
|
+
|
4
|
+
require "active_record"
|
5
|
+
|
6
|
+
require "signal"
|
7
|
+
require "support/observable"
|
8
|
+
require "support/callable"
|
9
|
+
require "support/user"
|
10
|
+
|
11
|
+
ActiveRecord::Base.establish_connection({
|
12
|
+
:adapter => "sqlite3",
|
13
|
+
:database => ":memory:"
|
14
|
+
})
|
15
|
+
|
16
|
+
ActiveRecord::Schema.define(:version => 0) do
|
17
|
+
create_table :users do |t|
|
18
|
+
t.string :username
|
19
|
+
end
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: signal
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Nando Vieira
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-01-26 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activerecord
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: sqlite3
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: A simple observer implementation for POROs (Plain Old Ruby Object) and
|
63
|
+
ActiveRecord objects.
|
64
|
+
email:
|
65
|
+
- fnando.vieira@gmail.com
|
66
|
+
executables: []
|
67
|
+
extensions: []
|
68
|
+
extra_rdoc_files: []
|
69
|
+
files:
|
70
|
+
- .gitignore
|
71
|
+
- .rspec
|
72
|
+
- Gemfile
|
73
|
+
- LICENSE.txt
|
74
|
+
- README.md
|
75
|
+
- Rakefile
|
76
|
+
- examples/activerecord_model.rb
|
77
|
+
- examples/arguments.rb
|
78
|
+
- examples/block_context.rb
|
79
|
+
- examples/blocks.rb
|
80
|
+
- examples/chain.rb
|
81
|
+
- examples/listener.rb
|
82
|
+
- lib/signal.rb
|
83
|
+
- lib/signal/active_record.rb
|
84
|
+
- lib/signal/listener.rb
|
85
|
+
- lib/signal/version.rb
|
86
|
+
- signal.gemspec
|
87
|
+
- spec/activerecord_spec.rb
|
88
|
+
- spec/signal_spec.rb
|
89
|
+
- spec/spec_helper.rb
|
90
|
+
- spec/support/callable.rb
|
91
|
+
- spec/support/observable.rb
|
92
|
+
- spec/support/user.rb
|
93
|
+
homepage: http://github.com/fnando/signal
|
94
|
+
licenses: []
|
95
|
+
post_install_message:
|
96
|
+
rdoc_options: []
|
97
|
+
require_paths:
|
98
|
+
- lib
|
99
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
100
|
+
none: false
|
101
|
+
requirements:
|
102
|
+
- - ! '>='
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
106
|
+
none: false
|
107
|
+
requirements:
|
108
|
+
- - ! '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
requirements: []
|
112
|
+
rubyforge_project:
|
113
|
+
rubygems_version: 1.8.24
|
114
|
+
signing_key:
|
115
|
+
specification_version: 3
|
116
|
+
summary: A simple observer implementation for POROs (Plain Old Ruby Object) and ActiveRecord
|
117
|
+
objects.
|
118
|
+
test_files:
|
119
|
+
- spec/activerecord_spec.rb
|
120
|
+
- spec/signal_spec.rb
|
121
|
+
- spec/spec_helper.rb
|
122
|
+
- spec/support/callable.rb
|
123
|
+
- spec/support/observable.rb
|
124
|
+
- spec/support/user.rb
|