chainable 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,3 @@
1
+ require 'spec/rake/spectask'
2
+
3
+ Spec::Rake::SpecTask.new { |t| t.spec_files = FileList['spec/**/*.rb'] }
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
+