enum_test.rb 14.9 KB
Newer Older
1 2
# frozen_string_literal: true

3
require "cases/helper"
4
require "models/author"
5
require "models/book"
6

7
class EnumTest < ActiveRecord::TestCase
8
  fixtures :books, :authors
9 10

  setup do
11
    @book = books(:awdr)
12 13 14
  end

  test "query state by predicate" do
G
George Claghorn 已提交
15
    assert @book.published?
16
    assert_not @book.written?
G
George Claghorn 已提交
17
    assert_not @book.proposed?
Y
Yury Korolev 已提交
18

G
George Claghorn 已提交
19
    assert @book.read?
20 21 22 23
    assert @book.in_english?
    assert @book.author_visibility_visible?
    assert @book.illustrator_visibility_visible?
    assert @book.with_medium_font_size?
24
    assert @book.medium_to_read?
25
  end
26

R
Robin Dupret 已提交
27
  test "query state with strings" do
G
George Claghorn 已提交
28 29
    assert_equal "published", @book.status
    assert_equal "read", @book.read_status
30 31 32
    assert_equal "english", @book.language
    assert_equal "visible", @book.author_visibility
    assert_equal "visible", @book.illustrator_visibility
33
    assert_equal "medium", @book.difficulty
34 35
  end

36
  test "find via scope" do
G
George Claghorn 已提交
37 38
    assert_equal @book, Book.published.first
    assert_equal @book, Book.read.first
39 40 41
    assert_equal @book, Book.in_english.first
    assert_equal @book, Book.author_visibility_visible.first
    assert_equal @book, Book.illustrator_visibility_visible.first
42
    assert_equal @book, Book.medium_to_read.first
43 44
    assert_equal books(:ddd), Book.forgotten.first
    assert_equal books(:rfr), authors(:david).unpublished_books.first
45 46
  end

G
Godfrey Chan 已提交
47
  test "find via where with values" do
G
George Claghorn 已提交
48
    published, written = Book.statuses[:published], Book.statuses[:written]
G
Godfrey Chan 已提交
49

G
George Claghorn 已提交
50
    assert_equal @book, Book.where(status: published).first
51
    assert_not_equal @book, Book.where(status: written).first
G
George Claghorn 已提交
52
    assert_equal @book, Book.where(status: [published]).first
53 54
    assert_not_equal @book, Book.where(status: [written]).first
    assert_not_equal @book, Book.where("status <> ?", published).first
G
Godfrey Chan 已提交
55
    assert_equal @book, Book.where("status <> ?", written).first
56 57
  end

58
  test "find via where with symbols" do
G
George Claghorn 已提交
59
    assert_equal @book, Book.where(status: :published).first
60
    assert_not_equal @book, Book.where(status: :written).first
G
George Claghorn 已提交
61
    assert_equal @book, Book.where(status: [:published]).first
62 63
    assert_not_equal @book, Book.where(status: [:written]).first
    assert_not_equal @book, Book.where.not(status: :published).first
64
    assert_equal @book, Book.where.not(status: :written).first
65
    assert_equal books(:ddd), Book.where(read_status: :forgotten).first
66 67 68
  end

  test "find via where with strings" do
G
George Claghorn 已提交
69
    assert_equal @book, Book.where(status: "published").first
70
    assert_not_equal @book, Book.where(status: "written").first
G
George Claghorn 已提交
71
    assert_equal @book, Book.where(status: ["published"]).first
72 73
    assert_not_equal @book, Book.where(status: ["written"]).first
    assert_not_equal @book, Book.where.not(status: "published").first
74
    assert_equal @book, Book.where.not(status: "written").first
75
    assert_equal books(:ddd), Book.where(read_status: "forgotten").first
76 77
  end

G
Godfrey Chan 已提交
78 79
  test "build from scope" do
    assert Book.written.build.written?
80
    assert_not Book.written.build.proposed?
G
Godfrey Chan 已提交
81 82 83 84
  end

  test "build from where" do
    assert Book.where(status: Book.statuses[:written]).build.written?
85
    assert_not Book.where(status: Book.statuses[:written]).build.proposed?
G
Godfrey Chan 已提交
86
    assert Book.where(status: :written).build.written?
87
    assert_not Book.where(status: :written).build.proposed?
G
Godfrey Chan 已提交
88
    assert Book.where(status: "written").build.written?
89
    assert_not Book.where(status: "written").build.proposed?
G
Godfrey Chan 已提交
90 91
  end

92 93 94
  test "update by declaration" do
    @book.written!
    assert @book.written?
95 96 97 98
    @book.in_english!
    assert @book.in_english?
    @book.author_visibility_visible!
    assert @book.author_visibility_visible?
99
  end
100

101 102 103 104
  test "update by setter" do
    @book.update! status: :written
    assert @book.written?
  end
105 106 107 108 109

  test "enum methods are overwritable" do
    assert_equal "do publish work...", @book.published!
    assert @book.published?
  end
110 111 112 113 114 115

  test "direct assignment" do
    @book.status = :written
    assert @book.written?
  end

116 117 118 119 120
  test "assign string value" do
    @book.status = "written"
    assert @book.written?
  end

121 122
  test "enum changed attributes" do
    old_status = @book.status
123
    old_language = @book.language
G
George Claghorn 已提交
124
    @book.status = :proposed
125
    @book.language = :spanish
126
    assert_equal old_status, @book.changed_attributes[:status]
127
    assert_equal old_language, @book.changed_attributes[:language]
128 129 130 131
  end

  test "enum changes" do
    old_status = @book.status
132
    old_language = @book.language
G
George Claghorn 已提交
133
    @book.status = :proposed
134
    @book.language = :spanish
135 136
    assert_equal [old_status, "proposed"], @book.changes[:status]
    assert_equal [old_language, "spanish"], @book.changes[:language]
137 138 139 140
  end

  test "enum attribute was" do
    old_status = @book.status
141
    old_language = @book.language
142
    @book.status = :published
143
    @book.language = :spanish
144
    assert_equal old_status, @book.attribute_was(:status)
145
    assert_equal old_language, @book.attribute_was(:language)
146 147 148
  end

  test "enum attribute changed" do
G
George Claghorn 已提交
149
    @book.status = :proposed
150
    @book.language = :french
151
    assert @book.attribute_changed?(:status)
152
    assert @book.attribute_changed?(:language)
153 154 155
  end

  test "enum attribute changed to" do
G
George Claghorn 已提交
156
    @book.status = :proposed
157
    @book.language = :french
158 159
    assert @book.attribute_changed?(:status, to: "proposed")
    assert @book.attribute_changed?(:language, to: "french")
160 161 162 163
  end

  test "enum attribute changed from" do
    old_status = @book.status
164
    old_language = @book.language
G
George Claghorn 已提交
165
    @book.status = :proposed
166
    @book.language = :french
167
    assert @book.attribute_changed?(:status, from: old_status)
168
    assert @book.attribute_changed?(:language, from: old_language)
169 170 171 172
  end

  test "enum attribute changed from old status to new status" do
    old_status = @book.status
173
    old_language = @book.language
G
George Claghorn 已提交
174
    @book.status = :proposed
175
    @book.language = :french
176 177
    assert @book.attribute_changed?(:status, from: old_status, to: "proposed")
    assert @book.attribute_changed?(:language, from: old_language, to: "french")
178 179
  end

180 181 182 183 184 185 186
  test "enum didn't change" do
    old_status = @book.status
    @book.status = old_status
    assert_not @book.attribute_changed?(:status)
  end

  test "persist changes that are dirty" do
G
George Claghorn 已提交
187
    @book.status = :proposed
188 189 190 191 192 193 194
    assert @book.attribute_changed?(:status)
    @book.status = :written
    assert @book.attribute_changed?(:status)
  end

  test "reverted changes that are not dirty" do
    old_status = @book.status
G
George Claghorn 已提交
195
    @book.status = :proposed
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
    assert @book.attribute_changed?(:status)
    @book.status = old_status
    assert_not @book.attribute_changed?(:status)
  end

  test "reverted changes are not dirty going from nil to value and back" do
    book = Book.create!(nullable_status: nil)

    book.nullable_status = :married
    assert book.attribute_changed?(:nullable_status)

    book.nullable_status = nil
    assert_not book.attribute_changed?(:nullable_status)
  end

211 212 213 214 215 216
  test "assign non existing value raises an error" do
    e = assert_raises(ArgumentError) do
      @book.status = :unknown
    end
    assert_equal "'unknown' is not a valid status", e.message
  end
217

218 219 220 221 222
  test "NULL values from database should be casted to nil" do
    Book.where(id: @book.id).update_all("status = NULL")
    assert_nil @book.reload.status
  end

223 224
  test "assign nil value" do
    @book.status = nil
225
    assert_nil @book.status
226 227 228
  end

  test "assign empty string value" do
229
    @book.status = ""
230
    assert_nil @book.status
231 232 233
  end

  test "assign long empty string value" do
234
    @book.status = "   "
235
    assert_nil @book.status
236 237
  end

238
  test "constant to access the mapping" do
239 240 241
    assert_equal 0, Book.statuses[:proposed]
    assert_equal 1, Book.statuses["written"]
    assert_equal 2, Book.statuses[:published]
242
  end
R
Robin Dupret 已提交
243

244 245 246
  test "building new objects with enum scopes" do
    assert Book.written.build.written?
    assert Book.read.build.read?
247 248
    assert Book.in_spanish.build.in_spanish?
    assert Book.illustrator_visibility_invisible.build.illustrator_visibility_invisible?
249
  end
R
Robin Dupret 已提交
250

251 252 253
  test "creating new objects with enum scopes" do
    assert Book.written.create.written?
    assert Book.read.create.read?
254 255
    assert Book.in_spanish.create.in_spanish?
    assert Book.illustrator_visibility_invisible.create.illustrator_visibility_invisible?
R
Robin Dupret 已提交
256
  end
257

258 259 260 261 262 263 264 265
  test "_before_type_cast" do
    assert_equal 2, @book.status_before_type_cast
    assert_equal "published", @book.status

    @book.status = "published"

    assert_equal "published", @book.status_before_type_cast
    assert_equal "published", @book.status
266
  end
267 268 269 270 271 272 273 274 275 276 277 278 279 280

  test "reserved enum names" do
    klass = Class.new(ActiveRecord::Base) do
      self.table_name = "books"
      enum status: [:proposed, :written, :published]
    end

    conflicts = [
      :column,     # generates class method .columns, which conflicts with an AR method
      :logger,     # generates #logger, which conflicts with an AR method
      :attributes, # generates #attributes=, which conflicts with an AR method
    ]

    conflicts.each_with_index do |name, i|
F
Franky W 已提交
281
      e = assert_raises(ArgumentError) do
282 283
        klass.class_eval { enum name => ["value_#{i}"] }
      end
F
Franky W 已提交
284
      assert_match(/You tried to define an enum named \"#{name}\" on the model/, e.message)
285 286 287 288 289 290 291 292 293 294 295 296 297 298
    end
  end

  test "reserved enum values" do
    klass = Class.new(ActiveRecord::Base) do
      self.table_name = "books"
      enum status: [:proposed, :written, :published]
    end

    conflicts = [
      :new,      # generates a scope that conflicts with an AR class method
      :valid,    # generates #valid?, which conflicts with an AR method
      :save,     # generates #save!, which conflicts with an AR method
      :proposed, # same value as an existing enum
299 300
      :public, :private, :protected, # some important methods on Module and Class
      :name, :parent, :superclass
301 302 303
    ]

    conflicts.each_with_index do |value, i|
F
Franky W 已提交
304
      e = assert_raises(ArgumentError, "enum value `#{value}` should not be allowed") do
305 306
        klass.class_eval { enum "status_#{i}" => [value] }
      end
F
Franky W 已提交
307
      assert_match(/You tried to define an enum named .* on the model/, e.message)
308 309 310 311 312
    end
  end

  test "overriding enum method should not raise" do
    assert_nothing_raised do
T
Tim Fenney 已提交
313
      Class.new(ActiveRecord::Base) do
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
        self.table_name = "books"

        def published!
          super
          "do publish work..."
        end

        enum status: [:proposed, :written, :published]

        def written!
          super
          "do written work..."
        end
      end
    end
  end
330 331 332

  test "validate uniqueness" do
    klass = Class.new(ActiveRecord::Base) do
333
      def self.name; "Book"; end
334 335 336 337 338 339 340 341 342 343 344 345 346
      enum status: [:proposed, :written]
      validates_uniqueness_of :status
    end
    klass.delete_all
    klass.create!(status: "proposed")
    book = klass.new(status: "written")
    assert book.valid?
    book.status = "proposed"
    assert_not book.valid?
  end

  test "validate inclusion of value in array" do
    klass = Class.new(ActiveRecord::Base) do
347
      def self.name; "Book"; end
348 349 350 351 352 353 354 355 356
      enum status: [:proposed, :written]
      validates_inclusion_of :status, in: ["written"]
    end
    klass.delete_all
    invalid_book = klass.new(status: "proposed")
    assert_not invalid_book.valid?
    valid_book = klass.new(status: "written")
    assert valid_book.valid?
  end
E
Evan Whalen 已提交
357 358

  test "enums are distinct per class" do
359 360 361 362 363 364 365 366
    klass1 = Class.new(ActiveRecord::Base) do
      self.table_name = "books"
      enum status: [:proposed, :written]
    end

    klass2 = Class.new(ActiveRecord::Base) do
      self.table_name = "books"
      enum status: [:drafted, :uploaded]
E
Evan Whalen 已提交
367
    end
368 369 370

    book1 = klass1.proposed.create!
    book1.status = :written
371
    assert_equal ["proposed", "written"], book1.status_change
372 373 374

    book2 = klass2.drafted.create!
    book2.status = :uploaded
375
    assert_equal ["drafted", "uploaded"], book2.status_change
E
Evan Whalen 已提交
376 377 378
  end

  test "enums are inheritable" do
379 380 381 382
    subklass1 = Class.new(Book)

    subklass2 = Class.new(Book) do
      enum status: [:drafted, :uploaded]
E
Evan Whalen 已提交
383
    end
384 385 386

    book1 = subklass1.proposed.create!
    book1.status = :written
387
    assert_equal ["proposed", "written"], book1.status_change
388 389 390

    book2 = subklass2.drafted.create!
    book2.status = :uploaded
391
    assert_equal ["drafted", "uploaded"], book2.status_change
E
Evan Whalen 已提交
392
  end
393 394 395 396 397 398 399 400 401 402 403 404 405 406

  test "declare multiple enums at a time" do
    klass = Class.new(ActiveRecord::Base) do
      self.table_name = "books"
      enum status: [:proposed, :written, :published],
           nullable_status: [:single, :married]
    end

    book1 = klass.proposed.create!
    assert book1.proposed?

    book2 = klass.single.create!
    assert book2.single?
  end
407

408 409 410 411 412 413 414 415 416
  test "enum with alias_attribute" do
    klass = Class.new(ActiveRecord::Base) do
      self.table_name = "books"
      alias_attribute :aliased_status, :status
      enum aliased_status: [:proposed, :written, :published]
    end

    book = klass.proposed.create!
    assert book.proposed?
417
    assert_equal "proposed", book.aliased_status
418 419 420

    book = klass.find(book.id)
    assert book.proposed?
421
    assert_equal "proposed", book.aliased_status
422 423
  end

424 425 426 427 428 429 430 431 432 433 434 435
  test "query state by predicate with prefix" do
    assert @book.author_visibility_visible?
    assert_not @book.author_visibility_invisible?
    assert @book.illustrator_visibility_visible?
    assert_not @book.illustrator_visibility_invisible?
  end

  test "query state by predicate with custom prefix" do
    assert @book.in_english?
    assert_not @book.in_spanish?
    assert_not @book.in_french?
  end
436

437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473
  test "query state by predicate with custom suffix" do
    assert     @book.medium_to_read?
    assert_not @book.easy_to_read?
    assert_not @book.hard_to_read?
  end

  test "enum methods with custom suffix defined" do
    assert @book.class.respond_to?(:easy_to_read)
    assert @book.class.respond_to?(:medium_to_read)
    assert @book.class.respond_to?(:hard_to_read)

    assert @book.respond_to?(:easy_to_read?)
    assert @book.respond_to?(:medium_to_read?)
    assert @book.respond_to?(:hard_to_read?)

    assert @book.respond_to?(:easy_to_read!)
    assert @book.respond_to?(:medium_to_read!)
    assert @book.respond_to?(:hard_to_read!)
  end

  test "update enum attributes with custom suffix" do
    @book.medium_to_read!
    assert_not @book.easy_to_read?
    assert     @book.medium_to_read?
    assert_not @book.hard_to_read?

    @book.easy_to_read!
    assert     @book.easy_to_read?
    assert_not @book.medium_to_read?
    assert_not @book.hard_to_read?

    @book.hard_to_read!
    assert_not @book.easy_to_read?
    assert_not @book.medium_to_read?
    assert     @book.hard_to_read?
  end

474 475 476 477 478
  test "uses default status when no status is provided in fixtures" do
    book = books(:tlg)
    assert book.proposed?, "expected fixture to default to proposed status"
    assert book.in_english?, "expected fixture to default to english language"
  end
479 480 481 482 483 484 485 486 487 488

  test "uses default value from database on initialization" do
    book = Book.new
    assert book.proposed?
  end

  test "uses default value from database on initialization when using custom mapping" do
    book = Book.new
    assert book.hard?
  end
489 490

  test "data type of Enum type" do
491
    assert_equal :integer, Book.type_for_attribute("status").type
492
  end
493
end