calculations_test.rb 38.0 KB
Newer Older
1 2
# frozen_string_literal: true

3
require "cases/helper"
4
require "models/book"
5 6
require "models/club"
require "models/company"
7
require "models/contract"
8 9 10 11 12
require "models/edge"
require "models/organization"
require "models/possession"
require "models/topic"
require "models/reply"
13
require "models/numeric_data"
14 15 16 17 18
require "models/minivan"
require "models/speedometer"
require "models/ship_part"
require "models/treasure"
require "models/developer"
19
require "models/post"
20 21
require "models/comment"
require "models/rating"
22
require "support/stubs/strong_parameters"
23

24
class CalculationsTest < ActiveRecord::TestCase
25
  fixtures :companies, :accounts, :topics, :speedometers, :minivans, :books, :posts, :comments
26 27

  def test_should_sum_field
28
    assert_equal 318, Account.sum(:credit_limit)
29 30
  end

31 32 33 34
  def test_should_sum_arel_attribute
    assert_equal 318, Account.sum(Account.arel_table[:credit_limit])
  end

35 36
  def test_should_average_field
    value = Account.average(:credit_limit)
37
    assert_equal 53.0, value
38 39
  end

40 41 42 43 44
  def test_should_average_arel_attribute
    value = Account.average(Account.arel_table[:credit_limit])
    assert_equal 53.0, value
  end

45 46 47 48
  def test_should_resolve_aliased_attributes
    assert_equal 318, Account.sum(:available_credit)
  end

49 50 51 52 53
  def test_should_return_decimal_average_of_integer_field
    value = Account.average(:id)
    assert_equal 3.5, value
  end

54
  def test_should_return_integer_average_if_db_returns_such
A
Aaron Patterson 已提交
55
    ShipPart.delete_all
56
    ShipPart.create!(id: 3, name: "foo")
A
Aaron Patterson 已提交
57
    value = ShipPart.average(:id)
58 59 60
    assert_equal 3, value
  end

61 62
  def test_should_return_nil_as_average
    assert_nil NumericData.average(:bank_balance)
63
  end
64

65 66 67 68
  def test_should_get_maximum_of_field
    assert_equal 60, Account.maximum(:credit_limit)
  end

69 70 71 72
  def test_should_get_maximum_of_arel_attribute
    assert_equal 60, Account.maximum(Account.arel_table[:credit_limit])
  end

73
  def test_should_get_maximum_of_field_with_include
J
Jon Leighton 已提交
74
    assert_equal 55, Account.where("companies.name != 'Summit'").references(:companies).includes(:firm).maximum(:credit_limit)
75 76
  end

77 78 79 80
  def test_should_get_maximum_of_arel_attribute_with_include
    assert_equal 55, Account.where("companies.name != 'Summit'").references(:companies).includes(:firm).maximum(Account.arel_table[:credit_limit])
  end

81 82 83 84
  def test_should_get_minimum_of_field
    assert_equal 50, Account.minimum(:credit_limit)
  end

85 86 87 88
  def test_should_get_minimum_of_arel_attribute
    assert_equal 50, Account.minimum(Account.arel_table[:credit_limit])
  end

89
  def test_should_group_by_field
J
Jon Leighton 已提交
90
    c = Account.group(:firm_id).sum(:credit_limit)
91
    [1, 6, 2].each do |firm_id|
92
      assert_includes c.keys, firm_id, "Group #{c.inspect} does not contain firm_id #{firm_id}"
93
    end
94
  end
95

96
  def test_should_group_by_arel_attribute
97
    c = Account.group(Account.arel_table[:firm_id]).sum(:credit_limit)
98
    [1, 6, 2].each do |firm_id|
99
      assert_includes c.keys, firm_id, "Group #{c.inspect} does not contain firm_id #{firm_id}"
100
    end
101 102
  end

103
  def test_should_group_by_multiple_fields
104
    c = Account.group("firm_id", :credit_limit).count(:all)
105
    [ [nil, 50], [1, 50], [6, 50], [6, 55], [9, 53], [2, 60] ].each { |firm_and_limit| assert_includes c.keys, firm_and_limit }
106 107 108
  end

  def test_should_group_by_multiple_fields_having_functions
109
    c = Topic.group(:author_name, "COALESCE(type, title)").count(:all)
110 111 112 113 114
    assert_equal 1, c[["Carl", "The Third Topic of the day"]]
    assert_equal 1, c[["Mary", "Reply"]]
    assert_equal 1, c[["David", "The First Topic"]]
    assert_equal 1, c[["Carl", "Reply"]]
  end
115 116

  def test_should_group_by_summed_field
J
Jon Leighton 已提交
117
    c = Account.group(:firm_id).sum(:credit_limit)
118 119 120
    assert_equal 50,   c[1]
    assert_equal 105,  c[6]
    assert_equal 60,   c[2]
121 122
  end

123
  def test_should_generate_valid_sql_with_joins_and_group
124
    assert_nothing_raised do
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
      AuditLog.joins(:developer).group(:id).count
    end
  end

  def test_should_calculate_against_given_relation
    developer = Developer.create!(name: "developer")
    developer.audit_logs.create!(message: "first log")
    developer.audit_logs.create!(message: "second log")

    c = developer.audit_logs.joins(:developer).group(:id).count

    assert_equal developer.audit_logs.count, c.size
    developer.audit_logs.each do |log|
      assert_equal 1, c[log.id]
    end
  end

142 143 144 145 146 147 148
  def test_should_not_use_alias_for_grouped_field
    assert_sql(/GROUP BY #{Regexp.escape(Account.connection.quote_table_name("accounts.firm_id"))}/i) do
      c = Account.group(:firm_id).order("accounts_firm_id").sum(:credit_limit)
      assert_equal [1, 2, 6, 9], c.keys.compact
    end
  end

149
  def test_should_order_by_grouped_field
150
    c = Account.group(:firm_id).order("firm_id").sum(:credit_limit)
151
    assert_equal [1, 2, 6, 9], c.keys.compact
152 153 154
  end

  def test_should_order_by_calculation
B
Ben Toews 已提交
155
    c = Account.group(:firm_id).order("sum_credit_limit desc, firm_id").sum(:credit_limit)
156 157
    assert_equal [105, 60, 53, 50, 50], c.keys.collect { |k| c[k] }
    assert_equal [6, 2, 9, 1], c.keys.compact
158 159
  end

160
  def test_should_limit_calculation
161
    c = Account.where("firm_id IS NOT NULL").group(:firm_id).order("firm_id").limit(2).sum(:credit_limit)
162 163 164 165
    assert_equal [1, 2], c.keys.compact
  end

  def test_should_limit_calculation_with_offset
166 167
    c = Account.where("firm_id IS NOT NULL").group(:firm_id).order("firm_id").
     limit(2).offset(1).sum(:credit_limit)
168 169 170
    assert_equal [2, 6], c.keys.compact
  end

171
  def test_limit_should_apply_before_count
172
    accounts = Account.order(:id).limit(4)
173 174 175 176 177

    assert_equal 3, accounts.count(:firm_id)
    assert_equal 3, accounts.select(:firm_id).count
  end

178
  def test_limit_should_apply_before_count_arel_attribute
179
    accounts = Account.order(:id).limit(4)
180 181 182 183 184 185

    firm_id_attribute = Account.arel_table[:firm_id]
    assert_equal 3, accounts.count(firm_id_attribute)
    assert_equal 3, accounts.select(firm_id_attribute).count
  end

186 187 188 189 190 191 192
  def test_count_should_shortcut_with_limit_zero
    accounts = Account.limit(0)

    assert_no_queries { assert_equal 0, accounts.count }
  end

  def test_limit_is_kept
A
Aaron Patterson 已提交
193 194
    return if current_adapter?(:OracleAdapter)

195
    queries = capture_sql { Account.limit(1).count }
196 197 198 199
    assert_equal 1, queries.length
    assert_match(/LIMIT/, queries.first)
  end

200 201 202
  def test_offset_is_kept
    return if current_adapter?(:OracleAdapter)

203
    queries = capture_sql { Account.offset(1).count }
204
    assert_equal 1, queries.length
205
    assert_match(/OFFSET/, queries.first)
206 207
  end

208 209 210
  def test_limit_with_offset_is_kept
    return if current_adapter?(:OracleAdapter)

211
    queries = capture_sql { Account.limit(1).offset(1).count }
212
    assert_equal 1, queries.length
213 214
    assert_match(/LIMIT/, queries.first)
    assert_match(/OFFSET/, queries.first)
215 216 217
  end

  def test_no_limit_no_offset
218
    queries = capture_sql { Account.count }
219 220 221 222 223
    assert_equal 1, queries.length
    assert_no_match(/LIMIT/, queries.first)
    assert_no_match(/OFFSET/, queries.first)
  end

224 225 226 227 228
  def test_count_on_invalid_columns_raises
    e = assert_raises(ActiveRecord::StatementInvalid) {
      Account.select("credit_limit, firm_name").count
    }

G
Gannon McGibbon 已提交
229 230
    assert_match %r{accounts}i, e.sql
    assert_match "credit_limit, firm_name", e.sql
231 232
  end

233
  def test_apply_distinct_in_count
234
    queries = capture_sql do
235 236 237 238 239 240 241 242 243
      Account.distinct.count
      Account.group(:firm_id).distinct.count
    end

    queries.each do |query|
      assert_match %r{\ASELECT(?! DISTINCT) COUNT\(DISTINCT\b}, query
    end
  end

244 245 246 247 248 249
  def test_count_with_eager_loading_and_custom_order
    posts = Post.includes(:comments).order("comments.id")
    assert_queries(1) { assert_equal 11, posts.count }
    assert_queries(1) { assert_equal 11, posts.count(:all) }
  end

250 251 252 253 254 255
  def test_count_with_eager_loading_and_custom_select_and_order
    posts = Post.includes(:comments).order("comments.id").select(:type)
    assert_queries(1) { assert_equal 11, posts.count }
    assert_queries(1) { assert_equal 11, posts.count(:all) }
  end

256 257 258 259 260 261
  def test_count_with_eager_loading_and_custom_order_and_distinct
    posts = Post.includes(:comments).order("comments.id").distinct
    assert_queries(1) { assert_equal 11, posts.count }
    assert_queries(1) { assert_equal 11, posts.count(:all) }
  end

262 263 264 265 266 267
  def test_distinct_count_all_with_custom_select_and_order
    accounts = Account.distinct.select("credit_limit % 10").order(Arel.sql("credit_limit % 10"))
    assert_queries(1) { assert_equal 3, accounts.count(:all) }
    assert_queries(1) { assert_equal 3, accounts.load.size }
  end

268 269 270 271 272 273 274 275 276 277 278 279
  def test_distinct_count_with_order_and_limit
    assert_equal 4, Account.distinct.order(:firm_id).limit(4).count
  end

  def test_distinct_count_with_order_and_offset
    assert_equal 4, Account.distinct.order(:firm_id).offset(2).count
  end

  def test_distinct_count_with_order_and_limit_and_offset
    assert_equal 4, Account.distinct.order(:firm_id).limit(4).offset(2).count
  end

280 281 282 283 284 285 286 287 288 289 290 291
  def test_distinct_joins_count_with_order_and_limit
    assert_equal 3, Account.joins(:firm).distinct.order(:firm_id).limit(3).count
  end

  def test_distinct_joins_count_with_order_and_offset
    assert_equal 3, Account.joins(:firm).distinct.order(:firm_id).offset(2).count
  end

  def test_distinct_joins_count_with_order_and_limit_and_offset
    assert_equal 3, Account.joins(:firm).distinct.order(:firm_id).limit(3).offset(2).count
  end

292 293 294 295 296 297 298 299 300 301 302 303
  def test_distinct_joins_count_with_group_by
    expected = { nil => 4, 1 => 1, 2 => 1, 4 => 1, 5 => 1, 7 => 1 }
    assert_equal expected, Post.left_joins(:comments).group(:post_id).distinct.count(:author_id)
    assert_equal expected, Post.left_joins(:comments).group(:post_id).distinct.select(:author_id).count
    assert_equal expected, Post.left_joins(:comments).group(:post_id).count("DISTINCT posts.author_id")
    assert_equal expected, Post.left_joins(:comments).group(:post_id).select("DISTINCT posts.author_id").count

    expected = { nil => 6, 1 => 1, 2 => 1, 4 => 1, 5 => 1, 7 => 1 }
    assert_equal expected, Post.left_joins(:comments).group(:post_id).distinct.count(:all)
    assert_equal expected, Post.left_joins(:comments).group(:post_id).distinct.select(:author_id).count(:all)
  end

304 305 306 307
  def test_distinct_count_with_group_by_and_order_and_limit
    assert_equal({ 6 => 2 }, Account.group(:firm_id).distinct.order("1 DESC").limit(1).count)
  end

308
  def test_should_group_by_summed_field_having_condition
309
    c = Account.group(:firm_id).having("sum(credit_limit) > 50").sum(:credit_limit)
310 311 312 313 314
    assert_nil        c[1]
    assert_equal 105, c[6]
    assert_equal 60,  c[2]
  end

315
  def test_should_group_by_summed_field_having_condition_from_select
316
    skip unless current_adapter?(:Mysql2Adapter, :SQLite3Adapter)
317
    c = Account.select("MIN(credit_limit) AS min_credit_limit").group(:firm_id).having("min_credit_limit > 50").sum(:credit_limit)
318 319 320 321 322
    assert_nil       c[1]
    assert_equal 60, c[2]
    assert_equal 53, c[9]
  end

323
  def test_should_group_by_summed_association
J
Jon Leighton 已提交
324
    c = Account.group(:firm).sum(:credit_limit)
325 326 327 328
    assert_equal 50,   c[companies(:first_firm)]
    assert_equal 105,  c[companies(:rails_core)]
    assert_equal 60,   c[companies(:first_client)]
  end
J
Jeremy Kemper 已提交
329

330
  def test_should_sum_field_with_conditions
331
    assert_equal 105, Account.where("firm_id = 6").sum(:credit_limit)
332 333
  end

334
  def test_should_return_zero_if_sum_conditions_return_nothing
335 336
    assert_equal 0, Account.where("1 = 2").sum(:credit_limit)
    assert_equal 0, companies(:rails_core).companies.where("1 = 2").sum(:id)
337 338
  end

339
  def test_sum_should_return_valid_values_for_decimals
340
    NumericData.create(bank_balance: 19.83)
341 342 343
    assert_equal 19.83, NumericData.sum(:bank_balance)
  end

344
  def test_should_return_type_casted_values_with_group_and_expression
345
    assert_equal 0.5, Account.group(:firm_name).sum("0.01 * credit_limit")["37signals"]
346 347
  end

348
  def test_should_group_by_summed_field_with_conditions
349
    c = Account.where("firm_id > 1").group(:firm_id).sum(:credit_limit)
350 351 352
    assert_nil        c[1]
    assert_equal 105, c[6]
    assert_equal 60,  c[2]
353
  end
J
Jeremy Kemper 已提交
354

355
  def test_should_group_by_summed_field_with_conditions_and_having
356 357
    c = Account.where("firm_id > 1").group(:firm_id).
     having("sum(credit_limit) > 60").sum(:credit_limit)
358 359 360
    assert_nil        c[1]
    assert_equal 105, c[6]
    assert_nil        c[2]
361 362 363
  end

  def test_should_group_by_fields_with_table_alias
364
    c = Account.group("accounts.firm_id").sum(:credit_limit)
365 366 367
    assert_equal 50,  c[1]
    assert_equal 105, c[6]
    assert_equal 60,  c[2]
368
  end
J
Jeremy Kemper 已提交
369

370 371 372 373 374 375 376 377 378 379 380
  def test_should_calculate_grouped_with_longer_field
    field = "a" * Account.connection.max_identifier_length

    Account.update_all("#{field} = credit_limit")

    c = Account.group(:firm_id).sum(field)
    assert_equal 50,  c[1]
    assert_equal 105, c[6]
    assert_equal 60,  c[2]
  end

381
  def test_should_calculate_with_invalid_field
382
    assert_equal 6, Account.calculate(:count, "*")
383
    assert_equal 6, Account.calculate(:count, :all)
384
  end
J
Jeremy Kemper 已提交
385

386
  def test_should_calculate_grouped_with_invalid_field
387
    c = Account.group("accounts.firm_id").count(:all)
388 389 390
    assert_equal 1, c[1]
    assert_equal 2, c[6]
    assert_equal 1, c[2]
391
  end
J
Jeremy Kemper 已提交
392

393
  def test_should_calculate_grouped_association_with_invalid_field
J
Jon Leighton 已提交
394
    c = Account.group(:firm).count(:all)
395 396 397 398
    assert_equal 1, c[companies(:first_firm)]
    assert_equal 2, c[companies(:rails_core)]
    assert_equal 1, c[companies(:first_client)]
  end
399

400
  def test_should_group_by_association_with_non_numeric_foreign_key
401 402
    Speedometer.create! id: "ABC"
    Minivan.create! id: "OMG", speedometer_id: "ABC"
403

404
    c = Minivan.group(:speedometer).count(:all)
405
    first_key = c.keys.first
406
    assert_equal Speedometer, first_key.class
407
    assert_equal 1, c[first_key]
408
  end
J
Jeremy Kemper 已提交
409

410
  def test_should_calculate_grouped_association_with_foreign_key_option
411
    Account.belongs_to :another_firm, class_name: "Firm", foreign_key: "firm_id"
J
Jon Leighton 已提交
412
    c = Account.group(:another_firm).count(:all)
413 414 415 416 417
    assert_equal 1, c[companies(:first_firm)]
    assert_equal 2, c[companies(:rails_core)]
    assert_equal 1, c[companies(:first_client)]
  end

418
  def test_should_calculate_grouped_by_function
J
Jon Leighton 已提交
419
    c = Company.group("UPPER(#{QUOTED_TYPE})").count(:all)
420
    assert_equal 2, c[nil]
421 422 423
    assert_equal 1, c["DEPENDENTFIRM"]
    assert_equal 5, c["CLIENT"]
    assert_equal 2, c["FIRM"]
424
  end
J
Jeremy Kemper 已提交
425

426
  def test_should_calculate_grouped_by_function_with_table_alias
J
Jon Leighton 已提交
427
    c = Company.group("UPPER(companies.#{QUOTED_TYPE})").count(:all)
428
    assert_equal 2, c[nil]
429 430 431
    assert_equal 1, c["DEPENDENTFIRM"]
    assert_equal 5, c["CLIENT"]
    assert_equal 2, c["FIRM"]
432
  end
J
Jeremy Kemper 已提交
433

434 435 436
  def test_should_not_overshadow_enumerable_sum
    assert_equal 6, [1, 2, 3].sum(&:abs)
  end
437 438 439 440 441

  def test_should_sum_scoped_field
    assert_equal 15, companies(:rails_core).companies.sum(:id)
  end

442 443 444 445
  def test_should_sum_scoped_field_with_from
    assert_equal Club.count, Organization.clubs.count
  end

446
  def test_should_sum_scoped_field_with_conditions
447
    assert_equal 8,  companies(:rails_core).companies.where("id > 7").sum(:id)
448 449 450
  end

  def test_should_group_by_scoped_field
J
Jon Leighton 已提交
451
    c = companies(:rails_core).companies.group(:name).sum(:id)
452 453
    assert_equal 7, c["Leetsoft"]
    assert_equal 8, c["Jadedpixel"]
454 455
  end

E
Emilio Tagua 已提交
456
  def test_should_group_by_summed_field_through_association_and_having
457 458 459
    c = companies(:rails_core).companies.group(:name).having("sum(id) > 7").sum(:id)
    assert_nil      c["Leetsoft"]
    assert_equal 8, c["Jadedpixel"]
460
  end
461

462
  def test_should_count_selected_field_with_include
463 464
    assert_equal 6, Account.includes(:firm).distinct.count
    assert_equal 4, Account.includes(:firm).distinct.select(:credit_limit).count
465 466
    assert_equal 4, Account.includes(:firm).distinct.count("DISTINCT credit_limit")
    assert_equal 4, Account.includes(:firm).distinct.count("DISTINCT(credit_limit)")
467
  end
J
Jeremy Kemper 已提交
468

469 470
  def test_should_not_perform_joined_include_by_default
    assert_equal Account.count, Account.includes(:firm).count
471
    queries = capture_sql { Account.includes(:firm).count }
472 473 474 475
    assert_no_match(/join/i, queries.last)
  end

  def test_should_perform_joined_include_when_referencing_included_tables
476
    joined_count = Account.includes(:firm).where(companies: { name: "37signals" }).count
477 478 479
    assert_equal 1, joined_count
  end

480
  def test_should_count_scoped_select
481
    Account.update_all("credit_limit = NULL")
482
    assert_equal 0, Account.select("credit_limit").count
483 484 485
  end

  def test_should_count_scoped_select_with_options
486
    Account.update_all("credit_limit = NULL")
487 488
    Account.last.update_columns("credit_limit" => 49)
    Account.first.update_columns("credit_limit" => 51)
489

490
    assert_equal 1, Account.select("credit_limit").where("credit_limit >= 50").count
491 492
  end

493
  def test_should_count_manual_select_with_include
494
    assert_equal 6, Account.select("DISTINCT accounts.id").includes(:firm).count
495 496
  end

497 498 499 500 501
  def test_should_count_manual_select_with_count_all
    assert_equal 5, Account.select("DISTINCT accounts.firm_id").count(:all)
  end

  def test_should_count_with_manual_distinct_select_and_distinct
502
    assert_equal 4, Account.select("DISTINCT accounts.firm_id").distinct(true).count
503 504 505 506 507 508 509 510 511 512 513 514
  end

  def test_should_count_manual_select_with_group_with_count_all
    expected = { nil => 1, 1 => 1, 2 => 1, 6 => 2, 9 => 1 }
    actual = Account.select("DISTINCT accounts.firm_id").group("accounts.firm_id").count(:all)
    assert_equal expected, actual
  end

  def test_should_count_manual_with_count_all
    assert_equal 6, Account.count(:all)
  end

515 516 517 518 519
  def test_count_selected_arel_attribute
    assert_equal 5, Account.select(Account.arel_table[:firm_id]).count
    assert_equal 4, Account.distinct.select(Account.arel_table[:firm_id]).count
  end

520 521 522
  def test_count_with_column_parameter
    assert_equal 5, Account.count(:firm_id)
  end
J
Jeremy Kemper 已提交
523

524 525 526 527 528 529 530 531
  def test_count_with_arel_attribute
    assert_equal 5, Account.count(Account.arel_table[:firm_id])
  end

  def test_count_with_arel_star
    assert_equal 6, Account.count(Arel.star)
  end

532 533
  def test_count_with_distinct
    assert_equal 4, Account.select(:credit_limit).distinct.count
534 535
  end

536 537 538 539
  def test_count_with_aliased_attribute
    assert_equal 6, Account.count(:available_credit)
  end

540
  def test_count_with_column_and_options_parameter
J
Jon Leighton 已提交
541
    assert_equal 2, Account.where("credit_limit = 50 AND firm_id IS NOT NULL").count(:firm_id)
542
  end
J
Jeremy Kemper 已提交
543

O
oleg dashevskii 已提交
544
  def test_should_count_field_in_joined_table
545 546
    assert_equal 5, Account.joins(:firm).count("companies.id")
    assert_equal 4, Account.joins(:firm).distinct.count("companies.id")
O
oleg dashevskii 已提交
547 548
  end

549 550 551 552 553 554 555 556 557 558
  def test_count_arel_attribute_in_joined_table_with
    assert_equal 5, Account.joins(:firm).count(Company.arel_table[:id])
    assert_equal 4, Account.joins(:firm).distinct.count(Company.arel_table[:id])
  end

  def test_count_selected_arel_attribute_in_joined_table
    assert_equal 5, Account.joins(:firm).select(Company.arel_table[:id]).count
    assert_equal 4, Account.joins(:firm).distinct.select(Company.arel_table[:id]).count
  end

O
oleg dashevskii 已提交
559
  def test_should_count_field_in_joined_table_with_group_by
560
    c = Account.group("accounts.firm_id").joins(:firm).count("companies.id")
O
oleg dashevskii 已提交
561

562
    [1, 6, 2, 9].each { |firm_id| assert_includes c.keys, firm_id }
O
oleg dashevskii 已提交
563 564
  end

565
  def test_should_count_field_of_root_table_with_conflicting_group_by_column
566 567 568 569
    expected = { 1 => 2, 2 => 1, 4 => 5, 5 => 2, 7 => 1 }
    assert_equal expected, Post.joins(:comments).group(:post_id).count
    assert_equal expected, Post.joins(:comments).group("comments.post_id").count
    assert_equal expected, Post.joins(:comments).group(:post_id).select("DISTINCT posts.author_id").count(:all)
570 571
  end

572 573 574 575 576 577 578
  def test_count_with_no_parameters_isnt_deprecated
    assert_not_deprecated { Account.count }
  end

  def test_count_with_too_many_parameters_raises
    assert_raise(ArgumentError) { Account.count(1, 2, 3) }
  end
579

L
Lauro Caetano 已提交
580 581 582 583 584 585 586 587 588
  def test_count_with_order
    assert_equal 6, Account.order(:credit_limit).count
  end

  def test_count_with_reverse_order
    assert_equal 6, Account.order(:credit_limit).reverse_order.count
  end

  def test_count_with_where_and_order
589 590 591
    assert_equal 1, Account.where(firm_name: "37signals").count
    assert_equal 1, Account.where(firm_name: "37signals").order(:firm_name).count
    assert_equal 1, Account.where(firm_name: "37signals").order(:firm_name).reverse_order.count
L
Lauro Caetano 已提交
592 593
  end

594 595 596 597
  def test_count_with_block
    assert_equal 4, Account.count { |account| account.credit_limit.modulo(10).zero? }
  end

598
  def test_should_sum_expression
599
    assert_equal 636, Account.sum("2 * credit_limit")
600
  end
601

602
  def test_sum_expression_returns_zero_when_no_records_to_sum
603
    assert_equal 0, Account.where("1 = 2").sum("2 * credit_limit")
604 605
  end

606
  def test_count_with_from_option
607
    assert_equal Company.count(:all), Company.from("companies").count(:all)
J
Jon Leighton 已提交
608
    assert_equal Account.where("credit_limit = 50").count(:all),
609
        Account.from("accounts").where("credit_limit = 50").count(:all)
610 611
    assert_equal Company.where(type: "Firm").count(:type),
        Company.where(type: "Firm").from("companies").count(:type)
612 613 614
  end

  def test_sum_with_from_option
615
    assert_equal Account.sum(:credit_limit), Account.from("accounts").sum(:credit_limit)
J
Jon Leighton 已提交
616
    assert_equal Account.where("credit_limit > 50").sum(:credit_limit),
617
        Account.where("credit_limit > 50").from("accounts").sum(:credit_limit)
618 619 620
  end

  def test_average_with_from_option
621
    assert_equal Account.average(:credit_limit), Account.from("accounts").average(:credit_limit)
J
Jon Leighton 已提交
622
    assert_equal Account.where("credit_limit > 50").average(:credit_limit),
623
        Account.where("credit_limit > 50").from("accounts").average(:credit_limit)
624 625 626
  end

  def test_minimum_with_from_option
627
    assert_equal Account.minimum(:credit_limit), Account.from("accounts").minimum(:credit_limit)
J
Jon Leighton 已提交
628
    assert_equal Account.where("credit_limit > 50").minimum(:credit_limit),
629
        Account.where("credit_limit > 50").from("accounts").minimum(:credit_limit)
630 631 632
  end

  def test_maximum_with_from_option
633
    assert_equal Account.maximum(:credit_limit), Account.from("accounts").maximum(:credit_limit)
J
Jon Leighton 已提交
634
    assert_equal Account.where("credit_limit > 50").maximum(:credit_limit),
635
        Account.where("credit_limit > 50").from("accounts").maximum(:credit_limit)
636 637
  end

638
  def test_maximum_with_not_auto_table_name_prefix_if_column_included
639
    Company.create!(name: "test", contracts: [Contract.new(developer_id: 7)])
640

A
Aaron Patterson 已提交
641
    assert_equal 7, Company.includes(:contracts).maximum(:developer_id)
642 643 644
  end

  def test_minimum_with_not_auto_table_name_prefix_if_column_included
645
    Company.create!(name: "test", contracts: [Contract.new(developer_id: 7)])
646

A
Aaron Patterson 已提交
647
    assert_equal 7, Company.includes(:contracts).minimum(:developer_id)
648 649 650
  end

  def test_sum_with_not_auto_table_name_prefix_if_column_included
651
    Company.create!(name: "test", contracts: [Contract.new(developer_id: 7)])
652

A
Aaron Patterson 已提交
653
    assert_equal 7, Company.includes(:contracts).sum(:developer_id)
654 655
  end

R
Ryuta Kamizono 已提交
656 657
  if current_adapter?(:Mysql2Adapter)
    def test_from_option_with_specified_index
658 659 660
      assert_equal Edge.count(:all), Edge.from("edges USE INDEX(unique_edge_index)").count(:all)
      assert_equal Edge.where("sink_id < 5").count(:all),
          Edge.from("edges USE INDEX(unique_edge_index)").where("sink_id < 5").count(:all)
661 662 663 664
    end
  end

  def test_from_option_with_table_different_than_class
665
    assert_equal Account.count(:all), Company.from("accounts").count(:all)
666
  end
667 668 669 670

  def test_distinct_is_honored_when_used_with_count_operation_after_group
    # Count the number of authors for approved topics
    approved_topics_count = Topic.group(:approved).count(:author_name)[true]
671
    assert_equal approved_topics_count, 4
672
    # Count the number of distinct authors for approved Topics
673
    distinct_authors_for_approved_count = Topic.group(:approved).distinct.count(:author_name)[true]
674
    assert_equal distinct_authors_for_approved_count, 3
675
  end
676 677

  def test_pluck
678
    assert_equal [1, 2, 3, 4, 5], Topic.order(:id).pluck(:id)
679 680
  end

681 682 683 684 685 686 687
  def test_pluck_with_empty_in
    Topic.send(:load_schema)
    assert_no_queries do
      assert_equal [], Topic.where(id: []).pluck(:id)
    end
  end

688
  def test_pluck_without_column_names
689 690 691 692 693
    if current_adapter?(:OracleAdapter)
      assert_equal [[1, "Firm", 1, nil, "37signals", nil, 1, nil, nil]], Company.order(:id).limit(1).pluck
    else
      assert_equal [[1, "Firm", 1, nil, "37signals", nil, 1, nil, ""]], Company.order(:id).limit(1).pluck
    end
694 695
  end

696 697
  def test_pluck_type_cast
    topic = topics(:first)
698
    relation = Topic.where(id: topic.id)
699 700 701 702 703
    assert_equal [ topic.approved ], relation.pluck(:approved)
    assert_equal [ topic.last_read ], relation.pluck(:last_read)
    assert_equal [ topic.written_on ], relation.pluck(:written_on)
  end

704 705 706 707 708 709 710 711 712 713 714 715
  def test_pluck_with_type_cast_does_not_corrupt_the_query_cache
    topic = topics(:first)
    relation = Topic.where(id: topic.id)
    assert_queries 1 do
      Topic.cache do
        kind = relation.select(:written_on).load.first.read_attribute_before_type_cast(:written_on).class
        relation.pluck(:written_on)
        assert_kind_of kind, relation.select(:written_on).load.first.read_attribute_before_type_cast(:written_on)
      end
    end
  end

716 717
  def test_pluck_and_distinct
    assert_equal [50, 53, 55, 60], Account.order(:credit_limit).distinct.pluck(:credit_limit)
718 719 720 721 722 723 724 725
  end

  def test_pluck_in_relation
    company = Company.first
    contract = company.contracts.create!
    assert_equal [contract.id], company.contracts.pluck(:id)
  end

726
  def test_pluck_on_aliased_attribute
727
    assert_equal "The First Topic", Topic.order(:id).pluck(:heading).first
728 729
  end

730
  def test_pluck_with_serialization
731
    t = Topic.create!(content: { foo: :bar })
732
    assert_equal [{ foo: :bar }], Topic.where(id: t.id).pluck(:content)
733 734 735
  end

  def test_pluck_with_qualified_column_name
B
Ben Toews 已提交
736
    assert_equal [1, 2, 3, 4, 5], Topic.order(:id).pluck("topics.id")
737
  end
738 739

  def test_pluck_auto_table_name_prefix
740
    c = Company.create!(name: "test", contracts: [Contract.new])
741 742 743
    assert_equal [c.id], Company.joins(:contracts).pluck(:id)
  end

744
  def test_pluck_if_table_included
745
    c = Company.create!(name: "test", contracts: [Contract.new(developer_id: 7)])
746 747 748
    assert_equal [c.id], Company.includes(:contracts).where("contracts.id" => c.contracts.first).pluck(:id)
  end

749
  def test_pluck_not_auto_table_name_prefix_if_column_joined
750 751 752
    company = Company.create!(name: "test", contracts: [Contract.new(developer_id: 7)])
    metadata = company.contracts.first.metadata
    assert_equal [metadata], Company.joins(:contracts).pluck(:metadata)
753
  end
T
twinturbo 已提交
754

755
  def test_pluck_with_selection_clause
B
Ben Toews 已提交
756 757 758
    assert_equal [50, 53, 55, 60], Account.pluck(Arel.sql("DISTINCT credit_limit")).sort
    assert_equal [50, 53, 55, 60], Account.pluck(Arel.sql("DISTINCT accounts.credit_limit")).sort
    assert_equal [50, 53, 55, 60], Account.pluck(Arel.sql("DISTINCT(credit_limit)")).sort
759
    assert_equal [50 + 53 + 55 + 60], Account.pluck(Arel.sql("SUM(DISTINCT(credit_limit))"))
760 761
  end

T
twinturbo 已提交
762
  def test_plucks_with_ids
J
Jon Leighton 已提交
763
    assert_equal Company.all.map(&:id).sort, Company.ids.sort
T
twinturbo 已提交
764
  end
765

766 767
  def test_pluck_with_includes_limit_and_empty_result
    assert_equal [], Topic.includes(:replies).limit(0).pluck(:id)
768
    assert_equal [], Topic.includes(:replies).limit(1).where("0 = 1").pluck(:id)
769 770
  end

771 772 773 774 775
  def test_pluck_with_includes_offset
    assert_equal [5], Topic.includes(:replies).order(:id).offset(4).pluck(:id)
    assert_equal [], Topic.includes(:replies).order(:id).offset(5).pluck(:id)
  end

776
  def test_pluck_with_join
777
    assert_equal [[2, 2], [4, 4]], Reply.includes(:topic).order(:id).pluck(:id, :"topics.id")
778 779
  end

780 781 782 783 784 785
  def test_group_by_with_order_by_virtual_count_attribute
    expected = { "SpecialPost" => 1, "StiPost" => 2 }
    actual = Post.group(:type).order(:count).limit(2).maximum(:comments_count)
    assert_equal expected, actual
  end if current_adapter?(:PostgreSQLAdapter)

786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803
  def test_group_by_with_limit
    expected = { "Post" => 8, "SpecialPost" => 1 }
    actual = Post.includes(:comments).group(:type).order(:type).limit(2).count("comments.id")
    assert_equal expected, actual
  end

  def test_group_by_with_offset
    expected = { "SpecialPost" => 1, "StiPost" => 2 }
    actual = Post.includes(:comments).group(:type).order(:type).offset(1).count("comments.id")
    assert_equal expected, actual
  end

  def test_group_by_with_limit_and_offset
    expected = { "SpecialPost" => 1 }
    actual = Post.includes(:comments).group(:type).order(:type).offset(1).limit(1).count("comments.id")
    assert_equal expected, actual
  end

C
Carlos Antonio da Silva 已提交
804
  def test_group_by_with_quoted_count_and_order_by_alias
805 806 807 808 809 810
    quoted_posts_id = Post.connection.quote_table_name("posts.id")
    expected = { "SpecialPost" => 1, "StiPost" => 1, "Post" => 9 }
    actual = Post.group(:type).order("count_posts_id").count(quoted_posts_id)
    assert_equal expected, actual
  end

811
  def test_pluck_not_auto_table_name_prefix_if_column_included
812
    Company.create!(name: "test", contracts: [Contract.new(developer_id: 7)])
B
Ben Toews 已提交
813
    ids = Company.includes(:contracts).pluck(:developer_id)
814 815 816
    assert_equal Company.count, ids.length
    assert_equal [7], ids.compact
  end
817 818 819 820

  def test_pluck_multiple_columns
    assert_equal [
      [1, "The First Topic"], [2, "The Second Topic of the day"],
821 822
      [3, "The Third Topic of the day"], [4, "The Fourth Topic of the day"],
      [5, "The Fifth Topic of the day"]
823
    ], Topic.order(:id).pluck(:id, :title)
824 825
    assert_equal [
      [1, "The First Topic", "David"], [2, "The Second Topic of the day", "Mary"],
826 827
      [3, "The Third Topic of the day", "Carl"], [4, "The Fourth Topic of the day", "Carl"],
      [5, "The Fifth Topic of the day", "Jason"]
828 829 830 831 832
    ], Topic.order(:id).pluck(:id, :title, :author_name)
  end

  def test_pluck_with_multiple_columns_and_selection_clause
    assert_equal [[1, 50], [2, 50], [3, 50], [4, 60], [5, 55], [6, 53]],
833
      Account.order(:id).pluck("id, credit_limit")
834 835 836
  end

  def test_pluck_with_multiple_columns_and_includes
837
    Company.create!(name: "test", contracts: [Contract.new(developer_id: 7)])
B
Ben Toews 已提交
838
    companies_and_developers = Company.order("companies.id").includes(:contracts).pluck(:name, :developer_id)
839 840 841 842

    assert_equal Company.count, companies_and_developers.length
    assert_equal ["37signals", nil], companies_and_developers.first
    assert_equal ["test", 7], companies_and_developers.last
843
  end
844 845

  def test_pluck_with_reserved_words
846
    Possession.create!(where: "Over There")
847 848 849

    assert_equal ["Over There"], Possession.pluck(:where)
  end
850 851 852

  def test_pluck_replaces_select_clause
    taks_relation = Topic.select(:approved, :id).order(:id)
853
    assert_equal [1, 2, 3, 4, 5], taks_relation.pluck(:id)
854
    assert_equal [false, true, true, true, true], taks_relation.pluck(:approved)
855
  end
856 857 858

  def test_pluck_columns_with_same_name
    expected = [["The First Topic", "The Second Topic of the day"], ["The Third Topic of the day", "The Fourth Topic of the day"]]
859
    actual = Topic.joins(:replies).order(:id)
B
Ben Toews 已提交
860
      .pluck("topics.title", "replies_topics.title")
861 862
    assert_equal expected, actual
  end
863 864

  def test_calculation_with_polymorphic_relation
865 866 867 868
    part = ShipPart.create!(name: "has trinket")
    part.trinkets.create!

    assert_equal part.id, ShipPart.joins(:trinkets).sum(:id)
869 870
  end

S
Sean Griffin 已提交
871 872 873 874 875 876 877
  def test_pluck_joined_with_polymorphic_relation
    part = ShipPart.create!(name: "has trinket")
    part.trinkets.create!

    assert_equal [part.id], ShipPart.joins(:trinkets).pluck(:id)
  end

878 879
  def test_pluck_loaded_relation
    companies = Company.order(:id).limit(3).load
Y
yuuji.yaginuma 已提交
880

881
    assert_queries(0) do
882
      assert_equal ["37signals", "Summit", "Microsoft"], companies.pluck(:name)
883 884 885 886 887
    end
  end

  def test_pluck_loaded_relation_multiple_columns
    companies = Company.order(:id).limit(3).load
Y
yuuji.yaginuma 已提交
888

889
    assert_queries(0) do
890
      assert_equal [[1, "37signals"], [2, "Summit"], [3, "Microsoft"]], companies.pluck(:id, :name)
891 892 893 894
    end
  end

  def test_pluck_loaded_relation_sql_fragment
895
    companies = Company.order(:name).limit(3).load
Y
yuuji.yaginuma 已提交
896

897
    assert_queries(1) do
B
Ben Toews 已提交
898
      assert_equal ["37signals", "Apex", "Ex Nihilo"], companies.pluck(Arel.sql("DISTINCT name"))
899 900 901
    end
  end

902 903
  def test_pick_one
    assert_equal "The First Topic", Topic.order(:id).pick(:heading)
904 905 906 907
    assert_no_queries do
      assert_nil Topic.none.pick(:heading)
      assert_nil Topic.where(id: 9999999999999999999).pick(:heading)
    end
908 909 910 911
  end

  def test_pick_two
    assert_equal ["David", "david@loudthinking.com"], Topic.order(:id).pick(:author_name, :author_email_address)
912 913 914 915
    assert_no_queries do
      assert_nil Topic.none.pick(:author_name, :author_email_address)
      assert_nil Topic.where(id: 9999999999999999999).pick(:author_name, :author_email_address)
    end
916 917
  end

Y
Yuji Hanamura 已提交
918 919 920 921 922
  def test_pick_delegate_to_all
    cool_first = minivans(:cool_first)
    assert_equal cool_first.color, Minivan.pick(:color)
  end

923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946
  def test_pick_loaded_relation
    companies = Company.order(:id).limit(3).load

    assert_no_queries do
      assert_equal "37signals", companies.pick(:name)
    end
  end

  def test_pick_loaded_relation_multiple_columns
    companies = Company.order(:id).limit(3).load

    assert_no_queries do
      assert_equal [1, "37signals"], companies.pick(:id, :name)
    end
  end

  def test_pick_loaded_relation_sql_fragment
    companies = Company.order(:name).limit(3).load

    assert_queries 1 do
      assert_equal "37signals", companies.pick(Arel.sql("DISTINCT name"))
    end
  end

947 948 949 950 951 952
  def test_grouped_calculation_with_polymorphic_relation
    part = ShipPart.create!(name: "has trinket")
    part.trinkets.create!

    assert_equal({ "has trinket" => part.id }, ShipPart.joins(:trinkets).group("ship_parts.name").sum(:id))
  end
953 954 955 956 957

  def test_calculation_grouped_by_association_doesnt_error_when_no_records_have_association
    Client.update_all(client_of: nil)
    assert_equal({ nil => Client.count }, Client.group(:firm).count)
  end
958 959

  def test_should_reference_correct_aliases_while_joining_tables_of_has_many_through_association
960
    assert_nothing_raised do
961
      developer = Developer.create!(name: "developer")
962 963 964
      developer.ratings.includes(comment: :post).where(posts: { id: 1 }).count
    end
  end
965 966 967 968 969 970 971 972 973 974

  def test_sum_uses_enumerable_version_when_block_is_given
    block_called = false
    relation = Client.all.load

    assert_no_queries do
      assert_equal 0, relation.sum { block_called = true; 0 }
    end
    assert block_called
  end
975 976

  def test_having_with_strong_parameters
977
    params = ProtectedParams.new(credit_limit: "50")
978 979 980 981 982 983 984 985 986 987

    assert_raises(ActiveModel::ForbiddenAttributesError) do
      Account.group(:id).having(params)
    end

    result = Account.group(:id).having(params.permit!)
    assert_equal 50, result[0].credit_limit
    assert_equal 50, result[1].credit_limit
    assert_equal 50, result[2].credit_limit
  end
988 989

  def test_group_by_attribute_with_custom_type
R
Ryuta Kamizono 已提交
990
    assert_equal({ "proposed" => 2, "published" => 2 }, Book.group(:status).count)
991
  end
992

993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076
  def test_select_avg_with_group_by_as_virtual_attribute_with_sql
    rails_core = companies(:rails_core)

    sql = <<~SQL
      SELECT firm_id, AVG(credit_limit) AS avg_credit_limit
      FROM accounts
      WHERE firm_id = ?
      GROUP BY firm_id
      LIMIT 1
    SQL

    account = Account.find_by_sql([sql, rails_core]).first

    # id was not selected, so it should be nil
    # (cannot select id because it wasn't used in the GROUP BY clause)
    assert_nil account.id

    # firm_id was explicitly selected, so it should be present
    assert_equal(rails_core, account.firm)

    # avg_credit_limit should be present as a virtual attribute
    assert_equal(52.5, account.avg_credit_limit)
  end

  def test_select_avg_with_group_by_as_virtual_attribute_with_ar
    rails_core = companies(:rails_core)

    account = Account
      .select(:firm_id, "AVG(credit_limit) AS avg_credit_limit")
      .where(firm: rails_core)
      .group(:firm_id)
      .take!

    # id was not selected, so it should be nil
    # (cannot select id because it wasn't used in the GROUP BY clause)
    assert_nil account.id

    # firm_id was explicitly selected, so it should be present
    assert_equal(rails_core, account.firm)

    # avg_credit_limit should be present as a virtual attribute
    assert_equal(52.5, account.avg_credit_limit)
  end

  def test_select_avg_with_joins_and_group_by_as_virtual_attribute_with_sql
    rails_core = companies(:rails_core)

    sql = <<~SQL
      SELECT companies.*, AVG(accounts.credit_limit) AS avg_credit_limit
      FROM companies
      INNER JOIN accounts ON companies.id = accounts.firm_id
      WHERE companies.id = ?
      GROUP BY companies.id
      LIMIT 1
    SQL

    firm = DependentFirm.find_by_sql([sql, rails_core]).first

    # all the DependentFirm attributes should be present
    assert_equal rails_core, firm
    assert_equal rails_core.name, firm.name

    # avg_credit_limit should be present as a virtual attribute
    assert_equal(52.5, firm.avg_credit_limit)
  end

  def test_select_avg_with_joins_and_group_by_as_virtual_attribute_with_ar
    rails_core = companies(:rails_core)

    firm = DependentFirm
      .select("companies.*", "AVG(accounts.credit_limit) AS avg_credit_limit")
      .where(id: rails_core)
      .joins(:account)
      .group(:id)
      .take!

    # all the DependentFirm attributes should be present
    assert_equal rails_core, firm
    assert_equal rails_core.name, firm.name

    # avg_credit_limit should be present as a virtual attribute
    assert_equal(52.5, firm.avg_credit_limit)
  end

1077 1078 1079
  def test_count_with_block_and_column_name_raises_an_error
    assert_raises(ArgumentError) do
      Account.count(:firm_id) { true }
1080 1081 1082
    end
  end

1083 1084 1085
  def test_sum_with_block_and_column_name_raises_an_error
    assert_raises(ArgumentError) do
      Account.sum(:firm_id) { 1 }
1086 1087
    end
  end
1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129

  test "#skip_query_cache! for #pluck" do
    Account.cache do
      assert_queries(1) do
        Account.pluck(:credit_limit)
        Account.pluck(:credit_limit)
      end

      assert_queries(2) do
        Account.all.skip_query_cache!.pluck(:credit_limit)
        Account.all.skip_query_cache!.pluck(:credit_limit)
      end
    end
  end

  test "#skip_query_cache! for a simple calculation" do
    Account.cache do
      assert_queries(1) do
        Account.calculate(:sum, :credit_limit)
        Account.calculate(:sum, :credit_limit)
      end

      assert_queries(2) do
        Account.all.skip_query_cache!.calculate(:sum, :credit_limit)
        Account.all.skip_query_cache!.calculate(:sum, :credit_limit)
      end
    end
  end

  test "#skip_query_cache! for a grouped calculation" do
    Account.cache do
      assert_queries(1) do
        Account.group(:firm_id).calculate(:sum, :credit_limit)
        Account.group(:firm_id).calculate(:sum, :credit_limit)
      end

      assert_queries(2) do
        Account.all.skip_query_cache!.group(:firm_id).calculate(:sum, :credit_limit)
        Account.all.skip_query_cache!.group(:firm_id).calculate(:sum, :credit_limit)
      end
    end
  end
1130
end