hooks 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+ Gemfile.lock
2
+
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.3
5
+ - 2.0.0
@@ -1,3 +1,8 @@
1
+ h2 0.3.0
2
+
3
+ * The callback chain can now be halted by configuring the hook as @halts_on_falsey: true@ and returning @nil@ or @false@ from the callback.
4
+ * Internal refactorings: hooks are now encapsulated in @Hook@ instances and run their callback chains.
5
+
1
6
  h2. 0.2.2
2
7
 
3
8
  * `#run_hook` now returns the list of callback results.
data/Gemfile CHANGED
@@ -1,3 +1,3 @@
1
- source :rubygems
1
+ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
@@ -0,0 +1,165 @@
1
+ # Hooks
2
+
3
+ _Generic hooks with callbacks for Ruby._
4
+
5
+
6
+ ## Introduction
7
+
8
+ _Hooks_ lets you define hooks declaratively in your ruby class. You can add callbacks to your hook, which will be run as soon as _you_ run the hook!
9
+
10
+ It's almost like ActiveSupport::Callbacks but 76,6% less complex. Instead, it is not more than a few lines of code, one method compilation, no `method_missing` and no magic.
11
+
12
+ Also, you may pass additional arguments to your callbacks when invoking a hook.
13
+
14
+ ## Example
15
+
16
+ Let's take... a cat.
17
+
18
+ ```ruby
19
+ require 'hooks'
20
+
21
+ class Cat
22
+ include Hooks
23
+
24
+ define_hooks :before_dinner, :after_dinner
25
+ ```
26
+
27
+ Now you can add callbacks to your hook declaratively in your class.
28
+
29
+ ```ruby
30
+ before_dinner :wash_paws
31
+
32
+ after_dinner do
33
+ puts "Ice cream for #{self}!"
34
+ end
35
+
36
+ after_dinner :have_a_desert # => refers to Cat#have_a_desert
37
+
38
+ def have_a_desert
39
+ puts "Hell, yeah!"
40
+ end
41
+ ```
42
+
43
+ This will run the block and `#have_a_desert` from above.
44
+
45
+ ```ruby
46
+ cat.run_hook :after_dinner
47
+ # => Ice cream for #<Cat:0x8df9d84>!
48
+ Hell, yeah!
49
+ ```
50
+
51
+ Callback blocks and methods will be executed with instance context. Note how `self` in the block refers to the Cat instance.
52
+
53
+
54
+ ## Inheritance
55
+
56
+ Hooks are inherited, here's a complete example to put it all together.
57
+
58
+ ```ruby
59
+ class Garfield < Cat
60
+
61
+ after_dinner :want_some_more
62
+
63
+ def want_some_more
64
+ puts "Is that all?"
65
+ end
66
+ end
67
+
68
+
69
+ Garfield.new.run_hook :after_dinner
70
+ # => Ice cream for #<Cat:0x8df9d84>!
71
+ Hell, yeah!
72
+ Is that all?
73
+ ```
74
+
75
+ Note how the callbacks are invoked in the order they were inherited.
76
+
77
+
78
+ ## Options for Callbacks
79
+
80
+ You're free to pass any number of arguments to #run_callback, those will be passed to the callbacks.
81
+
82
+ ```ruby
83
+ cat.run_hook :before_dinner, cat, Time.now
84
+ ```
85
+
86
+ The callbacks should be ready for receiving parameters.
87
+
88
+ ```ruby
89
+ before_dinner :wash_pawns
90
+ before_dinner do |who, when|
91
+ ...
92
+ end
93
+
94
+ def wash_pawns(who, when)
95
+ ```
96
+
97
+ Not sure why a cat should have ice cream for dinner. Beside that, I was tempted naming this gem _hooker_.
98
+
99
+
100
+ ## Running And Halting Hooks
101
+
102
+ Using `#run_hook` doesn't only run all callbacks for this hook but also returns an array of the results from each callback method or block.
103
+
104
+ ```ruby
105
+ class Garfield
106
+ include Hooks
107
+ define_hook :after_dark
108
+
109
+ after_dark { "Chase mice" }
110
+ after_dark { "Enjoy supper" }
111
+ end
112
+
113
+ Garfield.new.run_hook :after_dark
114
+ # => ["Chase mice", "Enjoy supper"]
115
+ ```
116
+
117
+ This is handy if you need to collect data from your callbacks without having to access a global (brrr) variable.
118
+
119
+ With the `:halts_on_falsey` option you can halt the callback chain when a callback returns `nil` or `false`.
120
+
121
+ ```ruby
122
+ class Garfield
123
+ include Hooks
124
+ define_hook :after_dark, halts_on_falsey: true
125
+
126
+ after_dark { "Chase mice" }
127
+ after_dark { nil }
128
+ after_dark { "Enjoy supper" }
129
+ end
130
+
131
+ result = Garfield.new.run_hook :after_dark
132
+ # => ["Chase mice"]
133
+ ```
134
+
135
+ This will only run the first two callbacks. Note that the result doesn't contain the `nil` value. You even can check if the chain was halted.
136
+
137
+ ```ruby
138
+ result.halted? #=> true
139
+ ```
140
+
141
+ ## Installation
142
+
143
+ In your Gemfile, do
144
+
145
+ ```ruby
146
+ gem hooks
147
+ ```
148
+
149
+ ## Anybody using it?
150
+
151
+ * Hooks is already used in [Apotomo](http://github.com/apotonick/apotomo), a hot widget framework for Rails.
152
+ * The [datamappify](https://github.com/fredwu/datamappify) gem uses hooks and the author Fred Wu contributed to this gem!
153
+
154
+ ## Similar libraries
155
+
156
+ * http://github.com/nakajima/aspectory
157
+ * http://github.com/auser/backcall
158
+ * http://github.com/mmcgrana/simple_callbacks
159
+
160
+
161
+ ## License
162
+
163
+ Copyright (c) 2013, Nick Sutterer
164
+
165
+ Released under the MIT License.
@@ -9,14 +9,15 @@ Gem::Specification.new do |s|
9
9
  s.platform = Gem::Platform::RUBY
10
10
  s.authors = ["Nick Sutterer"]
11
11
  s.email = ["apotonick@gmail.com"]
12
- s.homepage = "http://nicksda.apotomo.de/tag/hooks"
12
+ s.homepage = "http://nicksda.apotomo.de/2010/09/hooks-and-callbacks-for-ruby-but-simple/"
13
13
  s.summary = %q{Generic hooks with callbacks for Ruby.}
14
14
  s.description = %q{Declaratively define hooks, add callbacks and run them with the options you like.}
15
-
15
+
16
16
  s.files = `git ls-files`.split("\n")
17
17
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
18
  s.require_paths = ["lib"]
19
-
20
- s.add_development_dependency "shoulda"
19
+
20
+ s.add_development_dependency "minitest", ">= 5.0.0"
21
21
  s.add_development_dependency "rake"
22
+ s.add_development_dependency "pry"
22
23
  end
@@ -1,4 +1,5 @@
1
1
  require "hooks/inheritable_attribute"
2
+ require "hooks/hook"
2
3
 
3
4
  # Almost like ActiveSupport::Callbacks but 76,6% less complex.
4
5
  #
@@ -12,50 +13,50 @@ require "hooks/inheritable_attribute"
12
13
  # before_dinner :wash_paws
13
14
  # after_dinner { puts "Ice cream!" }
14
15
  # after_dinner :have_a_desert # => refers to CatWidget#have_a_desert
15
- #
16
+ #
16
17
  # Running the callbacks happens on instances. It will run the block and #have_a_desert from above.
17
18
  #
18
19
  # cat.run_hook :after_dinner
19
20
  module Hooks
20
- VERSION = "0.2.2"
21
-
21
+ VERSION = "0.3.0"
22
+
22
23
  def self.included(base)
23
- base.extend InheritableAttribute
24
- base.extend ClassMethods
24
+ base.class_eval do
25
+ extend InheritableAttribute
26
+ extend ClassMethods
27
+ inheritable_attr :_hooks
28
+ self._hooks= HookSet.new
29
+ end
25
30
  end
26
-
31
+
27
32
  module ClassMethods
28
33
  def define_hooks(*names)
34
+ options = extract_options!(names)
35
+
29
36
  names.each do |name|
30
- setup_hook(name)
37
+ setup_hook(name, options)
31
38
  end
32
39
  end
33
40
  alias_method :define_hook, :define_hooks
34
-
41
+
35
42
  # Like Hooks#run_hook but for the class. Note that +:callbacks+ must be class methods.
36
43
  #
37
44
  # Example:
38
45
  #
39
46
  # class Cat
40
47
  # after_eight :grab_a_beer
41
- #
48
+ #
42
49
  # def self.grab_a_beer(*) # and so on...
43
- #
50
+ #
44
51
  # where <tt>Cat.run_hook :after_eight</tt> will call the class method +grab_a_beer+.
45
52
  def run_hook(name, *args)
46
53
  run_hook_for(name, self, *args)
47
- end
48
-
54
+ end
55
+
49
56
  def run_hook_for(name, scope, *args)
50
- callbacks_for_hook(name).map do |callback|
51
- if callback.kind_of? Symbol
52
- scope.send(callback, *args)
53
- else
54
- scope.instance_exec(*args, &callback)
55
- end
56
- end
57
+ _hooks[name].run(scope, *args)
57
58
  end
58
-
59
+
59
60
  # Returns the callbacks for +name+. Handy if you want to run the callbacks yourself, say when
60
61
  # they should be executed in another context.
61
62
  #
@@ -68,33 +69,29 @@ module Hooks
68
69
  #
69
70
  # would run callbacks in the object _instance_ context, passing +self+ as block parameter.
70
71
  def callbacks_for_hook(name)
71
- send("_#{name}_callbacks")
72
+ _hooks[name]
72
73
  end
73
-
74
+
74
75
  private
75
-
76
- def setup_hook(name)
77
- accessor_name = "_#{name}_callbacks"
78
-
79
- setup_hook_accessors(accessor_name)
80
- define_hook_writer(name, accessor_name)
76
+ def setup_hook(name, options)
77
+ _hooks[name] = Hook.new(options)
78
+ define_hook_writer(name)
81
79
  end
82
-
83
- def define_hook_writer(hook, accessor_name)
80
+
81
+ def define_hook_writer(name)
84
82
  instance_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
85
- def #{hook}(method=nil, &block)
86
- #{accessor_name} << (block || method)
83
+ def #{name}(method=nil, &block)
84
+ _hooks[:#{name}] << (block || method)
87
85
  end
88
86
  RUBY_EVAL
89
87
  end
90
-
91
- def setup_hook_accessors(accessor_name)
92
- inheritable_attr(accessor_name)
93
- send("#{accessor_name}=", []) # initialize ivar.
94
- end
88
+
89
+ def extract_options!(args)
90
+ args.last.is_a?(Hash) ? args.pop : {}
91
+ end
95
92
  end
96
-
97
- # Runs the callbacks (method/block) for the specified hook +name+. Additional arguments will
93
+
94
+ # Runs the callbacks (method/block) for the specified hook +name+. Additional arguments will
98
95
  # be passed to the callback.
99
96
  #
100
97
  # Example:
@@ -102,10 +99,18 @@ module Hooks
102
99
  # cat.run_hook :after_dinner, "i want ice cream!"
103
100
  #
104
101
  # will invoke the callbacks like
105
- #
102
+ #
106
103
  # desert("i want ice cream!")
107
104
  # block.call("i want ice cream!")
108
105
  def run_hook(name, *args)
109
106
  self.class.run_hook_for(name, self, *args)
110
107
  end
108
+
109
+ class HookSet < Hash
110
+ def clone
111
+ super.tap do |cloned|
112
+ each { |name, callbacks| cloned[name] = callbacks.clone }
113
+ end
114
+ end
115
+ end
111
116
  end
@@ -0,0 +1,81 @@
1
+ module Hooks
2
+ class Hook < Array
3
+ def initialize(options)
4
+ super()
5
+ @options = options
6
+ end
7
+
8
+ # The chain contains the return values of the executed callbacks.
9
+ #
10
+ # Example:
11
+ #
12
+ # class Person
13
+ # define_hook :before_eating
14
+ #
15
+ # before_eating :wash_hands
16
+ # before_eating :locate_food
17
+ # before_eating :sit_down
18
+ #
19
+ # def wash_hands; :washed_hands; end
20
+ # def locate_food; :located_food; false; end
21
+ # def sit_down; :sat_down; end
22
+ # end
23
+ #
24
+ # result = person.run_hook(:before_eating)
25
+ # result.chain #=> [:washed_hands, false, :sat_down]
26
+ #
27
+ # If <tt>:halts_on_falsey</tt> is enabled:
28
+ #
29
+ # class Person
30
+ # define_hook :before_eating, :halts_on_falsey => true
31
+ # # ...
32
+ # end
33
+ #
34
+ # result = person.run_hook(:before_eating)
35
+ # result.chain #=> [:washed_hands]
36
+ def run(scope, *args)
37
+ inject(Results.new) do |results, callback|
38
+ executed = execute_callback(scope, callback, *args)
39
+
40
+ return results.halted! unless continue_execution?(executed)
41
+ results << executed
42
+ end
43
+ end
44
+
45
+ private
46
+ def execute_callback(scope, callback, *args)
47
+ if callback.kind_of?(Symbol)
48
+ scope.send(callback, *args)
49
+ else
50
+ scope.instance_exec(*args, &callback)
51
+ end
52
+ end
53
+
54
+ def continue_execution?(result)
55
+ @options[:halts_on_falsey] ? result : true
56
+ end
57
+
58
+ class Results < Array
59
+ # so much code for nothing...
60
+ def initialize(*)
61
+ super
62
+ @halted = false
63
+ end
64
+
65
+ def halted!
66
+ @halted = true
67
+ self
68
+ end
69
+
70
+ # Returns true or false based on whether all callbacks
71
+ # in the hook chain were successfully executed.
72
+ def halted?
73
+ @halted
74
+ end
75
+
76
+ def not_halted?
77
+ not @halted
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,31 @@
1
+ require 'test_helper'
2
+
3
+ class HookTest < MiniTest::Spec
4
+ subject { Hooks::Hook.new({}) }
5
+
6
+ it "exposes array behaviour for callbacks" do
7
+ subject << :play_music
8
+ subject << :drink_beer
9
+
10
+ subject.to_a.must_equal [:play_music, :drink_beer]
11
+ end
12
+ end
13
+
14
+ class ResultsTest < MiniTest::Spec
15
+ subject { Hooks::Hook::Results.new }
16
+
17
+ describe "#halted?" do
18
+ it "defaults to false" do
19
+ subject.halted?.must_equal false
20
+ end
21
+
22
+ it "responds to #halted!" do
23
+ subject.halted!
24
+ subject.halted?.must_equal true
25
+ end
26
+
27
+ it "responds to #not_halted?" do
28
+ subject.not_halted?.must_equal true
29
+ end
30
+ end
31
+ end
@@ -1,156 +1,198 @@
1
1
  require 'test_helper'
2
2
 
3
- class HooksTest < Test::Unit::TestCase
3
+ class HooksTest < MiniTest::Spec
4
4
  class TestClass
5
5
  include Hooks
6
-
6
+
7
7
  def executed
8
8
  @executed ||= [];
9
9
  end
10
10
  end
11
-
12
-
13
- context "Hooks.define_hook" do
14
- setup do
15
- @klass = Class.new(TestClass)
16
-
17
- @mum = @klass.new
18
- @mum.class.define_hook :after_eight
19
- end
20
-
21
- should "provide accessors to the stored callbacks" do
22
- assert_equal [], @klass._after_eight_callbacks
23
- @klass._after_eight_callbacks << :dine
24
- assert_equal [:dine], @klass._after_eight_callbacks
11
+
12
+
13
+ describe "Hooks.define_hook" do
14
+ let(:klass) do
15
+ Class.new(TestClass) do
16
+ define_hook :after_eight
17
+ end
25
18
  end
26
-
27
- should "respond to Class.callbacks_for_hook" do
28
- assert_equal [], @klass.callbacks_for_hook(:after_eight)
29
- @klass.after_eight :dine
30
- assert_equal [:dine], @klass.callbacks_for_hook(:after_eight)
19
+
20
+ subject { klass.new }
21
+
22
+ it "respond to Class.callbacks_for_hook" do
23
+ assert_equal [], klass.callbacks_for_hook(:after_eight)
24
+ klass.after_eight :dine
25
+ assert_equal [:dine], klass.callbacks_for_hook(:after_eight)
31
26
  end
32
27
 
33
- should "accept multiple hook names" do
34
- @mum.class.define_hooks :before_ten, :after_ten
35
- assert_equal [], @klass.callbacks_for_hook(:before_ten)
36
- assert_equal [], @klass.callbacks_for_hook(:after_ten)
28
+ it "accept multiple hook names" do
29
+ subject.class.define_hooks :before_ten, :after_ten
30
+ assert_equal [], klass.callbacks_for_hook(:before_ten)
31
+ assert_equal [], klass.callbacks_for_hook(:after_ten)
37
32
  end
38
-
39
- context "creates a public writer for the hook that" do
40
- should "accepts method names" do
41
- @klass.after_eight :dine
42
- assert_equal [:dine], @klass._after_eight_callbacks
33
+
34
+ describe "creates a public writer for the hook that" do
35
+ it "accepts method names" do
36
+ klass.after_eight :dine
37
+ assert_equal [:dine], klass._hooks[:after_eight]
43
38
  end
44
-
45
- should "accepts blocks" do
46
- @klass.after_eight do true; end
47
- assert @klass._after_eight_callbacks.first.kind_of? Proc
39
+
40
+ it "accepts blocks" do
41
+ klass.after_eight do true; end
42
+ assert klass._hooks[:after_eight].first.kind_of? Proc
48
43
  end
49
-
50
- should "be inherited" do
51
- @klass.after_eight :dine
52
- subklass = Class.new(@klass)
53
-
54
- assert_equal [:dine], subklass._after_eight_callbacks
44
+
45
+ it "be inherited" do
46
+ klass.after_eight :dine
47
+ subklass = Class.new(klass)
48
+
49
+ assert_equal [:dine], subklass._hooks[:after_eight]
55
50
  end
51
+ # TODO: check if options are not shared!
56
52
  end
57
-
58
- context "Hooks#run_hook" do
59
- should "run without parameters" do
60
- @mum.instance_eval do
53
+
54
+ describe "Hooks#run_hook" do
55
+ it "run without parameters" do
56
+ subject.instance_eval do
61
57
  def a; executed << :a; nil; end
62
58
  def b; executed << :b; end
63
-
59
+
64
60
  self.class.after_eight :b
65
61
  self.class.after_eight :a
66
62
  end
67
-
68
- @mum.run_hook(:after_eight)
69
-
70
- assert_equal [:b, :a], @mum.executed
63
+
64
+ subject.run_hook(:after_eight)
65
+
66
+ assert_equal [:b, :a], subject.executed
71
67
  end
72
-
73
- should "accept arbitrary parameters" do
74
- @mum.instance_eval do
68
+
69
+ it "accept arbitrary parameters" do
70
+ subject.instance_eval do
75
71
  def a(me, arg); executed << arg+1; end
76
72
  end
77
- @mum.class.after_eight :a
78
- @mum.class.after_eight lambda { |me, arg| me.executed << arg-1 }
79
-
80
- @mum.run_hook(:after_eight, @mum, 1)
81
-
82
- assert_equal [2, 0], @mum.executed
73
+ subject.class.after_eight :a
74
+ subject.class.after_eight lambda { |me, arg| me.executed << arg-1 }
75
+
76
+ subject.run_hook(:after_eight, subject, 1)
77
+
78
+ assert_equal [2, 0], subject.executed
83
79
  end
84
80
 
85
- should "execute block callbacks in instance context" do
86
- @mum.class.after_eight { executed << :c }
87
- @mum.run_hook(:after_eight)
88
- assert_equal [:c], @mum.executed
81
+ it "execute block callbacks in instance context" do
82
+ subject.class.after_eight { executed << :c }
83
+ subject.run_hook(:after_eight)
84
+ assert_equal [:c], subject.executed
89
85
  end
90
86
 
91
- should "return callback results in order" do
92
- @mum.class.after_eight { :dinner_out }
93
- @mum.class.after_eight { :party_hard }
94
- @mum.class.after_eight { :taxi_home }
87
+ it "returns all callbacks in order" do
88
+ subject.class.after_eight { :dinner_out }
89
+ subject.class.after_eight { :party_hard }
90
+ subject.class.after_eight { :taxi_home }
91
+
92
+ results = subject.run_hook(:after_eight)
95
93
 
96
- results = @mum.run_hook(:after_eight)
97
94
  assert_equal [:dinner_out, :party_hard, :taxi_home], results
95
+ assert_equal false, results.halted?
96
+ assert_equal true, results.not_halted?
97
+ end
98
+
99
+ describe "halts_on_falsey: true" do
100
+ let(:klass) do
101
+ Class.new(TestClass) do
102
+ define_hook :after_eight, :halts_on_falsey => true
103
+ end
104
+ end
105
+
106
+ [nil, false].each do |falsey|
107
+ it "returns successful callbacks in order (with #{falsey.inspect})" do
108
+ ordered = []
109
+
110
+ subject.class.after_eight { :dinner_out }
111
+ subject.class.after_eight { :party_hard; falsey }
112
+ subject.class.after_eight { :taxi_home }
113
+
114
+ results = subject.run_hook(:after_eight)
115
+
116
+ assert_equal [:dinner_out], results
117
+ assert_equal true, results.halted?
118
+ end
119
+ end
120
+ end
121
+
122
+ describe "halts_on_falsey: false" do
123
+ [nil, false].each do |falsey|
124
+ it "returns all callbacks in order (with #{falsey.inspect})" do
125
+ ordered = []
126
+
127
+ subject.class.after_eight { :dinner_out }
128
+ subject.class.after_eight { :party_hard; falsey }
129
+ subject.class.after_eight { :taxi_home }
130
+
131
+ results = subject.run_hook(:after_eight)
132
+
133
+ assert_equal [:dinner_out, falsey, :taxi_home], results
134
+ assert_equal false, results.halted?
135
+ end
136
+ end
98
137
  end
99
138
  end
100
-
101
- context "in class context" do
102
- should "run a callback block" do
139
+
140
+ describe "in class context" do
141
+ it "run a callback block" do
103
142
  executed = []
104
- @klass.after_eight do
143
+ klass.after_eight do
105
144
  executed << :klass
106
145
  end
107
- @klass.run_hook :after_eight
108
-
146
+ klass.run_hook(:after_eight)
147
+
109
148
  assert_equal [:klass], executed
110
149
  end
111
-
112
- should "run a class methods" do
150
+
151
+ it "run a class methods" do
113
152
  executed = []
114
- @klass.instance_eval do
153
+ klass.instance_eval do
115
154
  after_eight :have_dinner
116
-
155
+
117
156
  def have_dinner(executed)
118
157
  executed << :have_dinner
119
158
  end
120
159
  end
121
- @klass.run_hook :after_eight, executed
122
-
160
+ klass.run_hook(:after_eight, executed)
161
+
123
162
  assert_equal [:have_dinner], executed
124
163
  end
125
164
  end
126
165
  end
127
-
128
- context "Deriving" do
129
- setup do
130
- @klass = Class.new(TestClass)
131
-
132
- @mum = @klass.new
133
- @mum.class.define_hook :after_eight
134
- end
135
-
136
- should "inherit the hook" do
137
- @klass.class_eval do
166
+
167
+ describe "Inheritance" do
168
+ let (:superclass) {
169
+ Class.new(TestClass) do
170
+ define_hook :after_eight
171
+
138
172
  after_eight :take_shower
139
-
140
- def take_shower
141
- executed << :take_shower
142
- end
143
173
  end
144
-
145
- @kid = Class.new(@klass) do
146
- after_eight :have_dinner
147
-
148
- def have_dinner
149
- executed << :have_dinner
150
- end
151
- end.new
152
-
153
- assert_equal [:take_shower, :have_dinner], @kid.class.callbacks_for_hook(:after_eight)
174
+ }
175
+
176
+ let (:subclass) { Class.new(superclass) do after_eight :have_dinner end }
177
+
178
+ it "inherits callbacks from the hook" do
179
+ subclass.callbacks_for_hook(:after_eight).must_equal [:take_shower, :have_dinner]
180
+ end
181
+
182
+ it "doesn't mix up superclass hooks" do
183
+ subclass.superclass.callbacks_for_hook(:after_eight).must_equal [:take_shower]
154
184
  end
155
185
  end
156
186
  end
187
+
188
+ class HookSetTest < MiniTest::Spec
189
+ subject { Hooks::HookSet.new }
190
+
191
+ it "responds to #clone" do
192
+ subject[:after_eight] = [:drink_beer]
193
+ clone = subject.clone
194
+ clone[:after_eight] << :open_fridge
195
+
196
+ subject.must_equal(:after_eight => [:drink_beer])
197
+ end
198
+ end
@@ -1,53 +1,51 @@
1
1
  require 'test_helper'
2
2
 
3
- class HooksTest < Test::Unit::TestCase
4
- context "Hooks.define_hook" do
5
- setup do
6
- @klass = Class.new(Object) do
3
+ class HooksTest < MiniTest::Spec
4
+ describe "Hooks.define_hook" do
5
+ subject {
6
+ Class.new(Object) do
7
7
  extend Hooks::InheritableAttribute
8
+ inheritable_attr :drinks
8
9
  end
9
-
10
- @mum = @klass.new
11
- @klass.inheritable_attr :drinks
12
- end
13
-
14
- should "provide a reader with empty inherited attributes, already" do
15
- assert_equal nil, @klass.drinks
10
+ }
11
+
12
+ it "provides a reader with empty inherited attributes, already" do
13
+ assert_equal nil, subject.drinks
16
14
  end
17
-
18
- should "provide a reader with empty inherited attributes in a derived class" do
19
- assert_equal nil, Class.new(@klass).drinks
20
- #@klass.drinks = true
21
- #Class.new(@klass).drinks # TODO: crashes.
15
+
16
+ it "provides a reader with empty inherited attributes in a derived class" do
17
+ assert_equal nil, Class.new(subject).drinks
18
+ #subject.drinks = true
19
+ #Class.new(subject).drinks # TODO: crashes.
22
20
  end
23
-
24
- should "provide an attribute copy in subclasses" do
25
- @klass.drinks = []
26
- assert @klass.drinks.object_id != Class.new(@klass).drinks.object_id
21
+
22
+ it "provides an attribute copy in subclasses" do
23
+ subject.drinks = []
24
+ assert subject.drinks.object_id != Class.new(subject).drinks.object_id
27
25
  end
28
-
29
- should "provide a writer" do
30
- @klass.drinks = [:cabernet]
31
- assert_equal [:cabernet], @klass.drinks
26
+
27
+ it "provides a writer" do
28
+ subject.drinks = [:cabernet]
29
+ assert_equal [:cabernet], subject.drinks
32
30
  end
33
-
34
- should "inherit attributes" do
35
- @klass.drinks = [:cabernet]
36
-
37
- subklass_a = Class.new(@klass)
31
+
32
+ it "inherits attributes" do
33
+ subject.drinks = [:cabernet]
34
+
35
+ subklass_a = Class.new(subject)
38
36
  subklass_a.drinks << :becks
39
-
40
- subklass_b = Class.new(@klass)
41
-
42
- assert_equal [:cabernet], @klass.drinks
37
+
38
+ subklass_b = Class.new(subject)
39
+
40
+ assert_equal [:cabernet], subject.drinks
43
41
  assert_equal [:cabernet, :becks], subklass_a.drinks
44
42
  assert_equal [:cabernet], subklass_b.drinks
45
43
  end
46
-
47
- should "not inherit attributes if we set explicitely" do
48
- @klass.drinks = [:cabernet]
49
- subklass = Class.new(@klass)
50
-
44
+
45
+ it "does not inherit attributes if we set explicitely" do
46
+ subject.drinks = [:cabernet]
47
+ subklass = Class.new(subject)
48
+
51
49
  subklass.drinks = [:merlot] # we only want merlot explicitely.
52
50
  assert_equal [:merlot], subklass.drinks # no :cabernet, here
53
51
  end
@@ -1,10 +1,3 @@
1
- require 'rubygems'
2
-
3
- # wycats says...
4
- require 'bundler'
5
- Bundler.setup
6
- require 'test/unit'
7
- require 'shoulda'
1
+ require 'minitest/autorun'
2
+ require 'pry'
8
3
  require 'hooks'
9
-
10
- $:.unshift File.dirname(__FILE__) # add current dir to LOAD_PATHS
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hooks
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,10 +9,26 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-18 00:00:00.000000000 Z
12
+ date: 2013-05-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: shoulda
15
+ name: minitest
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 5.0.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: 5.0.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
16
32
  requirement: !ruby/object:Gem::Requirement
17
33
  none: false
18
34
  requirements:
@@ -28,7 +44,7 @@ dependencies:
28
44
  - !ruby/object:Gem::Version
29
45
  version: '0'
30
46
  - !ruby/object:Gem::Dependency
31
- name: rake
47
+ name: pry
32
48
  requirement: !ruby/object:Gem::Requirement
33
49
  none: false
34
50
  requirements:
@@ -51,17 +67,21 @@ executables: []
51
67
  extensions: []
52
68
  extra_rdoc_files: []
53
69
  files:
70
+ - .gitignore
71
+ - .travis.yml
54
72
  - CHANGES.textile
55
73
  - Gemfile
56
- - README.rdoc
74
+ - README.md
57
75
  - Rakefile
58
76
  - hooks.gemspec
59
77
  - lib/hooks.rb
78
+ - lib/hooks/hook.rb
60
79
  - lib/hooks/inheritable_attribute.rb
80
+ - test/hook_test.rb
61
81
  - test/hooks_test.rb
62
82
  - test/inheritable_attribute_test.rb
63
83
  - test/test_helper.rb
64
- homepage: http://nicksda.apotomo.de/tag/hooks
84
+ homepage: http://nicksda.apotomo.de/2010/09/hooks-and-callbacks-for-ruby-but-simple/
65
85
  licenses: []
66
86
  post_install_message:
67
87
  rdoc_options: []
@@ -81,7 +101,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
81
101
  version: '0'
82
102
  requirements: []
83
103
  rubyforge_project:
84
- rubygems_version: 1.8.24
104
+ rubygems_version: 1.8.25
85
105
  signing_key:
86
106
  specification_version: 3
87
107
  summary: Generic hooks with callbacks for Ruby.
@@ -1,109 +0,0 @@
1
- = Hooks
2
-
3
- <em>Generic hooks with callbacks for Ruby.</em>
4
-
5
-
6
- == Introduction
7
-
8
- _Hooks_ lets you define hooks declaratively in your ruby class. You can add callbacks to your hook, which will be run as soon as _you_ run the hook!
9
-
10
- It's almost like ActiveSupport::Callbacks but 76,6% less complex. Instead, it is not more than 60 lines of code, one method compilation, no +method_missing+ and no magic.
11
-
12
- Also, you may pass additional arguments to your callbacks when invoking a hook.
13
-
14
- == Example
15
-
16
- Let's take... a cat.
17
-
18
- require 'hooks'
19
-
20
- class Cat
21
- include Hooks
22
-
23
- define_hooks :before_dinner, :after_dinner
24
-
25
- Now you can add callbacks to your hook declaratively in your class.
26
-
27
- before_dinner :wash_paws
28
-
29
- after_dinner do
30
- puts "Ice cream for #{self}!"
31
- end
32
-
33
- after_dinner :have_a_desert # => refers to Cat#have_a_desert
34
-
35
- def have_a_desert
36
- puts "Hell, yeah!"
37
- end
38
-
39
- This will run the block and <tt>#have_a_desert</tt> from above.
40
-
41
- cat.run_hook :after_dinner
42
- # => Ice cream for #<Cat:0x8df9d84>!
43
- Hell, yeah!
44
-
45
- Callback blocks and methods will be executed with instance context. Note how +self+ in the block refers to the Cat instance.
46
-
47
-
48
- == Inheritance
49
-
50
- Hooks are inherited, here's a complete example to put it all together.
51
-
52
- class Garfield < Cat
53
-
54
- after_dinner :want_some_more
55
-
56
- def want_some_more
57
- puts "Is that all?"
58
- end
59
- end
60
-
61
-
62
- Garfield.new.run_hook :after_dinner
63
- # => Ice cream for #<Cat:0x8df9d84>!
64
- Hell, yeah!
65
- Is that all?
66
-
67
- Note how the callbacks are invoked in the order they were inherited.
68
-
69
-
70
- == Options for Callbacks
71
-
72
- You're free to pass any number of arguments to #run_callback, those will be passed to the callbacks.
73
-
74
- cat.run_hook :before_dinner, cat, Time.now
75
-
76
- The callbacks should be ready for receiving parameters.
77
-
78
- before_dinner :wash_pawns
79
- before_dinner do |who, when|
80
- ...
81
- end
82
-
83
- def wash_pawns(who, when)
84
-
85
-
86
- Not sure why a cat should have ice cream for dinner. Beside that, I was tempted naming this gem _hooker_.
87
-
88
-
89
- == Installation
90
-
91
- gem install hooks
92
-
93
-
94
- == Anybody using it?
95
-
96
- * Hooks is already used in [Apotomo:http://github.com/apotonick/apotomo], a hot widget framework for Rails. Look at +lib/apotomo/widget.rb+ for examples and into +lib/apotomo/tree_node.rb+ to learn how modules-driven code might benefit from hooks.
97
-
98
- == Similar libraries
99
-
100
- * http://github.com/nakajima/aspectory
101
- * http://github.com/auser/backcall
102
- * http://github.com/mmcgrana/simple_callbacks
103
-
104
-
105
- == License
106
-
107
- Copyright (c) 2010, Nick Sutterer
108
-
109
- Released under the MIT License.