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