hash_ext_test.rb 57.1 KB
Newer Older
1
require 'abstract_unit'
J
Jeremy Kemper 已提交
2
require 'active_support/core_ext/hash'
3 4
require 'bigdecimal'
require 'active_support/core_ext/string/access'
5
require 'active_support/ordered_hash'
6
require 'active_support/core_ext/object/conversions'
7
require 'active_support/core_ext/object/deep_dup'
8
require 'active_support/inflections'
9

10
class HashExtTest < ActiveSupport::TestCase
11
  class IndifferentHash < ActiveSupport::HashWithIndifferentAccess
12
  end
13

14 15
  class SubclassingArray < Array
  end
16

17 18 19
  class SubclassingHash < Hash
  end

20 21 22 23 24 25
  class NonIndifferentHash < Hash
    def nested_under_indifferent_access
      self
    end
  end

26 27 28 29 30 31 32 33 34 35
  class HashByConversion
    def initialize(hash)
      @hash = hash
    end

    def to_hash
      @hash
    end
  end

36 37
  def setup
    @strings = { 'a' => 1, 'b' => 2 }
38
    @nested_strings = { 'a' => { 'b' => { 'c' => 3 } } }
39
    @symbols = { :a  => 1, :b  => 2 }
40
    @nested_symbols = { :a => { :b => { :c => 3 } } }
41
    @mixed   = { :a  => 1, 'b' => 2 }
42
    @nested_mixed   = { 'a' => { :b => { 'c' => 3 } } }
43
    @fixnums = {  0  => 1,  1  => 2 }
44
    @nested_fixnums = {  0  => { 1  => { 2 => 3} } }
45
    @illegal_symbols = { [] => 3 }
46
    @nested_illegal_symbols = { [] => { [] => 3} }
47 48
    @upcase_strings = { 'A' => 1, 'B' => 2 }
    @nested_upcase_strings = { 'A' => { 'B' => { 'C' => 3 } } }
49 50 51 52
    @string_array_of_hashes = { 'a' => [ { 'b' => 2 }, { 'c' => 3 }, 4 ] }
    @symbol_array_of_hashes = { :a => [ { :b => 2 }, { :c => 3 }, 4 ] }
    @mixed_array_of_hashes = { :a => [ { :b => 2 }, { 'c' => 3 }, 4 ] }
    @upcase_array_of_hashes = { 'A' => [ { 'B' => 2 }, { 'C' => 3 }, 4 ] }
53 54 55 56
  end

  def test_methods
    h = {}
57 58
    assert_respond_to h, :transform_keys
    assert_respond_to h, :transform_keys!
59 60
    assert_respond_to h, :deep_transform_keys
    assert_respond_to h, :deep_transform_keys!
61 62
    assert_respond_to h, :symbolize_keys
    assert_respond_to h, :symbolize_keys!
63 64
    assert_respond_to h, :deep_symbolize_keys
    assert_respond_to h, :deep_symbolize_keys!
65 66
    assert_respond_to h, :stringify_keys
    assert_respond_to h, :stringify_keys!
67 68
    assert_respond_to h, :deep_stringify_keys
    assert_respond_to h, :deep_stringify_keys!
69 70
    assert_respond_to h, :to_options
    assert_respond_to h, :to_options!
71 72
    assert_respond_to h, :compact
    assert_respond_to h, :compact!
73 74
    assert_respond_to h, :except
    assert_respond_to h, :except!
75 76
  end

77 78 79 80 81 82
  def test_transform_keys
    assert_equal @upcase_strings, @strings.transform_keys{ |key| key.to_s.upcase }
    assert_equal @upcase_strings, @symbols.transform_keys{ |key| key.to_s.upcase }
    assert_equal @upcase_strings, @mixed.transform_keys{ |key| key.to_s.upcase }
  end

83 84 85 86 87 88
  def test_transform_keys_not_mutates
    transformed_hash = @mixed.dup
    transformed_hash.transform_keys{ |key| key.to_s.upcase }
    assert_equal @mixed, transformed_hash
  end

89 90 91 92
  def test_deep_transform_keys
    assert_equal @nested_upcase_strings, @nested_symbols.deep_transform_keys{ |key| key.to_s.upcase }
    assert_equal @nested_upcase_strings, @nested_strings.deep_transform_keys{ |key| key.to_s.upcase }
    assert_equal @nested_upcase_strings, @nested_mixed.deep_transform_keys{ |key| key.to_s.upcase }
93 94 95
    assert_equal @upcase_array_of_hashes, @string_array_of_hashes.deep_transform_keys{ |key| key.to_s.upcase }
    assert_equal @upcase_array_of_hashes, @symbol_array_of_hashes.deep_transform_keys{ |key| key.to_s.upcase }
    assert_equal @upcase_array_of_hashes, @mixed_array_of_hashes.deep_transform_keys{ |key| key.to_s.upcase }
96 97
  end

98 99 100 101 102 103
  def test_deep_transform_keys_not_mutates
    transformed_hash = @nested_mixed.deep_dup
    transformed_hash.deep_transform_keys{ |key| key.to_s.upcase }
    assert_equal @nested_mixed, transformed_hash
  end

104 105 106 107 108 109
  def test_transform_keys!
    assert_equal @upcase_strings, @symbols.dup.transform_keys!{ |key| key.to_s.upcase }
    assert_equal @upcase_strings, @strings.dup.transform_keys!{ |key| key.to_s.upcase }
    assert_equal @upcase_strings, @mixed.dup.transform_keys!{ |key| key.to_s.upcase }
  end

110 111 112 113 114 115 116
  def test_transform_keys_with_bang_mutates
    transformed_hash = @mixed.dup
    transformed_hash.transform_keys!{ |key| key.to_s.upcase }
    assert_equal @upcase_strings, transformed_hash
    assert_equal @mixed, { :a => 1, "b" => 2 }
  end

117
  def test_deep_transform_keys!
118 119 120
    assert_equal @nested_upcase_strings, @nested_symbols.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
    assert_equal @nested_upcase_strings, @nested_strings.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
    assert_equal @nested_upcase_strings, @nested_mixed.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
121 122 123
    assert_equal @upcase_array_of_hashes, @string_array_of_hashes.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
    assert_equal @upcase_array_of_hashes, @symbol_array_of_hashes.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
    assert_equal @upcase_array_of_hashes, @mixed_array_of_hashes.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
124 125 126 127 128 129 130 131
  end

  def test_deep_transform_keys_with_bang_mutates
    transformed_hash = @nested_mixed.deep_dup
    transformed_hash.deep_transform_keys!{ |key| key.to_s.upcase }
    assert_equal @nested_upcase_strings, transformed_hash
    assert_equal @nested_mixed, { 'a' => { :b => { 'c' => 3 } } }
  end
132

133 134 135 136 137 138
  def test_symbolize_keys
    assert_equal @symbols, @symbols.symbolize_keys
    assert_equal @symbols, @strings.symbolize_keys
    assert_equal @symbols, @mixed.symbolize_keys
  end

139 140 141 142 143 144
  def test_symbolize_keys_not_mutates
    transformed_hash = @mixed.dup
    transformed_hash.symbolize_keys
    assert_equal @mixed, transformed_hash
  end

145 146 147 148
  def test_deep_symbolize_keys
    assert_equal @nested_symbols, @nested_symbols.deep_symbolize_keys
    assert_equal @nested_symbols, @nested_strings.deep_symbolize_keys
    assert_equal @nested_symbols, @nested_mixed.deep_symbolize_keys
149 150 151
    assert_equal @symbol_array_of_hashes, @string_array_of_hashes.deep_symbolize_keys
    assert_equal @symbol_array_of_hashes, @symbol_array_of_hashes.deep_symbolize_keys
    assert_equal @symbol_array_of_hashes, @mixed_array_of_hashes.deep_symbolize_keys
152 153
  end

154 155 156 157 158 159
  def test_deep_symbolize_keys_not_mutates
    transformed_hash = @nested_mixed.deep_dup
    transformed_hash.deep_symbolize_keys
    assert_equal @nested_mixed, transformed_hash
  end

160 161 162 163
  def test_symbolize_keys!
    assert_equal @symbols, @symbols.dup.symbolize_keys!
    assert_equal @symbols, @strings.dup.symbolize_keys!
    assert_equal @symbols, @mixed.dup.symbolize_keys!
164
  end
165

166 167 168 169 170 171 172
  def test_symbolize_keys_with_bang_mutates
    transformed_hash = @mixed.dup
    transformed_hash.deep_symbolize_keys!
    assert_equal @symbols, transformed_hash
    assert_equal @mixed, { :a => 1, "b" => 2 }
  end

173
  def test_deep_symbolize_keys!
174 175 176
    assert_equal @nested_symbols, @nested_symbols.deep_dup.deep_symbolize_keys!
    assert_equal @nested_symbols, @nested_strings.deep_dup.deep_symbolize_keys!
    assert_equal @nested_symbols, @nested_mixed.deep_dup.deep_symbolize_keys!
177 178 179
    assert_equal @symbol_array_of_hashes, @string_array_of_hashes.deep_dup.deep_symbolize_keys!
    assert_equal @symbol_array_of_hashes, @symbol_array_of_hashes.deep_dup.deep_symbolize_keys!
    assert_equal @symbol_array_of_hashes, @mixed_array_of_hashes.deep_dup.deep_symbolize_keys!
180 181 182 183 184 185 186
  end

  def test_deep_symbolize_keys_with_bang_mutates
    transformed_hash = @nested_mixed.deep_dup
    transformed_hash.deep_symbolize_keys!
    assert_equal @nested_symbols, transformed_hash
    assert_equal @nested_mixed, { 'a' => { :b => { 'c' => 3 } } }
187 188
  end

189 190 191
  def test_symbolize_keys_preserves_keys_that_cant_be_symbolized
    assert_equal @illegal_symbols, @illegal_symbols.symbolize_keys
    assert_equal @illegal_symbols, @illegal_symbols.dup.symbolize_keys!
192 193
  end

194 195
  def test_deep_symbolize_keys_preserves_keys_that_cant_be_symbolized
    assert_equal @nested_illegal_symbols, @nested_illegal_symbols.deep_symbolize_keys
196
    assert_equal @nested_illegal_symbols, @nested_illegal_symbols.deep_dup.deep_symbolize_keys!
197 198
  end

199 200 201 202 203
  def test_symbolize_keys_preserves_fixnum_keys
    assert_equal @fixnums, @fixnums.symbolize_keys
    assert_equal @fixnums, @fixnums.dup.symbolize_keys!
  end

204 205
  def test_deep_symbolize_keys_preserves_fixnum_keys
    assert_equal @nested_fixnums, @nested_fixnums.deep_symbolize_keys
206
    assert_equal @nested_fixnums, @nested_fixnums.deep_dup.deep_symbolize_keys!
207 208
  end

209 210 211 212 213 214
  def test_stringify_keys
    assert_equal @strings, @symbols.stringify_keys
    assert_equal @strings, @strings.stringify_keys
    assert_equal @strings, @mixed.stringify_keys
  end

215 216 217 218 219 220
  def test_stringify_keys_not_mutates
    transformed_hash = @mixed.dup
    transformed_hash.stringify_keys
    assert_equal @mixed, transformed_hash
  end

221 222 223 224
  def test_deep_stringify_keys
    assert_equal @nested_strings, @nested_symbols.deep_stringify_keys
    assert_equal @nested_strings, @nested_strings.deep_stringify_keys
    assert_equal @nested_strings, @nested_mixed.deep_stringify_keys
225 226 227
    assert_equal @string_array_of_hashes, @string_array_of_hashes.deep_stringify_keys
    assert_equal @string_array_of_hashes, @symbol_array_of_hashes.deep_stringify_keys
    assert_equal @string_array_of_hashes, @mixed_array_of_hashes.deep_stringify_keys
228 229
  end

230 231 232 233 234 235
  def test_deep_stringify_keys_not_mutates
    transformed_hash = @nested_mixed.deep_dup
    transformed_hash.deep_stringify_keys
    assert_equal @nested_mixed, transformed_hash
  end

236 237 238 239 240 241
  def test_stringify_keys!
    assert_equal @strings, @symbols.dup.stringify_keys!
    assert_equal @strings, @strings.dup.stringify_keys!
    assert_equal @strings, @mixed.dup.stringify_keys!
  end

242 243 244 245 246 247 248
  def test_stringify_keys_with_bang_mutates
    transformed_hash = @mixed.dup
    transformed_hash.stringify_keys!
    assert_equal @strings, transformed_hash
    assert_equal @mixed, { :a => 1, "b" => 2 }
  end

249
  def test_deep_stringify_keys!
250 251 252
    assert_equal @nested_strings, @nested_symbols.deep_dup.deep_stringify_keys!
    assert_equal @nested_strings, @nested_strings.deep_dup.deep_stringify_keys!
    assert_equal @nested_strings, @nested_mixed.deep_dup.deep_stringify_keys!
253 254 255
    assert_equal @string_array_of_hashes, @string_array_of_hashes.deep_dup.deep_stringify_keys!
    assert_equal @string_array_of_hashes, @symbol_array_of_hashes.deep_dup.deep_stringify_keys!
    assert_equal @string_array_of_hashes, @mixed_array_of_hashes.deep_dup.deep_stringify_keys!
256 257 258 259 260 261 262
  end

  def test_deep_stringify_keys_with_bang_mutates
    transformed_hash = @nested_mixed.deep_dup
    transformed_hash.deep_stringify_keys!
    assert_equal @nested_strings, transformed_hash
    assert_equal @nested_mixed, { 'a' => { :b => { 'c' => 3 } } }
263 264
  end

265
  def test_symbolize_keys_for_hash_with_indifferent_access
266
    assert_instance_of Hash, @symbols.with_indifferent_access.symbolize_keys
267 268 269 270 271
    assert_equal @symbols, @symbols.with_indifferent_access.symbolize_keys
    assert_equal @symbols, @strings.with_indifferent_access.symbolize_keys
    assert_equal @symbols, @mixed.with_indifferent_access.symbolize_keys
  end

272 273 274 275 276 277 278 279
  def test_deep_symbolize_keys_for_hash_with_indifferent_access
    assert_instance_of Hash, @nested_symbols.with_indifferent_access.deep_symbolize_keys
    assert_equal @nested_symbols, @nested_symbols.with_indifferent_access.deep_symbolize_keys
    assert_equal @nested_symbols, @nested_strings.with_indifferent_access.deep_symbolize_keys
    assert_equal @nested_symbols, @nested_mixed.with_indifferent_access.deep_symbolize_keys
  end


280 281 282 283 284 285
  def test_symbolize_keys_bang_for_hash_with_indifferent_access
    assert_raise(NoMethodError) { @symbols.with_indifferent_access.dup.symbolize_keys! }
    assert_raise(NoMethodError) { @strings.with_indifferent_access.dup.symbolize_keys! }
    assert_raise(NoMethodError) { @mixed.with_indifferent_access.dup.symbolize_keys! }
  end

286
  def test_deep_symbolize_keys_bang_for_hash_with_indifferent_access
287 288 289
    assert_raise(NoMethodError) { @nested_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! }
    assert_raise(NoMethodError) { @nested_strings.with_indifferent_access.deep_dup.deep_symbolize_keys! }
    assert_raise(NoMethodError) { @nested_mixed.with_indifferent_access.deep_dup.deep_symbolize_keys! }
290 291
  end

292 293 294 295 296
  def test_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access
    assert_equal @illegal_symbols, @illegal_symbols.with_indifferent_access.symbolize_keys
    assert_raise(NoMethodError) { @illegal_symbols.with_indifferent_access.dup.symbolize_keys! }
  end

297 298
  def test_deep_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access
    assert_equal @nested_illegal_symbols, @nested_illegal_symbols.with_indifferent_access.deep_symbolize_keys
299
    assert_raise(NoMethodError) { @nested_illegal_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! }
300 301
  end

302 303 304 305 306
  def test_symbolize_keys_preserves_fixnum_keys_for_hash_with_indifferent_access
    assert_equal @fixnums, @fixnums.with_indifferent_access.symbolize_keys
    assert_raise(NoMethodError) { @fixnums.with_indifferent_access.dup.symbolize_keys! }
  end

307 308
  def test_deep_symbolize_keys_preserves_fixnum_keys_for_hash_with_indifferent_access
    assert_equal @nested_fixnums, @nested_fixnums.with_indifferent_access.deep_symbolize_keys
309
    assert_raise(NoMethodError) { @nested_fixnums.with_indifferent_access.deep_dup.deep_symbolize_keys! }
310 311
  end

312
  def test_stringify_keys_for_hash_with_indifferent_access
313
    assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.stringify_keys
314 315 316 317 318
    assert_equal @strings, @symbols.with_indifferent_access.stringify_keys
    assert_equal @strings, @strings.with_indifferent_access.stringify_keys
    assert_equal @strings, @mixed.with_indifferent_access.stringify_keys
  end

319 320 321 322 323 324 325
  def test_deep_stringify_keys_for_hash_with_indifferent_access
    assert_instance_of ActiveSupport::HashWithIndifferentAccess, @nested_symbols.with_indifferent_access.deep_stringify_keys
    assert_equal @nested_strings, @nested_symbols.with_indifferent_access.deep_stringify_keys
    assert_equal @nested_strings, @nested_strings.with_indifferent_access.deep_stringify_keys
    assert_equal @nested_strings, @nested_mixed.with_indifferent_access.deep_stringify_keys
  end

326
  def test_stringify_keys_bang_for_hash_with_indifferent_access
327
    assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.dup.stringify_keys!
328 329 330
    assert_equal @strings, @symbols.with_indifferent_access.dup.stringify_keys!
    assert_equal @strings, @strings.with_indifferent_access.dup.stringify_keys!
    assert_equal @strings, @mixed.with_indifferent_access.dup.stringify_keys!
331 332
  end

333 334
  def test_deep_stringify_keys_bang_for_hash_with_indifferent_access
    assert_instance_of ActiveSupport::HashWithIndifferentAccess, @nested_symbols.with_indifferent_access.dup.deep_stringify_keys!
335 336 337
    assert_equal @nested_strings, @nested_symbols.with_indifferent_access.deep_dup.deep_stringify_keys!
    assert_equal @nested_strings, @nested_strings.with_indifferent_access.deep_dup.deep_stringify_keys!
    assert_equal @nested_strings, @nested_mixed.with_indifferent_access.deep_dup.deep_stringify_keys!
338 339
  end

340 341 342 343 344 345
  def test_nested_under_indifferent_access
    foo = { "foo" => SubclassingHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
    assert_kind_of ActiveSupport::HashWithIndifferentAccess, foo["foo"]

    foo = { "foo" => NonIndifferentHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
    assert_kind_of NonIndifferentHash, foo["foo"]
346 347 348

    foo = { "foo" => IndifferentHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
    assert_kind_of IndifferentHash, foo["foo"]
349 350
  end

351
  def test_indifferent_assorted
352 353 354
    @strings = @strings.with_indifferent_access
    @symbols = @symbols.with_indifferent_access
    @mixed   = @mixed.with_indifferent_access
355

356
    assert_equal 'a', @strings.__send__(:convert_key, :a)
357

358 359 360
    assert_equal 1, @strings.fetch('a')
    assert_equal 1, @strings.fetch(:a.to_s)
    assert_equal 1, @strings.fetch(:a)
361

362
    hashes = { :@strings => @strings, :@symbols => @symbols, :@mixed => @mixed }
363 364 365
    method_map = { :'[]' => 1, :fetch => 1, :values_at => [1],
      :has_key? => true, :include? => true, :key? => true,
      :member? => true }
366

367 368
    hashes.each do |name, hash|
      method_map.sort_by { |m| m.to_s }.each do |meth, expected|
369
        assert_equal(expected, hash.__send__(meth, 'a'),
370
                     "Calling #{name}.#{meth} 'a'")
371
        assert_equal(expected, hash.__send__(meth, :a),
372
                     "Calling #{name}.#{meth} :a")
373 374
      end
    end
375

376 377 378 379 380 381
    assert_equal [1, 2], @strings.values_at('a', 'b')
    assert_equal [1, 2], @strings.values_at(:a, :b)
    assert_equal [1, 2], @symbols.values_at('a', 'b')
    assert_equal [1, 2], @symbols.values_at(:a, :b)
    assert_equal [1, 2], @mixed.values_at('a', 'b')
    assert_equal [1, 2], @mixed.values_at(:a, :b)
382
  end
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411

  def test_indifferent_reading
    hash = HashWithIndifferentAccess.new
    hash["a"] = 1
    hash["b"] = true
    hash["c"] = false
    hash["d"] = nil

    assert_equal 1, hash[:a]
    assert_equal true, hash[:b]
    assert_equal false, hash[:c]
    assert_equal nil, hash[:d]
    assert_equal nil, hash[:e]
  end

  def test_indifferent_reading_with_nonnil_default
    hash = HashWithIndifferentAccess.new(1)
    hash["a"] = 1
    hash["b"] = true
    hash["c"] = false
    hash["d"] = nil

    assert_equal 1, hash[:a]
    assert_equal true, hash[:b]
    assert_equal false, hash[:c]
    assert_equal nil, hash[:d]
    assert_equal 1, hash[:e]
  end

412 413 414 415 416
  def test_indifferent_writing
    hash = HashWithIndifferentAccess.new
    hash[:a] = 1
    hash['b'] = 2
    hash[3] = 3
417

418 419 420 421 422
    assert_equal hash['a'], 1
    assert_equal hash['b'], 2
    assert_equal hash[:a], 1
    assert_equal hash[:b], 2
    assert_equal hash[3], 3
423 424
  end

425 426 427 428
  def test_indifferent_update
    hash = HashWithIndifferentAccess.new
    hash[:a] = 'a'
    hash['b'] = 'b'
429

430 431 432 433 434 435 436 437 438 439 440 441 442 443 444
    updated_with_strings = hash.update(@strings)
    updated_with_symbols = hash.update(@symbols)
    updated_with_mixed = hash.update(@mixed)

    assert_equal updated_with_strings[:a], 1
    assert_equal updated_with_strings['a'], 1
    assert_equal updated_with_strings['b'], 2

    assert_equal updated_with_symbols[:a], 1
    assert_equal updated_with_symbols['b'], 2
    assert_equal updated_with_symbols[:b], 2

    assert_equal updated_with_mixed[:a], 1
    assert_equal updated_with_mixed['b'], 2

445
    assert [updated_with_strings, updated_with_symbols, updated_with_mixed].all? { |h| h.keys.size == 2 }
446 447
  end

448 449 450 451 452 453
  def test_update_with_to_hash_conversion
    hash = HashWithIndifferentAccess.new
    hash.update HashByConversion.new({ :a => 1 })
    assert_equal hash['a'], 1
  end

454 455 456 457
  def test_indifferent_merging
    hash = HashWithIndifferentAccess.new
    hash[:a] = 'failure'
    hash['b'] = 'failure'
458

459
    other = { 'a' => 1, :b => 2 }
460

461
    merged = hash.merge(other)
462

463 464 465
    assert_equal HashWithIndifferentAccess, merged.class
    assert_equal 1, merged[:a]
    assert_equal 2, merged['b']
466

467
    hash.update(other)
468

469 470 471
    assert_equal 1, hash[:a]
    assert_equal 2, hash['b']
  end
472

473 474 475 476 477 478
  def test_merge_with_to_hash_conversion
    hash = HashWithIndifferentAccess.new
    merged = hash.merge HashByConversion.new({ :a => 1 })
    assert_equal merged['a'], 1
  end

479 480 481 482 483 484 485 486 487 488 489 490
  def test_indifferent_replace
    hash = HashWithIndifferentAccess.new
    hash[:a] = 42

    replaced = hash.replace(b: 12)

    assert hash.key?('b')
    assert !hash.key?(:a)
    assert_equal 12, hash[:b]
    assert_same hash, replaced
  end

491 492 493 494 495 496 497 498 499 500 501 502
  def test_replace_with_to_hash_conversion
    hash = HashWithIndifferentAccess.new
    hash[:a] = 42

    replaced = hash.replace(HashByConversion.new(b: 12))

    assert hash.key?('b')
    assert !hash.key?(:a)
    assert_equal 12, hash[:b]
    assert_same hash, replaced
  end

503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525
  def test_indifferent_merging_with_block
    hash = HashWithIndifferentAccess.new
    hash[:a] = 1
    hash['b'] = 3

    other = { 'a' => 4, :b => 2, 'c' => 10 }

    merged = hash.merge(other) { |key, old, new| old > new ? old : new }

    assert_equal HashWithIndifferentAccess, merged.class
    assert_equal 4, merged[:a]
    assert_equal 3, merged['b']
    assert_equal 10, merged[:c]

    other_indifferent = HashWithIndifferentAccess.new('a' => 9, :b => 2)

    merged = hash.merge(other_indifferent) { |key, old, new| old + new }

    assert_equal HashWithIndifferentAccess, merged.class
    assert_equal 10, merged[:a]
    assert_equal 5, merged[:b]
  end

526 527 528 529 530 531 532
  def test_indifferent_reverse_merging
    hash = HashWithIndifferentAccess.new('some' => 'value', 'other' => 'value')
    hash.reverse_merge!(:some => 'noclobber', :another => 'clobber')
    assert_equal 'value', hash[:some]
    assert_equal 'clobber', hash[:another]
  end

533 534 535 536 537 538 539 540 541 542
  def test_indifferent_deleting
    get_hash = proc{ { :a => 'foo' }.with_indifferent_access }
    hash = get_hash.call
    assert_equal hash.delete(:a), 'foo'
    assert_equal hash.delete(:a), nil
    hash = get_hash.call
    assert_equal hash.delete('a'), 'foo'
    assert_equal hash.delete('a'), nil
  end

543 544 545 546 547
  def test_indifferent_select
    hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select {|k,v| v == 1}

    assert_equal({ 'a' => 1 }, hash)
    assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
548 549 550 551 552 553
  end

  def test_indifferent_select_returns_a_hash_when_unchanged
    hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select {|k,v| true}

    assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578
  end

  def test_indifferent_select_bang
    indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings)
    indifferent_strings.select! {|k,v| v == 1}

    assert_equal({ 'a' => 1 }, indifferent_strings)
    assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings
  end

  def test_indifferent_reject
    hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).reject {|k,v| v != 1}

    assert_equal({ 'a' => 1 }, hash)
    assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
  end

  def test_indifferent_reject_bang
    indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings)
    indifferent_strings.reject! {|k,v| v != 1}

    assert_equal({ 'a' => 1 }, indifferent_strings)
    assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings
  end

579 580 581 582 583 584 585 586 587 588
  def test_indifferent_to_hash
    # Should convert to a Hash with String keys.
    assert_equal @strings, @mixed.with_indifferent_access.to_hash

    # Should preserve the default value.
    mixed_with_default = @mixed.dup
    mixed_with_default.default = '1234'
    roundtrip = mixed_with_default.with_indifferent_access.to_hash
    assert_equal @strings, roundtrip
    assert_equal '1234', roundtrip.default
589 590 591 592
    new_to_hash = @nested_mixed.with_indifferent_access.to_hash
    assert_not new_to_hash.instance_of?(HashWithIndifferentAccess)
    assert_not new_to_hash["a"].instance_of?(HashWithIndifferentAccess)
    assert_not new_to_hash["a"]["b"].instance_of?(HashWithIndifferentAccess)
593 594
  end

595 596 597 598 599 600 601
  def test_lookup_returns_the_same_object_that_is_stored_in_hash_indifferent_access
    hash = HashWithIndifferentAccess.new {|h, k| h[k] = []}
    hash[:a] << 1

    assert_equal [1], hash[:a]
  end

602 603 604 605 606 607 608
  def test_with_indifferent_access_has_no_side_effects_on_existing_hash
    hash = {content: [{:foo => :bar, 'bar' => 'baz'}]}
    hash.with_indifferent_access

    assert_equal [:foo, "bar"], hash[:content].first.keys
  end

609 610 611
  def test_indifferent_hash_with_array_of_hashes
    hash = { "urls" => { "url" => [ { "address" => "1" }, { "address" => "2" } ] }}.with_indifferent_access
    assert_equal "1", hash[:urls][:url].first[:address]
612 613 614 615 616

    hash = hash.to_hash
    assert_not hash.instance_of?(HashWithIndifferentAccess)
    assert_not hash["urls"].instance_of?(HashWithIndifferentAccess)
    assert_not hash["urls"]["url"].first.instance_of?(HashWithIndifferentAccess)
617
  end
618

619 620 621 622 623 624
  def test_should_preserve_array_subclass_when_value_is_array
    array = SubclassingArray.new
    array << { "address" => "1" }
    hash = { "urls" => { "url" => array }}.with_indifferent_access
    assert_equal SubclassingArray, hash[:urls][:url].class
  end
625

626 627 628 629 630 631
  def test_should_preserve_array_class_when_hash_value_is_frozen_array
    array = SubclassingArray.new
    array << { "address" => "1" }
    hash = { "urls" => { "url" => array.freeze }}.with_indifferent_access
    assert_equal SubclassingArray, hash[:urls][:url].class
  end
632

633 634 635
  def test_stringify_and_symbolize_keys_on_indifferent_preserves_hash
    h = HashWithIndifferentAccess.new
    h[:first] = 1
636
    h = h.stringify_keys
637 638 639
    assert_equal 1, h['first']
    h = HashWithIndifferentAccess.new
    h['first'] = 1
640
    h = h.symbolize_keys
641 642 643
    assert_equal 1, h[:first]
  end

644 645 646 647 648 649 650 651 652 653 654
  def test_deep_stringify_and_deep_symbolize_keys_on_indifferent_preserves_hash
    h = HashWithIndifferentAccess.new
    h[:first] = 1
    h = h.deep_stringify_keys
    assert_equal 1, h['first']
    h = HashWithIndifferentAccess.new
    h['first'] = 1
    h = h.deep_symbolize_keys
    assert_equal 1, h[:first]
  end

655 656 657 658 659 660 661
  def test_to_options_on_indifferent_preserves_hash
    h = HashWithIndifferentAccess.new
    h['first'] = 1
    h.to_options!
    assert_equal 1, h['first']
  end

662 663 664 665 666 667 668 669
  def test_to_options_on_indifferent_preserves_works_as_hash_with_dup
    h = HashWithIndifferentAccess.new({ a: { b: 'b' } })
    dup = h.dup

    dup[:a][:c] = 'c'
    assert_equal 'c', h[:a][:c]
  end

A
Akshay Vishnoi 已提交
670
  def test_indifferent_sub_hashes
671 672 673 674 675 676 677
    h = {'user' => {'id' => 5}}.with_indifferent_access
    ['user', :user].each {|user| [:id, 'id'].each {|id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5"}}

    h = {:user => {:id => 5}}.with_indifferent_access
    ['user', :user].each {|user| [:id, 'id'].each {|id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5"}}
  end

678 679 680 681 682 683 684 685 686 687 688
  def test_indifferent_duplication
    # Should preserve default value
    h = HashWithIndifferentAccess.new
    h.default = '1234'
    assert_equal h.default, h.dup.default

    # Should preserve class for subclasses
    h = IndifferentHash.new
    assert_equal h.class, h.dup.class
  end

689 690 691
  def test_assert_valid_keys
    assert_nothing_raised do
      { :failure => "stuff", :funny => "business" }.assert_valid_keys([ :failure, :funny ])
692
      { :failure => "stuff", :funny => "business" }.assert_valid_keys(:failure, :funny)
693
    end
694

695
    exception = assert_raise ArgumentError do
696
      { :failore => "stuff", :funny => "business" }.assert_valid_keys([ :failure, :funny ])
697 698 699 700
    end
    assert_equal "Unknown key: :failore. Valid keys are: :failure, :funny", exception.message

    exception = assert_raise ArgumentError do
701
      { :failore => "stuff", :funny => "business" }.assert_valid_keys(:failure, :funny)
702
    end
703
    assert_equal "Unknown key: :failore. Valid keys are: :failure, :funny", exception.message
704
  end
705

706 707 708 709 710
  def test_assorted_keys_not_stringified
    original = {Object.new => 2, 1 => 2, [] => true}
    indiff = original.with_indifferent_access
    assert(!indiff.keys.any? {|k| k.kind_of? String}, "A key was converted to a string!")
  end
711

712 713 714 715 716 717 718 719 720 721
  def test_deep_merge
    hash_1 = { :a => "a", :b => "b", :c => { :c1 => "c1", :c2 => "c2", :c3 => { :d1 => "d1" } } }
    hash_2 = { :a => 1, :c => { :c1 => 2, :c3 => { :d2 => "d2" } } }
    expected = { :a => 1, :b => "b", :c => { :c1 => 2, :c2 => "c2", :c3 => { :d1 => "d1", :d2 => "d2" } } }
    assert_equal expected, hash_1.deep_merge(hash_2)

    hash_1.deep_merge!(hash_2)
    assert_equal expected, hash_1
  end

722 723 724 725 726 727 728 729 730 731
  def test_deep_merge_with_block
    hash_1 = { :a => "a", :b => "b", :c => { :c1 => "c1", :c2 => "c2", :c3 => { :d1 => "d1" } } }
    hash_2 = { :a => 1, :c => { :c1 => 2, :c3 => { :d2 => "d2" } } }
    expected = { :a => [:a, "a", 1], :b => "b", :c => { :c1 => [:c1, "c1", 2], :c2 => "c2", :c3 => { :d1 => "d1", :d2 => "d2" } } }
    assert_equal(expected, hash_1.deep_merge(hash_2) { |k,o,n| [k, o, n] })

    hash_1.deep_merge!(hash_2) { |k,o,n| [k, o, n] }
    assert_equal expected, hash_1
  end

732 733 734 735
  def test_deep_merge_with_falsey_values
    hash_1 = { e: false }
    hash_2 = { e: 'e' }
    expected = { e: [:e, false, 'e'] }
736
    assert_equal(expected, hash_1.deep_merge(hash_2) { |k, o, n| [k, o, n] })
737

738
    hash_1.deep_merge!(hash_2) { |k, o, n| [k, o, n] }
739 740 741
    assert_equal expected, hash_1
  end

742 743 744 745 746 747 748 749 750 751 752 753
  def test_deep_merge_on_indifferent_access
    hash_1 = HashWithIndifferentAccess.new({ :a => "a", :b => "b", :c => { :c1 => "c1", :c2 => "c2", :c3 => { :d1 => "d1" } } })
    hash_2 = HashWithIndifferentAccess.new({ :a => 1, :c => { :c1 => 2, :c3 => { :d2 => "d2" } } })
    hash_3 = { :a => 1, :c => { :c1 => 2, :c3 => { :d2 => "d2" } } }
    expected = { "a" => 1, "b" => "b", "c" => { "c1" => 2, "c2" => "c2", "c3" => { "d1" => "d1", "d2" => "d2" } } }
    assert_equal expected, hash_1.deep_merge(hash_2)
    assert_equal expected, hash_1.deep_merge(hash_3)

    hash_1.deep_merge!(hash_2)
    assert_equal expected, hash_1
  end

754 755 756 757 758 759 760 761 762 763
  def test_store_on_indifferent_access
    hash = HashWithIndifferentAccess.new
    hash.store(:test1, 1)
    hash.store('test1', 11)
    hash[:test2] = 2
    hash['test2'] = 22
    expected = { "test1" => 11, "test2" => 22 }
    assert_equal expected, hash
  end

764 765 766 767 768 769 770 771 772
  def test_constructor_on_indifferent_access
    hash = HashWithIndifferentAccess[:foo, 1]
    assert_equal 1, hash[:foo]
    assert_equal 1, hash['foo']
    hash[:foo] = 3
    assert_equal 3, hash[:foo]
    assert_equal 3, hash['foo']
  end

773
  def test_reverse_merge
774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790
    defaults = { :a => "x", :b => "y", :c => 10 }.freeze
    options  = { :a => 1, :b => 2 }
    expected = { :a => 1, :b => 2, :c => 10 }

    # Should merge defaults into options, creating a new hash.
    assert_equal expected, options.reverse_merge(defaults)
    assert_not_equal expected, options

    # Should merge! defaults into options, replacing options.
    merged = options.dup
    assert_equal expected, merged.reverse_merge!(defaults)
    assert_equal expected, merged

    # Should be an alias for reverse_merge!
    merged = options.dup
    assert_equal expected, merged.reverse_update(defaults)
    assert_equal expected, merged
791
  end
792

793 794 795 796 797 798 799
  def test_slice
    original = { :a => 'x', :b => 'y', :c => 10 }
    expected = { :a => 'x', :b => 'y' }

    # Should return a new hash with only the given keys.
    assert_equal expected, original.slice(:a, :b)
    assert_not_equal expected, original
800 801 802 803 804
  end

  def test_slice_inplace
    original = { :a => 'x', :b => 'y', :c => 10 }
    expected = { :c => 10 }
805 806 807 808 809

    # Should replace the hash with only the given keys.
    assert_equal expected, original.slice!(:a, :b)
  end

810 811 812 813 814 815 816
  def test_slice_with_an_array_key
    original = { :a => 'x', :b => 'y', :c => 10, [:a, :b] => "an array key" }
    expected = { [:a, :b] => "an array key", :c => 10 }

    # Should return a new hash with only the given keys when given an array key.
    assert_equal expected, original.slice([:a, :b], :c)
    assert_not_equal expected, original
817 818 819 820 821
  end

  def test_slice_inplace_with_an_array_key
    original = { :a => 'x', :b => 'y', :c => 10, [:a, :b] => "an array key" }
    expected = { :a => 'x', :b => 'y' }
822 823 824 825 826 827 828 829 830 831 832 833 834

    # Should replace the hash with only the given keys when given an array key.
    assert_equal expected, original.slice!([:a, :b], :c)
  end

  def test_slice_with_splatted_keys
    original = { :a => 'x', :b => 'y', :c => 10, [:a, :b] => "an array key" }
    expected = { :a => 'x', :b => "y" }

    # Should grab each of the splatted keys.
    assert_equal expected, original.slice(*[:a, :b])
  end

835 836 837 838 839 840 841 842
  def test_indifferent_slice
    original = { :a => 'x', :b => 'y', :c => 10 }.with_indifferent_access
    expected = { :a => 'x', :b => 'y' }.with_indifferent_access

    [['a', 'b'], [:a, :b]].each do |keys|
      # Should return a new hash with only the given keys.
      assert_equal expected, original.slice(*keys), keys.inspect
      assert_not_equal expected, original
843 844
    end
  end
845

846 847 848 849 850
  def test_indifferent_slice_inplace
    original = { :a => 'x', :b => 'y', :c => 10 }.with_indifferent_access
    expected = { :c => 10 }.with_indifferent_access

    [['a', 'b'], [:a, :b]].each do |keys|
851 852 853 854 855
      # Should replace the hash with only the given keys.
      copy = original.dup
      assert_equal expected, copy.slice!(*keys)
    end
  end
856

857 858 859 860 861 862 863 864 865 866
  def test_indifferent_slice_access_with_symbols
    original = {'login' => 'bender', 'password' => 'shiny', 'stuff' => 'foo'}
    original = original.with_indifferent_access

    slice = original.slice(:login, :password)

    assert_equal 'bender', slice[:login]
    assert_equal 'bender', slice['login']
  end

867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884
  def test_slice_bang_does_not_override_default
    hash = Hash.new(0)
    hash.update(a: 1, b: 2)

    hash.slice!(:a)

    assert_equal 0, hash[:c]
  end

  def test_slice_bang_does_not_override_default_proc
    hash = Hash.new { |h, k| h[k] = [] }
    hash.update(a: 1, b: 2)

    hash.slice!(:a)

    assert_equal [], hash[:c]
  end

885 886 887
  def test_extract
    original = {:a => 1, :b => 2, :c => 3, :d => 4}
    expected = {:a => 1, :b => 2}
888
    remaining = {:c => 3, :d => 4}
889

890
    assert_equal expected, original.extract!(:a, :b, :x)
891
    assert_equal remaining, original
892 893 894 895 896 897 898 899 900 901 902 903 904
  end

  def test_extract_nils
    original = {:a => nil, :b => nil}
    expected = {:a => nil}
    extracted = original.extract!(:a, :x)

    assert_equal expected, extracted
    assert_equal nil, extracted[:a]
    assert_equal nil, extracted[:x]
  end

  def test_indifferent_extract
905
    original = {:a => 1, 'b' => 2, :c => 3, 'd' => 4}.with_indifferent_access
906
    expected = {:a => 1, :b => 2}.with_indifferent_access
907
    remaining = {:c => 3, :d => 4}.with_indifferent_access
908

909 910 911 912 913
    [['a', 'b'], [:a, :b]].each do |keys|
      copy = original.dup
      assert_equal expected, copy.extract!(*keys)
      assert_equal remaining, copy
    end
914 915
  end

916 917 918 919
  def test_except
    original = { :a => 'x', :b => 'y', :c => 10 }
    expected = { :a => 'x', :b => 'y' }

E
Edward Tsech 已提交
920
    # Should return a new hash without the given keys.
921 922 923
    assert_equal expected, original.except(:c)
    assert_not_equal expected, original

E
Edward Tsech 已提交
924
    # Should replace the hash without the given keys.
925 926 927
    assert_equal expected, original.except!(:c)
    assert_equal expected, original
  end
928

929 930 931
  def test_except_with_more_than_one_argument
    original = { :a => 'x', :b => 'y', :c => 10 }
    expected = { :a => 'x' }
932

933
    assert_equal expected, original.except(:b, :c)
934 935 936

    assert_equal expected, original.except!(:b, :c)
    assert_equal expected, original
937 938
  end

939 940 941 942
  def test_except_with_original_frozen
    original = { :a => 'x', :b => 'y' }
    original.freeze
    assert_nothing_raised { original.except(:a) }
943 944

    assert_raise(RuntimeError) { original.except!(:a) }
945 946
  end

947 948 949 950
  def test_except_with_mocha_expectation_on_original
    original = { :a => 'x', :b => 'y' }
    original.expects(:delete).never
    original.except(:a)
951
  end
952 953 954 955

  def test_compact
    hash_contain_nil_value = @symbols.merge(z: nil)
    hash_with_only_nil_values = { a: nil, b: nil }
956

957 958 959
    h = hash_contain_nil_value.dup
    assert_equal(@symbols, h.compact)
    assert_equal(hash_contain_nil_value, h)
960

961 962 963 964 965 966 967 968
    h = hash_with_only_nil_values.dup
    assert_equal({}, h.compact)
    assert_equal(hash_with_only_nil_values, h)
  end

  def test_compact!
    hash_contain_nil_value = @symbols.merge(z: nil)
    hash_with_only_nil_values = { a: nil, b: nil }
969

970 971 972
    h = hash_contain_nil_value.dup
    assert_equal(@symbols, h.compact!)
    assert_equal(@symbols, h)
973

974 975 976 977
    h = hash_with_only_nil_values.dup
    assert_equal({}, h.compact!)
    assert_equal({}, h)
  end
978 979 980 981 982 983

  def test_new_with_to_hash_conversion
    hash = HashWithIndifferentAccess.new(HashByConversion.new(a: 1))
    assert hash.key?('a')
    assert_equal 1, hash[:a]
  end
984
end
985

986 987 988 989 990 991 992 993 994 995 996
class IWriteMyOwnXML
  def to_xml(options = {})
    options[:indent] ||= 2
    xml = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
    xml.instruct! unless options[:skip_instruct]
    xml.level_one do
      xml.tag!(:second_level, 'content')
    end
  end
end

997
class HashExtToParamTests < ActiveSupport::TestCase
998 999 1000 1001 1002 1003 1004 1005 1006 1007
  class ToParam < String
    def to_param
      "#{self}-1"
    end
  end

  def test_string_hash
    assert_equal '', {}.to_param
    assert_equal 'hello=world', { :hello => "world" }.to_param
    assert_equal 'hello=10', { "hello" => 10 }.to_param
1008
    assert_equal 'hello=world&say_bye=true', {:hello => "world", "say_bye" => true}.to_param
1009 1010 1011
  end

  def test_number_hash
1012
    assert_equal '10=20&30=40&50=60', {10 => 20, 30 => 40, 50 => 60}.to_param
1013 1014 1015
  end

  def test_to_param_hash
1016
    assert_equal 'custom-1=param-1&custom2-1=param2-1', {ToParam.new('custom') => ToParam.new('param'), ToParam.new('custom2') => ToParam.new('param2')}.to_param
1017
  end
1018 1019 1020 1021

  def test_to_param_hash_escapes_its_keys_and_values
    assert_equal 'param+1=A+string+with+%2F+characters+%26+that+should+be+%3F+escaped', { 'param 1' => 'A string with / characters & that should be ? escaped' }.to_param
  end
1022

1023
  def test_to_param_orders_by_key_in_ascending_order
V
Vishnu Atrai 已提交
1024
    assert_equal 'a=2&b=1&c=0', Hash[*%w(b 1 c 0 a 2)].to_param
1025
  end
1026 1027
end

1028
class HashToXmlTest < ActiveSupport::TestCase
1029 1030 1031 1032
  def setup
    @xml_options = { :root => :person, :skip_instruct => true, :indent => 0 }
  end

1033
  def test_one_level
1034 1035 1036 1037
    xml = { :name => "David", :street => "Paulina" }.to_xml(@xml_options)
    assert_equal "<person>", xml.first(8)
    assert xml.include?(%(<street>Paulina</street>))
    assert xml.include?(%(<name>David</name>))
1038 1039
  end

1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053
  def test_one_level_dasherize_false
    xml = { :name => "David", :street_name => "Paulina" }.to_xml(@xml_options.merge(:dasherize => false))
    assert_equal "<person>", xml.first(8)
    assert xml.include?(%(<street_name>Paulina</street_name>))
    assert xml.include?(%(<name>David</name>))
  end

  def test_one_level_dasherize_true
    xml = { :name => "David", :street_name => "Paulina" }.to_xml(@xml_options.merge(:dasherize => true))
    assert_equal "<person>", xml.first(8)
    assert xml.include?(%(<street-name>Paulina</street-name>))
    assert xml.include?(%(<name>David</name>))
  end

1054 1055 1056 1057 1058 1059 1060
  def test_one_level_camelize_true
    xml = { :name => "David", :street_name => "Paulina" }.to_xml(@xml_options.merge(:camelize => true))
    assert_equal "<Person>", xml.first(8)
    assert xml.include?(%(<StreetName>Paulina</StreetName>))
    assert xml.include?(%(<Name>David</Name>))
  end

1061 1062 1063 1064 1065 1066 1067
  def test_one_level_camelize_lower
    xml = { :name => "David", :street_name => "Paulina" }.to_xml(@xml_options.merge(:camelize => :lower))
    assert_equal "<person>", xml.first(8)
    assert xml.include?(%(<streetName>Paulina</streetName>))
    assert xml.include?(%(<name>David</name>))
  end

1068
  def test_one_level_with_types
1069
    xml = { :name => "David", :street => "Paulina", :age => 26, :age_in_millis => 820497600000, :moved_on => Date.new(2005, 11, 15), :resident => :yes }.to_xml(@xml_options)
1070 1071 1072 1073
    assert_equal "<person>", xml.first(8)
    assert xml.include?(%(<street>Paulina</street>))
    assert xml.include?(%(<name>David</name>))
    assert xml.include?(%(<age type="integer">26</age>))
1074
    assert xml.include?(%(<age-in-millis type="integer">820497600000</age-in-millis>))
1075
    assert xml.include?(%(<moved-on type="date">2005-11-15</moved-on>))
1076
    assert xml.include?(%(<resident type="symbol">yes</resident>))
1077 1078 1079
  end

  def test_one_level_with_nils
1080 1081 1082 1083
    xml = { :name => "David", :street => "Paulina", :age => nil }.to_xml(@xml_options)
    assert_equal "<person>", xml.first(8)
    assert xml.include?(%(<street>Paulina</street>))
    assert xml.include?(%(<name>David</name>))
1084
    assert xml.include?(%(<age nil="true"/>))
1085 1086 1087 1088 1089 1090 1091
  end

  def test_one_level_with_skipping_types
    xml = { :name => "David", :street => "Paulina", :age => nil }.to_xml(@xml_options.merge(:skip_types => true))
    assert_equal "<person>", xml.first(8)
    assert xml.include?(%(<street>Paulina</street>))
    assert xml.include?(%(<name>David</name>))
1092
    assert xml.include?(%(<age nil="true"/>))
1093 1094
  end

1095
  def test_one_level_with_yielding
1096 1097
    xml = { :name => "David", :street => "Paulina" }.to_xml(@xml_options) do |x|
      x.creator("Rails")
1098 1099 1100 1101 1102 1103 1104 1105
    end

    assert_equal "<person>", xml.first(8)
    assert xml.include?(%(<street>Paulina</street>))
    assert xml.include?(%(<name>David</name>))
    assert xml.include?(%(<creator>Rails</creator>))
  end

1106
  def test_two_levels
1107 1108 1109 1110
    xml = { :name => "David", :address => { :street => "Paulina" } }.to_xml(@xml_options)
    assert_equal "<person>", xml.first(8)
    assert xml.include?(%(<address><street>Paulina</street></address>))
    assert xml.include?(%(<name>David</name>))
1111
  end
1112 1113 1114 1115 1116 1117 1118

  def test_two_levels_with_second_level_overriding_to_xml
    xml = { :name => "David", :address => { :street => "Paulina" }, :child => IWriteMyOwnXML.new }.to_xml(@xml_options)
    assert_equal "<person>", xml.first(8)
    assert xml.include?(%(<address><street>Paulina</street></address>))
    assert xml.include?(%(<level_one><second_level>content</second_level></level_one>))
  end
1119

1120 1121 1122
  def test_two_levels_with_array
    xml = { :name => "David", :addresses => [{ :street => "Paulina" }, { :street => "Evergreen" }] }.to_xml(@xml_options)
    assert_equal "<person>", xml.first(8)
1123
    assert xml.include?(%(<addresses type="array"><address>))
1124 1125 1126 1127
    assert xml.include?(%(<address><street>Paulina</street></address>))
    assert xml.include?(%(<address><street>Evergreen</street></address>))
    assert xml.include?(%(<name>David</name>))
  end
1128

1129 1130
  def test_three_levels_with_array
    xml = { :name => "David", :addresses => [{ :streets => [ { :name => "Paulina" }, { :name => "Paulina" } ] } ] }.to_xml(@xml_options)
1131
    assert xml.include?(%(<addresses type="array"><address><streets type="array"><street><name>))
1132
  end
1133

1134 1135 1136 1137 1138
  def test_timezoned_attributes
    xml = {
      :created_at => Time.utc(1999,2,2),
      :local_created_at => Time.utc(1999,2,2).in_time_zone('Eastern Time (US & Canada)')
    }.to_xml(@xml_options)
1139 1140
    assert_match %r{<created-at type=\"dateTime\">1999-02-02T00:00:00Z</created-at>}, xml
    assert_match %r{<local-created-at type=\"dateTime\">1999-02-01T19:00:00-05:00</local-created-at>}, xml
1141 1142
  end

1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191
  def test_multiple_records_from_xml_with_attributes_other_than_type_ignores_them_without_exploding
    topics_xml = <<-EOT
      <topics type="array" page="1" page-count="1000" per-page="2">
        <topic>
          <title>The First Topic</title>
          <author-name>David</author-name>
          <id type="integer">1</id>
          <approved type="boolean">false</approved>
          <replies-count type="integer">0</replies-count>
          <replies-close-in type="integer">2592000000</replies-close-in>
          <written-on type="date">2003-07-16</written-on>
          <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
          <content>Have a nice day</content>
          <author-email-address>david@loudthinking.com</author-email-address>
          <parent-id nil="true"></parent-id>
        </topic>
        <topic>
          <title>The Second Topic</title>
          <author-name>Jason</author-name>
          <id type="integer">1</id>
          <approved type="boolean">false</approved>
          <replies-count type="integer">0</replies-count>
          <replies-close-in type="integer">2592000000</replies-close-in>
          <written-on type="date">2003-07-16</written-on>
          <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
          <content>Have a nice day</content>
          <author-email-address>david@loudthinking.com</author-email-address>
          <parent-id></parent-id>
        </topic>
      </topics>
    EOT

    expected_topic_hash = {
      :title => "The First Topic",
      :author_name => "David",
      :id => 1,
      :approved => false,
      :replies_count => 0,
      :replies_close_in => 2592000000,
      :written_on => Date.new(2003, 7, 16),
      :viewed_at => Time.utc(2003, 7, 16, 9, 28),
      :content => "Have a nice day",
      :author_email_address => "david@loudthinking.com",
      :parent_id => nil
    }.stringify_keys

    assert_equal expected_topic_hash, Hash.from_xml(topics_xml)["topics"].first
  end

1192 1193 1194 1195 1196 1197
  def test_single_record_from_xml
    topic_xml = <<-EOT
      <topic>
        <title>The First Topic</title>
        <author-name>David</author-name>
        <id type="integer">1</id>
1198
        <approved type="boolean"> true </approved>
1199
        <replies-count type="integer">0</replies-count>
1200
        <replies-close-in type="integer">2592000000</replies-close-in>
1201 1202 1203 1204
        <written-on type="date">2003-07-16</written-on>
        <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
        <author-email-address>david@loudthinking.com</author-email-address>
        <parent-id></parent-id>
1205 1206
        <ad-revenue type="decimal">1.5</ad-revenue>
        <optimum-viewing-angle type="float">135</optimum-viewing-angle>
1207 1208
      </topic>
    EOT
1209

1210 1211 1212 1213
    expected_topic_hash = {
      :title => "The First Topic",
      :author_name => "David",
      :id => 1,
1214
      :approved => true,
1215
      :replies_count => 0,
1216
      :replies_close_in => 2592000000,
1217 1218 1219
      :written_on => Date.new(2003, 7, 16),
      :viewed_at => Time.utc(2003, 7, 16, 9, 28),
      :author_email_address => "david@loudthinking.com",
1220 1221
      :parent_id => nil,
      :ad_revenue => BigDecimal("1.50"),
1222
      :optimum_viewing_angle => 135.0,
1223
    }.stringify_keys
1224

1225
    assert_equal expected_topic_hash, Hash.from_xml(topic_xml)["topic"]
1226 1227
  end

1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240
  def test_single_record_from_xml_with_nil_values
    topic_xml = <<-EOT
      <topic>
        <title></title>
        <id type="integer"></id>
        <approved type="boolean"></approved>
        <written-on type="date"></written-on>
        <viewed-at type="datetime"></viewed-at>
        <parent-id></parent-id>
      </topic>
    EOT

    expected_topic_hash = {
1241
      :title      => nil,
1242 1243 1244
      :id         => nil,
      :approved   => nil,
      :written_on => nil,
1245
      :viewed_at  => nil,
1246 1247 1248
      :parent_id  => nil
    }.stringify_keys

1249
    assert_equal expected_topic_hash, Hash.from_xml(topic_xml)["topic"]
1250 1251
  end

1252 1253
  def test_multiple_records_from_xml
    topics_xml = <<-EOT
1254
      <topics type="array">
1255 1256 1257 1258 1259 1260
        <topic>
          <title>The First Topic</title>
          <author-name>David</author-name>
          <id type="integer">1</id>
          <approved type="boolean">false</approved>
          <replies-count type="integer">0</replies-count>
1261
          <replies-close-in type="integer">2592000000</replies-close-in>
1262 1263 1264 1265
          <written-on type="date">2003-07-16</written-on>
          <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
          <content>Have a nice day</content>
          <author-email-address>david@loudthinking.com</author-email-address>
1266
          <parent-id nil="true"></parent-id>
1267 1268 1269 1270 1271 1272 1273
        </topic>
        <topic>
          <title>The Second Topic</title>
          <author-name>Jason</author-name>
          <id type="integer">1</id>
          <approved type="boolean">false</approved>
          <replies-count type="integer">0</replies-count>
1274
          <replies-close-in type="integer">2592000000</replies-close-in>
1275 1276 1277 1278 1279 1280 1281 1282
          <written-on type="date">2003-07-16</written-on>
          <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
          <content>Have a nice day</content>
          <author-email-address>david@loudthinking.com</author-email-address>
          <parent-id></parent-id>
        </topic>
      </topics>
    EOT
1283

1284 1285 1286 1287 1288 1289
    expected_topic_hash = {
      :title => "The First Topic",
      :author_name => "David",
      :id => 1,
      :approved => false,
      :replies_count => 0,
1290
      :replies_close_in => 2592000000,
1291 1292 1293 1294 1295 1296
      :written_on => Date.new(2003, 7, 16),
      :viewed_at => Time.utc(2003, 7, 16, 9, 28),
      :content => "Have a nice day",
      :author_email_address => "david@loudthinking.com",
      :parent_id => nil
    }.stringify_keys
1297

1298
    assert_equal expected_topic_hash, Hash.from_xml(topics_xml)["topics"].first
1299
  end
1300

1301 1302 1303
  def test_single_record_from_xml_with_attributes_other_than_type
    topic_xml = <<-EOT
    <rsp stat="ok">
1304 1305 1306
      <photos page="1" pages="1" perpage="100" total="16">
        <photo id="175756086" owner="55569174@N00" secret="0279bf37a1" server="76" title="Colored Pencil PhotoBooth Fun" ispublic="1" isfriend="0" isfamily="0"/>
      </photos>
1307 1308
    </rsp>
    EOT
1309

1310 1311 1312 1313 1314 1315 1316 1317 1318 1319
    expected_topic_hash = {
      :id => "175756086",
      :owner => "55569174@N00",
      :secret => "0279bf37a1",
      :server => "76",
      :title => "Colored Pencil PhotoBooth Fun",
      :ispublic => "1",
      :isfriend => "0",
      :isfamily => "0",
    }.stringify_keys
1320

1321
    assert_equal expected_topic_hash, Hash.from_xml(topic_xml)["rsp"]["photos"]["photo"]
1322
  end
1323

1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339
  def test_all_caps_key_from_xml
    test_xml = <<-EOT
      <ABC3XYZ>
        <TEST>Lorem Ipsum</TEST>
      </ABC3XYZ>
    EOT

    expected_hash = {
      "ABC3XYZ" => {
        "TEST" => "Lorem Ipsum"
      }
    }

    assert_equal expected_hash, Hash.from_xml(test_xml)
  end

1340 1341 1342 1343 1344 1345 1346 1347 1348 1349
  def test_empty_array_from_xml
    blog_xml = <<-XML
      <blog>
        <posts type="array"></posts>
      </blog>
    XML
    expected_blog_hash = {"blog" => {"posts" => []}}
    assert_equal expected_blog_hash, Hash.from_xml(blog_xml)
  end

1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360
  def test_empty_array_with_whitespace_from_xml
    blog_xml = <<-XML
      <blog>
        <posts type="array">
        </posts>
      </blog>
    XML
    expected_blog_hash = {"blog" => {"posts" => []}}
    assert_equal expected_blog_hash, Hash.from_xml(blog_xml)
  end

1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384
  def test_array_with_one_entry_from_xml
    blog_xml = <<-XML
      <blog>
        <posts type="array">
          <post>a post</post>
        </posts>
      </blog>
    XML
    expected_blog_hash = {"blog" => {"posts" => ["a post"]}}
    assert_equal expected_blog_hash, Hash.from_xml(blog_xml)
  end

  def test_array_with_multiple_entries_from_xml
    blog_xml = <<-XML
      <blog>
        <posts type="array">
          <post>a post</post>
          <post>another post</post>
        </posts>
      </blog>
    XML
    expected_blog_hash = {"blog" => {"posts" => ["a post", "another post"]}}
    assert_equal expected_blog_hash, Hash.from_xml(blog_xml)
  end
1385

1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413
  def test_file_from_xml
    blog_xml = <<-XML
      <blog>
        <logo type="file" name="logo.png" content_type="image/png">
        </logo>
      </blog>
    XML
    hash = Hash.from_xml(blog_xml)
    assert hash.has_key?('blog')
    assert hash['blog'].has_key?('logo')

    file = hash['blog']['logo']
    assert_equal 'logo.png', file.original_filename
    assert_equal 'image/png', file.content_type
  end

  def test_file_from_xml_with_defaults
    blog_xml = <<-XML
      <blog>
        <logo type="file">
        </logo>
      </blog>
    XML
    file = Hash.from_xml(blog_xml)['blog']['logo']
    assert_equal 'untitled', file.original_filename
    assert_equal 'application/octet-stream', file.content_type
  end

1414 1415 1416 1417 1418 1419 1420 1421
  def test_tag_with_attrs_and_whitespace
    xml = <<-XML
      <blog name="bacon is the best">
      </blog>
    XML
    hash = Hash.from_xml(xml)
    assert_equal "bacon is the best", hash['blog']['name']
  end
1422

1423
  def test_empty_cdata_from_xml
1424
    xml = "<data><![CDATA[]]></data>"
1425

1426
    assert_equal "", Hash.from_xml(xml)["data"]
1427
  end
1428

1429 1430 1431 1432 1433 1434 1435 1436 1437
  def test_xsd_like_types_from_xml
    bacon_xml = <<-EOT
    <bacon>
      <weight type="double">0.5</weight>
      <price type="decimal">12.50</price>
      <chunky type="boolean"> 1 </chunky>
      <expires-at type="dateTime">2007-12-25T12:34:56+0000</expires-at>
      <notes type="string"></notes>
      <illustration type="base64Binary">YmFiZS5wbmc=</illustration>
1438
      <caption type="binary" encoding="base64">VGhhdCdsbCBkbywgcGlnLg==</caption>
1439 1440 1441 1442 1443 1444 1445 1446 1447
    </bacon>
    EOT

    expected_bacon_hash = {
      :weight => 0.5,
      :chunky => true,
      :price => BigDecimal("12.50"),
      :expires_at => Time.utc(2007,12,25,12,34,56),
      :notes => "",
1448 1449
      :illustration => "babe.png",
      :caption => "That'll do, pig."
1450 1451 1452 1453
    }.stringify_keys

    assert_equal expected_bacon_hash, Hash.from_xml(bacon_xml)["bacon"]
  end
1454

1455 1456 1457 1458 1459
  def test_type_trickles_through_when_unknown
    product_xml = <<-EOT
    <product>
      <weight type="double">0.5</weight>
      <image type="ProductImage"><filename>image.gif</filename></image>
1460

1461 1462 1463 1464 1465 1466 1467 1468
    </product>
    EOT

    expected_product_hash = {
      :weight => 0.5,
      :image => {'type' => 'ProductImage', 'filename' => 'image.gif' },
    }.stringify_keys

1469
    assert_equal expected_product_hash, Hash.from_xml(product_xml)["product"]
1470
  end
1471

1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493
  def test_from_xml_raises_on_disallowed_type_attributes
    assert_raise ActiveSupport::XMLConverter::DisallowedType do
      Hash.from_xml '<product><name type="foo">value</name></product>', %w(foo)
    end
  end

  def test_from_xml_disallows_symbol_and_yaml_types_by_default
    assert_raise ActiveSupport::XMLConverter::DisallowedType do
      Hash.from_xml '<product><name type="symbol">value</name></product>'
    end

    assert_raise ActiveSupport::XMLConverter::DisallowedType do
      Hash.from_xml '<product><name type="yaml">value</name></product>'
    end
  end

  def test_from_trusted_xml_allows_symbol_and_yaml_types
    expected = { 'product' => { 'name' => :value }}
    assert_equal expected, Hash.from_trusted_xml('<product><name type="symbol">value</name></product>')
    assert_equal expected, Hash.from_trusted_xml('<product><name type="yaml">:value</name></product>')
  end

1494 1495 1496 1497
  def test_should_use_default_value_for_unknown_key
    hash_wia = HashWithIndifferentAccess.new(3)
    assert_equal 3, hash_wia[:new_key]
  end
1498

1499 1500 1501 1502
  def test_should_use_default_value_if_no_key_is_supplied
    hash_wia = HashWithIndifferentAccess.new(3)
    assert_equal 3, hash_wia.default
  end
1503

1504 1505 1506 1507
  def test_should_nil_if_no_default_value_is_supplied
    hash_wia = HashWithIndifferentAccess.new
    assert_nil hash_wia.default
  end
1508

1509
  def test_should_return_dup_for_with_indifferent_access
1510 1511
    hash_wia = HashWithIndifferentAccess.new
    assert_equal hash_wia, hash_wia.with_indifferent_access
1512
    assert_not_same hash_wia, hash_wia.with_indifferent_access
1513 1514
  end

1515 1516 1517 1518 1519
  def test_should_copy_the_default_value_when_converting_to_hash_with_indifferent_access
    hash = Hash.new(3)
    hash_wia = hash.with_indifferent_access
    assert_equal 3, hash_wia.default
  end
1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530

  # The XML builder seems to fail miserably when trying to tag something
  # with the same name as a Kernel method (throw, test, loop, select ...)
  def test_kernel_method_names_to_xml
    hash     = { :throw => { :ball => 'red' } }
    expected = '<person><throw><ball>red</ball></throw></person>'

    assert_nothing_raised do
      assert_equal expected, hash.to_xml(@xml_options)
    end
  end
1531 1532

  def test_empty_string_works_for_typecast_xml_value
1533
    assert_nothing_raised do
1534
      ActiveSupport::XMLConverter.new("").to_h
1535 1536
    end
  end
1537

1538
  def test_escaping_to_xml
1539 1540
    hash = {
      :bare_string        => 'First & Last Name',
1541 1542
      :pre_escaped_string => 'First &amp; Last Name'
    }.stringify_keys
1543

1544 1545 1546
    expected_xml = '<person><bare-string>First &amp; Last Name</bare-string><pre-escaped-string>First &amp;amp; Last Name</pre-escaped-string></person>'
    assert_equal expected_xml, hash.to_xml(@xml_options)
  end
1547

1548 1549
  def test_unescaping_from_xml
    xml_string = '<person><bare-string>First &amp; Last Name</bare-string><pre-escaped-string>First &amp;amp; Last Name</pre-escaped-string></person>'
1550 1551
    expected_hash = {
      :bare_string        => 'First & Last Name',
1552 1553 1554 1555
      :pre_escaped_string => 'First &amp; Last Name'
    }.stringify_keys
    assert_equal expected_hash, Hash.from_xml(xml_string)['person']
  end
1556

1557
  def test_roundtrip_to_xml_from_xml
1558 1559
    hash = {
      :bare_string        => 'First & Last Name',
1560 1561 1562 1563 1564
      :pre_escaped_string => 'First &amp; Last Name'
    }.stringify_keys

    assert_equal hash, Hash.from_xml(hash.to_xml(@xml_options))['person']
  end
1565

1566 1567 1568 1569 1570 1571 1572 1573 1574 1575
  def test_datetime_xml_type_with_utc_time
    alert_xml = <<-XML
      <alert>
        <alert_at type="datetime">2008-02-10T15:30:45Z</alert_at>
      </alert>
    XML
    alert_at = Hash.from_xml(alert_xml)['alert']['alert_at']
    assert alert_at.utc?
    assert_equal Time.utc(2008, 2, 10, 15, 30, 45), alert_at
  end
1576

1577 1578 1579 1580 1581 1582 1583 1584 1585 1586
  def test_datetime_xml_type_with_non_utc_time
    alert_xml = <<-XML
      <alert>
        <alert_at type="datetime">2008-02-10T10:30:45-05:00</alert_at>
      </alert>
    XML
    alert_at = Hash.from_xml(alert_xml)['alert']['alert_at']
    assert alert_at.utc?
    assert_equal Time.utc(2008, 2, 10, 15, 30, 45), alert_at
  end
1587

1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602
  def test_datetime_xml_type_with_far_future_date
    alert_xml = <<-XML
      <alert>
        <alert_at type="datetime">2050-02-10T15:30:45Z</alert_at>
      </alert>
    XML
    alert_at = Hash.from_xml(alert_xml)['alert']['alert_at']
    assert alert_at.utc?
    assert_equal 2050,  alert_at.year
    assert_equal 2,     alert_at.month
    assert_equal 10,    alert_at.day
    assert_equal 15,    alert_at.hour
    assert_equal 30,    alert_at.min
    assert_equal 45,    alert_at.sec
  end
1603 1604 1605 1606 1607 1608 1609

  def test_to_xml_dups_options
    options = {:skip_instruct => true}
    {}.to_xml(options)
    # :builder, etc, shouldn't be added to options
    assert_equal({:skip_instruct => true}, options)
  end
1610

1611
  def test_expansion_count_is_limited
J
Jeremy Kemper 已提交
1612 1613 1614 1615 1616 1617 1618 1619
    expected =
      case ActiveSupport::XmlMini.backend.name
      when 'ActiveSupport::XmlMini_REXML';        RuntimeError
      when 'ActiveSupport::XmlMini_Nokogiri';     Nokogiri::XML::SyntaxError
      when 'ActiveSupport::XmlMini_NokogiriSAX';  RuntimeError
      when 'ActiveSupport::XmlMini_LibXML';       LibXML::XML::Error
      when 'ActiveSupport::XmlMini_LibXMLSAX';    LibXML::XML::Error
      end
1620

1621
    assert_raise expected do
1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639
      attack_xml = <<-EOT
      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE member [
        <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;">
        <!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;">
        <!ENTITY c "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;">
        <!ENTITY d "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;">
        <!ENTITY e "&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;">
        <!ENTITY f "&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;">
        <!ENTITY g "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
      ]>
      <member>
      &a;
      </member>
      EOT
      Hash.from_xml(attack_xml)
    end
  end
J
Jeremy Kemper 已提交
1640
end