inheritance_test.rb 16.5 KB
Newer Older
1
require 'cases/helper'
2
require 'models/author'
J
Jeremy Kemper 已提交
3
require 'models/company'
4 5
require 'models/person'
require 'models/post'
J
Jeremy Kemper 已提交
6 7
require 'models/project'
require 'models/subscriber'
8
require 'models/vegetables'
9
require 'models/shop'
D
Initial  
David Heinemeier Hansson 已提交
10

11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
module InheritanceTestHelper
  def with_store_full_sti_class(&block)
    assign_store_full_sti_class true, &block
  end

  def without_store_full_sti_class(&block)
    assign_store_full_sti_class false, &block
  end

  def assign_store_full_sti_class(flag)
    old_store_full_sti_class = ActiveRecord::Base.store_full_sti_class
    ActiveRecord::Base.store_full_sti_class = flag
    yield
  ensure
    ActiveRecord::Base.store_full_sti_class = old_store_full_sti_class
  end
end

29
class InheritanceTest < ActiveRecord::TestCase
30
  include InheritanceTestHelper
31
  fixtures :companies, :projects, :subscribers, :accounts, :vegetables
32 33

  def test_class_with_store_full_sti_class_returns_full_name
34 35 36
    with_store_full_sti_class do
      assert_equal 'Namespaced::Company', Namespaced::Company.sti_name
    end
37 38
  end

39
  def test_class_with_blank_sti_name
40
    company = Company.first
A
Aaron Patterson 已提交
41
    company = company.dup
42
    company.extend(Module.new {
43
      def _read_attribute(name)
44 45 46 47 48
        return '  ' if name == 'type'
        super
      end
    })
    company.save!
J
Jon Leighton 已提交
49
    company = Company.all.to_a.find { |x| x.id == company.id }
50 51 52
    assert_equal '  ', company.type
  end

53
  def test_class_without_store_full_sti_class_returns_demodulized_name
54 55 56
    without_store_full_sti_class do
      assert_equal 'Company', Namespaced::Company.sti_name
    end
57 58
  end

59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
  def test_compute_type_success
    assert_equal Author, ActiveRecord::Base.send(:compute_type, 'Author')
  end

  def test_compute_type_nonexistent_constant
    e = assert_raises NameError do
      ActiveRecord::Base.send :compute_type, 'NonexistentModel'
    end
    assert_equal 'uninitialized constant ActiveRecord::Base::NonexistentModel', e.message
    assert_equal 'ActiveRecord::Base::NonexistentModel', e.name
  end

  def test_compute_type_no_method_error
    ActiveSupport::Dependencies.stub(:safe_constantize, proc{ raise NoMethodError }) do
      assert_raises NoMethodError do
        ActiveRecord::Base.send :compute_type, 'InvalidModel'
      end
    end
  end

  def test_compute_type_on_undefined_method
    error = nil
    begin
      Class.new(Author) do
        alias_method :foo, :bar
      end
    rescue => e
      error = e
    end

    ActiveSupport::Dependencies.stub(:safe_constantize, proc{ raise e }) do

      exception = assert_raises NameError do
        ActiveRecord::Base.send :compute_type, 'InvalidModel'
      end
      assert_equal error.message, exception.message
    end
  end

  def test_compute_type_argument_error
    ActiveSupport::Dependencies.stub(:safe_constantize, proc{ raise ArgumentError }) do
      assert_raises ArgumentError do
        ActiveRecord::Base.send :compute_type, 'InvalidModel'
      end
    end
  end

106
  def test_should_store_demodulized_class_name_with_store_full_sti_class_option_disabled
107 108 109 110
    without_store_full_sti_class do
      item = Namespaced::Company.new
      assert_equal 'Company', item[:type]
    end
111
  end
112

113
  def test_should_store_full_class_name_with_store_full_sti_class_option_enabled
114 115 116 117
    with_store_full_sti_class do
      item = Namespaced::Company.new
      assert_equal 'Namespaced::Company', item[:type]
    end
118
  end
119

120
  def test_different_namespace_subclass_should_load_correctly_with_store_full_sti_class_option
121 122 123 124 125
    with_store_full_sti_class do
      item = Namespaced::Company.create name: "Wolverine 2"
      assert_not_nil Company.find(item.id)
      assert_not_nil Namespaced::Company.find(item.id)
    end
126
  end
D
Initial  
David Heinemeier Hansson 已提交
127

128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
  def test_descends_from_active_record
    assert !ActiveRecord::Base.descends_from_active_record?

    # Abstract subclass of AR::Base.
    assert LoosePerson.descends_from_active_record?

    # Concrete subclass of an abstract class.
    assert LooseDescendant.descends_from_active_record?

    # Concrete subclass of AR::Base.
    assert TightPerson.descends_from_active_record?

    # Concrete subclass of a concrete class but has no type column.
    assert TightDescendant.descends_from_active_record?

    # Concrete subclass of AR::Base.
    assert Post.descends_from_active_record?

    # Abstract subclass of a concrete class which has a type column.
    # This is pathological, as you'll never have Sub < Abstract < Concrete.
    assert !StiPost.descends_from_active_record?

    # Concrete subclasses an abstract class which has a type column.
    assert !SubStiPost.descends_from_active_record?
  end

154
  def test_company_descends_from_active_record
155
    assert !ActiveRecord::Base.descends_from_active_record?
156 157 158 159 160
    assert AbstractCompany.descends_from_active_record?, 'AbstractCompany should descend from ActiveRecord::Base'
    assert Company.descends_from_active_record?, 'Company should descend from ActiveRecord::Base'
    assert !Class.new(Company).descends_from_active_record?, 'Company subclass should not descend from ActiveRecord::Base'
  end

161 162 163 164 165 166
  def test_abstract_class
    assert !ActiveRecord::Base.abstract_class?
    assert LoosePerson.abstract_class?
    assert !LooseDescendant.abstract_class?
  end

167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
  def test_inheritance_base_class
    assert_equal Post, Post.base_class
    assert_equal Post, SpecialPost.base_class
    assert_equal Post, StiPost.base_class
    assert_equal SubStiPost, SubStiPost.base_class
  end

  def test_abstract_inheritance_base_class
    assert_equal LoosePerson, LoosePerson.base_class
    assert_equal LooseDescendant, LooseDescendant.base_class
    assert_equal TightPerson, TightPerson.base_class
    assert_equal TightPerson, TightDescendant.base_class
  end

  def test_base_class_activerecord_error
J
Jon Leighton 已提交
182
    klass = Class.new { include ActiveRecord::Inheritance }
183 184 185
    assert_raise(ActiveRecord::ActiveRecordError) { klass.base_class }
  end

186
  def test_a_bad_type_column
187
    Company.connection.insert "INSERT INTO companies (id, #{QUOTED_TYPE}, name) VALUES(100, 'bad_class!', 'Not happening')"
188

189
    assert_raise(ActiveRecord::SubclassNotFound) { Company.find(100) }
D
Initial  
David Heinemeier Hansson 已提交
190 191 192
  end

  def test_inheritance_find
193 194 195 196
    assert_kind_of Firm, Company.find(1), "37signals should be a firm"
    assert_kind_of Firm, Firm.find(1), "37signals should be a firm"
    assert_kind_of Client, Company.find(2), "Summit should be a client"
    assert_kind_of Client, Client.find(2), "Summit should be a client"
D
Initial  
David Heinemeier Hansson 已提交
197
  end
J
Jeremy Kemper 已提交
198

D
Initial  
David Heinemeier Hansson 已提交
199
  def test_alt_inheritance_find
200 201 202 203
    assert_kind_of Cucumber, Vegetable.find(1)
    assert_kind_of Cucumber, Cucumber.find(1)
    assert_kind_of Cabbage, Vegetable.find(2)
    assert_kind_of Cabbage, Cabbage.find(2)
D
Initial  
David Heinemeier Hansson 已提交
204 205
  end

206 207 208 209 210 211 212
  def test_alt_becomes_works_with_sti
    vegetable = Vegetable.find(1)
    assert_kind_of Vegetable, vegetable
    cabbage = vegetable.becomes(Cabbage)
    assert_kind_of Cabbage, cabbage
  end

213 214 215 216 217 218
  def test_becomes_and_change_tracking_for_inheritance_columns
    cucumber = Vegetable.find(1)
    cabbage = cucumber.becomes!(Cabbage)
    assert_equal ['Cucumber', 'Cabbage'], cabbage.custom_type_change
  end

219 220 221 222 223 224 225 226 227 228 229
  def test_alt_becomes_bang_resets_inheritance_type_column
    vegetable = Vegetable.create!(name: "Red Pepper")
    assert_nil vegetable.custom_type

    cabbage = vegetable.becomes!(Cabbage)
    assert_equal "Cabbage", cabbage.custom_type

    vegetable = cabbage.becomes!(Vegetable)
    assert_nil cabbage.custom_type
  end

D
Initial  
David Heinemeier Hansson 已提交
230
  def test_inheritance_find_all
231
    companies = Company.all.merge!(:order => 'id').to_a
232 233
    assert_kind_of Firm, companies[0], "37signals should be a firm"
    assert_kind_of Client, companies[1], "Summit should be a client"
D
Initial  
David Heinemeier Hansson 已提交
234
  end
J
Jeremy Kemper 已提交
235

D
Initial  
David Heinemeier Hansson 已提交
236
  def test_alt_inheritance_find_all
237 238 239
    companies = Vegetable.all.merge!(:order => 'id').to_a
    assert_kind_of Cucumber, companies[0]
    assert_kind_of Cabbage, companies[1]
D
Initial  
David Heinemeier Hansson 已提交
240 241 242 243 244 245
  end

  def test_inheritance_save
    firm = Firm.new
    firm.name = "Next Angle"
    firm.save
J
Jeremy Kemper 已提交
246

D
Initial  
David Heinemeier Hansson 已提交
247
    next_angle = Company.find(firm.id)
248
    assert_kind_of Firm, next_angle, "Next Angle should be a firm"
D
Initial  
David Heinemeier Hansson 已提交
249
  end
J
Jeremy Kemper 已提交
250

D
Initial  
David Heinemeier Hansson 已提交
251
  def test_alt_inheritance_save
252 253 254 255 256
    cabbage = Cabbage.new(:name => 'Savoy')
    cabbage.save!

    savoy = Vegetable.find(cabbage.id)
    assert_kind_of Cabbage, savoy
D
Initial  
David Heinemeier Hansson 已提交
257 258
  end

259 260
  def test_inheritance_new_with_default_class
    company = Company.new
261
    assert_equal Company, company.class
262 263 264 265
  end

  def test_inheritance_new_with_base_class
    company = Company.new(:type => 'Company')
266
    assert_equal Company, company.class
267 268 269 270
  end

  def test_inheritance_new_with_subclass
    firm = Company.new(:type => 'Firm')
271
    assert_equal Firm, firm.class
272 273
  end

274 275 276 277
  def test_new_with_abstract_class
    e = assert_raises(NotImplementedError) do
      AbstractCompany.new
    end
278
    assert_equal("AbstractCompany is an abstract class and cannot be instantiated.", e.message)
279 280 281 282 283 284
  end

  def test_new_with_ar_base
    e = assert_raises(NotImplementedError) do
      ActiveRecord::Base.new
    end
285
    assert_equal("ActiveRecord::Base is an abstract class and cannot be instantiated.", e.message)
286 287
  end

288 289 290 291 292 293 294 295
  def test_new_with_invalid_type
    assert_raise(ActiveRecord::SubclassNotFound) { Company.new(:type => 'InvalidType') }
  end

  def test_new_with_unrelated_type
    assert_raise(ActiveRecord::SubclassNotFound) { Company.new(:type => 'Account') }
  end

296 297 298 299 300 301 302 303 304 305
  def test_new_with_unrelated_namespaced_type
    without_store_full_sti_class do
      e = assert_raises ActiveRecord::SubclassNotFound do
        Namespaced::Company.new(type: 'Firm')
      end

      assert_equal "Invalid single-table inheritance type: Namespaced::Firm is not a subclass of Namespaced::Company", e.message
    end
  end

306 307 308 309
  def test_new_with_complex_inheritance
    assert_nothing_raised { Client.new(type: 'VerySpecialClient') }
  end

310 311 312 313 314 315 316
  def test_new_without_storing_full_sti_class
    without_store_full_sti_class do
      item = Company.new(type: 'SpecialCo')
      assert_instance_of Company::SpecialCo, item
    end
  end

317 318 319 320 321 322 323 324 325 326 327
  def test_new_with_autoload_paths
    path = File.expand_path('../../models/autoloadable', __FILE__)
    ActiveSupport::Dependencies.autoload_paths << path

    firm = Company.new(:type => 'ExtraFirm')
    assert_equal ExtraFirm, firm.class
  ensure
    ActiveSupport::Dependencies.autoload_paths.reject! { |p| p == path }
    ActiveSupport::Dependencies.clear
  end

D
Initial  
David Heinemeier Hansson 已提交
328
  def test_inheritance_condition
329
    assert_equal 11, Company.count
330
    assert_equal 2, Firm.count
331
    assert_equal 5, Client.count
D
Initial  
David Heinemeier Hansson 已提交
332
  end
J
Jeremy Kemper 已提交
333

D
Initial  
David Heinemeier Hansson 已提交
334
  def test_alt_inheritance_condition
335 336 337
    assert_equal 4, Vegetable.count
    assert_equal 1, Cucumber.count
    assert_equal 3, Cabbage.count
D
Initial  
David Heinemeier Hansson 已提交
338 339 340
  end

  def test_finding_incorrect_type_data
341
    assert_raise(ActiveRecord::RecordNotFound) { Firm.find(2) }
D
Initial  
David Heinemeier Hansson 已提交
342 343
    assert_nothing_raised   { Firm.find(1) }
  end
J
Jeremy Kemper 已提交
344

D
Initial  
David Heinemeier Hansson 已提交
345
  def test_alt_finding_incorrect_type_data
346 347
    assert_raise(ActiveRecord::RecordNotFound) { Cucumber.find(2) }
    assert_nothing_raised   { Cucumber.find(1) }
D
Initial  
David Heinemeier Hansson 已提交
348 349 350 351
  end

  def test_update_all_within_inheritance
    Client.update_all "name = 'I am a client'"
J
Jon Leighton 已提交
352
    assert_equal "I am a client", Client.first.name
353
    # Order by added as otherwise Oracle tests were failing because of different order of results
354
    assert_equal "37signals", Firm.all.merge!(:order => "id").to_a.first.name
D
Initial  
David Heinemeier Hansson 已提交
355
  end
J
Jeremy Kemper 已提交
356

D
Initial  
David Heinemeier Hansson 已提交
357
  def test_alt_update_all_within_inheritance
358 359 360
    Cabbage.update_all "name = 'the cabbage'"
    assert_equal "the cabbage", Cabbage.first.name
    assert_equal ["my cucumber"], Cucumber.all.map(&:name).uniq
D
Initial  
David Heinemeier Hansson 已提交
361 362 363 364
  end

  def test_destroy_all_within_inheritance
    Client.destroy_all
365 366
    assert_equal 0, Client.count
    assert_equal 2, Firm.count
D
Initial  
David Heinemeier Hansson 已提交
367
  end
J
Jeremy Kemper 已提交
368

D
Initial  
David Heinemeier Hansson 已提交
369
  def test_alt_destroy_all_within_inheritance
370 371 372
    Cabbage.destroy_all
    assert_equal 0, Cabbage.count
    assert_equal 1, Cucumber.count
D
Initial  
David Heinemeier Hansson 已提交
373 374 375
  end

  def test_find_first_within_inheritance
376 377 378
    assert_kind_of Firm, Company.all.merge!(:where => "name = '37signals'").first
    assert_kind_of Firm, Firm.all.merge!(:where => "name = '37signals'").first
    assert_nil Client.all.merge!(:where => "name = '37signals'").first
D
Initial  
David Heinemeier Hansson 已提交
379
  end
J
Jeremy Kemper 已提交
380

D
Initial  
David Heinemeier Hansson 已提交
381
  def test_alt_find_first_within_inheritance
382 383 384
    assert_kind_of Cabbage, Vegetable.all.merge!(:where => "name = 'his cabbage'").first
    assert_kind_of Cabbage, Cabbage.all.merge!(:where => "name = 'his cabbage'").first
    assert_nil Cucumber.all.merge!(:where => "name = 'his cabbage'").first
D
Initial  
David Heinemeier Hansson 已提交
385 386 387 388
  end

  def test_complex_inheritance
    very_special_client = VerySpecialClient.create("name" => "veryspecial")
389
    assert_equal very_special_client, VerySpecialClient.where("name = 'veryspecial'").first
390 391 392 393
    assert_equal very_special_client, SpecialClient.all.merge!(:where => "name = 'veryspecial'").first
    assert_equal very_special_client, Company.all.merge!(:where => "name = 'veryspecial'").first
    assert_equal very_special_client, Client.all.merge!(:where => "name = 'veryspecial'").first
    assert_equal 1, Client.all.merge!(:where => "name = 'Summit'").to_a.size
D
Initial  
David Heinemeier Hansson 已提交
394 395 396 397
    assert_equal very_special_client, Client.find(very_special_client.id)
  end

  def test_alt_complex_inheritance
398 399 400 401 402 403 404
    king_cole = KingCole.create("name" => "uniform heads")
    assert_equal king_cole, KingCole.where("name = 'uniform heads'").first
    assert_equal king_cole, GreenCabbage.all.merge!(:where => "name = 'uniform heads'").first
    assert_equal king_cole, Cabbage.all.merge!(:where => "name = 'uniform heads'").first
    assert_equal king_cole, Vegetable.all.merge!(:where => "name = 'uniform heads'").first
    assert_equal 1, Cabbage.all.merge!(:where => "name = 'his cabbage'").to_a.size
    assert_equal king_cole, Cabbage.find(king_cole.id)
D
Initial  
David Heinemeier Hansson 已提交
405
  end
J
Jeremy Kemper 已提交
406

407
  def test_eager_load_belongs_to_something_inherited
408
    account = Account.all.merge!(:includes => :firm).find(1)
409
    assert account.association(:firm).loaded?, "association was not eager loaded"
410
  end
J
Jeremy Kemper 已提交
411

412 413
  def test_alt_eager_loading
    cabbage = RedCabbage.all.merge!(:includes => :seller).find(4)
414
    assert cabbage.association(:seller).loaded?, "association was not eager loaded"
415 416
  end

417 418
  def test_eager_load_belongs_to_primary_key_quoting
    con = Account.connection
419
    assert_sql(/#{con.quote_table_name('companies')}.#{con.quote_column_name('id')} = 1/) do
420
      Account.all.merge!(:includes => :firm).find(1)
421 422 423
    end
  end

424 425 426 427
  def test_inherits_custom_primary_key
    assert_equal Subscriber.primary_key, SpecialSubscriber.primary_key
  end

428
  def test_inheritance_without_mapping
429
    assert_kind_of SpecialSubscriber, SpecialSubscriber.find("webster132")
430
    assert_nothing_raised { s = SpecialSubscriber.new("name" => "And breaaaaathe!"); s.id = 'roger'; s.save }
431
  end
432

433
  def test_scope_inherited_properly
434 435
    assert_nothing_raised { Company.of_first_firm }
    assert_nothing_raised { Client.of_first_firm }
436 437
  end
end
438

439
class InheritanceComputeTypeTest < ActiveRecord::TestCase
440
  include InheritanceTestHelper
441 442 443
  fixtures :companies

  def setup
444
    ActiveSupport::Dependencies.log_activity = true
445 446
  end

G
Guo Xiang Tan 已提交
447
  teardown do
448
    ActiveSupport::Dependencies.log_activity = false
449 450 451 452 453
    self.class.const_remove :FirmOnTheFly rescue nil
    Firm.const_remove :FirmOnTheFly rescue nil
  end

  def test_instantiation_doesnt_try_to_require_corresponding_file
454 455 456 457
    without_store_full_sti_class do
      foo = Firm.first.clone
      foo.type = 'FirmOnTheFly'
      foo.save!
458

459 460
      # Should fail without FirmOnTheFly in the type condition.
      assert_raise(ActiveRecord::RecordNotFound) { Firm.find(foo.id) }
461

462 463 464
      # Nest FirmOnTheFly in the test case where Dependencies won't see it.
      self.class.const_set :FirmOnTheFly, Class.new(Firm)
      assert_raise(ActiveRecord::SubclassNotFound) { Firm.find(foo.id) }
465

466 467 468
      # Nest FirmOnTheFly in Firm where Dependencies will see it.
      # This is analogous to nesting models in a migration.
      Firm.const_set :FirmOnTheFly, Class.new(Firm)
469

470 471 472 473
      # And instantiate will find the existing constant rather than trying
      # to require firm_on_the_fly.
      assert_nothing_raised { assert_kind_of Firm::FirmOnTheFly, Firm.find(foo.id) }
    end
474
  end
475 476 477 478 479 480

  def test_sti_type_from_attributes_disabled_in_non_sti_class
    phone = Shop::Product::Type.new(name: 'Phone')
    product = Shop::Product.new(:type => phone)
    assert product.save
  end
481 482 483 484 485

  def test_inheritance_new_with_subclass_as_default
    original_type = Company.columns_hash["type"].default
    ActiveRecord::Base.connection.change_column_default :companies, :type, 'Firm'
    Company.reset_column_information
486 487

    firm = Company.new # without arguments
488 489
    assert_equal 'Firm', firm.type
    assert_instance_of Firm, firm
490 491

    firm = Company.new(firm_name: 'Shri Hans Plastic') # with arguments
492 493
    assert_equal 'Firm', firm.type
    assert_instance_of Firm, firm
494 495

    firm = Company.new(type: 'Client') # overwrite the default type
496 497 498 499 500 501
    assert_equal 'Client', firm.type
    assert_instance_of Client, firm
  ensure
    ActiveRecord::Base.connection.change_column_default :companies, :type, original_type
    Company.reset_column_information
  end
502
end