chainable 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +27 -0
- data/README.rdoc +67 -0
- data/Rakefile +3 -0
- data/lib/chainable.rb +44 -0
- data/spec/chainable/auto_chain_spec.rb +38 -0
- data/spec/chainable/chain_method_spec.rb +83 -0
- metadata +59 -0
data/LICENSE
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
copyright (c) 2009 Konstantin Haase. All rights reserved.
|
2
|
+
|
3
|
+
Developed by: Konstantin Haase
|
4
|
+
http://github.com/rkh/chainable
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
+
of this software and associated documentation files (the "Software"), to
|
8
|
+
deal with the Software without restriction, including without limitation the
|
9
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
10
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
11
|
+
furnished to do so, subject to the following conditions:
|
12
|
+
1. Redistributions of source code must retain the above copyright notice,
|
13
|
+
this list of conditions and the following disclaimers.
|
14
|
+
2. Redistributions in binary form must reproduce the above copyright
|
15
|
+
notice, this list of conditions and the following disclaimers in the
|
16
|
+
documentation and/or other materials provided with the distribution.
|
17
|
+
3. Neither the name of Konstantin Haase, nor the names of other contributors
|
18
|
+
may be used to endorse or promote products derived from this Software without
|
19
|
+
specific prior written permission.
|
20
|
+
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
24
|
+
CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
26
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
27
|
+
WITH THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
<b>A word of warning:</b>
|
2
|
+
|
3
|
+
This is heavy ruby abuse. It even got the Evil of the Day Award™ from zenspider.
|
4
|
+
|
5
|
+
== Thou shalt not use alias_method_chain!
|
6
|
+
- http://yehudakatz.com/2009/03/06/alias_method_chain-in-models/
|
7
|
+
- http://yehudakatz.com/2009/01/18/other-ways-to-wrap-a-method/
|
8
|
+
- http://www.codefluency.com/articles/2009/01/03/wrapping-a-method-in-ruby
|
9
|
+
|
10
|
+
== What it does
|
11
|
+
Chainable is an alternative to alias_method_chain, that uses inheritance, rather
|
12
|
+
than aliasing. Instead it does the following when "chaining" a method:
|
13
|
+
|
14
|
+
- copy the original method to a new model
|
15
|
+
- include the model
|
16
|
+
- overwrite the method
|
17
|
+
|
18
|
+
Thus you can use super and keep your method list clean, too!
|
19
|
+
It even supports a (rather dangerous) auto chaining mode, so you do not have
|
20
|
+
to explicitly chain a method, but chain a method when ever it would be
|
21
|
+
overwritten.
|
22
|
+
|
23
|
+
|
24
|
+
Example:
|
25
|
+
|
26
|
+
class Foo
|
27
|
+
|
28
|
+
def foo
|
29
|
+
10
|
30
|
+
end
|
31
|
+
|
32
|
+
# now chain to foo
|
33
|
+
chain_method :foo do
|
34
|
+
super + 3
|
35
|
+
end
|
36
|
+
|
37
|
+
# or turn on auto chaining
|
38
|
+
auto_chain do
|
39
|
+
|
40
|
+
def bar
|
41
|
+
10
|
42
|
+
end
|
43
|
+
|
44
|
+
def bar
|
45
|
+
super + 1
|
46
|
+
end
|
47
|
+
|
48
|
+
def bar
|
49
|
+
super ** 2
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
f = Foo.new
|
56
|
+
puts f.foo # => 13
|
57
|
+
puts f.bar # => 121
|
58
|
+
|
59
|
+
Of course you can do this with any class (or module):
|
60
|
+
|
61
|
+
Array.class_eval do
|
62
|
+
chain_method :each
|
63
|
+
def each
|
64
|
+
return super if block_given? or RUBY_VERSION >= "1.8.7"
|
65
|
+
MyStuff::Enumerator.new self, :each
|
66
|
+
end
|
67
|
+
end
|
data/Rakefile
ADDED
data/lib/chainable.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require "ruby2ruby"
|
2
|
+
|
3
|
+
module Chainable
|
4
|
+
|
5
|
+
def self.skip_chain
|
6
|
+
return if @auto_chain
|
7
|
+
@auto_chain = true
|
8
|
+
yield
|
9
|
+
@auto_chain = false
|
10
|
+
end
|
11
|
+
|
12
|
+
def chain_method(name, &block)
|
13
|
+
name = name.to_s
|
14
|
+
if instance_methods(false).include? name
|
15
|
+
begin
|
16
|
+
code = Ruby2Ruby.translate self, name
|
17
|
+
include Module.new { eval code }
|
18
|
+
rescue NameError
|
19
|
+
m = instance_method name
|
20
|
+
include Module.new { define_method(name) { |*a, &b| m.bind(self).call(*a, &b) } }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
block ||= Proc.new { super }
|
24
|
+
define_method(name, &block)
|
25
|
+
end
|
26
|
+
|
27
|
+
def auto_chain &block
|
28
|
+
class << self
|
29
|
+
chain_method :method_added do |name|
|
30
|
+
Chainable.skip_chain { chain_method name }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
result = yield
|
34
|
+
class << self
|
35
|
+
remove_method :method_added
|
36
|
+
end
|
37
|
+
result
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
Module.class_eval do
|
43
|
+
include Chainable
|
44
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "lib/chainable"
|
2
|
+
|
3
|
+
describe Chainable do
|
4
|
+
|
5
|
+
it "should chain all methods defined inside auto_chain" do
|
6
|
+
a_class = Class.new do
|
7
|
+
auto_chain do
|
8
|
+
define_method(:foo) { 100 }
|
9
|
+
define_method(:foo) { super * 2 }
|
10
|
+
define_method(:foo) { super + 22 }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
a_class.new.foo.should == 222
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should allow defining methods both inside and outside of auto_chain" do
|
17
|
+
a_class = Class.new do
|
18
|
+
define_method(:foo) { 100 }
|
19
|
+
chain_method :foo
|
20
|
+
auto_chain { define_method(:foo) { super * 2 } }
|
21
|
+
define_method(:foo) { super + 22 }
|
22
|
+
end
|
23
|
+
a_class.new.foo.should == 222
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should allow auto_chain with core functions" do
|
27
|
+
# We screw with String#reverse, this could mess up other specs.
|
28
|
+
String.class_eval do
|
29
|
+
chain_method :reverse # or we would overwrite the original
|
30
|
+
auto_chain do
|
31
|
+
define_method(:reverse) { super * 2 }
|
32
|
+
define_method(:reverse) { super.downcase }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
"Test".reverse.should == "tsettset"
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require "lib/chainable"
|
2
|
+
|
3
|
+
describe Chainable do
|
4
|
+
|
5
|
+
before :each do
|
6
|
+
@a_class = Class.new do
|
7
|
+
def foo
|
8
|
+
:foo
|
9
|
+
end
|
10
|
+
def foo2
|
11
|
+
foo.to_s.upcase
|
12
|
+
end
|
13
|
+
def to_i
|
14
|
+
42
|
15
|
+
end
|
16
|
+
def inspect
|
17
|
+
random
|
18
|
+
super
|
19
|
+
end
|
20
|
+
define_method(:random) { @some_value ||= rand(1000) }
|
21
|
+
end
|
22
|
+
@an_instance = @a_class.new
|
23
|
+
@original_results = @a_class.instance_methods(false).inject({}) do |h, m|
|
24
|
+
h.merge m => @an_instance.send(m)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should not change the behaviour of the original methods" do
|
29
|
+
5.times do
|
30
|
+
@original_results.each do |method_name, method_result|
|
31
|
+
@a_class.class_eval { chain_method method_name }
|
32
|
+
@an_instance.send(method_name).should == method_result
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should work for core methods" do
|
38
|
+
Array.class_eval { chain_method :join }
|
39
|
+
["hello", "world"].join(" ").should == "hello world"
|
40
|
+
String.class_eval { chain_method(:inspect) { "%#{super}" } }
|
41
|
+
"hello world".inspect.should == '%"hello world"'
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should define a new method, when block given" do
|
45
|
+
@a_class.class_eval do
|
46
|
+
chain_method(:to_i) { super - 5 }
|
47
|
+
chain_method(:foo2) { "foo2" }
|
48
|
+
end
|
49
|
+
@an_instance.to_i.should == @original_results["to_i"] - 5
|
50
|
+
@an_instance.foo2.should == "foo2"
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should allow redefining the method later" do
|
54
|
+
@a_class.class_eval do
|
55
|
+
chain_method :to_i
|
56
|
+
def to_i
|
57
|
+
super + 20
|
58
|
+
end
|
59
|
+
end
|
60
|
+
@an_instance.to_i.should == @original_results["to_i"] + 20
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should keep inheritance intact" do
|
64
|
+
a_module = Module.new do
|
65
|
+
define_method(:inspect) { "some inspect result" }
|
66
|
+
define_method(:foo) { "not foo" }
|
67
|
+
end
|
68
|
+
@a_class.class_eval do
|
69
|
+
include a_module
|
70
|
+
chain_method(:foo) { super }
|
71
|
+
end
|
72
|
+
another_class = Class.new(@a_class) do
|
73
|
+
define_method(:foo) { :bar }
|
74
|
+
define_method(:inspect) { super }
|
75
|
+
end
|
76
|
+
another_instance = another_class.new
|
77
|
+
@an_instance.inspect.should == "some inspect result"
|
78
|
+
@an_instance.foo.should == @original_results["foo"]
|
79
|
+
another_instance.inspect.should == "some inspect result"
|
80
|
+
another_instance.foo.should == :bar
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
metadata
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: chainable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Konstantin Haase
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-03-20 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: never use alias_method_chain, again
|
17
|
+
email: konstantin.mailinglists@googlemail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README.rdoc
|
24
|
+
- LICENSE
|
25
|
+
files:
|
26
|
+
- LICENSE
|
27
|
+
- Rakefile
|
28
|
+
- README.rdoc
|
29
|
+
- lib/chainable.rb
|
30
|
+
- spec/chainable/auto_chain_spec.rb
|
31
|
+
- spec/chainable/chain_method_spec.rb
|
32
|
+
has_rdoc: true
|
33
|
+
homepage: http://rkh.github.com/chainable
|
34
|
+
post_install_message:
|
35
|
+
rdoc_options: []
|
36
|
+
|
37
|
+
require_paths:
|
38
|
+
- lib
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: "0"
|
44
|
+
version:
|
45
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: "0"
|
50
|
+
version:
|
51
|
+
requirements: []
|
52
|
+
|
53
|
+
rubyforge_project:
|
54
|
+
rubygems_version: 1.3.1
|
55
|
+
signing_key:
|
56
|
+
specification_version: 2
|
57
|
+
summary: never use alias_method_chain, again
|
58
|
+
test_files: []
|
59
|
+
|