meta_programming 0.0.1 → 0.0.8

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/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