relations_test.rb 17.1 KB
Newer Older
1
require "cases/helper"
2 3
require 'models/tag'
require 'models/tagging'
4 5
require 'models/post'
require 'models/topic'
6
require 'models/comment'
7 8
require 'models/reply'
require 'models/author'
9
require 'models/comment'
10 11 12
require 'models/entrant'
require 'models/developer'
require 'models/company'
13
require 'models/bird'
14

E
Emilio Tagua 已提交
15
class RelationTest < ActiveRecord::TestCase
16 17
  fixtures :authors, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :posts, :comments,
    :taggings
18

19 20 21 22 23 24
  def test_scoped
    topics = Topic.scoped
    assert_kind_of ActiveRecord::Relation, topics
    assert_equal 4, topics.size
  end

25 26 27 28 29 30
  def test_scoped_all
    topics = Topic.scoped.all
    assert_kind_of Array, topics
    assert_no_queries { assert_equal 4, topics.size }
  end

P
Pratik Naik 已提交
31 32 33 34 35 36 37 38 39 40 41
  def test_loaded_all
    topics = Topic.scoped

    assert_queries(1) do
      2.times { assert_equal 4, topics.all.size }
    end

    assert topics.loaded?
  end

  def test_scoped_first
P
Pratik Naik 已提交
42
    topics = Topic.scoped.order('id ASC')
P
Pratik Naik 已提交
43 44 45 46 47 48 49 50 51

    assert_queries(1) do
      2.times { assert_equal "The First Topic", topics.first.title }
    end

    assert ! topics.loaded?
  end

  def test_loaded_first
P
Pratik Naik 已提交
52
    topics = Topic.scoped.order('id ASC')
P
Pratik Naik 已提交
53 54 55 56 57 58 59 60 61

    assert_queries(1) do
      topics.all # force load
      2.times { assert_equal "The First Topic", topics.first.title }
    end

    assert topics.loaded?
  end

62 63 64 65 66 67 68 69 70
  def test_reload
    topics = Topic.scoped

    assert_queries(1) do
      2.times { topics.to_a }
    end

    assert topics.loaded?

71 72
    original_size = topics.to_a.size
    Topic.create! :title => 'fake'
73

74 75 76
    assert_queries(1) { topics.reload }
    assert_equal original_size + 1, topics.size
    assert topics.loaded?
77 78
  end

79
  def test_finding_with_conditions
80 81
    assert_equal ["David"], Author.where(:name => 'David').map(&:name)
    assert_equal ['Mary'],  Author.where(["name = ?", 'Mary']).map(&:name)
82
    assert_equal ['Mary'],  Author.where("name = ?", 'Mary').map(&:name)
83 84 85
  end

  def test_finding_with_order
86
    topics = Topic.order('id')
87
    assert_equal 4, topics.to_a.size
88 89 90 91
    assert_equal topics(:first).title, topics.first.title
  end

  def test_finding_with_order_and_take
92
    entrants = Entrant.order("id ASC").limit(2).to_a
93

P
Pratik Naik 已提交
94 95
    assert_equal 2, entrants.size
    assert_equal entrants(:first).name, entrants.first.name
96 97 98
  end

  def test_finding_with_order_limit_and_offset
99
    entrants = Entrant.order("id ASC").limit(2).offset(1)
100

101
    assert_equal 2, entrants.to_a.size
P
Pratik Naik 已提交
102
    assert_equal entrants(:second).name, entrants.first.name
103

104
    entrants = Entrant.order("id ASC").limit(2).offset(2)
105
    assert_equal 1, entrants.to_a.size
P
Pratik Naik 已提交
106
    assert_equal entrants(:third).name, entrants.first.name
107 108 109
  end

  def test_finding_with_group
110
    developers = Developer.group("salary").select("salary").to_a
111 112 113 114
    assert_equal 4, developers.size
    assert_equal 4, developers.map(&:salary).uniq.size
  end

115 116
  def test_select_with_block
    even_ids = Developer.scoped.select {|d| d.id % 2 == 0 }.map(&:id)
117
    assert_equal [2, 4, 6, 8, 10], even_ids.sort
118 119
  end

120
  def test_finding_with_hash_conditions_on_joined_table
121
    firms = DependentFirm.joins(:account).where({:name => 'RailsCore', :accounts => { :credit_limit => 55..60 }}).to_a
122 123 124 125 126
    assert_equal 1, firms.size
    assert_equal companies(:rails_core), firms.first
  end

  def test_find_all_with_join
127
    developers_on_project_one = Developer.joins('LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id').
128
      where('project_id=1').to_a
129 130 131 132 133 134 135 136

    assert_equal 3, developers_on_project_one.length
    developer_names = developers_on_project_one.map { |d| d.name }
    assert developer_names.include?('David')
    assert developer_names.include?('Jamis')
  end

  def test_find_on_hash_conditions
137
    assert_equal Topic.find(:all, :conditions => {:approved => false}), Topic.where({ :approved => false }).to_a
138 139 140
  end

  def test_joins_with_string_array
141
    person_with_reader_and_post = Post.joins([
142 143 144 145 146 147
        "INNER JOIN categorizations ON categorizations.post_id = posts.id",
        "INNER JOIN categories ON categories.id = categorizations.category_id AND categories.type = 'SpecialCategory'"
      ]
    ).to_a
    assert_equal 1, person_with_reader_and_post.size
  end
148

149 150
  def test_scoped_responds_to_delegated_methods
    relation = Topic.scoped
151 152

    ["map", "uniq", "sort", "insert", "delete", "update"].each do |method|
153
      assert_respond_to relation, method, "Topic.scoped should respond to #{method.inspect}"
154 155 156
    end
  end

157 158 159 160 161 162 163
  def test_respond_to_private_arel_methods
    relation = Topic.scoped

    assert ! relation.respond_to?(:matching_attributes)
    assert relation.respond_to?(:matching_attributes, true)
  end

164 165 166 167
  def test_respond_to_dynamic_finders
    relation = Topic.scoped

    ["find_by_title", "find_by_title_and_author_name", "find_or_create_by_title", "find_or_initialize_by_title_and_author_name"].each do |method|
168
      assert_respond_to relation, method, "Topic.scoped should respond to #{method.inspect}"
169 170
    end
  end
171

172 173 174 175 176
  def test_respond_to_class_methods_and_named_scopes
    assert DeveloperOrderedBySalary.scoped.respond_to?(:all_ordered_by_name)
    assert Topic.scoped.respond_to?(:by_lifo)
  end

177
  def test_find_with_readonly_option
178 179
    Developer.scoped.each { |d| assert !d.readonly? }
    Developer.scoped.readonly.each { |d| assert d.readonly? }
180
  end
181 182

  def test_eager_association_loading_of_stis_with_multiple_references
183
    authors = Author.eager_load(:posts => { :special_comments => { :post => [ :special_comments, :very_special_comment ] } }).
184
      order('comments.body, very_special_comments_posts.body').where('posts.id = 4').to_a
185

186 187 188 189 190 191 192
    assert_equal [authors(:david)], authors
    assert_no_queries do
      authors.first.posts.first.special_comments.first.post.special_comments
      authors.first.posts.first.special_comments.first.post.very_special_comment
    end
  end

193
  def test_find_with_preloaded_associations
194
    assert_queries(2) do
195
      posts = Post.preload(:comments)
196
      assert posts.first.comments.first
197
    end
198

199
    assert_queries(2) do
200
      posts = Post.preload(:comments).to_a
201
      assert posts.first.comments.first
202
    end
203

204
    assert_queries(2) do
205
      posts = Post.preload(:author)
206
      assert posts.first.author
207
    end
208

209
    assert_queries(2) do
210
      posts = Post.preload(:author).to_a
211 212 213 214 215 216
      assert posts.first.author
    end

    assert_queries(3) do
      posts = Post.preload(:author, :comments).to_a
      assert posts.first.author
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
      assert posts.first.comments.first
    end
  end

  def test_find_with_included_associations
    assert_queries(2) do
      posts = Post.includes(:comments)
      assert posts.first.comments.first
    end

    assert_queries(2) do
      posts = Post.scoped.includes(:comments)
      assert posts.first.comments.first
    end

    assert_queries(2) do
      posts = Post.includes(:author)
      assert posts.first.author
    end

    assert_queries(3) do
      posts = Post.includes(:author, :comments).to_a
      assert posts.first.author
240
      assert posts.first.comments.first
241 242
    end
  end
243

P
Pratik Naik 已提交
244
  def test_default_scope_with_conditions_string
245
    assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeveloperCalledDavid.scoped.map(&:id).sort
246
    assert_nil DeveloperCalledDavid.create!.name
247 248 249
  end

  def test_default_scope_with_conditions_hash
250
    assert_equal Developer.find_all_by_name('Jamis').map(&:id).sort, DeveloperCalledJamis.scoped.map(&:id).sort
251 252 253
    assert_equal 'Jamis', DeveloperCalledJamis.create!.name
  end

254 255 256 257 258
  def test_default_scoping_finder_methods
    developers = DeveloperCalledDavid.order('id').map(&:id).sort
    assert_equal Developer.find_all_by_name('David').map(&:id).sort, developers
  end

259 260
  def test_loading_with_one_association
    posts = Post.preload(:comments)
261 262 263 264
    post = posts.find { |p| p.id == 1 }
    assert_equal 2, post.comments.size
    assert post.comments.include?(comments(:greetings))

265
    post = Post.where("posts.title = 'Welcome to the weblog'").preload(:comments).first
266 267 268
    assert_equal 2, post.comments.size
    assert post.comments.include?(comments(:greetings))

269
    posts = Post.preload(:last_comment)
270 271 272 273 274
    post = posts.find { |p| p.id == 1 }
    assert_equal Post.find(1).last_comment, post.last_comment
  end

  def test_loading_with_one_association_with_non_preload
275
    posts = Post.eager_load(:last_comment).order('comments.id DESC')
276 277 278
    post = posts.find { |p| p.id == 1 }
    assert_equal Post.find(1).last_comment, post.last_comment
  end
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297

  def test_dynamic_find_by_attributes
    david = authors(:david)
    author = Author.preload(:taggings).find_by_id(david.id)
    expected_taggings = taggings(:welcome_general, :thinking_general)

    assert_no_queries do
      assert_equal expected_taggings, author.taggings.uniq.sort_by { |t| t.id }
    end

    authors = Author.scoped
    assert_equal david, authors.find_by_id_and_name(david.id, david.name)
    assert_equal david, authors.find_by_id_and_name!(david.id, david.name)
  end

  def test_dynamic_find_by_attributes_bang
    author = Author.scoped.find_by_id!(authors(:david).id)
    assert_equal "David", author.name

P
Pratik Naik 已提交
298
    assert_raises(ActiveRecord::RecordNotFound) { Author.scoped.find_by_id_and_name!(20, 'invalid') }
299 300 301 302 303 304 305 306 307
  end

  def test_dynamic_find_all_by_attributes
    authors = Author.scoped

    davids = authors.find_all_by_name('David')
    assert_kind_of Array, davids
    assert_equal [authors(:david)], davids
  end
308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328

  def test_dynamic_find_or_initialize_by_attributes
    authors = Author.scoped

    lifo = authors.find_or_initialize_by_name('Lifo')
    assert_equal "Lifo", lifo.name
    assert lifo.new_record?

    assert_equal authors(:david), authors.find_or_initialize_by_name(:name => 'David')
  end

  def test_dynamic_find_or_create_by_attributes
    authors = Author.scoped

    lifo = authors.find_or_create_by_name('Lifo')
    assert_equal "Lifo", lifo.name
    assert ! lifo.new_record?

    assert_equal authors(:david), authors.find_or_create_by_name(:name => 'David')
  end

P
Pratik Naik 已提交
329 330 331 332 333 334
  def test_find_id
    authors = Author.scoped

    david = authors.find(authors(:david).id)
    assert_equal 'David', david.name

P
Pratik Naik 已提交
335
    assert_raises(ActiveRecord::RecordNotFound) { authors.where(:name => 'lifo').find('42') }
P
Pratik Naik 已提交
336
  end
337

P
Pratik Naik 已提交
338 339 340 341 342 343 344 345 346 347
  def test_find_ids
    authors = Author.order('id ASC')

    results = authors.find(authors(:david).id, authors(:mary).id)
    assert_kind_of Array, results
    assert_equal 2, results.size
    assert_equal 'David', results[0].name
    assert_equal 'Mary', results[1].name
    assert_equal results, authors.find([authors(:david).id, authors(:mary).id])

P
Pratik Naik 已提交
348 349
    assert_raises(ActiveRecord::RecordNotFound) { authors.where(:name => 'lifo').find(authors(:david).id, '42') }
    assert_raises(ActiveRecord::RecordNotFound) { authors.find(['42', 43]) }
P
Pratik Naik 已提交
350 351
  end

352 353 354 355 356
  def test_find_in_empty_array
    authors = Author.scoped.where(:id => [])
    assert authors.all.blank?
  end

P
Pratik Naik 已提交
357 358 359 360 361
  def test_exists
    davids = Author.where(:name => 'David')
    assert davids.exists?
    assert davids.exists?(authors(:david).id)
    assert ! davids.exists?(authors(:mary).id)
P
Pratik Naik 已提交
362 363
    assert ! davids.exists?("42")
    assert ! davids.exists?(42)
P
Pratik Naik 已提交
364 365 366 367 368

    fake  = Author.where(:name => 'fake author')
    assert ! fake.exists?
    assert ! fake.exists?(authors(:david).id)
  end
369 370 371 372 373 374

  def test_last
    authors = Author.scoped
    assert_equal authors(:mary), authors.last
  end

P
Pratik Naik 已提交
375 376 377 378 379 380 381 382 383 384 385 386 387
  def test_destroy_all
    davids = Author.where(:name => 'David')

    # Force load
    assert_equal [authors(:david)], davids.to_a
    assert davids.loaded?

    assert_difference('Author.count', -1) { davids.destroy_all }

    assert_equal [], davids.to_a
    assert davids.loaded?
  end

P
Pratik Naik 已提交
388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407
  def test_delete_all
    davids = Author.where(:name => 'David')

    assert_difference('Author.count', -1) { davids.delete_all }
    assert ! davids.loaded?
  end

  def test_delete_all_loaded
    davids = Author.where(:name => 'David')

    # Force load
    assert_equal [authors(:david)], davids.to_a
    assert davids.loaded?

    assert_difference('Author.count', -1) { davids.delete_all }

    assert_equal [], davids.to_a
    assert davids.loaded?
  end

408 409 410 411
  def test_relation_merging
    devs = Developer.where("salary >= 80000") & Developer.limit(2) & Developer.order('id ASC').where("id < 3")
    assert_equal [developers(:david), developers(:jamis)], devs.to_a

P
Pratik Naik 已提交
412
    dev_with_count = Developer.limit(1) & Developer.order('id DESC') & Developer.select('developers.*')
413
    assert_equal [developers(:poor_jamis)], dev_with_count.to_a
414 415 416 417 418 419
  end

  def test_relation_merging_with_eager_load
    relations = []
    relations << (Post.order('comments.id DESC') & Post.eager_load(:last_comment) & Post.scoped)
    relations << (Post.eager_load(:last_comment) & Post.order('comments.id DESC') & Post.scoped)
420

421 422 423 424 425 426
    relations.each do |posts|
      post = posts.find { |p| p.id == 1 }
      assert_equal Post.find(1).last_comment, post.last_comment
    end
  end

427 428 429 430 431
  def test_relation_merging_with_locks
    devs = Developer.lock.where("salary >= 80000").order("id DESC") & Developer.limit(2)
    assert devs.locked.present?
  end

432 433 434 435
  def test_relation_merging_with_preload
    [Post.scoped & Post.preload(:author), Post.preload(:author) & Post.scoped].each do |posts|
      assert_queries(2) { assert posts.first.author }
    end
436
  end
437

P
Pratik Naik 已提交
438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462
  def test_count
    posts = Post.scoped

    assert_equal 7, posts.count
    assert_equal 7, posts.count(:all)
    assert_equal 7, posts.count(:id)

    assert_equal 1, posts.where('comments_count > 1').count
    assert_equal 5, posts.where(:comments_count => 0).count
  end

  def test_count_with_distinct
    posts = Post.scoped

    assert_equal 3, posts.count(:comments_count, :distinct => true)
    assert_equal 7, posts.count(:comments_count, :distinct => false)

    assert_equal 3, posts.select(:comments_count).count(:distinct => true)
    assert_equal 7, posts.select(:comments_count).count(:distinct => false)
  end

  def test_count_explicit_columns
    Post.update_all(:comments_count => nil)
    posts = Post.scoped

P
Pratik Naik 已提交
463
    assert_equal [0], posts.select('comments_count').where('id is not null').group('id').order('id').count.values.uniq
464 465
    assert_equal 0, posts.where('id is not null').select('comments_count').count

P
Pratik Naik 已提交
466 467 468 469 470
    assert_equal 7, posts.select('comments_count').count('id')
    assert_equal 0, posts.select('comments_count').count
    assert_equal 0, posts.count(:comments_count)
    assert_equal 0, posts.count('comments_count')
  end
471 472 473 474 475 476 477 478 479 480 481 482

  def test_size
    posts = Post.scoped

    assert_queries(1) { assert_equal 7, posts.size }
    assert ! posts.loaded?

    best_posts = posts.where(:comments_count => 0)
    best_posts.to_a # force load
    assert_no_queries { assert_equal 5, best_posts.size }
  end

483 484 485 486 487 488 489
  def test_count_complex_chained_relations
    posts = Post.select('comments_count').where('id is not null').group("author_id").where("comments_count > 0")

    expected = { 1 => 2 }
    assert_equal expected, posts.count
  end

P
Pratik Naik 已提交
490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505
  def test_any
    posts = Post.scoped

    assert_queries(3) do
      assert posts.any? # Uses COUNT()
      assert ! posts.where(:id => nil).any?

      assert posts.any? {|p| p.id > 0 }
      assert ! posts.any? {|p| p.id <= 0 }
    end

    assert posts.loaded?
  end

  def test_many
    posts = Post.scoped
506

P
Pratik Naik 已提交
507 508 509 510 511
    assert_queries(2) do
      assert posts.many? # Uses COUNT()
      assert posts.many? {|p| p.id > 0 }
      assert ! posts.many? {|p| p.id < 2 }
    end
512

P
Pratik Naik 已提交
513 514 515
    assert posts.loaded?
  end

516 517 518 519 520 521
  def test_many_with_limits
    posts = Post.scoped

    assert posts.many?
    assert ! posts.limit(1).many?
  end
P
Pratik Naik 已提交
522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537

  def test_build
    posts = Post.scoped

    post = posts.new
    assert_kind_of Post, post
  end

  def test_scoped_build
    posts = Post.where(:title => 'You told a lie')

    post = posts.new
    assert_kind_of Post, post
    assert_equal 'You told a lie', post.title
  end

538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559
  def test_create
    birds = Bird.scoped

    sparrow = birds.create
    assert_kind_of Bird, sparrow
    assert sparrow.new_record?

    hen = birds.where(:name => 'hen').create
    assert ! hen.new_record?
    assert_equal 'hen', hen.name
  end

  def test_create_bang
    birds = Bird.scoped

    assert_raises(ActiveRecord::RecordInvalid) { birds.create! }

    hen = birds.where(:name => 'hen').create!
    assert_kind_of Bird, hen
    assert ! hen.new_record?
    assert_equal 'hen', hen.name
  end
P
Pratik Naik 已提交
560

561 562 563 564 565 566 567 568
  def test_explicit_create_scope
    hens = Bird.where(:name => 'hen')
    assert_equal 'hen', hens.new.name

    hens = hens.create_with(:name => 'cock')
    assert_equal 'cock', hens.new.name
  end

P
Pratik Naik 已提交
569 570 571 572 573 574 575 576 577 578 579
  def test_except
    relation = Post.where(:author_id => 1).order('id ASC').limit(1)
    assert_equal [posts(:welcome)], relation.all

    author_posts = relation.except(:order, :limit)
    assert_equal Post.where(:author_id => 1).all, author_posts.all

    all_posts = relation.except(:where, :order, :limit)
    assert_equal Post.all, all_posts.all
  end

P
Pratik Naik 已提交
580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595
  def test_anonymous_extension
    relation = Post.where(:author_id => 1).order('id ASC') do
      def author
        'lifo'
      end
    end

    assert_equal "lifo", relation.author
    assert_equal "lifo", relation.limit(1).author
  end

  def test_named_extension
    relation = Post.where(:author_id => 1).order('id ASC').extending(Post::NamedExtension)
    assert_equal "lifo", relation.author
    assert_equal "lifo", relation.limit(1).author
  end
596 597 598 599

  def test_order_by_relation_attribute
    assert_equal Post.order(Post.arel_table[:title]).all, Post.order("title").all
  end
P
Pratik Naik 已提交
600
end