meta_programming 0.2.0 → 0.2.1
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 +27 -5
- data/lib/meta_programming/class.rb +10 -0
- data/lib/meta_programming/object.rb +11 -11
- data/lib/meta_programming.rb +1 -0
- data/spec/dynamic_proxy_spec.rb +20 -0
- data/spec/meta_programming_spec.rb +17 -0
- metadata +5 -3
data/README.rdoc
CHANGED
@@ -27,6 +27,14 @@ I consider this Gem in personal beta and suspect it will be in this state for
|
|
27
27
|
a while. If you use it, expect changes that may break your code until the
|
28
28
|
lexigraph has a chance to mature.
|
29
29
|
|
30
|
+
If you include metaprogramming in your projects, I suggest you limit the version
|
31
|
+
dependency to 0.2.x, something like
|
32
|
+
|
33
|
+
s.add_dependency('meta_programming', '>= 0.2.0', '< 0.3.0')
|
34
|
+
|
35
|
+
Any 0.2.x version will be backward compatible with any earlier 0.2.x version. A
|
36
|
+
bump to 0.3.0 cannot be guaranteed backward compatible.
|
37
|
+
|
30
38
|
I welcome comments and collaborators who
|
31
39
|
would like to develop a library for simplifying common patterns and limit
|
32
40
|
the pitfalls of Ruby meta-programming.
|
@@ -94,7 +102,7 @@ The matcher may be any of a symbol, string, regular expression or Proc/lambda.
|
|
94
102
|
==== String, symbols and regular expression matchers
|
95
103
|
|
96
104
|
define_ghost_method('my_method) {|sym, *args| ... }
|
97
|
-
define_ghost_method(
|
105
|
+
define_ghost_method(:my_method) {|sym, *args| ... }
|
98
106
|
define_ghost_method(/^my_method$/) {|sym, *args| ... }
|
99
107
|
|
100
108
|
String and symbol matchers will only process the method if its name matches the string
|
@@ -110,14 +118,14 @@ method block. This is not the case with lambda matchers.
|
|
110
118
|
Lambda matchers provide an opportunity to greatly scrutinize the method call. The method is
|
111
119
|
invoked for any lambda not evaluating to nil or false.
|
112
120
|
|
113
|
-
proc = lambda{|method_name
|
121
|
+
proc = lambda{|method_name| ...matching code... }
|
114
122
|
define_ghost_method(proc) {|proc_result, *args| ... }
|
115
123
|
|
116
124
|
Proc and lambda matchers differ from the other matcher in that the second parameter passes
|
117
125
|
the result of the Proc/lambda matcher to the method block. This feature lets the matcher
|
118
126
|
pre-process the name of the method passed to the body.
|
119
127
|
|
120
|
-
proc = lambda{|sym
|
128
|
+
proc = lambda{|sym| sym =~ /^not_(.+\?)$/ && self.class.method_defined?($1.to_sym) && $1.to_sym }
|
121
129
|
|
122
130
|
The lambda matcher is evaluated in the context of the object and can
|
123
131
|
call methods of the object as in the above example. The lambda matcher is also called
|
@@ -127,12 +135,26 @@ matcher to avoid a stack overflow error. Instead call method_defined? on the cl
|
|
127
135
|
The extra flexibility comes at greater responsibility. If you only want to pass the
|
128
136
|
method name symbol to the method block, don't forget to return it from the lambda.
|
129
137
|
|
130
|
-
proc = lambda{|sym
|
138
|
+
proc = lambda{|sym| sym =~ /^not_.+\?$/ && sym }
|
131
139
|
|
132
140
|
|
133
141
|
==== Critique
|
134
142
|
|
135
|
-
|
143
|
+
2010.04.30
|
144
|
+
|
145
|
+
I'm not satisfied with the ghost method implementation.
|
146
|
+
|
147
|
+
2010.05.02
|
148
|
+
|
149
|
+
I like the fact that define_ghost_method blocks and lambda matchers are now evaluated
|
150
|
+
in the context of the object and that the object is no longer passed into the method block
|
151
|
+
and lambda matcher. The method now behaves similarly to its cousin, +define_method+.
|
152
|
+
|
153
|
+
== Testing
|
154
|
+
|
155
|
+
Passes for Ruby
|
156
|
+
* 1.8.7
|
157
|
+
* 1.9.1
|
136
158
|
|
137
159
|
== Dependencies
|
138
160
|
|
@@ -4,6 +4,16 @@ module MetaProgramming
|
|
4
4
|
raise 'This module may only be included in class Class' unless base.name == 'Class'
|
5
5
|
end
|
6
6
|
|
7
|
+
#opts :matcher=>/xx/, :except=>[:method_name, //, ''], :only=>[:method_name, //, '']
|
8
|
+
# def cast_proxy(target_klass, opts={}, &block)
|
9
|
+
# matcher = lambda{|sym| target_klass.method_defined?(sym) && sym}
|
10
|
+
# define_ghost_method(matcher) {|sym, *args| block.call }
|
11
|
+
# end
|
12
|
+
|
13
|
+
def dynamic_proxy(target, opts={}, &block)
|
14
|
+
|
15
|
+
end
|
16
|
+
|
7
17
|
def blank_slate(opts={})
|
8
18
|
opts[:except] = opts[:except] ? (opts[:except].is_a?(Array) ? opts[:except] : [opts[:except]]) : []
|
9
19
|
exceptions = opts[:except].map(&:to_s)
|
@@ -39,10 +39,10 @@ module MetaProgramming
|
|
39
39
|
class_eval do
|
40
40
|
method_name_with_ext, method_name_without_ext = Helpers.compose_chaining_symbols(method_name, ext)
|
41
41
|
instance_variable = Helpers.escape_method_name(method_name_with_ext)
|
42
|
-
if
|
43
|
-
raise "#{
|
44
|
-
if
|
45
|
-
|
42
|
+
if (method_defined?(method_name_with_ext) || private_method_defined?(method_name_with_ext))
|
43
|
+
raise(MetaProgramming::AliasMethodChainError, "#{method_name_without_ext} already exists. Circular references not permitted.") if (method_defined?(method_name_without_ext) || private_method_defined?(method_name_without_ext))
|
44
|
+
raise(MetaProgramming::AliasMethodChainError, "#{method_name_with_ext} already chained. Rechaining not permitted") if eigenclass.instance_variable_defined?("@#{instance_variable}")
|
45
|
+
if (method_defined?(method_name.to_sym) || private_method_defined?(method_name.to_sym))
|
46
46
|
alias_method method_name_without_ext, method_name.to_sym
|
47
47
|
alias_method method_name.to_sym, method_name_with_ext
|
48
48
|
case method_access_level(method_name_without_ext)
|
@@ -74,16 +74,16 @@ module MetaProgramming
|
|
74
74
|
def define_ghost_method(matcher, &block)
|
75
75
|
raise "Must have a block" unless block_given?
|
76
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
|
-
|
78
|
-
_ghost_method_handler = "_ghost_method_handler_#{
|
79
|
-
_ghost_method_matcher = "_ghost_method_matcher_#{
|
77
|
+
uniq_ext = "#{self.name.gsub(/^.+>::/,'')}_#{matcher.class.name.gsub(/^.+>::/,'')}#{matcher.hash.abs.to_s}"
|
78
|
+
_ghost_method_handler = "_ghost_method_handler_#{uniq_ext}".to_sym
|
79
|
+
_ghost_method_matcher = "_ghost_method_matcher_#{uniq_ext}".to_sym
|
80
80
|
define_method(_ghost_method_handler, block)
|
81
81
|
private _ghost_method_handler
|
82
82
|
if matcher.is_a?(Proc)
|
83
83
|
define_method(_ghost_method_matcher, matcher)
|
84
84
|
private _ghost_method_matcher
|
85
85
|
end
|
86
|
-
define_chained_method(:method_missing,
|
86
|
+
define_chained_method(:method_missing, uniq_ext.to_sym) do |symbol, *args|
|
87
87
|
handled = case matcher
|
88
88
|
when Regexp then !(symbol.to_s =~ matcher).nil?
|
89
89
|
when String, Symbol then (symbol == matcher.to_sym)
|
@@ -102,11 +102,11 @@ module MetaProgramming
|
|
102
102
|
raise handler_error, "#{handler_error.message} in a ghost method block called with symbol :#{symbol}."
|
103
103
|
end
|
104
104
|
else
|
105
|
-
__send__("method_missing_without_#{
|
105
|
+
__send__("method_missing_without_#{uniq_ext}".to_sym, symbol, *args)
|
106
106
|
end
|
107
107
|
end
|
108
108
|
#cripple respond_to? in deference of 1.8 -- the include_private no longer works
|
109
|
-
define_chained_method(:respond_to?,
|
109
|
+
define_chained_method(:respond_to?, uniq_ext.to_sym) do |method_name| #1.9 only, include_private=nil|
|
110
110
|
responds = case matcher
|
111
111
|
when Regexp then !(method_name.to_s =~ matcher).nil?
|
112
112
|
when String, Symbol then method_name == matcher.to_sym
|
@@ -117,7 +117,7 @@ module MetaProgramming
|
|
117
117
|
raise matcher_error, "#{matcher_error.message} in a ghost method matcher called in respond_to? for symbol :#{method_name}. Be sure to use self.class.method_defined? instead of respond_to? in a lambda matcher."
|
118
118
|
end
|
119
119
|
end
|
120
|
-
responds || __send__("respond_to_without_#{
|
120
|
+
responds || __send__("respond_to_without_#{uniq_ext}?".to_sym, method_name) #1.9 only, include_private)
|
121
121
|
end
|
122
122
|
end
|
123
123
|
end
|
data/lib/meta_programming.rb
CHANGED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "MetaProgramming" do
|
4
|
+
|
5
|
+
describe "dynamic_proxy method" do
|
6
|
+
# it "should result in NoMethodError when target object is nil"
|
7
|
+
# it "should take the symbol of an instance variable as the target"
|
8
|
+
# it "should take the string representation of an instance variable as the target"
|
9
|
+
# it "should take the symbol of a method as the target"
|
10
|
+
# it "should accept and obey a :matcher option"
|
11
|
+
# it "should accept and obey an :except option"
|
12
|
+
# it "should accept and obey an :only option"
|
13
|
+
# it "should delegate method calls when no block given"
|
14
|
+
# it "should pass the method name to the block"
|
15
|
+
# it "should pass a block to forward "
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
|
@@ -72,6 +72,23 @@ describe "MetaProgramming" do
|
|
72
72
|
end
|
73
73
|
end
|
74
74
|
describe "safe_alias_method_chain method" do
|
75
|
+
it "should warn against circular references" do
|
76
|
+
class L1
|
77
|
+
def method_call
|
78
|
+
'the real one'
|
79
|
+
end
|
80
|
+
def method_call_with_alias
|
81
|
+
method_call_without_alias
|
82
|
+
end
|
83
|
+
safe_alias_method_chain :method_call, :alias
|
84
|
+
end
|
85
|
+
class L2 < L1
|
86
|
+
def method_call_with_alias
|
87
|
+
method_call_without_alias
|
88
|
+
end
|
89
|
+
end
|
90
|
+
lambda { L2.class_eval { safe_alias_method_chain :method_call, :alias } }.should raise_exception(MetaProgramming::AliasMethodChainError, /Circular references/)
|
91
|
+
end
|
75
92
|
it "should chain for a primary protected method" do
|
76
93
|
class D1
|
77
94
|
def primary(array); array << 'primary'; end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 2
|
8
|
-
-
|
9
|
-
version: 0.2.
|
8
|
+
- 1
|
9
|
+
version: 0.2.1
|
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-05-
|
17
|
+
date: 2010-05-21 00:00:00 -07:00
|
18
18
|
default_executable:
|
19
19
|
dependencies: []
|
20
20
|
|
@@ -35,6 +35,7 @@ files:
|
|
35
35
|
- spec/spec_helper.rb
|
36
36
|
- spec/blank_slate_spec.rb
|
37
37
|
- spec/spec.opts
|
38
|
+
- spec/dynamic_proxy_spec.rb
|
38
39
|
- init.rb
|
39
40
|
- LICENSE
|
40
41
|
- Rakefile
|
@@ -76,3 +77,4 @@ test_files:
|
|
76
77
|
- spec/meta_programming_spec.rb
|
77
78
|
- spec/ghost_methods_spec.rb
|
78
79
|
- spec/blank_slate_spec.rb
|
80
|
+
- spec/dynamic_proxy_spec.rb
|