/activesupport/test/core_ext/module_test.rb
Ruby | 510 lines | 397 code | 107 blank | 6 comment | 4 complexity | 19c919451c57b4e542264c82cdd13195 MD5 | raw file
1# frozen_string_literal: true
2
3require "abstract_unit"
4require "active_support/core_ext/module"
5
6Somewhere = Struct.new(:street, :city) do
7 attr_accessor :name
8end
9
10Someone = Struct.new(:name, :place) do
11 delegate :street, :city, :to_f, to: :place
12 delegate :name=, to: :place, prefix: true
13 delegate :upcase, to: "place.city"
14 delegate :table_name, to: :class
15 delegate :table_name, to: :class, prefix: true
16
17 def self.table_name
18 "some_table"
19 end
20
21 self::FAILED_DELEGATE_LINE = __LINE__ + 1
22 delegate :foo, to: :place
23
24 self::FAILED_DELEGATE_LINE_2 = __LINE__ + 1
25 delegate :bar, to: :place, allow_nil: true
26
27 private
28
29 def private_name
30 "Private"
31 end
32end
33
34Invoice = Struct.new(:client) do
35 delegate :street, :city, :name, to: :client, prefix: true
36 delegate :street, :city, :name, to: :client, prefix: :customer
37end
38
39Project = Struct.new(:description, :person) do
40 delegate :name, to: :person, allow_nil: true
41 delegate :to_f, to: :description, allow_nil: true
42end
43
44Developer = Struct.new(:client) do
45 delegate :name, to: :client, prefix: nil
46end
47
48Event = Struct.new(:case) do
49 delegate :foo, to: :case
50end
51
52Tester = Struct.new(:client) do
53 delegate :name, to: :client, prefix: false
54
55 def foo; 1; end
56end
57
58Product = Struct.new(:name) do
59 delegate :name, to: :manufacturer, prefix: true
60 delegate :name, to: :type, prefix: true
61
62 def manufacturer
63 @manufacturer ||= begin
64 nil.unknown_method
65 end
66 end
67
68 def type
69 @type ||= begin
70 nil.type_name
71 end
72 end
73end
74
75module ExtraMissing
76 def method_missing(sym, *args)
77 if sym == :extra_missing
78 42
79 else
80 super
81 end
82 end
83
84 def respond_to_missing?(sym, priv = false)
85 sym == :extra_missing || super
86 end
87end
88
89DecoratedTester = Struct.new(:client) do
90 include ExtraMissing
91
92 delegate_missing_to :client
93end
94
95class DecoratedReserved
96 delegate_missing_to :case
97
98 attr_reader :case
99
100 def initialize(kase)
101 @case = kase
102 end
103end
104
105class Block
106 def hello?
107 true
108 end
109end
110
111HasBlock = Struct.new(:block) do
112 delegate :hello?, to: :block
113end
114
115class ParameterSet
116 delegate :[], :[]=, to: :@params
117
118 def initialize
119 @params = { foo: "bar" }
120 end
121end
122
123class Name
124 delegate :upcase, to: :@full_name
125
126 def initialize(first, last)
127 @full_name = "#{first} #{last}"
128 end
129end
130
131class SideEffect
132 attr_reader :ints
133
134 delegate :to_i, to: :shift, allow_nil: true
135 delegate :to_s, to: :shift
136
137 def initialize
138 @ints = [1, 2, 3]
139 end
140
141 def shift
142 @ints.shift
143 end
144end
145
146class ModuleTest < ActiveSupport::TestCase
147 def setup
148 @david = Someone.new("David", Somewhere.new("Paulina", "Chicago"))
149 end
150
151 def test_delegation_to_methods
152 assert_equal "Paulina", @david.street
153 assert_equal "Chicago", @david.city
154 end
155
156 def test_delegation_to_assignment_method
157 @david.place_name = "Fred"
158 assert_equal "Fred", @david.place.name
159 end
160
161 def test_delegation_to_index_get_method
162 @params = ParameterSet.new
163 assert_equal "bar", @params[:foo]
164 end
165
166 def test_delegation_to_index_set_method
167 @params = ParameterSet.new
168 @params[:foo] = "baz"
169 assert_equal "baz", @params[:foo]
170 end
171
172 def test_delegation_down_hierarchy
173 assert_equal "CHICAGO", @david.upcase
174 end
175
176 def test_delegation_to_instance_variable
177 david = Name.new("David", "Hansson")
178 assert_equal "DAVID HANSSON", david.upcase
179 end
180
181 def test_delegation_to_class_method
182 assert_equal "some_table", @david.table_name
183 assert_equal "some_table", @david.class_table_name
184 end
185
186 def test_missing_delegation_target
187 assert_raise(ArgumentError) do
188 Name.send :delegate, :nowhere
189 end
190 assert_raise(ArgumentError) do
191 Name.send :delegate, :noplace, tos: :hollywood
192 end
193 end
194
195 def test_delegation_target_when_prefix_is_true
196 assert_nothing_raised do
197 Name.send :delegate, :go, to: :you, prefix: true
198 end
199 assert_nothing_raised do
200 Name.send :delegate, :go, to: :_you, prefix: true
201 end
202 assert_raise(ArgumentError) do
203 Name.send :delegate, :go, to: :You, prefix: true
204 end
205 assert_raise(ArgumentError) do
206 Name.send :delegate, :go, to: :@you, prefix: true
207 end
208 end
209
210 def test_delegation_prefix
211 invoice = Invoice.new(@david)
212 assert_equal "David", invoice.client_name
213 assert_equal "Paulina", invoice.client_street
214 assert_equal "Chicago", invoice.client_city
215 end
216
217 def test_delegation_custom_prefix
218 invoice = Invoice.new(@david)
219 assert_equal "David", invoice.customer_name
220 assert_equal "Paulina", invoice.customer_street
221 assert_equal "Chicago", invoice.customer_city
222 end
223
224 def test_delegation_prefix_with_nil_or_false
225 assert_equal "David", Developer.new(@david).name
226 assert_equal "David", Tester.new(@david).name
227 end
228
229 def test_delegation_prefix_with_instance_variable
230 assert_raise ArgumentError do
231 Class.new do
232 def initialize(client)
233 @client = client
234 end
235 delegate :name, :address, to: :@client, prefix: true
236 end
237 end
238 end
239
240 def test_delegation_with_allow_nil
241 rails = Project.new("Rails", Someone.new("David"))
242 assert_equal "David", rails.name
243 end
244
245 def test_delegation_with_allow_nil_and_nil_value
246 rails = Project.new("Rails")
247 assert_nil rails.name
248 end
249
250 # Ensures with check for nil, not for a falseish target.
251 def test_delegation_with_allow_nil_and_false_value
252 project = Project.new(false, false)
253 assert_raise(NoMethodError) { project.name }
254 end
255
256 def test_delegation_with_allow_nil_and_invalid_value
257 rails = Project.new("Rails", "David")
258 assert_raise(NoMethodError) { rails.name }
259 end
260
261 def test_delegation_with_allow_nil_and_nil_value_and_prefix
262 Project.class_eval do
263 delegate :name, to: :person, allow_nil: true, prefix: true
264 end
265 rails = Project.new("Rails")
266 assert_nil rails.person_name
267 end
268
269 def test_delegation_without_allow_nil_and_nil_value
270 david = Someone.new("David")
271 assert_raise(Module::DelegationError) { david.street }
272 end
273
274 def test_delegation_to_method_that_exists_on_nil
275 nil_person = Someone.new(nil)
276 assert_equal 0.0, nil_person.to_f
277 end
278
279 def test_delegation_to_method_that_exists_on_nil_when_allowing_nil
280 nil_project = Project.new(nil)
281 assert_equal 0.0, nil_project.to_f
282 end
283
284 def test_delegation_does_not_raise_error_when_removing_singleton_instance_methods
285 parent = Class.new do
286 def self.parent_method; end
287 end
288
289 assert_nothing_raised do
290 Class.new(parent) do
291 class << self
292 delegate :parent_method, to: :superclass
293 end
294 end
295 end
296 end
297
298 def test_delegation_line_number
299 _, line = Someone.instance_method(:foo).source_location
300 assert_equal Someone::FAILED_DELEGATE_LINE, line
301 end
302
303 def test_delegate_line_with_nil
304 _, line = Someone.instance_method(:bar).source_location
305 assert_equal Someone::FAILED_DELEGATE_LINE_2, line
306 end
307
308 def test_delegation_exception_backtrace
309 someone = Someone.new("foo", "bar")
310 someone.foo
311 rescue NoMethodError => e
312 file_and_line = "#{__FILE__}:#{Someone::FAILED_DELEGATE_LINE}"
313 # We can't simply check the first line of the backtrace, because JRuby reports the call to __send__ in the backtrace.
314 assert e.backtrace.any? { |a| a.include?(file_and_line) },
315 "[#{e.backtrace.inspect}] did not include [#{file_and_line}]"
316 end
317
318 def test_delegation_exception_backtrace_with_allow_nil
319 someone = Someone.new("foo", "bar")
320 someone.bar
321 rescue NoMethodError => e
322 file_and_line = "#{__FILE__}:#{Someone::FAILED_DELEGATE_LINE_2}"
323 # We can't simply check the first line of the backtrace, because JRuby reports the call to __send__ in the backtrace.
324 assert e.backtrace.any? { |a| a.include?(file_and_line) },
325 "[#{e.backtrace.inspect}] did not include [#{file_and_line}]"
326 end
327
328 def test_delegation_invokes_the_target_exactly_once
329 se = SideEffect.new
330
331 assert_equal 1, se.to_i
332 assert_equal [2, 3], se.ints
333
334 assert_equal "2", se.to_s
335 assert_equal [3], se.ints
336 end
337
338 def test_delegation_doesnt_mask_nested_no_method_error_on_nil_receiver
339 product = Product.new("Widget")
340
341 # Nested NoMethodError is a different name from the delegation
342 assert_raise(NoMethodError) { product.manufacturer_name }
343
344 # Nested NoMethodError is the same name as the delegation
345 assert_raise(NoMethodError) { product.type_name }
346 end
347
348 def test_delegation_with_method_arguments
349 has_block = HasBlock.new(Block.new)
350 assert_predicate has_block, :hello?
351 end
352
353 def test_delegate_missing_to_with_method
354 assert_equal "David", DecoratedTester.new(@david).name
355 end
356
357 def test_delegate_missing_to_with_reserved_methods
358 assert_equal "David", DecoratedReserved.new(@david).name
359 end
360
361 def test_delegate_missing_to_does_not_delegate_to_private_methods
362 e = assert_raises(NoMethodError) do
363 DecoratedReserved.new(@david).private_name
364 end
365
366 assert_match(/undefined method `private_name' for/, e.message)
367 end
368
369 def test_delegate_missing_to_does_not_delegate_to_fake_methods
370 e = assert_raises(NoMethodError) do
371 DecoratedReserved.new(@david).my_fake_method
372 end
373
374 assert_match(/undefined method `my_fake_method' for/, e.message)
375 end
376
377 def test_delegate_missing_to_raises_delegation_error_if_target_nil
378 e = assert_raises(Module::DelegationError) do
379 DecoratedTester.new(nil).name
380 end
381
382 assert_equal "name delegated to client, but client is nil", e.message
383 end
384
385 def test_delegate_missing_to_affects_respond_to
386 assert_respond_to DecoratedTester.new(@david), :name
387 assert_not_respond_to DecoratedTester.new(@david), :private_name
388 assert_not_respond_to DecoratedTester.new(@david), :my_fake_method
389
390 assert DecoratedTester.new(@david).respond_to?(:name, true)
391 assert_not DecoratedTester.new(@david).respond_to?(:private_name, true)
392 assert_not DecoratedTester.new(@david).respond_to?(:my_fake_method, true)
393 end
394
395 def test_delegate_missing_to_respects_superclass_missing
396 assert_equal 42, DecoratedTester.new(@david).extra_missing
397
398 assert_respond_to DecoratedTester.new(@david), :extra_missing
399 end
400
401 def test_delegate_with_case
402 event = Event.new(Tester.new)
403 assert_equal 1, event.foo
404 end
405
406 def test_private_delegate
407 location = Class.new do
408 def initialize(place)
409 @place = place
410 end
411
412 private(*delegate(:street, :city, to: :@place))
413 end
414
415 place = location.new(Somewhere.new("Such street", "Sad city"))
416
417 assert_not_respond_to place, :street
418 assert_not_respond_to place, :city
419
420 assert place.respond_to?(:street, true) # Asking for private method
421 assert place.respond_to?(:city, true)
422 end
423
424 def test_private_delegate_prefixed
425 location = Class.new do
426 def initialize(place)
427 @place = place
428 end
429
430 private(*delegate(:street, :city, to: :@place, prefix: :the))
431 end
432
433 place = location.new(Somewhere.new("Such street", "Sad city"))
434
435 assert_not_respond_to place, :street
436 assert_not_respond_to place, :city
437
438 assert_not_respond_to place, :the_street
439 assert place.respond_to?(:the_street, true)
440 assert_not_respond_to place, :the_city
441 assert place.respond_to?(:the_city, true)
442 end
443
444 def test_private_delegate_with_private_option
445 location = Class.new do
446 def initialize(place)
447 @place = place
448 end
449
450 delegate(:street, :city, to: :@place, private: true)
451 end
452
453 place = location.new(Somewhere.new("Such street", "Sad city"))
454
455 assert_not_respond_to place, :street
456 assert_not_respond_to place, :city
457
458 assert place.respond_to?(:street, true) # Asking for private method
459 assert place.respond_to?(:city, true)
460 end
461
462 def test_some_public_some_private_delegate_with_private_option
463 location = Class.new do
464 def initialize(place)
465 @place = place
466 end
467
468 delegate(:street, to: :@place)
469 delegate(:city, to: :@place, private: true)
470 end
471
472 place = location.new(Somewhere.new("Such street", "Sad city"))
473
474 assert_respond_to place, :street
475 assert_not_respond_to place, :city
476
477 assert place.respond_to?(:city, true) # Asking for private method
478 end
479
480 def test_private_delegate_prefixed_with_private_option
481 location = Class.new do
482 def initialize(place)
483 @place = place
484 end
485
486 delegate(:street, :city, to: :@place, prefix: :the, private: true)
487 end
488
489 place = location.new(Somewhere.new("Such street", "Sad city"))
490
491 assert_not_respond_to place, :the_street
492 assert place.respond_to?(:the_street, true)
493 assert_not_respond_to place, :the_city
494 assert place.respond_to?(:the_city, true)
495 end
496
497 def test_delegate_with_private_option_returns_names_of_delegate_methods
498 location = Class.new do
499 def initialize(place)
500 @place = place
501 end
502 end
503
504 assert_equal [:street, :city],
505 location.delegate(:street, :city, to: :@place, private: true)
506
507 assert_equal [:the_street, :the_city],
508 location.delegate(:street, :city, to: :@place, prefix: :the, private: true)
509 end
510end