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 +92 -2
- data/Rakefile +1 -2
- data/lib/meta_programming/class.rb +5 -3
- data/lib/meta_programming/object.rb +72 -13
- data/spec/blank_slate_spec.rb +53 -0
- data/spec/ghost_methods_spec.rb +203 -0
- data/spec/meta_programming_spec.rb +50 -4
- data/spec/spec_helper.rb +1 -0
- metadata +8 -3
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 =
|
10
|
-
|
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
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
29
|
-
when
|
30
|
-
when
|
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
|
-
|
43
|
-
|
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 '
|
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
|
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
|
-
}.
|
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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|
-
-
|
9
|
-
version: 0.0.
|
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-
|
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
|