calculations_test.rb 32.6 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

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

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

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

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

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

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

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

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

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

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

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

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

76 77 78 79
  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

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

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

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

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

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

  def test_should_group_by_multiple_fields_having_functions
108
    c = Topic.group(:author_name, "COALESCE(type, title)").count(:all)
109 110 111 112 113
    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
114 115

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

122
  def test_should_generate_valid_sql_with_joins_and_group
123
    assert_nothing_raised do
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
      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

141
  def test_should_order_by_grouped_field
142
    c = Account.group(:firm_id).order("firm_id").sum(:credit_limit)
143
    assert_equal [1, 2, 6, 9], c.keys.compact
144 145 146
  end

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

152
  def test_should_limit_calculation
153
    c = Account.where("firm_id IS NOT NULL").group(:firm_id).order("firm_id").limit(2).sum(:credit_limit)
154 155 156 157
    assert_equal [1, 2], c.keys.compact
  end

  def test_should_limit_calculation_with_offset
158 159
    c = Account.where("firm_id IS NOT NULL").group(:firm_id).order("firm_id").
     limit(2).offset(1).sum(:credit_limit)
160 161 162
    assert_equal [2, 6], c.keys.compact
  end

163
  def test_limit_should_apply_before_count
164
    accounts = Account.limit(4)
165 166 167 168 169

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

170
  def test_limit_should_apply_before_count_arel_attribute
171
    accounts = Account.limit(4)
172 173 174 175 176 177

    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

178 179 180 181 182 183 184
  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 已提交
185 186
    return if current_adapter?(:OracleAdapter)

187
    queries = assert_sql { Account.limit(1).count }
188 189 190 191
    assert_equal 1, queries.length
    assert_match(/LIMIT/, queries.first)
  end

192 193 194
  def test_offset_is_kept
    return if current_adapter?(:OracleAdapter)

195 196
    queries = assert_sql { Account.offset(1).count }
    assert_equal 1, queries.length
197
    assert_match(/OFFSET/, queries.first)
198 199
  end

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

    queries = assert_sql { Account.limit(1).offset(1).count }
204
    assert_equal 1, queries.length
205 206
    assert_match(/LIMIT/, queries.first)
    assert_match(/OFFSET/, queries.first)
207 208 209 210 211 212 213 214 215
  end

  def test_no_limit_no_offset
    queries = assert_sql { Account.count }
    assert_equal 1, queries.length
    assert_no_match(/LIMIT/, queries.first)
    assert_no_match(/OFFSET/, queries.first)
  end

216 217 218 219 220
  def test_count_on_invalid_columns_raises
    e = assert_raises(ActiveRecord::StatementInvalid) {
      Account.select("credit_limit, firm_name").count
    }

G
Gannon McGibbon 已提交
221 222
    assert_match %r{accounts}i, e.sql
    assert_match "credit_limit, firm_name", e.sql
223 224
  end

225 226 227 228 229 230 231
  def test_apply_distinct_in_count
    queries = assert_sql do
      Account.distinct.count
      Account.group(:firm_id).distinct.count
    end

    queries.each do |query|
232 233 234
      # `table_alias_length` in `column_alias_for` would execute
      # "SHOW max_identifier_length" statement in PostgreSQL adapter.
      next if query == "SHOW max_identifier_length"
235 236 237 238
      assert_match %r{\ASELECT(?! DISTINCT) COUNT\(DISTINCT\b}, query
    end
  end

239 240 241 242 243 244
  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

245 246 247 248 249 250
  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

251 252 253 254 255 256
  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

257 258 259 260 261 262 263 264 265 266 267 268
  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

269 270 271 272 273 274 275 276 277 278 279 280
  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

281 282 283 284
  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

285
  def test_should_group_by_summed_field_having_condition
286
    c = Account.group(:firm_id).having("sum(credit_limit) > 50").sum(:credit_limit)
287 288 289 290 291
    assert_nil        c[1]
    assert_equal 105, c[6]
    assert_equal 60,  c[2]
  end

292
  def test_should_group_by_summed_field_having_condition_from_select
293
    skip unless current_adapter?(:Mysql2Adapter, :SQLite3Adapter)
294
    c = Account.select("MIN(credit_limit) AS min_credit_limit").group(:firm_id).having("min_credit_limit > 50").sum(:credit_limit)
295 296 297 298 299
    assert_nil       c[1]
    assert_equal 60, c[2]
    assert_equal 53, c[9]
  end

300
  def test_should_group_by_summed_association
J
Jon Leighton 已提交
301
    c = Account.group(:firm).sum(:credit_limit)
302 303 304 305
    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 已提交
306

307
  def test_should_sum_field_with_conditions
308
    assert_equal 105, Account.where("firm_id = 6").sum(:credit_limit)
309 310
  end

311
  def test_should_return_zero_if_sum_conditions_return_nothing
312 313
    assert_equal 0, Account.where("1 = 2").sum(:credit_limit)
    assert_equal 0, companies(:rails_core).companies.where("1 = 2").sum(:id)
314 315
  end

316
  def test_sum_should_return_valid_values_for_decimals
317
    NumericData.create(bank_balance: 19.83)
318 319 320
    assert_equal 19.83, NumericData.sum(:bank_balance)
  end

321
  def test_should_return_type_casted_values_with_group_and_expression
322
    assert_equal 0.5, Account.group(:firm_name).sum("0.01 * credit_limit")["37signals"]
323 324
  end

325
  def test_should_group_by_summed_field_with_conditions
326
    c = Account.where("firm_id > 1").group(:firm_id).sum(:credit_limit)
327 328 329
    assert_nil        c[1]
    assert_equal 105, c[6]
    assert_equal 60,  c[2]
330
  end
J
Jeremy Kemper 已提交
331

332
  def test_should_group_by_summed_field_with_conditions_and_having
333 334
    c = Account.where("firm_id > 1").group(:firm_id).
     having("sum(credit_limit) > 60").sum(:credit_limit)
335 336 337
    assert_nil        c[1]
    assert_equal 105, c[6]
    assert_nil        c[2]
338 339 340
  end

  def test_should_group_by_fields_with_table_alias
341
    c = Account.group("accounts.firm_id").sum(:credit_limit)
342 343 344
    assert_equal 50,  c[1]
    assert_equal 105, c[6]
    assert_equal 60,  c[2]
345
  end
J
Jeremy Kemper 已提交
346

347
  def test_should_calculate_with_invalid_field
348
    assert_equal 6, Account.calculate(:count, "*")
349
    assert_equal 6, Account.calculate(:count, :all)
350
  end
J
Jeremy Kemper 已提交
351

352
  def test_should_calculate_grouped_with_invalid_field
353
    c = Account.group("accounts.firm_id").count(:all)
354 355 356
    assert_equal 1, c[1]
    assert_equal 2, c[6]
    assert_equal 1, c[2]
357
  end
J
Jeremy Kemper 已提交
358

359
  def test_should_calculate_grouped_association_with_invalid_field
J
Jon Leighton 已提交
360
    c = Account.group(:firm).count(:all)
361 362 363 364
    assert_equal 1, c[companies(:first_firm)]
    assert_equal 2, c[companies(:rails_core)]
    assert_equal 1, c[companies(:first_client)]
  end
365

366
  def test_should_group_by_association_with_non_numeric_foreign_key
367 368
    Speedometer.create! id: "ABC"
    Minivan.create! id: "OMG", speedometer_id: "ABC"
369

370
    c = Minivan.group(:speedometer).count(:all)
371
    first_key = c.keys.first
372
    assert_equal Speedometer, first_key.class
373
    assert_equal 1, c[first_key]
374
  end
J
Jeremy Kemper 已提交
375

376
  def test_should_calculate_grouped_association_with_foreign_key_option
377
    Account.belongs_to :another_firm, class_name: "Firm", foreign_key: "firm_id"
J
Jon Leighton 已提交
378
    c = Account.group(:another_firm).count(:all)
379 380 381 382 383
    assert_equal 1, c[companies(:first_firm)]
    assert_equal 2, c[companies(:rails_core)]
    assert_equal 1, c[companies(:first_client)]
  end

384
  def test_should_calculate_grouped_by_function
J
Jon Leighton 已提交
385
    c = Company.group("UPPER(#{QUOTED_TYPE})").count(:all)
386
    assert_equal 2, c[nil]
387 388 389
    assert_equal 1, c["DEPENDENTFIRM"]
    assert_equal 5, c["CLIENT"]
    assert_equal 2, c["FIRM"]
390
  end
J
Jeremy Kemper 已提交
391

392
  def test_should_calculate_grouped_by_function_with_table_alias
J
Jon Leighton 已提交
393
    c = Company.group("UPPER(companies.#{QUOTED_TYPE})").count(:all)
394
    assert_equal 2, c[nil]
395 396 397
    assert_equal 1, c["DEPENDENTFIRM"]
    assert_equal 5, c["CLIENT"]
    assert_equal 2, c["FIRM"]
398
  end
J
Jeremy Kemper 已提交
399

400 401 402
  def test_should_not_overshadow_enumerable_sum
    assert_equal 6, [1, 2, 3].sum(&:abs)
  end
403 404 405 406 407

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

408 409 410 411
  def test_should_sum_scoped_field_with_from
    assert_equal Club.count, Organization.clubs.count
  end

412
  def test_should_sum_scoped_field_with_conditions
413
    assert_equal 8,  companies(:rails_core).companies.where("id > 7").sum(:id)
414 415 416
  end

  def test_should_group_by_scoped_field
J
Jon Leighton 已提交
417
    c = companies(:rails_core).companies.group(:name).sum(:id)
418 419
    assert_equal 7, c["Leetsoft"]
    assert_equal 8, c["Jadedpixel"]
420 421
  end

E
Emilio Tagua 已提交
422
  def test_should_group_by_summed_field_through_association_and_having
423 424 425
    c = companies(:rails_core).companies.group(:name).having("sum(id) > 7").sum(:id)
    assert_nil      c["Leetsoft"]
    assert_equal 8, c["Jadedpixel"]
426
  end
427

428
  def test_should_count_selected_field_with_include
429 430
    assert_equal 6, Account.includes(:firm).distinct.count
    assert_equal 4, Account.includes(:firm).distinct.select(:credit_limit).count
431 432
    assert_equal 4, Account.includes(:firm).distinct.count("DISTINCT credit_limit")
    assert_equal 4, Account.includes(:firm).distinct.count("DISTINCT(credit_limit)")
433
  end
J
Jeremy Kemper 已提交
434

435 436 437 438 439 440 441
  def test_should_not_perform_joined_include_by_default
    assert_equal Account.count, Account.includes(:firm).count
    queries = assert_sql { Account.includes(:firm).count }
    assert_no_match(/join/i, queries.last)
  end

  def test_should_perform_joined_include_when_referencing_included_tables
442
    joined_count = Account.includes(:firm).where(companies: { name: "37signals" }).count
443 444 445
    assert_equal 1, joined_count
  end

446
  def test_should_count_scoped_select
447
    Account.update_all("credit_limit = NULL")
448
    assert_equal 0, Account.select("credit_limit").count
449 450 451
  end

  def test_should_count_scoped_select_with_options
452
    Account.update_all("credit_limit = NULL")
453 454
    Account.last.update_columns("credit_limit" => 49)
    Account.first.update_columns("credit_limit" => 51)
455

456
    assert_equal 1, Account.select("credit_limit").where("credit_limit >= 50").count
457 458
  end

459
  def test_should_count_manual_select_with_include
460
    assert_equal 6, Account.select("DISTINCT accounts.id").includes(:firm).count
461 462
  end

463 464 465 466 467
  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

468 469 470
  def test_count_with_column_parameter
    assert_equal 5, Account.count(:firm_id)
  end
J
Jeremy Kemper 已提交
471

472 473 474 475 476 477 478 479
  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

480 481
  def test_count_with_distinct
    assert_equal 4, Account.select(:credit_limit).distinct.count
482 483
  end

484 485 486 487
  def test_count_with_aliased_attribute
    assert_equal 6, Account.count(:available_credit)
  end

488
  def test_count_with_column_and_options_parameter
J
Jon Leighton 已提交
489
    assert_equal 2, Account.where("credit_limit = 50 AND firm_id IS NOT NULL").count(:firm_id)
490
  end
J
Jeremy Kemper 已提交
491

O
oleg dashevskii 已提交
492
  def test_should_count_field_in_joined_table
493 494
    assert_equal 5, Account.joins(:firm).count("companies.id")
    assert_equal 4, Account.joins(:firm).distinct.count("companies.id")
O
oleg dashevskii 已提交
495 496
  end

497 498 499 500 501 502 503 504 505 506
  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 已提交
507
  def test_should_count_field_in_joined_table_with_group_by
508
    c = Account.group("accounts.firm_id").joins(:firm).count("companies.id")
O
oleg dashevskii 已提交
509

510
    [1, 6, 2, 9].each { |firm_id| assert_includes c.keys, firm_id }
O
oleg dashevskii 已提交
511 512
  end

513 514
  def test_should_count_field_of_root_table_with_conflicting_group_by_column
    assert_equal({ 1 => 1 }, Firm.joins(:accounts).group(:firm_id).count)
515
    assert_equal({ 1 => 1 }, Firm.joins(:accounts).group("accounts.firm_id").count)
516 517
  end

518 519 520 521 522 523 524
  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
525

L
Lauro Caetano 已提交
526 527 528 529 530 531 532 533 534
  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
535 536 537
    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 已提交
538 539
  end

540 541 542 543
  def test_count_with_block
    assert_equal 4, Account.count { |account| account.credit_limit.modulo(10).zero? }
  end

544
  def test_should_sum_expression
545
    if current_adapter?(:SQLite3Adapter, :Mysql2Adapter, :PostgreSQLAdapter, :OracleAdapter)
546 547
      assert_equal 636, Account.sum("2 * credit_limit")
    else
548
      assert_equal 636, Account.sum("2 * credit_limit").to_i
549
    end
550
  end
551

552
  def test_sum_expression_returns_zero_when_no_records_to_sum
553
    assert_equal 0, Account.where("1 = 2").sum("2 * credit_limit")
554 555
  end

556
  def test_count_with_from_option
557
    assert_equal Company.count(:all), Company.from("companies").count(:all)
J
Jon Leighton 已提交
558
    assert_equal Account.where("credit_limit = 50").count(:all),
559
        Account.from("accounts").where("credit_limit = 50").count(:all)
560 561
    assert_equal Company.where(type: "Firm").count(:type),
        Company.where(type: "Firm").from("companies").count(:type)
562 563 564
  end

  def test_sum_with_from_option
565
    assert_equal Account.sum(:credit_limit), Account.from("accounts").sum(:credit_limit)
J
Jon Leighton 已提交
566
    assert_equal Account.where("credit_limit > 50").sum(:credit_limit),
567
        Account.where("credit_limit > 50").from("accounts").sum(:credit_limit)
568 569 570
  end

  def test_average_with_from_option
571
    assert_equal Account.average(:credit_limit), Account.from("accounts").average(:credit_limit)
J
Jon Leighton 已提交
572
    assert_equal Account.where("credit_limit > 50").average(:credit_limit),
573
        Account.where("credit_limit > 50").from("accounts").average(:credit_limit)
574 575 576
  end

  def test_minimum_with_from_option
577
    assert_equal Account.minimum(:credit_limit), Account.from("accounts").minimum(:credit_limit)
J
Jon Leighton 已提交
578
    assert_equal Account.where("credit_limit > 50").minimum(:credit_limit),
579
        Account.where("credit_limit > 50").from("accounts").minimum(:credit_limit)
580 581 582
  end

  def test_maximum_with_from_option
583
    assert_equal Account.maximum(:credit_limit), Account.from("accounts").maximum(:credit_limit)
J
Jon Leighton 已提交
584
    assert_equal Account.where("credit_limit > 50").maximum(:credit_limit),
585
        Account.where("credit_limit > 50").from("accounts").maximum(:credit_limit)
586 587
  end

588
  def test_maximum_with_not_auto_table_name_prefix_if_column_included
589
    Company.create!(name: "test", contracts: [Contract.new(developer_id: 7)])
590

A
Aaron Patterson 已提交
591
    assert_equal 7, Company.includes(:contracts).maximum(:developer_id)
592 593 594
  end

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

A
Aaron Patterson 已提交
597
    assert_equal 7, Company.includes(:contracts).minimum(:developer_id)
598 599 600
  end

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

A
Aaron Patterson 已提交
603
    assert_equal 7, Company.includes(:contracts).sum(:developer_id)
604 605
  end

R
Ryuta Kamizono 已提交
606 607
  if current_adapter?(:Mysql2Adapter)
    def test_from_option_with_specified_index
608 609 610
      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)
611 612 613 614
    end
  end

  def test_from_option_with_table_different_than_class
615
    assert_equal Account.count(:all), Company.from("accounts").count(:all)
616
  end
617 618 619 620

  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]
621
    assert_equal approved_topics_count, 4
622
    # Count the number of distinct authors for approved Topics
623
    distinct_authors_for_approved_count = Topic.group(:approved).distinct.count(:author_name)[true]
624
    assert_equal distinct_authors_for_approved_count, 3
625
  end
626 627

  def test_pluck
628
    assert_equal [1, 2, 3, 4, 5], Topic.order(:id).pluck(:id)
629 630
  end

631
  def test_pluck_without_column_names
632 633 634 635 636
    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
637 638
  end

639 640
  def test_pluck_type_cast
    topic = topics(:first)
641
    relation = Topic.where(id: topic.id)
642 643 644 645 646
    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

647 648 649 650 651 652 653 654 655 656 657 658
  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

659 660
  def test_pluck_and_distinct
    assert_equal [50, 53, 55, 60], Account.order(:credit_limit).distinct.pluck(:credit_limit)
661 662 663 664 665 666 667 668
  end

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

669
  def test_pluck_on_aliased_attribute
670
    assert_equal "The First Topic", Topic.order(:id).pluck(:heading).first
671 672
  end

673
  def test_pluck_with_serialization
674
    t = Topic.create!(content: { foo: :bar })
675
    assert_equal [{ foo: :bar }], Topic.where(id: t.id).pluck(:content)
676 677 678
  end

  def test_pluck_with_qualified_column_name
B
Ben Toews 已提交
679
    assert_equal [1, 2, 3, 4, 5], Topic.order(:id).pluck("topics.id")
680
  end
681 682

  def test_pluck_auto_table_name_prefix
683
    c = Company.create!(name: "test", contracts: [Contract.new])
684 685 686
    assert_equal [c.id], Company.joins(:contracts).pluck(:id)
  end

687
  def test_pluck_if_table_included
688
    c = Company.create!(name: "test", contracts: [Contract.new(developer_id: 7)])
689 690 691
    assert_equal [c.id], Company.includes(:contracts).where("contracts.id" => c.contracts.first).pluck(:id)
  end

692
  def test_pluck_not_auto_table_name_prefix_if_column_joined
693
    Company.create!(name: "test", contracts: [Contract.new(developer_id: 7)])
B
Ben Toews 已提交
694
    assert_equal [7], Company.joins(:contracts).pluck(:developer_id)
695
  end
T
twinturbo 已提交
696

697
  def test_pluck_with_selection_clause
B
Ben Toews 已提交
698 699 700
    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
701 702 703 704

    # MySQL returns "SUM(DISTINCT(credit_limit))" as the column name unless
    # an alias is provided.  Without the alias, the column cannot be found
    # and properly typecast.
B
Ben Toews 已提交
705
    assert_equal [50 + 53 + 55 + 60], Account.pluck(Arel.sql("SUM(DISTINCT(credit_limit)) as credit_limit"))
706 707
  end

T
twinturbo 已提交
708
  def test_plucks_with_ids
J
Jon Leighton 已提交
709
    assert_equal Company.all.map(&:id).sort, Company.ids.sort
T
twinturbo 已提交
710
  end
711

712 713
  def test_pluck_with_includes_limit_and_empty_result
    assert_equal [], Topic.includes(:replies).limit(0).pluck(:id)
714
    assert_equal [], Topic.includes(:replies).limit(1).where("0 = 1").pluck(:id)
715 716
  end

717 718 719 720 721
  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

722 723 724 725
  def test_pluck_with_join
    assert_equal [[2, 2], [4, 4]], Reply.includes(:topic).pluck(:id, :"topics.id")
  end

726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743
  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

744
  def test_pluck_not_auto_table_name_prefix_if_column_included
745
    Company.create!(name: "test", contracts: [Contract.new(developer_id: 7)])
B
Ben Toews 已提交
746
    ids = Company.includes(:contracts).pluck(:developer_id)
747 748 749
    assert_equal Company.count, ids.length
    assert_equal [7], ids.compact
  end
750 751 752 753

  def test_pluck_multiple_columns
    assert_equal [
      [1, "The First Topic"], [2, "The Second Topic of the day"],
754 755
      [3, "The Third Topic of the day"], [4, "The Fourth Topic of the day"],
      [5, "The Fifth Topic of the day"]
756
    ], Topic.order(:id).pluck(:id, :title)
757 758
    assert_equal [
      [1, "The First Topic", "David"], [2, "The Second Topic of the day", "Mary"],
759 760
      [3, "The Third Topic of the day", "Carl"], [4, "The Fourth Topic of the day", "Carl"],
      [5, "The Fifth Topic of the day", "Jason"]
761 762 763 764 765
    ], 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]],
B
Ben Toews 已提交
766
      Account.pluck("id, credit_limit")
767 768 769
  end

  def test_pluck_with_multiple_columns_and_includes
770
    Company.create!(name: "test", contracts: [Contract.new(developer_id: 7)])
B
Ben Toews 已提交
771
    companies_and_developers = Company.order("companies.id").includes(:contracts).pluck(:name, :developer_id)
772 773 774 775

    assert_equal Company.count, companies_and_developers.length
    assert_equal ["37signals", nil], companies_and_developers.first
    assert_equal ["test", 7], companies_and_developers.last
776
  end
777 778

  def test_pluck_with_reserved_words
779
    Possession.create!(where: "Over There")
780 781 782

    assert_equal ["Over There"], Possession.pluck(:where)
  end
783 784 785

  def test_pluck_replaces_select_clause
    taks_relation = Topic.select(:approved, :id).order(:id)
786
    assert_equal [1, 2, 3, 4, 5], taks_relation.pluck(:id)
787
    assert_equal [false, true, true, true, true], taks_relation.pluck(:approved)
788
  end
789 790 791 792

  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"]]
    actual = Topic.joins(:replies)
B
Ben Toews 已提交
793
      .pluck("topics.title", "replies_topics.title")
794 795
    assert_equal expected, actual
  end
796 797

  def test_calculation_with_polymorphic_relation
798 799 800 801
    part = ShipPart.create!(name: "has trinket")
    part.trinkets.create!

    assert_equal part.id, ShipPart.joins(:trinkets).sum(:id)
802 803
  end

S
Sean Griffin 已提交
804 805 806 807 808 809 810
  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

811
  def test_pluck_loaded_relation
Y
yuuji.yaginuma 已提交
812
    Company.attribute_names # Load schema information so we don't query below
813
    companies = Company.order(:id).limit(3).load
Y
yuuji.yaginuma 已提交
814

815
    assert_no_queries do
816
      assert_equal ["37signals", "Summit", "Microsoft"], companies.pluck(:name)
817 818 819 820
    end
  end

  def test_pluck_loaded_relation_multiple_columns
Y
yuuji.yaginuma 已提交
821
    Company.attribute_names # Load schema information so we don't query below
822
    companies = Company.order(:id).limit(3).load
Y
yuuji.yaginuma 已提交
823

824
    assert_no_queries do
825
      assert_equal [[1, "37signals"], [2, "Summit"], [3, "Microsoft"]], companies.pluck(:id, :name)
826 827 828 829
    end
  end

  def test_pluck_loaded_relation_sql_fragment
Y
yuuji.yaginuma 已提交
830
    Company.attribute_names # Load schema information so we don't query below
831
    companies = Company.order(:name).limit(3).load
Y
yuuji.yaginuma 已提交
832

833
    assert_queries 1 do
B
Ben Toews 已提交
834
      assert_equal ["37signals", "Apex", "Ex Nihilo"], companies.pluck(Arel.sql("DISTINCT name"))
835 836 837
    end
  end

838 839
  def test_pick_one
    assert_equal "The First Topic", Topic.order(:id).pick(:heading)
840
    assert_nil Topic.none.pick(:heading)
841 842 843 844 845
    assert_nil Topic.where("1=0").pick(:heading)
  end

  def test_pick_two
    assert_equal ["David", "david@loudthinking.com"], Topic.order(:id).pick(:author_name, :author_email_address)
846
    assert_nil Topic.none.pick(:author_name, :author_email_address)
847 848 849
    assert_nil Topic.where("1=0").pick(:author_name, :author_email_address)
  end

Y
Yuji Hanamura 已提交
850 851 852 853 854
  def test_pick_delegate_to_all
    cool_first = minivans(:cool_first)
    assert_equal cool_first.color, Minivan.pick(:color)
  end

855 856 857 858 859 860
  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
861 862 863 864 865

  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
866 867

  def test_should_reference_correct_aliases_while_joining_tables_of_has_many_through_association
868
    assert_nothing_raised do
869
      developer = Developer.create!(name: "developer")
870 871 872
      developer.ratings.includes(comment: :post).where(posts: { id: 1 }).count
    end
  end
873 874 875 876 877 878 879 880 881 882

  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
883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903

  def test_having_with_strong_parameters
    protected_params = Class.new do
      attr_reader :permitted
      alias :permitted? :permitted

      def initialize(parameters)
        @parameters = parameters
        @permitted = false
      end

      def to_h
        @parameters
      end

      def permit!
        @permitted = true
        self
      end
    end

904
    params = protected_params.new(credit_limit: "50")
905 906 907 908 909 910 911 912 913 914

    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
915 916

  def test_group_by_attribute_with_custom_type
R
Ryuta Kamizono 已提交
917
    assert_equal({ "proposed" => 2, "published" => 2 }, Book.group(:status).count)
918
  end
919 920 921 922 923 924 925 926 927 928 929 930

  def test_deprecate_count_with_block_and_column_name
    assert_deprecated do
      assert_equal 6, Account.count(:firm_id) { true }
    end
  end

  def test_deprecate_sum_with_block_and_column_name
    assert_deprecated do
      assert_equal 6, Account.sum(:firm_id) { 1 }
    end
  end
931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972

  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
973
end