chainable 0.1.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.
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
+