hooks 0.2.2 → 0.3.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.
@@ -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.