relations_test.rb 17.6 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

N
Neeraj Singh 已提交
19 20 21 22 23 24 25
  def test_apply_relation_as_where_id
    posts = Post.arel_table
    post_authors = posts.where(posts[:author_id].eq(1)).project(posts[:id])
    assert_equal 5, post_authors.to_a.size
    assert_equal 5, Post.where(:id => post_authors).size
  end

26 27 28 29 30 31
  def test_scoped
    topics = Topic.scoped
    assert_kind_of ActiveRecord::Relation, topics
    assert_equal 4, topics.size
  end

32 33 34 35 36 37 38 39 40 41
  def test_to_json
    assert_nothing_raised  { Bird.scoped.to_json }
    assert_nothing_raised  { Bird.scoped.all.to_json }
  end

  def test_to_yaml
    assert_nothing_raised  { Bird.scoped.to_yaml }
    assert_nothing_raised  { Bird.scoped.all.to_yaml }
  end

42 43 44 45 46 47
  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 已提交
48 49 50 51 52 53 54 55 56 57 58
  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 已提交
59
    topics = Topic.scoped.order('id ASC')
P
Pratik Naik 已提交
60 61 62 63 64 65 66 67 68

    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 已提交
69
    topics = Topic.scoped.order('id ASC')
P
Pratik Naik 已提交
70 71 72 73 74 75 76 77 78

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

    assert topics.loaded?
  end

79 80 81 82 83 84 85 86 87
  def test_reload
    topics = Topic.scoped

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

    assert topics.loaded?

88 89
    original_size = topics.to_a.size
    Topic.create! :title => 'fake'
90

91 92 93
    assert_queries(1) { topics.reload }
    assert_equal original_size + 1, topics.size
    assert topics.loaded?
94 95
  end

96
  def test_finding_with_conditions
97 98
    assert_equal ["David"], Author.where(:name => 'David').map(&:name)
    assert_equal ['Mary'],  Author.where(["name = ?", 'Mary']).map(&:name)
99
    assert_equal ['Mary'],  Author.where("name = ?", 'Mary').map(&:name)
100 101 102
  end

  def test_finding_with_order
103
    topics = Topic.order('id')
104
    assert_equal 4, topics.to_a.size
105 106 107 108
    assert_equal topics(:first).title, topics.first.title
  end

  def test_finding_with_order_and_take
109
    entrants = Entrant.order("id ASC").limit(2).to_a
110

P
Pratik Naik 已提交
111 112
    assert_equal 2, entrants.size
    assert_equal entrants(:first).name, entrants.first.name
113 114 115
  end

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

118
    assert_equal 2, entrants.to_a.size
P
Pratik Naik 已提交
119
    assert_equal entrants(:second).name, entrants.first.name
120

121
    entrants = Entrant.order("id ASC").limit(2).offset(2)
122
    assert_equal 1, entrants.to_a.size
P
Pratik Naik 已提交
123
    assert_equal entrants(:third).name, entrants.first.name
124 125 126
  end

  def test_finding_with_group
127
    developers = Developer.group("salary").select("salary").to_a
128 129 130 131
    assert_equal 4, developers.size
    assert_equal 4, developers.map(&:salary).uniq.size
  end

132 133
  def test_select_with_block
    even_ids = Developer.scoped.select {|d| d.id % 2 == 0 }.map(&:id)
134
    assert_equal [2, 4, 6, 8, 10], even_ids.sort
135 136
  end

137
  def test_finding_with_hash_conditions_on_joined_table
138
    firms = DependentFirm.joins(:account).where({:name => 'RailsCore', :accounts => { :credit_limit => 55..60 }}).to_a
139 140 141 142 143
    assert_equal 1, firms.size
    assert_equal companies(:rails_core), firms.first
  end

  def test_find_all_with_join
144
    developers_on_project_one = Developer.joins('LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id').
145
      where('project_id=1').to_a
146 147 148 149 150 151 152 153

    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
154
    assert_equal Topic.find(:all, :conditions => {:approved => false}), Topic.where({ :approved => false }).to_a
155 156 157
  end

  def test_joins_with_string_array
158
    person_with_reader_and_post = Post.joins([
159 160 161 162 163 164
        "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
165

166 167
  def test_scoped_responds_to_delegated_methods
    relation = Topic.scoped
168 169

    ["map", "uniq", "sort", "insert", "delete", "update"].each do |method|
170
      assert_respond_to relation, method, "Topic.scoped should respond to #{method.inspect}"
171 172 173
    end
  end

174 175 176 177 178 179 180
  def test_respond_to_private_arel_methods
    relation = Topic.scoped

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

181 182 183 184
  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|
185
      assert_respond_to relation, method, "Topic.scoped should respond to #{method.inspect}"
186 187
    end
  end
188

189 190 191 192 193
  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

194
  def test_find_with_readonly_option
195 196
    Developer.scoped.each { |d| assert !d.readonly? }
    Developer.scoped.readonly.each { |d| assert d.readonly? }
197
  end
198 199

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

203 204 205 206 207 208 209
    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

210
  def test_find_with_preloaded_associations
211
    assert_queries(2) do
212
      posts = Post.preload(:comments)
213
      assert posts.first.comments.first
214
    end
215

216
    assert_queries(2) do
217
      posts = Post.preload(:comments).to_a
218
      assert posts.first.comments.first
219
    end
220

221
    assert_queries(2) do
222
      posts = Post.preload(:author)
223
      assert posts.first.author
224
    end
225

226
    assert_queries(2) do
227
      posts = Post.preload(:author).to_a
228 229 230 231 232 233
      assert posts.first.author
    end

    assert_queries(3) do
      posts = Post.preload(:author, :comments).to_a
      assert posts.first.author
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
      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
257
      assert posts.first.comments.first
258 259
    end
  end
260

P
Pratik Naik 已提交
261
  def test_default_scope_with_conditions_string
262
    assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeveloperCalledDavid.scoped.map(&:id).sort
263
    assert_nil DeveloperCalledDavid.create!.name
264 265 266
  end

  def test_default_scope_with_conditions_hash
267
    assert_equal Developer.find_all_by_name('Jamis').map(&:id).sort, DeveloperCalledJamis.scoped.map(&:id).sort
268 269 270
    assert_equal 'Jamis', DeveloperCalledJamis.create!.name
  end

271 272 273 274 275
  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

276 277
  def test_loading_with_one_association
    posts = Post.preload(:comments)
278 279 280 281
    post = posts.find { |p| p.id == 1 }
    assert_equal 2, post.comments.size
    assert post.comments.include?(comments(:greetings))

282
    post = Post.where("posts.title = 'Welcome to the weblog'").preload(:comments).first
283 284 285
    assert_equal 2, post.comments.size
    assert post.comments.include?(comments(:greetings))

286
    posts = Post.preload(:last_comment)
287 288 289 290 291
    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
292
    posts = Post.eager_load(:last_comment).order('comments.id DESC')
293 294 295
    post = posts.find { |p| p.id == 1 }
    assert_equal Post.find(1).last_comment, post.last_comment
  end
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314

  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 已提交
315
    assert_raises(ActiveRecord::RecordNotFound) { Author.scoped.find_by_id_and_name!(20, 'invalid') }
316 317 318 319 320 321 322 323 324
  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
325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345

  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 已提交
346 347 348 349 350 351
  def test_find_id
    authors = Author.scoped

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

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

P
Pratik Naik 已提交
355 356 357 358 359 360 361 362 363 364
  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 已提交
365 366
    assert_raises(ActiveRecord::RecordNotFound) { authors.where(:name => 'lifo').find(authors(:david).id, '42') }
    assert_raises(ActiveRecord::RecordNotFound) { authors.find(['42', 43]) }
P
Pratik Naik 已提交
367 368
  end

369 370 371 372 373
  def test_find_in_empty_array
    authors = Author.scoped.where(:id => [])
    assert authors.all.blank?
  end

P
Pratik Naik 已提交
374 375 376 377 378
  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 已提交
379 380
    assert ! davids.exists?("42")
    assert ! davids.exists?(42)
P
Pratik Naik 已提交
381 382 383 384 385

    fake  = Author.where(:name => 'fake author')
    assert ! fake.exists?
    assert ! fake.exists?(authors(:david).id)
  end
386 387 388 389 390 391

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

P
Pratik Naik 已提交
392 393 394 395 396 397 398 399 400 401 402 403 404
  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 已提交
405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424
  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

425 426 427 428
  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 已提交
429
    dev_with_count = Developer.limit(1) & Developer.order('id DESC') & Developer.select('developers.*')
430
    assert_equal [developers(:poor_jamis)], dev_with_count.to_a
431 432 433 434 435 436
  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)
437

438 439 440 441 442 443
    relations.each do |posts|
      post = posts.find { |p| p.id == 1 }
      assert_equal Post.find(1).last_comment, post.last_comment
    end
  end

444 445 446 447 448
  def test_relation_merging_with_locks
    devs = Developer.lock.where("salary >= 80000").order("id DESC") & Developer.limit(2)
    assert devs.locked.present?
  end

449 450 451 452
  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
453
  end
454

P
Pratik Naik 已提交
455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479
  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 已提交
480
    assert_equal [0], posts.select('comments_count').where('id is not null').group('id').order('id').count.values.uniq
481 482
    assert_equal 0, posts.where('id is not null').select('comments_count').count

P
Pratik Naik 已提交
483 484 485 486 487
    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
488 489 490 491 492 493 494 495 496 497 498 499

  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

500 501 502 503 504 505 506
  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 已提交
507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522
  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
523

P
Pratik Naik 已提交
524 525 526 527 528
    assert_queries(2) do
      assert posts.many? # Uses COUNT()
      assert posts.many? {|p| p.id > 0 }
      assert ! posts.many? {|p| p.id < 2 }
    end
529

P
Pratik Naik 已提交
530 531 532
    assert posts.loaded?
  end

533 534 535 536 537 538
  def test_many_with_limits
    posts = Post.scoped

    assert posts.many?
    assert ! posts.limit(1).many?
  end
P
Pratik Naik 已提交
539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554

  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

555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576
  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 已提交
577

578 579 580 581 582 583 584 585
  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 已提交
586 587 588 589 590 591 592 593 594 595 596
  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 已提交
597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612
  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
613 614 615 616

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