hooks 0.1 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,12 +5,10 @@
5
5
 
6
6
  == Introduction
7
7
 
8
- Hooks lets you define hooks declaratively in your ruby class. You can add callbacks to your hook, which will be
8
+ _Hooks_ lets you define hooks declaratively in your ruby class. You can add callbacks to your hook, which will be
9
9
  run as soon as _you_ run the hook!
10
10
 
11
- It's almost like ActiveSupport::Callbacks but 76,6% less complex.
12
-
13
- Instead, it is not more than 80 lines of code, one method compilation, no +method_missing+ and no magic.
11
+ 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.
14
12
 
15
13
 
16
14
  == Example
@@ -35,7 +33,7 @@ Now you can add callbacks to your hook declaratively in your class.
35
33
  puts "Hell, yeah!"
36
34
  end
37
35
 
38
- Running the callbacks happens on instances. It will run the block and +#have_a_desert+ from above.
36
+ Running the callbacks happens on instances. It will run the block and #have_a_desert from above.
39
37
 
40
38
  cat.run_hook :after_dinner
41
39
  # => Ice cream!
@@ -73,9 +71,13 @@ The current gem requires
73
71
  * active_support 2.3.x
74
72
 
75
73
 
74
+ == Anybody using it?
75
+
76
+ * 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.
77
+
76
78
  == Similar libraries
77
79
 
78
- * http://github.com/nakajima/aspectory/tree/master/lib/aspectory/
80
+ * http://github.com/nakajima/aspectory
79
81
  * http://github.com/auser/backcall
80
82
  * http://github.com/mmcgrana/simple_callbacks
81
83
 
data/Rakefile CHANGED
@@ -13,8 +13,8 @@ Rake::TestTask.new(:test) do |test|
13
13
  end
14
14
 
15
15
  require 'jeweler'
16
- $:.unshift File.dirname(__FILE__) # add current dir to LOAD_PATHS
17
- require 'lib/hooks'
16
+ $:.unshift File.dirname(__FILE__)+"/lib" # add current dir to LOAD_PATHS
17
+ require 'hooks'
18
18
 
19
19
  Jeweler::Tasks.new do |spec|
20
20
  spec.name = "hooks"
@@ -26,8 +26,6 @@ Jeweler::Tasks.new do |spec|
26
26
  spec.email = "apotonick@gmail.com"
27
27
 
28
28
  spec.files = FileList["[A-Z]*", File.join(*%w[{lib} ** *]).to_s]
29
-
30
- spec.add_dependency 'activesupport', '>= 2.3.0'
31
29
  end
32
30
 
33
31
  Jeweler::GemcutterTasks.new
@@ -1,4 +1,4 @@
1
- require 'active_support/core_ext/class/inheritable_attributes.rb'
1
+ require "hooks/inheritable_attribute"
2
2
 
3
3
  # Almost like ActiveSupport::Callbacks but 76,6% less complex.
4
4
  #
@@ -16,9 +16,10 @@ require 'active_support/core_ext/class/inheritable_attributes.rb'
16
16
  #
17
17
  # cat.run_hook :after_dinner
18
18
  module Hooks
19
- VERSION = "0.1"
19
+ VERSION = "0.1.1"
20
20
 
21
21
  def self.included(base)
22
+ base.extend InheritableAttribute
22
23
  base.extend ClassMethods
23
24
  end
24
25
 
@@ -30,21 +31,41 @@ module Hooks
30
31
  define_hook_writer(name, accessor_name)
31
32
  end
32
33
 
33
- private
34
- def define_hook_writer(hook, accessor_name)
35
- instance_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
36
- def #{hook}(method=nil, &block)
37
- callback = block_given? ? block : method
38
- #{accessor_name} << callback
39
- end
40
- RUBY_EVAL
41
- end
42
-
43
- def setup_hook_accessors(accessor_name)
44
- class_inheritable_array(accessor_name, :instance_writer => false)
45
- send("#{accessor_name}=", []) # initialize ivar.
34
+ # Like Hooks#run_hook but for the class. Note that +:callbacks+ must be class methods.
35
+ #
36
+ # Example:
37
+ #
38
+ # class Cat
39
+ # after_eight :grab_a_beer
40
+ #
41
+ # def self.grab_a_beer(*) # and so on...
42
+ #
43
+ # where <tt>Cat.run_hook :after_eight</tt> will call the class method +grab_a_beer+.
44
+ def run_hook(name, *args)
45
+ run_hook_for(name, self, *args)
46
+ end
47
+
48
+ def run_hook_for(name, scope, *args)
49
+ send("_#{name}_callbacks").each do |callback|
50
+ scope.send(callback, *args) and next if callback.kind_of? Symbol
51
+ callback.call(*args)
46
52
  end
47
-
53
+ end
54
+
55
+ private
56
+ def define_hook_writer(hook, accessor_name)
57
+ instance_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
58
+ def #{hook}(method=nil, &block)
59
+ callback = block_given? ? block : method
60
+ #{accessor_name} << callback
61
+ end
62
+ RUBY_EVAL
63
+ end
64
+
65
+ def setup_hook_accessors(accessor_name)
66
+ inheritable_attr(accessor_name)
67
+ send("#{accessor_name}=", []) # initialize ivar.
68
+ end
48
69
  end
49
70
 
50
71
  # Runs the callbacks (method/block) for the specified hook +name+. Additional arguments will
@@ -59,9 +80,6 @@ module Hooks
59
80
  # desert("i want ice cream!")
60
81
  # block.call("i want ice cream!")
61
82
  def run_hook(name, *args)
62
- self.class.send("_#{name}_callbacks").each do |callback|
63
- send(callback, *args) and next if callback.kind_of? Symbol
64
- callback.call(*args)
65
- end
83
+ self.class.run_hook_for(name, self, *args)
66
84
  end
67
85
  end
@@ -0,0 +1,33 @@
1
+ module Hooks
2
+ module InheritableAttribute
3
+ # Creates an inheritable attribute with accessors in the singleton class. Derived classes inherit the
4
+ # attributes. This is especially helpful with arrays or hashes that are extended in the inheritance
5
+ # chain. Note that you have to initialize the inheritable attribute.
6
+ #
7
+ # Example:
8
+ #
9
+ # class Cat
10
+ # inheritable_attr :drinks
11
+ # self.drinks = ["Becks"]
12
+ #
13
+ # class Garfield < Cat
14
+ # self.drinks << "Fireman's 4"
15
+ #
16
+ # and then, later
17
+ #
18
+ # Cat.drinks #=> ["Becks"]
19
+ # Garfield.drinks #=> ["Becks", "Fireman's 4"]
20
+ def inheritable_attr(name)
21
+ instance_eval %Q{
22
+ def #{name}=(v)
23
+ @#{name} = v
24
+ end
25
+
26
+ def #{name}
27
+ return @#{name} unless superclass.respond_to?(:#{name})
28
+ @#{name} ||= superclass.#{name}.clone # only do this once.
29
+ end
30
+ }
31
+ end
32
+ end
33
+ end
@@ -31,9 +31,16 @@ class HooksTest < ActiveSupport::TestCase
31
31
  @klass.after_eight do true; end
32
32
  assert @klass._after_eight_callbacks.first.kind_of? Proc
33
33
  end
34
+
35
+ should "be inherited" do
36
+ @klass.after_eight :dine
37
+ subklass = Class.new(@klass)
38
+
39
+ assert_equal [:dine], subklass._after_eight_callbacks
40
+ end
34
41
  end
35
42
 
36
- context "Hooks.run_hook"do
43
+ context "Hooks#run_hook" do
37
44
  should "run without parameters" do
38
45
  @mum.instance_eval do
39
46
  def a; executed << :a; end
@@ -59,6 +66,32 @@ class HooksTest < ActiveSupport::TestCase
59
66
 
60
67
  assert_equal [2, 0], @mum.executed
61
68
  end
62
- end
69
+ end
70
+
71
+ context "in class context" do
72
+ should "run a callback block" do
73
+ executed = []
74
+ @klass.after_eight do
75
+ executed << :klass
76
+ end
77
+ @klass.run_hook :after_eight
78
+
79
+ assert_equal [:klass], executed
80
+ end
81
+
82
+ should "run a class methods" do
83
+ executed = []
84
+ @klass.instance_eval do
85
+ after_eight :have_dinner
86
+
87
+ def have_dinner(executed)
88
+ executed << :have_dinner
89
+ end
90
+ end
91
+ @klass.run_hook :after_eight, executed
92
+
93
+ assert_equal [:have_dinner], executed
94
+ end
95
+ end
63
96
  end
64
97
  end
@@ -0,0 +1,49 @@
1
+ require 'test_helper'
2
+
3
+ class HooksTest < ActiveSupport::TestCase
4
+ context "Hooks.define_hook" do
5
+ setup do
6
+ @klass = Class.new(Object) do
7
+ extend Hooks::InheritableAttribute
8
+ end
9
+
10
+ @mum = @klass.new
11
+ @klass.inheritable_attr :drinks
12
+ end
13
+
14
+ should "provide a reader with inherited attributes, already" do
15
+ assert_equal nil, @klass.drinks
16
+ end
17
+
18
+ should "provide an attribute copy in subclasses" do
19
+ @klass.drinks = []
20
+ assert @klass.drinks.object_id != Class.new(@klass).drinks.object_id
21
+ end
22
+
23
+ should "provide a writer" do
24
+ @klass.drinks = [:cabernet]
25
+ assert_equal [:cabernet], @klass.drinks
26
+ end
27
+
28
+ should "inherit attributes" do
29
+ @klass.drinks = [:cabernet]
30
+
31
+ subklass_a = Class.new(@klass)
32
+ subklass_a.drinks << :becks
33
+
34
+ subklass_b = Class.new(@klass)
35
+
36
+ assert_equal [:cabernet], @klass.drinks
37
+ assert_equal [:cabernet, :becks], subklass_a.drinks
38
+ assert_equal [:cabernet], subklass_b.drinks
39
+ end
40
+
41
+ should "not inherit attributes if we set explicitely" do
42
+ @klass.drinks = [:cabernet]
43
+ subklass = Class.new(@klass)
44
+
45
+ subklass.drinks = [:merlot] # we only want merlot explicitely.
46
+ assert_equal [:merlot], subklass.drinks # no :cabernet, here
47
+ end
48
+ end
49
+ end
metadata CHANGED
@@ -5,7 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 1
8
- version: "0.1"
8
+ - 1
9
+ version: 0.1.1
9
10
  platform: ruby
10
11
  authors:
11
12
  - Nick Sutterer
@@ -13,24 +14,10 @@ autorequire:
13
14
  bindir: bin
14
15
  cert_chain: []
15
16
 
16
- date: 2010-09-24 00:00:00 +02:00
17
+ date: 2010-10-03 00:00:00 +02:00
17
18
  default_executable:
18
- dependencies:
19
- - !ruby/object:Gem::Dependency
20
- name: activesupport
21
- prerelease: false
22
- requirement: &id001 !ruby/object:Gem::Requirement
23
- none: false
24
- requirements:
25
- - - ">="
26
- - !ruby/object:Gem::Version
27
- segments:
28
- - 2
29
- - 3
30
- - 0
31
- version: 2.3.0
32
- type: :runtime
33
- version_requirements: *id001
19
+ dependencies: []
20
+
34
21
  description: Declaratively define hooks, add callbacks and run them with the options you like.
35
22
  email: apotonick@gmail.com
36
23
  executables: []
@@ -45,6 +32,8 @@ files:
45
32
  - README.rdoc
46
33
  - Rakefile
47
34
  - lib/hooks.rb
35
+ - lib/hooks/inheritable_attribute.rb
36
+ - test/inheritable_attribute_test.rb
48
37
  - test/test_helper.rb
49
38
  - test/hooks_test.rb
50
39
  has_rdoc: true
@@ -80,5 +69,6 @@ signing_key:
80
69
  specification_version: 3
81
70
  summary: Generic hooks with callbacks for Ruby.
82
71
  test_files:
72
+ - test/inheritable_attribute_test.rb
83
73
  - test/test_helper.rb
84
74
  - test/hooks_test.rb