big_spoon 0.0.1
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/.rspec +1 -0
- data/.rvmrc +1 -0
- data/.simplecov +1 -0
- data/CHANGES.md +5 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +41 -0
- data/Guardfile +8 -0
- data/README.md +58 -0
- data/Rakefile +16 -0
- data/VERSION +1 -0
- data/big_spoon.gemspec +49 -0
- data/lib/big_spoon/hook.rb +138 -0
- data/lib/big_spoon.rb +25 -0
- data/spec/lib/big_spoon_spec.rb +91 -0
- data/spec/spec_helper.rb +10 -0
- metadata +66 -0
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour
|
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use 1.9.3@working_girl --create
|
data/.simplecov
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
SimpleCov.start
|
data/CHANGES.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
diff-lcs (1.1.3)
|
5
|
+
ffi (1.1.5)
|
6
|
+
guard (1.3.0)
|
7
|
+
listen (>= 0.4.2)
|
8
|
+
thor (>= 0.14.6)
|
9
|
+
guard-rspec (1.2.1)
|
10
|
+
guard (>= 1.1)
|
11
|
+
listen (0.4.7)
|
12
|
+
rb-fchange (~> 0.0.5)
|
13
|
+
rb-fsevent (~> 0.9.1)
|
14
|
+
rb-inotify (~> 0.8.8)
|
15
|
+
multi_json (1.3.6)
|
16
|
+
rb-fchange (0.0.5)
|
17
|
+
ffi
|
18
|
+
rb-fsevent (0.9.1)
|
19
|
+
rb-inotify (0.8.8)
|
20
|
+
ffi (>= 0.5.0)
|
21
|
+
rspec (2.11.0)
|
22
|
+
rspec-core (~> 2.11.0)
|
23
|
+
rspec-expectations (~> 2.11.0)
|
24
|
+
rspec-mocks (~> 2.11.0)
|
25
|
+
rspec-core (2.11.1)
|
26
|
+
rspec-expectations (2.11.2)
|
27
|
+
diff-lcs (~> 1.1.3)
|
28
|
+
rspec-mocks (2.11.1)
|
29
|
+
simplecov (0.6.4)
|
30
|
+
multi_json (~> 1.0)
|
31
|
+
simplecov-html (~> 0.5.3)
|
32
|
+
simplecov-html (0.5.3)
|
33
|
+
thor (0.15.4)
|
34
|
+
|
35
|
+
PLATFORMS
|
36
|
+
ruby
|
37
|
+
|
38
|
+
DEPENDENCIES
|
39
|
+
guard-rspec
|
40
|
+
rspec
|
41
|
+
simplecov
|
data/Guardfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# Big Spoon
|
2
|
+
Like the big spoon, **Big Spoon** wraps around your own methods.
|
3
|
+
It adds before and after callbacks to ANY method in any Ruby class.
|
4
|
+
|
5
|
+
There were those who wanted to call it "sandwich," but they were killed.
|
6
|
+
|
7
|
+
**Big Spoon** adds _hooks_ around ANY method. It can do this before
|
8
|
+
or after the methods are defined, making it awesome for fun stuff like adding extra hooks around events at the top of
|
9
|
+
a class definition without having to worry about when the method gets defined.
|
10
|
+
|
11
|
+
I think I'm going to import it into all of my libraries to avoid alias\_method_chaining anything ever again.
|
12
|
+
|
13
|
+
Anyway, here's how it works for now, DON'T use it cause I haven't tested it with shit:
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
|
17
|
+
Let's say, for example, you want to reset an instance variable in a model when it gets reloaded.
|
18
|
+
|
19
|
+
For shits and giggles and real-world-clarity, I'll use an ActiveRecord model but this could be
|
20
|
+
anything that knows about `reload`.
|
21
|
+
|
22
|
+
```
|
23
|
+
class User < ActiveRecord::Base
|
24
|
+
def name
|
25
|
+
@name ||= "#{first_name} #{last_name}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
```
|
29
|
+
|
30
|
+
So now, when you do something like this:
|
31
|
+
|
32
|
+
```
|
33
|
+
user = User.new(:first_name => "Flip", :last_name => "Sasser")
|
34
|
+
user.name #=> "Flip Sasser"
|
35
|
+
user.first_name = "Elizabeth"
|
36
|
+
user.name #=> "Flip Sasser"
|
37
|
+
# OH NOE MY INSTANCE VARIABLE GOT CACHED LIKE IT SHOULD
|
38
|
+
user.reload.name #=> "Flip Sasser"
|
39
|
+
# OH NOE RELOAD DOESN'T RESET INSTANCE VARIABLES
|
40
|
+
```
|
41
|
+
|
42
|
+
But what if you could hook into `ActiveRecord::Base#reload`?
|
43
|
+
|
44
|
+
```
|
45
|
+
class User < ActiveRecord::Base
|
46
|
+
hooks do
|
47
|
+
before_reload { @name = nil }
|
48
|
+
end
|
49
|
+
|
50
|
+
def name
|
51
|
+
@name ||= "#{first_name} #{last_name}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
```
|
55
|
+
|
56
|
+
Now you can reset the instance variable. Neat, right? Obviously this is way more awesome way more places but that's the long and short of it.
|
57
|
+
|
58
|
+
Copyright © 2012 Delightful Widgets Inc. No warranty so don't sue me or my company THANKS!
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'rake'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'jeweler'
|
5
|
+
Jeweler::Tasks.new do |gemspec|
|
6
|
+
gemspec.name = "big_spoon"
|
7
|
+
gemspec.summary = "Adds before and after hooks to any method, because that's just how things should be"
|
8
|
+
gemspec.description = %{
|
9
|
+
BigSpoon will add a hooks method to every class. Call that method with a block and add all kinds of fun hooks before and after your methods.
|
10
|
+
}
|
11
|
+
gemspec.email = "flip@x451.com"
|
12
|
+
gemspec.homepage = "http://github.com/Plinq/big_spoon"
|
13
|
+
gemspec.authors = ["Flip Sasser"]
|
14
|
+
end
|
15
|
+
rescue LoadError
|
16
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
data/big_spoon.gemspec
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "big_spoon"
|
8
|
+
s.version = "0.0.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Flip Sasser"]
|
12
|
+
s.date = "2012-08-12"
|
13
|
+
s.description = "\n BigSpoon will add a hooks method to every class. Call that method with a block and add all kinds of fun hooks before and after your methods.\n "
|
14
|
+
s.email = "flip@x451.com"
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README.md"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".rspec",
|
20
|
+
".rvmrc",
|
21
|
+
".simplecov",
|
22
|
+
"CHANGES.md",
|
23
|
+
"Gemfile",
|
24
|
+
"Gemfile.lock",
|
25
|
+
"Guardfile",
|
26
|
+
"README.md",
|
27
|
+
"Rakefile",
|
28
|
+
"VERSION",
|
29
|
+
"big_spoon.gemspec",
|
30
|
+
"lib/big_spoon.rb",
|
31
|
+
"lib/big_spoon/hook.rb",
|
32
|
+
"spec/lib/big_spoon_spec.rb",
|
33
|
+
"spec/spec_helper.rb"
|
34
|
+
]
|
35
|
+
s.homepage = "http://github.com/Plinq/big_spoon"
|
36
|
+
s.require_paths = ["lib"]
|
37
|
+
s.rubygems_version = "1.8.24"
|
38
|
+
s.summary = "Adds before and after hooks to any method, because that's just how things should be"
|
39
|
+
|
40
|
+
if s.respond_to? :specification_version then
|
41
|
+
s.specification_version = 3
|
42
|
+
|
43
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
44
|
+
else
|
45
|
+
end
|
46
|
+
else
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
@@ -0,0 +1,138 @@
|
|
1
|
+
module BigSpoon
|
2
|
+
class Hook
|
3
|
+
class << self
|
4
|
+
def for(klass)
|
5
|
+
all[klass.to_s] ||= new(klass)
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
def all
|
10
|
+
@all ||= {}
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_accessor :klass
|
15
|
+
|
16
|
+
# Define a method to execute after another
|
17
|
+
def after(method_to_hook, method_to_call = nil, &block)
|
18
|
+
hook(:after, method_to_hook, method_to_call, &block)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Define a method to execute before another
|
22
|
+
def before(method_to_hook, method_to_call = nil, &block)
|
23
|
+
hook(:before, method_to_hook, method_to_call, &block)
|
24
|
+
end
|
25
|
+
|
26
|
+
# def clear!
|
27
|
+
# methods.each do |method_to_hook, hooks|
|
28
|
+
# unhook! method_to_hook
|
29
|
+
# end
|
30
|
+
# methods.clear
|
31
|
+
# end
|
32
|
+
|
33
|
+
# Execute after a method
|
34
|
+
def execute_after(method_to_hook, instance)
|
35
|
+
execute(:after, method_to_hook, instance)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Execute before a method
|
39
|
+
def execute_before(method_to_hook, instance)
|
40
|
+
execute(:before, method_to_hook, instance)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Alias a method for hookable-ness
|
44
|
+
def hook!(method_to_hook)
|
45
|
+
hooked_method = hooked_method(method_to_hook)
|
46
|
+
original_method = original_method(method_to_hook)
|
47
|
+
line = __LINE__; alias_these_hooks = <<-hooks
|
48
|
+
alias :#{original_method} :#{method_to_hook}
|
49
|
+
def #{hooked_method}(*args)
|
50
|
+
Hook.for(self.class).execute_before(:#{method_to_hook}, self)
|
51
|
+
result = #{original_method}
|
52
|
+
Hook.for(self.class).execute_after(:#{method_to_hook}, self)
|
53
|
+
result
|
54
|
+
end
|
55
|
+
alias :#{method_to_hook} :#{hooked_method}
|
56
|
+
hooks
|
57
|
+
klass.class_eval alias_these_hooks, __FILE__, line.succ
|
58
|
+
end
|
59
|
+
|
60
|
+
def initialize(klass)
|
61
|
+
self.klass = klass
|
62
|
+
end
|
63
|
+
|
64
|
+
def should_hook?(method_to_hook)
|
65
|
+
methods[method_to_hook.to_sym] && hookable?(method_to_hook) && !hooked?(method_to_hook)
|
66
|
+
end
|
67
|
+
|
68
|
+
def unhook!(method_to_hook)
|
69
|
+
hooked_method = hooked_method(method_to_hook)
|
70
|
+
original_method = original_method(method_to_hook)
|
71
|
+
line = __LINE__; alias_these_hooks = <<-hooks
|
72
|
+
alias :#{method_to_hook} #{original_method}
|
73
|
+
remove_method #{hooked_method}
|
74
|
+
hooks
|
75
|
+
klass.class_eval alias_these_hooks, __FILE__, line.succ
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
def execute(before_or_after, method_to_hook, instance)
|
80
|
+
methods[method_to_hook] ||= {}
|
81
|
+
methods[method_to_hook][before_or_after] ||= []
|
82
|
+
methods[method_to_hook][before_or_after].each do |hook_to_call|
|
83
|
+
case hook_to_call
|
84
|
+
when Proc
|
85
|
+
instance.instance_eval(&hook_to_call)
|
86
|
+
else
|
87
|
+
instance.send(hook_to_call)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def hook(before_or_after, method_to_hook, method_to_call = nil, &block)
|
93
|
+
method_to_hook = method_to_hook.to_sym
|
94
|
+
if block_given?
|
95
|
+
method_to_call = block
|
96
|
+
else
|
97
|
+
method_to_call = method_to_call.to_sym
|
98
|
+
end
|
99
|
+
|
100
|
+
methods[method_to_hook] ||= {}
|
101
|
+
methods[method_to_hook][before_or_after] ||= []
|
102
|
+
methods[method_to_hook][before_or_after].push(method_to_call) unless methods[method_to_hook][before_or_after].include?(method_to_call)
|
103
|
+
|
104
|
+
hook!(method_to_hook) if should_hook?(method_to_hook)
|
105
|
+
end
|
106
|
+
|
107
|
+
def hookable?(method_to_hook)
|
108
|
+
klass.method_defined?(method_to_hook)
|
109
|
+
end
|
110
|
+
|
111
|
+
def hooked?(method_to_hook)
|
112
|
+
klass.method_defined?(hooked_method(method_to_hook))
|
113
|
+
end
|
114
|
+
|
115
|
+
def hooked_method(method_to_hook)
|
116
|
+
"_big_spoon_alias_#{method_to_hook}"
|
117
|
+
end
|
118
|
+
|
119
|
+
def method_missing(method_name, *args)
|
120
|
+
case method_name.to_s
|
121
|
+
when /^after_(.+)$/
|
122
|
+
after $1, *args
|
123
|
+
when /^before_(.+)$/
|
124
|
+
before $1, *args
|
125
|
+
else
|
126
|
+
super
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def methods
|
131
|
+
@methods ||= {}
|
132
|
+
end
|
133
|
+
|
134
|
+
def original_method(method_to_hook)
|
135
|
+
"_big_spoon_original_#{method_to_hook}"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
data/lib/big_spoon.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'big_spoon/hook'
|
2
|
+
|
3
|
+
module BigSpoon
|
4
|
+
module ClassMethods
|
5
|
+
def hooks(options = {}, &block)
|
6
|
+
@hooks ||= Hook.for(self)
|
7
|
+
@hooks.instance_eval(&block) if block_given?
|
8
|
+
|
9
|
+
unless respond_to?(:_big_spoon_original_method_added)
|
10
|
+
class << self
|
11
|
+
alias :_big_spoon_original_method_added :method_added
|
12
|
+
def _big_spoon_alias_method_added(method_to_hook, *args)
|
13
|
+
if @hooks.should_hook?(method_to_hook)
|
14
|
+
@hooks.hook! method_to_hook
|
15
|
+
end
|
16
|
+
end
|
17
|
+
alias :method_added :_big_spoon_alias_method_added
|
18
|
+
end
|
19
|
+
end
|
20
|
+
@hooks
|
21
|
+
end # `hooks` method
|
22
|
+
end # `ClassMethods` module
|
23
|
+
end # `BigSpoon` module
|
24
|
+
|
25
|
+
Object.extend BigSpoon::ClassMethods
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'big_spoon'
|
2
|
+
|
3
|
+
class BigSpoonTest
|
4
|
+
hooks do
|
5
|
+
before :foo!, :hook_1
|
6
|
+
end
|
7
|
+
|
8
|
+
def foo!
|
9
|
+
:foo
|
10
|
+
end
|
11
|
+
|
12
|
+
def bar!
|
13
|
+
:bar
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
def by_all_means_hook
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
def dont_hook
|
22
|
+
false
|
23
|
+
end
|
24
|
+
|
25
|
+
def hook_1
|
26
|
+
:hook_1
|
27
|
+
end
|
28
|
+
|
29
|
+
def hook_2
|
30
|
+
:hook_2
|
31
|
+
end
|
32
|
+
|
33
|
+
def hook_3
|
34
|
+
:hook_3
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe BigSpoon do
|
39
|
+
before :each do
|
40
|
+
@big_spoon_test = BigSpoonTest.new
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should monkeypatch Object" do
|
44
|
+
Object.should respond_to(:hooks)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should allow me to add a hook before an object's methods BEFORE THEY EXIST" do
|
48
|
+
@big_spoon_test.should_receive(:hook_1)
|
49
|
+
@big_spoon_test.foo!
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should allow me to add a hook after an object's methods AFTER THEY EXIST" do
|
53
|
+
BigSpoonTest.hooks do
|
54
|
+
after :bar!, :hook_2
|
55
|
+
end
|
56
|
+
@big_spoon_test.should_receive(:hook_2)
|
57
|
+
@big_spoon_test.bar!
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should allow me to add a hook lambda" do
|
61
|
+
BigSpoonTest.hooks do
|
62
|
+
after :bar! do
|
63
|
+
puts self.inspect
|
64
|
+
end
|
65
|
+
end
|
66
|
+
@big_spoon_test.should_receive(:puts)
|
67
|
+
@big_spoon_test.should_receive(:inspect)
|
68
|
+
@big_spoon_test.bar!
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should still return the appropriate method's value" do
|
72
|
+
@big_spoon_test.should_receive(:hook_1)
|
73
|
+
@big_spoon_test.foo!.should == :foo
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should allow me to metaprogram before_* hooks" do
|
77
|
+
@big_spoon_test.should_receive(:hook_2)
|
78
|
+
BigSpoonTest.hooks do
|
79
|
+
before_foo! :hook_2
|
80
|
+
end
|
81
|
+
@big_spoon_test.foo!
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should allow me to metaprogram after_* hooks" do
|
85
|
+
@big_spoon_test.should_receive(:hook_3)
|
86
|
+
BigSpoonTest.hooks do
|
87
|
+
after_foo! :hook_3
|
88
|
+
end
|
89
|
+
@big_spoon_test.foo!
|
90
|
+
end
|
91
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# Add lib/ to the load path
|
2
|
+
$LOAD_PATH.unshift(File.expand_path(File.join('..', 'lib'), File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
# Load up our Gemfile
|
5
|
+
require 'rubygems'
|
6
|
+
require 'bundler/setup'
|
7
|
+
Bundler.require(:default, :test)
|
8
|
+
|
9
|
+
# Enable coverage reporting
|
10
|
+
require 'simplecov'
|
metadata
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: big_spoon
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Flip Sasser
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-08-12 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: ! "\n BigSpoon will add a hooks method to every class. Call that
|
15
|
+
method with a block and add all kinds of fun hooks before and after your methods.\n
|
16
|
+
\ "
|
17
|
+
email: flip@x451.com
|
18
|
+
executables: []
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files:
|
21
|
+
- README.md
|
22
|
+
files:
|
23
|
+
- .rspec
|
24
|
+
- .rvmrc
|
25
|
+
- .simplecov
|
26
|
+
- CHANGES.md
|
27
|
+
- Gemfile
|
28
|
+
- Gemfile.lock
|
29
|
+
- Guardfile
|
30
|
+
- README.md
|
31
|
+
- Rakefile
|
32
|
+
- VERSION
|
33
|
+
- big_spoon.gemspec
|
34
|
+
- lib/big_spoon.rb
|
35
|
+
- lib/big_spoon/hook.rb
|
36
|
+
- spec/lib/big_spoon_spec.rb
|
37
|
+
- spec/spec_helper.rb
|
38
|
+
homepage: http://github.com/Plinq/big_spoon
|
39
|
+
licenses: []
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options: []
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
none: false
|
46
|
+
requirements:
|
47
|
+
- - ! '>='
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
segments:
|
51
|
+
- 0
|
52
|
+
hash: 4152067215125062110
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ! '>='
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
requirements: []
|
60
|
+
rubyforge_project:
|
61
|
+
rubygems_version: 1.8.24
|
62
|
+
signing_key:
|
63
|
+
specification_version: 3
|
64
|
+
summary: Adds before and after hooks to any method, because that's just how things
|
65
|
+
should be
|
66
|
+
test_files: []
|