meta_programming 0.0.1 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -12,6 +12,36 @@ Use
12
12
 
13
13
  * require 'meta_programming'
14
14
 
15
+ Concepts/Terminology
16
+
17
+ * Metaprogramming_Ruby[http://pragprog.com/titles/ppmetr/metaprogramming-ruby]
18
+
19
+ == Beta Status
20
+
21
+ I consider this Gem in personal beta and suspect it will be in this state for
22
+ a while. If you use it, expect changes that may break your code until the
23
+ lexigraph has a chance to mature.
24
+
25
+ I welcome comments and collaborators who
26
+ would like to develop a library for simplifying common patterns and limit
27
+ the pitfalls of Ruby meta-programming.
28
+
29
+ == Issues
30
+
31
+ === 1.9 vs 1.8
32
+
33
+ There are considerable differences with the way that Ruby 1.9 and 1.8 handle dynamic
34
+ methods, procs and lambdas. For instance, Ruby 1.8.x cannot handle default parameter
35
+ values in the block parameter list
36
+
37
+ call_some_method(*args) do |param1, param2=nil| #oops for Ruby 1.8
38
+ ...
39
+ end
40
+
41
+ This raises support issues for this library. Should the library fully support both versions
42
+ of Ruby, cripple 1.8 at the expense of 1.9, or kiss the past good-bye and only support version 1.9?
43
+ Of course, the latter is clearer and easier, and may be the best path to pursue.
44
+
15
45
 
16
46
  == Description
17
47
 
@@ -19,10 +49,70 @@ Current Methods
19
49
  * eigenclass (or metaclass)
20
50
  * safe_alias_method_chain
21
51
  * define_chained_method
52
+ * define_method_missing_chain
53
+ * define_ghost_method
22
54
  * blank_slate
23
- * clean_room
24
55
 
25
56
 
57
+
58
+ == Usage
59
+
60
+ === define_ghost_method
61
+
62
+ +define_ghost_method+ creates a 'ghost' method for an undefined method that corresponds
63
+ to the 'matcher' parameter.
64
+
65
+ define_ghost_method(matcher) do |object, method_name_symbol, *args|
66
+ ...method body...
67
+ end
68
+
69
+ A block must be present and it takes (optionally) the receiving object, the method name (as
70
+ a symbol with exception of a lambda matcher) and the method arguments.
71
+
72
+ The matcher may be any of a symbol, string, regular expression or Proc/lambda.
73
+
74
+ ==== String, symbols and regular expression matchers
75
+
76
+ define_ghost_method('my_method) {|obj, sym, *args| ... }
77
+ define_ghost_method(':my_method) {|obj, sym, *args| ... }
78
+ define_ghost_method(/^my_method$/) {|obj, sym, *args| ... }
79
+
80
+ String and symbol matchers will only process the method if its name matches the string
81
+ or symbol exactly. Regular expressions provide greater flexibility and care must be
82
+ taken to define scrutinizing regular expression matchers. It is a good practice to use
83
+ the /^ and $/ matchers to limit the scope.
84
+
85
+ In all cases, the method name is passed in the second parameter as a symbol to the
86
+ method block. This is not the case with lambda matchers.
87
+
88
+ ==== Proc/lambda matchers
89
+
90
+ Lambda matchers provide an opportunity to greatly scrutinize the method call based on the
91
+ parameters passed to the method block (object, method_name, *args). The method is
92
+ invoked for any lambda not evaluating to nil or false.
93
+
94
+ proc = lambda{|object, method_name, *args| ...matching code... }
95
+ define_ghost_method(proc) {|obj, proc_result, *args| ... }
96
+
97
+ Proc and lambda matchers differ from the other matcher in that the second parameter passes
98
+ the result of the Proc/lambda matcher to the method block. This feature lets the matcher
99
+ pre-process the name of the method passed to the body.
100
+
101
+ proc = lambda{|obj, sym, *args| sym =~ /^not_(.+\?)$/ && $1.to_sym }
102
+
103
+ The extra flexibility comes at greater responsibility. If you only want to pass the
104
+ method name symbol to the method block, don't forget to return it from the lambda.
105
+
106
+ proc = lambda{|obj, sym, *args| sym =~ /^not_.+\?$/ && sym }
107
+
108
+ Additionally, the 'return' keyword has different effects between Procs and lambdas.
109
+ I suggest not explicitly using the 'return' keyword in your method blocks. It's a Ruby thing.
110
+ define_ghost_method will remind you if a LocalJumpError is raised.
111
+
112
+ ==== Critique
113
+
114
+ I'm not completely satisfied with the design of the implementation.
115
+
26
116
  == Dependencies
27
117
 
28
- none
118
+ none
data/Rakefile CHANGED
@@ -12,7 +12,6 @@ spec = Gem::Specification.new do |s|
12
12
  s.required_ruby_version = '>= 1.8.7'
13
13
  s.description = 'Collection of meta-programming methods for Ruby'
14
14
  s.summary = 'Collection of meta-programming methods for Ruby'
15
-
16
15
 
17
16
  exclude_folders = '' # 'spec/rails/{doc,lib,log,nbproject,tmp,vendor,test}'
18
17
  exclude_files = [] # FileList['**/*.log'] + FileList[exclude_folders+'/**/*'] + FileList[exclude_folders]
@@ -47,7 +46,7 @@ Rake::RDocTask.new(:rdoc) do |rdoc|
47
46
  rdoc.options << '--line-numbers' << '--inline-source'
48
47
  rdoc.rdoc_files.include('README.rdoc', 'LICENSE', 'lib/**/*.rb')
49
48
  end
50
-
49
+
51
50
  desc 'Generate a gemspec file.'
52
51
  task :gemspec do
53
52
  File.open("#{spec.name}.gemspec", 'w') do |f|
@@ -6,12 +6,14 @@ module MetaProgramming
6
6
 
7
7
  def blank_slate(opts={})
8
8
  opts[:except] = opts[:except] ? (opts[:except].is_a?(Array) ? opts[:except] : [opts[:except]]) : []
9
- exceptions = ['method_missing', 'respond_to\?', '^__'] + opts[:except].map{|ex| ex.to_s}
10
- regexp = Regexp.new(exceptions.join('|'))
9
+ exceptions = opts[:except].map(&:to_s)
10
+ exceptions += ['method_missing', 'respond_to?'] unless opts[:all]
11
+ matchers = exceptions.map{|ex| "^#{ex.gsub(/\?/, '\?')}$" }
12
+ matchers << '^__'
13
+ regexp = Regexp.new(matchers.join('|'))
11
14
  instance_methods.each do |m|
12
15
  undef_method m unless regexp.match(m.to_s)
13
16
  end
14
17
  end
15
- alias_method :clean_room, :blank_slate
16
18
  end
17
19
  end
@@ -13,25 +13,46 @@ module MetaProgramming
13
13
  end
14
14
 
15
15
  module ClassMethods
16
+
17
+ module Helpers
18
+ def self.compose_chaining_symbols(method_name, ext)
19
+ stripped_method_name, punctuation = method_name.to_s.sub(/([?!=])$/, ''), $1
20
+ ["#{stripped_method_name}_with_#{ext}#{punctuation}".to_sym,
21
+ "#{stripped_method_name}_without_#{ext}#{punctuation}".to_sym]
22
+ end
23
+
24
+ def self.escape_method_name(method_name)
25
+ method_name.to_s.gsub(/\?/, 'qmark').gsub(/\!/, 'bang').gsub(/\=/, 'equal')
26
+ end
27
+ end
28
+
29
+ def method_access_level(method_name)
30
+ case
31
+ when public_method_defined?(method_name.to_sym) then :public
32
+ when private_method_defined?(method_name.to_sym) then :private
33
+ when protected_method_defined?(method_name.to_sym) then :protected
34
+ else nil
35
+ end
36
+ end
37
+
16
38
  def safe_alias_method_chain(method_name, ext)
17
39
  class_eval do
18
- stripped_method_name, punctuation = method_name.to_s.sub(/([?!=])$/, ''), $1
19
- method_name_with_ext = "#{stripped_method_name}_with_#{ext}#{punctuation}".to_sym
20
- method_name_without_ext = "#{stripped_method_name}_without_#{ext}#{punctuation}".to_sym
21
- instance_variable = "#{stripped_method_name}_#{ext}_#{{'?'=>'questmark', '!'=>'bang', '='=>'equals'}[punctuation]}"
22
- if (method_defined?(method_name_with_ext) && !(eigenclass.instance_variable_defined?("@#{instance_variable}")))
23
- if method_defined?(method_name.to_sym)
40
+ method_name_with_ext, method_name_without_ext = Helpers.compose_chaining_symbols(method_name, ext)
41
+ instance_variable = Helpers.escape_method_name(method_name_with_ext)
42
+ if method_access_level(method_name_with_ext)
43
+ raise "#{method_name_with_ext} already chained. Rechaining not permitted" if eigenclass.instance_variable_defined?("@#{instance_variable}")
44
+ if method_access_level(method_name.to_sym)
24
45
  #alias_method_chain(method_name.to_sym, ext.to_sym)
25
46
  alias_method method_name_without_ext, method_name.to_sym
26
47
  alias_method method_name.to_sym, method_name_with_ext
27
- case
28
- when public_method_defined?(method_name_without_ext) then public(method_name.to_sym)
29
- when protected_method_defined?(method_name_without_ext) then protected(without_method.to_sym)
30
- when private_method_defined?(method_name_without_ext) then private(without_method.to_sym)
48
+ case method_access_level(method_name_without_ext)
49
+ when :public then public(method_name.to_sym)
50
+ when :protected then protected(method_name.to_sym)
51
+ when :private then private(method_name.to_sym)
31
52
  end
32
53
  else
33
- alias_method method_name.to_sym, method_name_with_ext
34
54
  define_method(method_name_without_ext) {|*args| }
55
+ alias_method method_name.to_sym, method_name_with_ext
35
56
  end
36
57
  eigenclass.instance_variable_set("@#{instance_variable}", true)
37
58
  end
@@ -39,10 +60,48 @@ module MetaProgramming
39
60
  end
40
61
 
41
62
  def define_chained_method(method_name, ext, &block)
42
- define_method("#{method_name}_with_#{ext}".to_sym, block)
43
- safe_alias_method_chain(method_name.to_sym, ext)
63
+ raise 'Must have block' unless block_given?
64
+ with, without = Helpers.compose_chaining_symbols(method_name, ext)
65
+ define_method(with, block)
66
+ safe_alias_method_chain(method_name.to_sym, ext.to_sym)
44
67
  end
45
68
 
69
+ def define_method_missing_chain(name, &block)
70
+ raise 'Must have block' unless block_given?
71
+ define_chained_method(:method_missing, name.to_sym, &block)
72
+ end
73
+
74
+ def define_ghost_method(matcher, &block)
75
+ raise "Must have a block" unless block_given?
76
+ raise ArgumentError, "Matcher argument must be either a 'string', :symbol, /regexp/ or proc" unless (matcher.nil? || [String, Symbol, Regexp, Proc].any?{|c| matcher.is_a?(c)})
77
+ ext = matcher.hash.abs.to_s
78
+ define_chained_method(:method_missing, ext.to_sym) do |symbol, *args|
79
+ begin
80
+ handled = nil
81
+ result = case matcher
82
+ when Regexp
83
+ yield(self, symbol, *args) if (handled = (symbol.to_s =~ matcher))
84
+ when String, Symbol
85
+ yield(self, symbol, *args) if (handled = (symbol == matcher.to_sym))
86
+ when Proc
87
+ handled = matcher.call(self, symbol)
88
+ yield(self, handled == true ? symbol : handled, *args) if handled
89
+ end
90
+ handled ? result : __send__("method_missing_without_#{ext}".to_sym, symbol, *args)
91
+ rescue LocalJumpError
92
+ raise LocalJumpError, "Remove the 'return' keyword in your method block."
93
+ end
94
+ end
95
+ #cripple respond_to? in deference of 1.8 -- the include_private no longer works
96
+ define_chained_method(:respond_to?, ext.to_sym) do |method_name| #1.9 only, include_private=nil|
97
+ responds = case matcher
98
+ when Regexp then method_name.to_s =~ matcher
99
+ when String, Symbol then method_name == matcher.to_sym
100
+ when Proc then matcher.call(self, method_name)
101
+ end
102
+ responds || __send__("respond_to_without_#{ext}?", method_name) #1.9 only, include_private)
103
+ end
104
+ end
46
105
  end
47
106
  end
48
107
  end
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+
3
+ describe "blank_slate" do
4
+
5
+ def collect_test_methods(instance, exceptions=[])
6
+ all_methods = [:public_methods, :protected_methods, :private_methods].map do |method|
7
+ instance.send(method)
8
+ end.flatten.map(&:to_sym).uniq
9
+ allowed_methods = all_methods.select{|method| method.to_s =~ /^__/} + (exceptions.is_a?(Array) ? exceptions : [exceptions]).map(&:to_sym)
10
+ all_methods - allowed_methods
11
+ end
12
+
13
+ it "should only have certain instance methods (it's a blank slate)" do
14
+ class BlankSlate1
15
+ blank_slate
16
+ end
17
+ test_methods = collect_test_methods(Object.new, [:method_missing, :respond_to?])
18
+
19
+ bs = BlankSlate1.new
20
+ test_methods.each do |method|
21
+ bs.respond_to?(method).should be_false
22
+ end
23
+ end
24
+
25
+ it "should remove all but ^__ methods for option :all" do
26
+ class BlankSlate2
27
+ blank_slate :all=>true
28
+ public :__send__
29
+ end
30
+ test_methods = collect_test_methods(Object.new)
31
+
32
+ bs = BlankSlate2.new
33
+ test_methods.each do |method|
34
+ # lambda { bs.__send__(method); nil }.should raise_exception(NoMethodError)
35
+ end
36
+ end
37
+
38
+ it "should work for exceptions" do
39
+ class BlankSlate3
40
+ blank_slate :except=>[:methods]
41
+ end
42
+ test_methods = collect_test_methods(Object.new, [:methods, :method_missing, :respond_to?])
43
+
44
+ bs=BlankSlate3.new
45
+ test_methods.each do |method|
46
+ bs.respond_to?(method).should == false
47
+ end
48
+
49
+ end
50
+
51
+ end
52
+
53
+
@@ -0,0 +1,203 @@
1
+ require 'spec_helper'
2
+
3
+ describe "MetaProgramming" do
4
+
5
+ describe "define_method_missing_chain method" do
6
+ it "should create a method_missing chain" do
7
+ class E1
8
+ def hello; 'hello'; end
9
+ define_method_missing_chain(:help) do |symbol, *args|
10
+ if symbol == :help
11
+ 'help'
12
+ else
13
+ method_missing_without_help(symbol, *args)
14
+ end
15
+ end
16
+ end
17
+ lambda { E1.new.help.should == 'help' }.should_not raise_exception
18
+ lambda { E1.new.helpme }.should raise_exception(NoMethodError)
19
+ end
20
+ it "should scream if defined twice" do
21
+ lambda {
22
+ class E2
23
+ define_method_missing_chain(:help) {}
24
+ define_method_missing_chain(:help) {}
25
+ end
26
+ }.should raise_exception
27
+ end
28
+ it "should scream if block missing" do
29
+ lambda {
30
+ class E3
31
+ define_method_missing_chain(:help)
32
+ end
33
+ }.should raise_exception
34
+ end
35
+ end
36
+ describe "define_ghost_method method" do
37
+ it "should scream if matcher is not string, symbol or regular expression" do
38
+ lambda {
39
+ class GHA2
40
+ define_ghost_method(1) { puts 1}
41
+ end
42
+ }.should raise_exception(ArgumentError)
43
+ end
44
+ it "should scream if there's no block given" do
45
+ lambda {
46
+ class GHA3
47
+ define_ghost_method(:tree)
48
+ end
49
+ }.should raise_exception
50
+ end
51
+ it "should define a ghost method using a symbol" do
52
+ class GHA1
53
+ define_ghost_method(:catch_this_one) { 'catch_this_one' }
54
+ end
55
+ lambda { GHA1.new.not_this_one }.should raise_exception(NoMethodError)
56
+ lambda { GHA1.new.catch_this_one.should == 'catch_this_one' }.should_not raise_exception
57
+ lambda { GHA1.new.catch_this_one_too }.should raise_exception(NoMethodError)
58
+ end
59
+ it "should warn about using 'return' keyword in method block" do
60
+ class GHA4
61
+ define_ghost_method(:catch_this_one) { return 'catch_this_one'}
62
+ end
63
+ lambda { GHA4.new.catch_this_one }.should raise_exception(LocalJumpError, /'return' keyword/)
64
+ end
65
+ it "should define a ghost method using a string" do
66
+ class GHA5
67
+ define_ghost_method('catch_another') { 'catch_another_one'}
68
+ end
69
+ lambda { GHA5.new.catch_another.should == 'catch_another_one'}.should_not raise_exception
70
+ lambda { GHA5.new.catch_another_one }.should raise_exception(NoMethodError)
71
+ end
72
+ it "should define a ghost method using a regular expression" do
73
+ class GHA6
74
+ define_ghost_method(/catch/) do |object, symbol|
75
+ symbol
76
+ end
77
+ end
78
+ lambda { GHA6.new.catch_this_one.should == :catch_this_one }.should_not raise_exception
79
+ lambda { GHA6.new.some_catch_here.should == :some_catch_here }.should_not raise_exception
80
+ lambda { GHA6.new.atchoo }.should raise_exception(NoMethodError)
81
+ end
82
+ it "should pass arguments to the block" do
83
+ class GHA7
84
+ define_ghost_method(/ghost/) do |object, symbol, *args|
85
+ "#{symbol.to_s.gsub(/_/, ' ')} #{args.join(' ')}"
86
+ end
87
+ end
88
+ lambda { GHA7.new.ghost_methods('kick', 'butt').should == 'ghost methods kick butt'}.should_not raise_exception
89
+ end
90
+ it "should pass parameters" do
91
+ class GHA9
92
+ define_ghost_method('test_args') do |obj, sym, *args|
93
+ args.join(' ')
94
+ end
95
+ end
96
+ GHA9.new.test_args('hi', 'you').should == 'hi you'
97
+ end
98
+ #ghost methods that can call blocks are not supported
99
+ # it "should work for methods called with blocks in 1.9" do
100
+ # class GHA8
101
+ # define_ghost_method(:call_block) do |obj, sym, *args|
102
+ # yield(*args)
103
+ # end
104
+ # end
105
+ # GHA8.new.call_block(%w(hi you)) do |*args|
106
+ # args.join(' ')
107
+ # end.should == 'hi you'
108
+ # end
109
+ it "should define ghost methods for class Object" do
110
+ class Object
111
+ define_ghost_method(:alive!) do
112
+ 'ALIVE'
113
+ end
114
+ end
115
+ lambda { Object.new.alive!.should == 'ALIVE'}.should_not raise_exception
116
+ end
117
+ it "should accept lambda as an advanced matcher and lambda parameters should be object, matcher_result, *args" do
118
+ class TestObject1
119
+ def yes?; true; end
120
+ def no?; false; end
121
+
122
+ matcher = lambda {|obj, sym, *args| sym.to_s =~ /^yn_(.*\?)$/ && obj.methods.map(&:to_sym).include?($1.to_sym) && $1.to_sym }
123
+ define_ghost_method(matcher) do |obj, res, *args|
124
+ obj.__send__(res, *args) ? 'yes' : 'no'
125
+ end
126
+ end
127
+ lambda { TestObject1.new.yes?.should == true}.should_not raise_exception
128
+ lambda { TestObject1.new.no?.should == false }.should_not raise_exception
129
+ lambda { TestObject1.new.yn_yes?.should == 'yes'}.should_not raise_exception
130
+ lambda { TestObject1.new.yn_no?.should == 'no'}.should_not raise_exception
131
+ lambda { TestObject1.new.yessir? }.should raise_exception(NoMethodError)
132
+ lambda { TestObject1.new.yn_yessir? }.should raise_exception(NoMethodError)
133
+ end
134
+ it "should accept Procs as an advanced matcher and the parameters should be obj, matcher_result, *args" do
135
+ class TestObject2
136
+ def yes?; true; end
137
+ def no?; false; end
138
+
139
+ matcher = Proc.new{|obj, sym, *args| sym.to_s =~ /^yn_(.*\?)$/ && obj.methods.map(&:to_sym).include?($1.to_sym) && $1.to_sym }
140
+ define_ghost_method(matcher) do |obj, res, *args|
141
+ obj.__send__(res, *args) ? 'yes' : 'no'
142
+ end
143
+ end
144
+ lambda { TestObject2.new.yes?.should == true}.should_not raise_exception
145
+ lambda { TestObject2.new.no?.should == false }.should_not raise_exception
146
+ lambda { TestObject2.new.yn_yes?.should == 'yes'}.should_not raise_exception
147
+ lambda { TestObject2.new.yn_no?.should == 'no'}.should_not raise_exception
148
+ lambda { TestObject2.new.yessir? }.should raise_exception(NoMethodError)
149
+ lambda { TestObject2.new.yn_yessir? }.should raise_exception(NoMethodError)
150
+ end
151
+ it "should create an appropriate respond_to? for the string matchers" do
152
+ class StringMatcher
153
+ def hello; end
154
+ define_ghost_method('my_method') { 'yes_my_method'}
155
+ end
156
+ lambda { StringMatcher.new.my_method.should == 'yes_my_method'}.should_not raise_exception
157
+ lambda { StringMatcher.new.respond_to?(:my_method2).should be_false}.should_not raise_exception
158
+ lambda { StringMatcher.new.respond_to?(:my_method).should be_true }.should_not raise_exception
159
+ StringMatcher.new.respond_to?(:hello).should be_true
160
+ end
161
+ it "should create an appropriate respond_to? for the symbol matchers" do
162
+ class SymbolMatcher
163
+ def hello; end
164
+ define_ghost_method(:my_method2) { 'yep_my_method'}
165
+ end
166
+ lambda { SymbolMatcher.new.my_method2.should == 'yep_my_method'}.should_not raise_exception
167
+ lambda { SymbolMatcher.new.respond_to?(:my_method2).should be_true}.should_not raise_exception
168
+ SymbolMatcher.new.respond_to?(:my_method).should be_false
169
+ SymbolMatcher.new.respond_to?(:hello).should be_true
170
+ end
171
+ it "should create an appropriate respond_to? for the regexp matchers" do
172
+ class RegexpMatcher
173
+ def hello; end
174
+ define_ghost_method(/^my_method$/) { 'yo_my_method'}
175
+ end
176
+ lambda { RegexpMatcher.new.my_method.should == 'yo_my_method'}.should_not raise_exception
177
+ lambda { RegexpMatcher.new.respond_to?(:my_method).should be_true}.should_not raise_exception
178
+ RegexpMatcher.new.respond_to?(:hello).should be_true
179
+ class RegexpMatcher2
180
+ def hello; end
181
+ define_ghost_method(/^my_method\d$/) { 'yoo_my_method'}
182
+ end
183
+ lambda { RegexpMatcher2.new.my_method1.should == 'yoo_my_method'}.should_not raise_exception
184
+ lambda { RegexpMatcher2.new.my_method }.should raise_exception(NoMethodError)
185
+ lambda { RegexpMatcher2.new.respond_to?(:my_method).should be_false}.should_not raise_exception
186
+ lambda { RegexpMatcher2.new.respond_to?(:my_method2).should be_true}.should_not raise_exception
187
+ lambda { RegexpMatcher2.new.respond_to?(:my_method10).should be_false}.should_not raise_exception
188
+ end
189
+ it "should create an appropriate respond_to? for the lambda matchers" do
190
+ class LambdaMatcher
191
+ def hello; end
192
+ define_ghost_method(lambda{|obj, sym| sym.to_s =~ /^my_method\d$/ }) { 'uhhuh_my_method'}
193
+ end
194
+ lambda { LambdaMatcher.new.my_method2.should == 'uhhuh_my_method'}.should_not raise_exception
195
+ lambda { LambdaMatcher.new.my_method}.should raise_exception(NoMethodError)
196
+ lambda { LambdaMatcher.new.respond_to?(:my_method).should be_false}.should_not raise_exception
197
+ lambda { LambdaMatcher.new.respond_to?(:my_method5).should be_true}.should_not raise_exception
198
+ lambda { LambdaMatcher.new.respond_to?(:my_methods).should be_false}.should_not raise_exception
199
+ LambdaMatcher.new.respond_to?(:hello).should be_true
200
+ end
201
+ end
202
+
203
+ end
@@ -1,4 +1,4 @@
1
- require 'lib/meta_programming'
1
+ require 'spec_helper'
2
2
 
3
3
  describe "MetaProgramming" do
4
4
  describe "eigenclass and metaclass methods" do
@@ -37,6 +37,19 @@ describe "MetaProgramming" do
37
37
  end
38
38
  B2.new.target(['init']).should == ['init', 'chain', 'target']
39
39
  end
40
+ it "should define and chain a predicate method" do
41
+ class B5; def hello; 'hello'; end; end
42
+ B5.new.hello.should == 'hello'
43
+ B5.new.respond_to?(:hello).should be_true
44
+ B5.new.respond_to?(:help).should be_false
45
+ class B5
46
+ define_chained_method(:respond_to?, :help) do |method_name|
47
+ method_name == :help ? true : respond_to_without_help?(method_name)
48
+ end
49
+ end
50
+ B5.new.respond_to?(:hello).should be_true
51
+ B5.new.respond_to?(:help).should be_true
52
+ end
40
53
  # it "should complain if block has wrong arity" do
41
54
  # lambda {
42
55
  # class B3
@@ -51,14 +64,36 @@ describe "MetaProgramming" do
51
64
  it "should define and chain a method safely if there is no target method" do
52
65
  class B4
53
66
  define_chained_method(:target, :chain) do |array|
54
- array << 'chain'
55
67
  target_without_chain(array)
68
+ array << 'chain'
56
69
  end
57
70
  B4.new.target(['init']).should == ['init', 'chain']
58
71
  end
59
72
  end
60
73
  end
61
74
  describe "safe_alias_method_chain method" do
75
+ it "should chain for a primary protected method" do
76
+ class D1
77
+ def primary(array); array << 'primary'; end
78
+ protected :primary
79
+ def primary_with_one(array); array << 'one'; primary_without_one(array); end
80
+ safe_alias_method_chain :primary, :one
81
+ end
82
+ D1.new.instance_eval do
83
+ primary(['init']).should == ['init', 'one', 'primary']
84
+ end
85
+ end
86
+ it "should chain for a primary private method" do
87
+ class D2
88
+ def primary(array); array << 'primary'; end
89
+ protected :primary
90
+ def primary_with_one(array); array << 'one'; primary_without_one(array); end
91
+ safe_alias_method_chain :primary, :one
92
+ end
93
+ D2.new.instance_eval do
94
+ primary(['init']).should == ['init', 'one', 'primary']
95
+ end
96
+ end
62
97
  it "should not chain for non-existent chaining method" do
63
98
  class A
64
99
  def primary(array); array << 'primary'; end
@@ -109,7 +144,7 @@ describe "MetaProgramming" do
109
144
  end
110
145
  A2.new.primary(['init']).should == ['init', 'two', 'one', 'primary']
111
146
  end
112
- it "should not cause endless loop when called twice" do
147
+ it "should scream if called twice for same method_name and extension" do
113
148
  lambda {
114
149
  class A3
115
150
  def primary(array); array << 'primary'; end
@@ -119,7 +154,7 @@ describe "MetaProgramming" do
119
154
  safe_alias_method_chain :primary, :one
120
155
  end
121
156
  A3.new.primary(['init']).should == ['init', 'one', 'primary']
122
- }.should_not raise_exception
157
+ }.should raise_exception
123
158
  end
124
159
  it "should chain with = punctuation" do
125
160
  class A5
@@ -155,5 +190,16 @@ describe "MetaProgramming" do
155
190
  A7.new.primary!(['init']).should == ['init', 'one', 'primary']
156
191
  }.should_not raise_exception
157
192
  end
193
+ it "should chain for an inherited method" do
194
+ class A8
195
+ def primary(array); array << 'primary'; end
196
+ end
197
+ A8.new.primary(['init']).should == ['init', 'primary']
198
+ class A9 < A8
199
+ def primary_with_sub(array); array << 'sub'; primary_without_sub(array); end
200
+ safe_alias_method_chain :primary, :sub
201
+ end
202
+ A9.new.primary(['init']).should == ['init', 'sub', 'primary']
203
+ end
158
204
  end
159
205
  end
@@ -0,0 +1 @@
1
+ require 'lib/meta_programming'
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 0
8
- - 1
9
- version: 0.0.1
8
+ - 8
9
+ version: 0.0.8
10
10
  platform: ruby
11
11
  authors:
12
12
  - Jeff Patmon
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-04-29 00:00:00 -07:00
17
+ date: 2010-05-01 00:00:00 -07:00
18
18
  default_executable:
19
19
  dependencies: []
20
20
 
@@ -31,6 +31,9 @@ files:
31
31
  - lib/meta_programming/object.rb
32
32
  - lib/meta_programming.rb
33
33
  - spec/meta_programming_spec.rb
34
+ - spec/ghost_methods_spec.rb
35
+ - spec/spec_helper.rb
36
+ - spec/blank_slate_spec.rb
34
37
  - spec/spec.opts
35
38
  - init.rb
36
39
  - LICENSE
@@ -71,3 +74,5 @@ specification_version: 3
71
74
  summary: Collection of meta-programming methods for Ruby
72
75
  test_files:
73
76
  - spec/meta_programming_spec.rb
77
+ - spec/ghost_methods_spec.rb
78
+ - spec/blank_slate_spec.rb