rconditions 0.1.1 → 0.2.0

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.
Files changed (4) hide show
  1. data/README +26 -31
  2. data/lib/rconditions.rb +30 -27
  3. data/test/rconditions_test.rb +47 -17
  4. metadata +2 -2
data/README CHANGED
@@ -7,21 +7,7 @@ will throw an exception. The exception will include auto-generated modules
7
7
  that give information which predicate methods passed or failed in the bang
8
8
  method's execution.
9
9
 
10
- == A Simple Example
11
-
12
- require "rconditions"
13
-
14
- begin
15
- [1,2].empty!
16
- rescue Array::NotEmptyError => e
17
- p e
18
- end
19
-
20
- # => #<Exception: Array::NotEmptyError>
21
-
22
- The raised exception includes a module defined on the class where the method is defined. The module's name is derived from the predicate method's name.
23
-
24
- == A More Complicated Example
10
+ == Example
25
11
 
26
12
  require "rconditions"
27
13
 
@@ -71,32 +57,33 @@ The raised exception includes a module defined on the class where the method is
71
57
  # => #<Exception: User::NotAdminError, Posting::SpamError,
72
58
  # Posting::NotVisibleError, User::NotCanViewPostingError>
73
59
 
74
- As you can see, the exception includes modules for each of the evaluated
75
- predicate methods: <tt>User#can_view_posting!(posting)</tt> in the context
76
- above called <tt>User#can_view_posting?(posting)</tt>, which called
77
- <tt>User#admin?</tt> and <tt>Posting#can_view_posting?</tt>, which called
60
+ As you can see, the raised exception includes modules for each of the
61
+ evaluated predicate methods: <tt>User#can_view_posting!(posting)</tt> in the
62
+ context above called <tt>User#can_view_posting?(posting)</tt>, which called
63
+ <tt>User#admin?</tt> and <tt>Posting#visible?</tt>, which called
78
64
  <tt>Posting#spam?</tt>.
79
65
 
80
66
  The results were
81
67
 
82
- * <tt>User#admin? # => false</tt>
83
- * <tt>Posting#spam? # => true</tt>
84
- * <tt>Posting#can_view_posting? # => false</tt>
85
- * <tt>User#can_view_posting? # => false</tt>
68
+ * <tt>User#admin? # => false</tt>
69
+ * <tt>Posting#spam? # => true</tt>
70
+ * <tt>Posting#visible? # => false</tt>
71
+ * <tt>User#can_view_posting? # => false</tt>
86
72
 
87
- so the included exception modules are
73
+ The names of the included exception modules are derived from the names of the
74
+ predicate methods:
88
75
 
89
76
  * <tt>User::NotAdminError</tt>
90
77
  * <tt>Posting::SpamError</tt>
91
- * <tt>Posting::NotCanViewPostingError</tt>
78
+ * <tt>Posting::NotVisibleError</tt>
92
79
  * <tt>User::NotCanViewPostingError</tt>
93
80
 
94
81
  == Performance
95
82
 
96
83
  As almost all predicate methods are extended by default, RConditions carries
97
84
  a performance penalty which varies with the number of calls to predicate
98
- methods. A simple test setup using mongrel/rails was slowed down by 10-20%
99
- by adding <tt>require "rconditions"</tt> to the bottom of its
85
+ methods. A simple test setup using mongrel/rails was slowed down by more than
86
+ 30% by adding <tt>require "rconditions"</tt> to the bottom of its
100
87
  <tt>enviroment.rb</tt>.
101
88
 
102
89
  The performance penalty can be avoided by applying RConditions only to new
@@ -104,12 +91,20 @@ methods, that is, methods defined after <tt>require "rconditions"</tt>. To
104
91
  achieve this, set <tt>RCONDITIONS_ONLY_FOR_NEW_METHODS = true</tt> before
105
92
  the require call. Our test setup had no measurable slowdown afterwards.
106
93
 
94
+ == Supported Ruby Versions
95
+
96
+ The test suite of RConditions passes for
97
+ * ruby 1.8.6
98
+ * ruby 1.9.0 (tested with revision 13680)
99
+
100
+ If you use another ruby version, please check whether the tests pass.
101
+
107
102
  == Limitations
108
103
 
109
- * RConditions currently only support predicate methods starting with a letter
110
- and containing only word characters except the question mark at the end.
111
- * RConditions does not support singleton methods. These includes all class
112
- methods.
104
+ * RConditions only processes predicate methods starting with a letter and
105
+ containing only word characters except the question mark at the end.
106
+ * <tt>Kernel#block_given?</tt> is not processed.
107
+ * Singleton methods are not processed. These includes all class methods.
113
108
 
114
109
  == License
115
110
 
data/lib/rconditions.rb CHANGED
@@ -12,6 +12,7 @@ module RConditions
12
12
  def extend_predicate_method_and_define_bang_method(mod, id)
13
13
  return if Thread.current[:rconditions_processing_predicate_method]
14
14
  return if (predicate_method = id.to_s) !~ PREDICATE_METHOD
15
+ return if mod == Kernel && predicate_method == 'block_given?'
15
16
 
16
17
  begin
17
18
  Thread.current[:rconditions_processing_predicate_method] = true
@@ -29,18 +30,24 @@ module RConditions
29
30
  # requiring RConditions.
30
31
  def extend_predicate_methods_and_define_bang_methods
31
32
  ObjectSpace.each_object(Module) do |mod|
32
- mod.instance_methods(false).each do |id|
33
+ (mod.instance_methods(false) + mod.private_instance_methods(false)).each do |id|
33
34
  RConditions.extend_predicate_method_and_define_bang_method(mod, id)
34
35
  end
35
36
  end
36
37
  end
37
-
38
- def exception_modules #:nodoc:
39
- Thread.current[:rconditions_exception_modules]
38
+
39
+ def collect_exception_modules #:nodoc:
40
+ mods = Thread.current[:rconditions_exception_modules] = []
41
+ yield
42
+ mods
43
+ ensure
44
+ Thread.current[:rconditions_exception_modules] = nil
40
45
  end
41
46
 
42
- def exception_modules=(value) #:nodoc:
43
- Thread.current[:rconditions_exception_modules] = value
47
+ def add_exception_module(mod) #:nodoc:
48
+ if mods = Thread.current[:rconditions_exception_modules]
49
+ mods << mod
50
+ end
44
51
  end
45
52
 
46
53
  private
@@ -57,7 +64,6 @@ module RConditions
57
64
 
58
65
  time = Time.now
59
66
  predicate_method_without_rconditions = "#{predicate_method_without_questionmark}_rconditions_#{mod.object_id}_#{time.to_i}_#{time.usec}"
60
- original_visibility = visibility(mod, predicate_method)
61
67
 
62
68
  mod.class_eval <<-EOS, __FILE__, __LINE__ + 1
63
69
  alias_method :#{predicate_method_without_rconditions}, :#{predicate_method}
@@ -65,10 +71,10 @@ module RConditions
65
71
 
66
72
  def #{predicate_method}(*args, &block)
67
73
  result = #{predicate_method_without_rconditions}(*args, &block)
68
- RConditions.exception_modules << (result ? #{positive_module} : #{negative_module}) if RConditions.exception_modules
74
+ ::RConditions.add_exception_module(result ? #{positive_module} : #{negative_module})
69
75
  result
70
76
  end
71
- #{original_visibility} :#{predicate_method}
77
+ #{visibility(mod, predicate_method)} :#{predicate_method}
72
78
  EOS
73
79
  end
74
80
 
@@ -76,21 +82,15 @@ module RConditions
76
82
  bang_method = predicate_method[0...-1] + '!'
77
83
  return if mod.method_defined?(bang_method) || mod.private_method_defined?(bang_method)
78
84
 
79
- visibility = visibility(mod, predicate_method)
80
-
81
85
  mod.class_eval <<-EOS, __FILE__, __LINE__ + 1
82
86
  def #{bang_method}(*args, &block)
83
- RConditions.exception_modules = []
84
- result = #{predicate_method}(*args, &block)
85
- return result if result
86
-
87
- e = Exception.new(RConditions.exception_modules.map { |m| m.name }.join(', '))
88
- RConditions.exception_modules.each { |m| e.extend(m) }
89
- raise e
90
- ensure
91
- RConditions.exception_modules = nil
87
+ mods = ::RConditions.collect_exception_modules do
88
+ result = #{predicate_method}(*args, &block)
89
+ return result if result
90
+ end
91
+ raise Exception.new(mods.join(', ')).extend(*mods)
92
92
  end
93
- #{visibility} :#{bang_method}
93
+ #{visibility(mod, predicate_method)} :#{bang_method}
94
94
  EOS
95
95
  end
96
96
 
@@ -99,10 +99,13 @@ module RConditions
99
99
  end
100
100
 
101
101
  def visibility(mod, id)
102
- case id
103
- when *mod.public_instance_methods(false): 'public'
104
- when *mod.protected_instance_methods(false): 'protected'
105
- when *mod.private_instance_methods(false): 'private'
102
+ case id
103
+ when *mod.public_instance_methods(false)
104
+ 'public'
105
+ when *mod.protected_instance_methods(false)
106
+ 'protected'
107
+ when *mod.private_instance_methods(false)
108
+ 'private'
106
109
  end
107
110
  end
108
111
  end
@@ -123,7 +126,7 @@ class Module #:nodoc:
123
126
  private :method_added_without_rconditions
124
127
 
125
128
  def method_added(id) #:nodoc:
126
- RConditions.extend_predicate_method_and_define_bang_method(self, id)
129
+ ::RConditions.extend_predicate_method_and_define_bang_method(self, id)
127
130
  method_added_without_rconditions(id)
128
131
  end
129
- end
132
+ end
@@ -1,6 +1,14 @@
1
1
  require 'test/unit'
2
2
  require 'rconditions'
3
3
 
4
+ class Array
5
+ # This method is used in the tests below to convert Arrays of Symbols to
6
+ # Arrays of Strings, so that the tests can be run with Ruby 1.9 as well.
7
+ def map_to_s
8
+ map {|each| each.to_s}
9
+ end
10
+ end
11
+
4
12
  class RConditionsTest < Test::Unit::TestCase
5
13
 
6
14
  class Foo
@@ -16,7 +24,18 @@ class RConditionsTest < Test::Unit::TestCase
16
24
  end
17
25
 
18
26
  def test_defines_bang_method_on_predicate_methods_defined_before_r_conditions_was_required
19
- assert [].class.method_defined?(:empty!)
27
+ assert Array.method_defined?(:empty!)
28
+ end
29
+
30
+ def test_defines_bang_method_on_private_predicate_methods_defined_before_r_conditions_was_required
31
+ assert Kernel.private_method_defined?(:autoload!)
32
+ end
33
+
34
+ # we cannot apply RConditions to Kernel#block_given?, since the
35
+ # implementation would require Binding.of_caller, which is broken since
36
+ # Ruby 1.8.5.
37
+ def test_defines_no_bang_method_for_block_given
38
+ assert !Kernel.private_method_defined?(:block_given!)
20
39
  end
21
40
 
22
41
  def test_defines_bang_method_on_predicate_methods_defined_after_r_conditions_was_required
@@ -24,13 +43,13 @@ class RConditionsTest < Test::Unit::TestCase
24
43
  end
25
44
 
26
45
  def test_does_not_define_methods_for_non_predicate_methods
27
- foo_methods_with_new_method = Foo.new.methods + ['new_method']
46
+ foo_methods_with_new_method = Foo.new.methods.map_to_s + ['new_method']
28
47
  eval "class Foo;def new_method; end; end"
29
- assert_equal foo_methods_with_new_method.sort, Foo.new.methods.sort
48
+ assert_equal foo_methods_with_new_method.sort, Foo.new.methods.map_to_s.sort
30
49
  end
31
50
 
32
51
  def test_bang_method_returns_true_if_predicate_method_returns_true
33
- assert Foo.new.d!
52
+ assert_equal true, Foo.new.d!
34
53
  end
35
54
 
36
55
  def test_bang_method_passes_predicate_method_result
@@ -82,9 +101,9 @@ class RConditionsTest < Test::Unit::TestCase
82
101
  end
83
102
 
84
103
  def test_no_visible_methods_added_except_bang_method
85
- foo_methods_with_predicate_method = Foo.new.methods + ['new_predicate_method?']
104
+ foo_methods_with_predicate_method = Foo.new.methods.map_to_s + ['new_predicate_method?']
86
105
  eval "class Foo;def new_predicate_method?; false; end; end"
87
- assert_equal ['new_predicate_method!'], Foo.new.methods - foo_methods_with_predicate_method
106
+ assert_equal ['new_predicate_method!'], Foo.new.methods.map_to_s - foo_methods_with_predicate_method
88
107
  end
89
108
 
90
109
  def test_no_bang_method_will_be_overridden
@@ -97,7 +116,7 @@ class RConditionsTest < Test::Unit::TestCase
97
116
  def test_no_bang_method_will_be_overridden_even_if_it_is_private
98
117
  eval "class Foo;private;def private_do_not_override_me!; 'private'; end; end"
99
118
  eval "class Foo;def private_do_not_override_me?; true; end; end"
100
- assert_equal 'private', Foo.new.__send__(:private_do_not_override_me!)
119
+ assert_equal 'private', Foo.new.instance_eval { private_do_not_override_me! }
101
120
  end
102
121
 
103
122
  def test_no_bang_method_will_be_overridden_even_if_it_is_in_a_superclas
@@ -128,8 +147,8 @@ class RConditionsTest < Test::Unit::TestCase
128
147
 
129
148
  def test_does_work_for_modules
130
149
  # any? is defined in the module Enumerable and included in Array
131
- assert Enumerable.instance_methods(false).include?('any?')
132
- assert !Array.instance_methods(false).include?('any?')
150
+ assert Enumerable.instance_methods(false).map_to_s.include?('any?')
151
+ assert !Array.instance_methods(false).map_to_s.include?('any?')
133
152
  assert Array.ancestors.include?(Enumerable)
134
153
 
135
154
  e = exception_raised_by { [].any! }
@@ -140,7 +159,18 @@ class RConditionsTest < Test::Unit::TestCase
140
159
  def test_does_work_for_alias_method
141
160
  eval <<-EOS
142
161
  class Foo
143
- alias_method :a_alias?, :a?
162
+ alias_method :a_alias_method?, :a?
163
+ end
164
+ EOS
165
+ e = exception_raised_by { Foo.new.a_alias_method! }
166
+ assert e.kind_of?(Foo::NotAAliasMethodError)
167
+ assert e.kind_of?(Foo::NotAError)
168
+ end
169
+
170
+ def test_does_work_for_alias
171
+ eval <<-EOS
172
+ class Foo
173
+ alias a_alias? a?
144
174
  end
145
175
  EOS
146
176
  e = exception_raised_by { Foo.new.a_alias! }
@@ -202,7 +232,7 @@ class RConditionsTest < Test::Unit::TestCase
202
232
  false
203
233
  end
204
234
  EOS
205
- Foo.send(:include, m)
235
+ Foo.instance_eval { include m }
206
236
  e = exception_raised_by { Foo.new.activated! }
207
237
  assert e.kind_of?(m.const_get(:NotActivatedError))
208
238
  self.class.const_set(:NowANamedModule, m)
@@ -241,13 +271,13 @@ class RConditionsTest < Test::Unit::TestCase
241
271
  end
242
272
  EOS
243
273
 
244
- assert Foo.public_instance_methods(false).include?('public_p?')
245
- assert Foo.protected_instance_methods(false).include?('protected_p?')
246
- assert Foo.private_instance_methods(false).include?('private_p?')
274
+ assert Foo.public_instance_methods(false).map_to_s.include?('public_p?')
275
+ assert Foo.protected_instance_methods(false).map_to_s.include?('protected_p?')
276
+ assert Foo.private_instance_methods(false).map_to_s.include?('private_p?')
247
277
 
248
- assert Foo.public_instance_methods(false).include?('public_p!')
249
- assert Foo.protected_instance_methods(false).include?('protected_p!')
250
- assert Foo.private_instance_methods(false).include?('private_p!')
278
+ assert Foo.public_instance_methods(false).map_to_s.include?('public_p!')
279
+ assert Foo.protected_instance_methods(false).map_to_s.include?('protected_p!')
280
+ assert Foo.private_instance_methods(false).map_to_s.include?('private_p!')
251
281
  end
252
282
 
253
283
  private
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.2
3
3
  specification_version: 1
4
4
  name: rconditions
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.1.1
7
- date: 2007-09-27 00:00:00 +02:00
6
+ version: 0.2.0
7
+ date: 2007-10-14 00:00:00 +02:00
8
8
  summary: RConditions generates a bang method (for example, Array#empty!) for each predicate method (for example, Array#empty?). The bang method will return true if the predicate method returns true, otherwise it will throw an exception. The exception will include auto-generated modules that give information on predicate methods passed or failed in the bang method's execution.
9
9
  require_paths:
10
10
  - lib