overridable 0.3.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/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 梁智敏
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,147 @@
1
+ # Overridable
2
+
3
+ Overridable is a pure ruby library which helps you to make your methods which are defined in classes to be able to be overrided by mixed-in modules.
4
+
5
+ ## Why?
6
+
7
+ We all know that it's impossible for modules to override methods that are defined in classes when they are included. For example:
8
+ class Thing
9
+ def foo
10
+ 'Thing.foo'
11
+ end
12
+ end
13
+
14
+ module Redef
15
+ def foo
16
+ 'Redef.foo'
17
+ end
18
+ end
19
+
20
+ Thing.send :include, Redef
21
+ Thing.new.foo #=> Thing.foo # not Redef.foo
22
+
23
+ Usually, in order to achieve that goal, we will write the Redef module like this:
24
+ module Redef
25
+ def foo_with_redef
26
+ 'Redef.foo'
27
+ end
28
+ # you can also do this by: alias_method_chain :foo, :redef, if you use ActiveSupport.
29
+ alias foo_without_redef foo
30
+ alias foo foo_with_redef
31
+ end
32
+
33
+ Thing.send :include, Redef
34
+ Thing.new.foo #=> Redef.foo
35
+
36
+ So it will likely become a with/without hell in your code ( you can find dozens of such methods in Rails' code ). And *overridable* is a library that provides a neat mean to resolve this problem.
37
+
38
+ ## How?
39
+
40
+ There are two ways to do this with *overridable*: in class or in module.
41
+
42
+ ### In class
43
+
44
+ Remember Thing and Redef in our first example? Let's make some changes:
45
+ require 'overridable'
46
+
47
+ Thing.class_eval {
48
+ include Overridable
49
+ overrides :foo
50
+
51
+ include Redef
52
+ }
53
+
54
+ Thing.new.foo #=> Redef.foo
55
+ That's it! You can specify which methods can be overrided by `overrides` method. One more example based on the previous one:
56
+ Thing.class_eval {
57
+ def bar; 'Thing.bar' end
58
+ def baz; 'Thing.baz' end
59
+ def id; 'Thing' end
60
+
61
+ overrides :bar, :baz
62
+ }
63
+
64
+ Redef.module_eval {
65
+ def bar; 'Redef.bar' end
66
+ def baz; 'Redef baz' end
67
+ def id; 'Redef' end
68
+ }
69
+
70
+ thing = Thing.new
71
+ thing.bar #=> 'Redef.bar'
72
+ thing.baz #=> 'Redef.baz'
73
+ thing.id #=> 'Thing'
74
+ Of course it's not the end of our story ;) How could I call this *override* if we cannot use `super`? Continue our example:
75
+ Redef.module_eval {
76
+ def bar
77
+ parent = super
78
+ me = 'Redef.bar'
79
+ "I'm #{me} and I overrided #{parent}"
80
+ end
81
+ }
82
+
83
+ Thing.new.bar => I'm Redef.bar and I overrided Thing.bar
84
+
85
+ ### In module
86
+
87
+ If you have many methods in your module and find that it's too annoying to use `overrides`, then you can mix Overridable::ModuleMixin in your module. Example ( we all like examples, don't we? ):
88
+ class Thing
89
+ def method_one; ... end
90
+ def method_two; ... end
91
+ ...
92
+ def method_n; ... end
93
+ end
94
+
95
+ module Redef
96
+ include Overridable::ModuleMixin
97
+
98
+ def method_one; ... end
99
+ def method_two; ... end
100
+ ...
101
+ def method_n; ... end
102
+ end
103
+
104
+ Thing.send :include, Redef #=> method_one, method_two, ..., method_n are all overrided.
105
+
106
+ ## Install
107
+
108
+ gem source -a http://gemcutter.org # you neednot do this if you have already had gemcutter in your source list
109
+ gem install overridable
110
+
111
+ ## Dependencies
112
+
113
+ * Ruby >= 1.8.7
114
+ * riot >= 0.10.0 just for test
115
+ * yard >= 0.4.0 just for generating document
116
+
117
+ ## Test
118
+
119
+ $> cd $GEM_HOME/gems/overridable-x.y.z
120
+ $> rake test # requires riot
121
+
122
+ ## TODO
123
+
124
+ These features **only** will be added when they are asked for.
125
+
126
+ * :all and :except for overrides
127
+ overrides :all, :except => [:whatever, :excepts]
128
+ * override with block
129
+ override :foo do |*args|
130
+ super # or not
131
+ # any other stuff
132
+ end
133
+ * tell me what's missing.
134
+
135
+ ## Note on Patches/Pull Requests
136
+
137
+ * Fork the project.
138
+ * Make your feature addition or bug fix.
139
+ * Add tests for it. This is important so I don't break it in a
140
+ future version unintentionally.
141
+ * Commit, do not mess with rakefile, version, or history.
142
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
143
+ * Send me a pull request. Bonus points for topic branches.
144
+
145
+ ## Copyright
146
+
147
+ Copyright (c) 2009 梁智敏. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,54 @@
1
+ #--*-- encoding: UTF-8 --*--
2
+ require 'rubygems'
3
+ require 'rake'
4
+
5
+ begin
6
+ require 'jeweler'
7
+ Jeweler::Tasks.new do |gem|
8
+ gem.name = "overridable"
9
+ gem.summary = %Q{Override methods without method alias.}
10
+ gem.description = %Q{Overridable is a pure ruby library which helps you to make your methods which are defined in classes to be able to be overrided by mixed-in modules.}
11
+ gem.email = "liang.gimi@gmail.com"
12
+ gem.homepage = "http://github.com/Gimi/overridable"
13
+ gem.authors = ["梁智敏(Gimi Liang)"]
14
+ gem.add_development_dependency "riot", ">= 0.10.0"
15
+ gem.add_development_dependency "yard", ">= 0.4.0"
16
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
17
+ end
18
+ Jeweler::GemcutterTasks.new
19
+ rescue LoadError
20
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
21
+ end
22
+
23
+ require 'rake/testtask'
24
+ Rake::TestTask.new(:test) do |test|
25
+ test.libs << 'lib' << 'test'
26
+ test.pattern = 'test/**/*_test.rb'
27
+ test.verbose = true
28
+ end
29
+
30
+ begin
31
+ require 'rcov/rcovtask'
32
+ Rcov::RcovTask.new do |test|
33
+ test.libs << 'test'
34
+ test.pattern = 'test/**/*_test.rb'
35
+ test.verbose = true
36
+ end
37
+ rescue LoadError
38
+ task :rcov do
39
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
40
+ end
41
+ end
42
+
43
+ task :test => :check_dependencies
44
+
45
+ task :default => :test
46
+
47
+ begin
48
+ require 'yard'
49
+ YARD::Rake::YardocTask.new
50
+ rescue LoadError
51
+ task :yardoc do
52
+ abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
53
+ end
54
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.0
@@ -0,0 +1,146 @@
1
+ # Including this module in your class will give your class the ability to make its methods to be overridable by included modules. Let's look at some examples for easier understanding.
2
+ # @example
3
+ # class Thing
4
+ # def foo
5
+ # puts 'Thing.foo'
6
+ # end
7
+ # end
8
+ #
9
+ # module Foo
10
+ # def foo
11
+ # puts 'Foo.foo'
12
+ # end
13
+ # end
14
+ #
15
+ # # If you don't use Overridable, you will get:
16
+ # Thing.send :include, Foo
17
+ # Thing.new.foo #=> print: Thing.foo\n
18
+ #
19
+ # # If you use Overridable *before* you include the module, things will become to be:
20
+ # Thing.class_eval {
21
+ # include Overridable
22
+ # overrides :foo # specifies which methods can be overrided.
23
+ # include Foo
24
+ # }
25
+ # Thing.new.foo => print: Foo.foo\n
26
+ #
27
+ # You are not just limited to write a brandnew method, but also call the original method by `super`.
28
+ # @example
29
+ # # Let's change the Foo module in the previous example:
30
+ # module Foo
31
+ # def foo
32
+ # super
33
+ # puts 'Foo.foo'
34
+ # end
35
+ # end
36
+ #
37
+ # Thing.new.foo #=> print: Thing.foo\nFoo.foo\n
38
+ #
39
+ # Thanks to this feature, you don't need method chains any more! ;)
40
+ #
41
+ # You can also use Overridable::ModuleMixin to make things even a bit easier for some situations. See Overridable::ModuleMixin for details.
42
+ module Overridable
43
+
44
+ # If your module includes this module, then classes which include your module
45
+ # will make their methods which also defined in your module overridable.
46
+ # Let's watch an example:
47
+ # @example
48
+ # module YourModule
49
+ # include Overridable::ModuleMixin
50
+ #
51
+ # def foo
52
+ # super
53
+ # puts "foo in your module"
54
+ # end
55
+ # end
56
+ #
57
+ # class YourClass
58
+ # def foo
59
+ # puts "foo in your class"
60
+ # end
61
+ # end
62
+ #
63
+ # YourClass.new.foo #=> print: foo in your class\n
64
+ #
65
+ # YourClass.send :include, YourModule
66
+ #
67
+ # YourClass.new.foo #=> print: foo in your class\nfoo in your module\n
68
+ #
69
+ # __NOTE__: If you need a custom `append_features` method in your module,
70
+ # define that method before include this module in yours, or this is not
71
+ # going to work.
72
+ # @example
73
+ # module YourModule
74
+ # def self.append_features mod
75
+ # # things ...
76
+ # end
77
+ #
78
+ # include Overridable::ModuleMixin
79
+ # end
80
+ module ModuleMixin
81
+ def self.append_features mod #:nodoc:
82
+ class << mod
83
+ include Overridable
84
+ overrides :append_features
85
+ include ClassMethods
86
+ end
87
+ end
88
+
89
+ module ClassMethods
90
+ def append_features mod #:nodoc:
91
+ # these must be done in `append_features`, not `included`
92
+ mod.send :include, Overridable
93
+ mod.overrides *(
94
+ public_instance_methods +
95
+ protected_instance_methods +
96
+ private_instance_methods
97
+ )
98
+ super
99
+ end
100
+ end
101
+ end
102
+
103
+ def self.included mod #:nodoc:
104
+ mod.extend ClassMethods
105
+ end
106
+
107
+ module ClassMethods #:nodoc:
108
+ # Specifies which methods can be overrided.
109
+ # @param [Symbol,String] method_names specifies one or more methods which can be overrided.
110
+ # @return nil
111
+ def overrides *method_names
112
+ return unless self.is_a?(Class) # do nothing if it's a module
113
+ methods =
114
+ method_names.map { |m| instance_method m rescue nil } \
115
+ .compact \
116
+ .select { |m| m.owner == self }
117
+ unless methods.empty?
118
+ # All overrided methods are defined in the same module
119
+ is_module_defined =
120
+ if method(:const_defined?).arity > 0 # 1.8.x
121
+ self.const_defined?(:OverridedMethods)
122
+ else # 1.9
123
+ self.const_defined?(:OverridedMethods, false)
124
+ end
125
+
126
+ unless is_module_defined
127
+ self.const_set(:OverridedMethods, Module.new)
128
+ include self.const_get(:OverridedMethods)
129
+ end
130
+ mod = const_get(:OverridedMethods)
131
+
132
+ old_verbose, $VERBOSE = $VERBOSE, nil # supress warnings
133
+ methods.each { |m|
134
+ remove_method m.name
135
+ mod.send :define_method, m.name do |*args, &blk|
136
+ m.bind(self).call(*args, &blk)
137
+ end
138
+ }
139
+ $VERBOSE = old_verbose
140
+
141
+ nil
142
+ end
143
+ end
144
+ end
145
+
146
+ end
@@ -0,0 +1,48 @@
1
+ require 'teststrap'
2
+
3
+ context "A module which includes Overridable::ModuleMixin has some methods defined in it." do
4
+ setup {
5
+ module SomeModule
6
+ include Overridable::ModuleMixin
7
+
8
+ def foo
9
+ 'SomeModule.foo'
10
+ end
11
+
12
+ def bar a
13
+ super + 'SomeModule.bar'
14
+ end
15
+ end
16
+
17
+ SomeModule
18
+ }
19
+
20
+ context "A class which has the same methods that are defined in that module," do
21
+ setup {
22
+ class SomeClass
23
+ def foo
24
+ 'SomeClass.foo'
25
+ end
26
+
27
+ private
28
+ def bar a
29
+ 'SomeClass.bar'
30
+ end
31
+ end
32
+
33
+ SomeClass
34
+ }
35
+
36
+ context "includes that module." do
37
+ setup { topic.send :include, SomeModule; topic }
38
+
39
+ asserts("foo should be overrided.") {
40
+ topic.new.foo
41
+ }.equals('SomeModule.foo')
42
+ asserts("bar should be overrided.") {
43
+ #topic.new.send :bar, :whatever
44
+ topic.new.bar :whatever
45
+ }.equals('SomeClass.bar' + 'SomeModule.bar')
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,164 @@
1
+ require 'teststrap'
2
+
3
+ context "a classes with some methods defined in it" do
4
+ setup {
5
+ class Thing
6
+ def no_arguments
7
+ 'This is Thing.'
8
+ end
9
+
10
+ def one_argument a
11
+ a
12
+ end
13
+
14
+ def any_arguments *args
15
+ args.join('-')
16
+ end
17
+
18
+ def with_block *args, &blk
19
+ blk.call(*args)
20
+ end
21
+
22
+ def keep_unchanged
23
+ 'unchanged'
24
+ end
25
+ end
26
+
27
+ Thing
28
+ }
29
+
30
+ context "includes a module which has the sames methods" do
31
+ setup {
32
+ module ModuleA
33
+ def no_arguments
34
+ 'This is ModuleA.'
35
+ end
36
+
37
+ def one_argument a
38
+ a * 10
39
+ end
40
+
41
+ def any_arguments *args
42
+ args.join('_')
43
+ end
44
+
45
+ def with_block *args, &blk
46
+ blk.call(*args.map! { |a| a * 10 })
47
+ end
48
+
49
+ def keep_unchanged
50
+ 'changed'
51
+ end
52
+ end
53
+
54
+ topic.send :include, ModuleA
55
+ topic
56
+ }
57
+
58
+ # If these tests fail, that means we don't need this library any more ;)
59
+ asserts("no_arguments should not be overrided") { topic.new.no_arguments } \
60
+ .equals('This is Thing.')
61
+ asserts("one_argument should not be overrided") { topic.new.one_argument(10) } \
62
+ .equals(10)
63
+ asserts("any_arguments should not be overrided") { topic.new.any_arguments('a', 'b', 'c') } \
64
+ .equals(%w[a b c].join('-'))
65
+ asserts("with_block should not be overrided") { topic.new.with_block('a', 'b', 'c') { |*args| args.join('-') } } \
66
+ .equals(%w[a b c].join('-'))
67
+ asserts("keep_unchanged should not be overrided") { topic.new.keep_unchanged } \
68
+ .equals('unchanged')
69
+ end
70
+
71
+ context "includes Overridable and specifies methods to be overrided," do
72
+ setup {
73
+ topic.class_eval {
74
+ include Overridable
75
+ overrides :no_arguments, :one_argument, :any_arguments, :with_block
76
+ }
77
+
78
+ topic
79
+ }
80
+
81
+ context "then includes a module which has the same methods" do
82
+ setup {
83
+ module ModuleB
84
+ def no_arguments
85
+ 'This is ModuleB.'
86
+ end
87
+
88
+ def one_argument a
89
+ a * 10
90
+ end
91
+
92
+ def any_arguments *args
93
+ args.join('_')
94
+ end
95
+
96
+ def with_block *args, &blk
97
+ blk.call(*args.map! { |a| a * 10 })
98
+ end
99
+
100
+ def keep_unchanged
101
+ 'changed'
102
+ end
103
+ end
104
+
105
+ topic.send :include, ModuleB
106
+ topic
107
+ }
108
+
109
+ asserts("no_arguments have been overrided") { topic.new.no_arguments } \
110
+ .equals('This is ModuleB.')
111
+ asserts("one_argument have been overrided") { topic.new.one_argument(10) } \
112
+ .equals(10 * 10)
113
+ asserts("any_arguments have been overrided") { topic.new.any_arguments('a', 'b', 'c') } \
114
+ .equals(%w[a b c].join('_'))
115
+ asserts("with_block have been overrided") { topic.new.with_block('a', 'b', 'c') { |*args| args.join('-') } } \
116
+ .equals(%w[a b c].map! { |e| e * 10 }.join('-'))
117
+ asserts("keep_unchanged should not be overrided") { topic.new.keep_unchanged } \
118
+ .equals('unchanged')
119
+ end
120
+
121
+ context "then includes a module with the same methods which call super in their bodies." do
122
+ setup {
123
+ module ModuleC
124
+ def no_arguments
125
+ super +
126
+ 'This is ModuleC.'
127
+ end
128
+
129
+ def one_argument a
130
+ super + 123
131
+ end
132
+
133
+ def any_arguments *args
134
+ [super, args.join('_')].join('@')
135
+ end
136
+
137
+ def with_block *args, &blk
138
+ super(*args.map! { |e| "|#{e}|" }, &blk)
139
+ end
140
+
141
+ def keep_unchanged
142
+ super
143
+ 'changed'
144
+ end
145
+ end
146
+
147
+ topic.send :include, ModuleC
148
+ topic
149
+ }
150
+
151
+ asserts("no_arguments have been overrided and work properly.") { topic.new.no_arguments } \
152
+ .equals('This is Thing.' + 'This is ModuleC.')
153
+ asserts("one_argument have been overrided and work properly.") { topic.new.one_argument(10) } \
154
+ .equals(10 + 123)
155
+ asserts("any_arguments have been overrided and work properly.") { topic.new.any_arguments('a', 'b', 'c') } \
156
+ .equals([%w[a b c].join('-'), %w[a b c].join('_')].join('@'))
157
+ asserts("with_block have been overrided and work properly.") { topic.new.with_block('a', 'b', 'c') { |*args| args.join('-') } } \
158
+ .equals(%w[a b c].map! { |e| "|#{e}|" }.join('-'))
159
+ asserts("keep_unchanged should not be overrided") { topic.new.keep_unchanged } \
160
+ .equals('unchanged')
161
+ end
162
+ end
163
+
164
+ end
data/test/teststrap.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'rubygems'
2
+ require 'riot'
3
+ require 'overridable'
4
+
5
+ # colorize is incompatible with Ruby 1.9 at this point
6
+ # and riot requires colorize
7
+ ::PLATFORM = ::RUBY_PLATFORM unless defined?(::PLATFORM)
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: overridable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - "\xE6\xA2\x81\xE6\x99\xBA\xE6\x95\x8F(Gimi Liang)"
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-11-24 00:00:00 +08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: riot
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.10.0
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: yard
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.4.0
34
+ version:
35
+ description: Overridable is a pure ruby library which helps you to make your methods which are defined in classes to be able to be overrided by mixed-in modules.
36
+ email: liang.gimi@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - LICENSE
43
+ - README.markdown
44
+ files:
45
+ - .document
46
+ - .gitignore
47
+ - LICENSE
48
+ - README.markdown
49
+ - Rakefile
50
+ - VERSION
51
+ - lib/overridable.rb
52
+ - test/modulemixin_test.rb
53
+ - test/overridable_test.rb
54
+ - test/teststrap.rb
55
+ has_rdoc: true
56
+ homepage: http://github.com/Gimi/overridable
57
+ licenses: []
58
+
59
+ post_install_message:
60
+ rdoc_options:
61
+ - --charset=UTF-8
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "0"
69
+ version:
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: "0"
75
+ version:
76
+ requirements: []
77
+
78
+ rubyforge_project:
79
+ rubygems_version: 1.3.5
80
+ signing_key:
81
+ specification_version: 3
82
+ summary: Override methods without method alias.
83
+ test_files:
84
+ - test/overridable_test.rb
85
+ - test/modulemixin_test.rb
86
+ - test/teststrap.rb