length: four shapes for one option
Two earlier courses pointed here
In ActiveRecord: model introduction, the lesson "Validations: a rule before saving" introduced validates as a rule Rails checks before it saves a record. It ended by saying the options, the ways a check can fail, and how to report failures to a user would get a course of their own. In ActiveRecord: creating records, the lesson "When save returns false" went one step further. It showed .save returning false when a validation fails and article.errors.full_messages explaining why, and it pointed ahead to the same course. That course also covered .save! and .create! raising ActiveRecord::RecordInvalid, so this one doesn't repeat it.
This is that course. It starts with an option two models in this project already use: length.
length already has two shapes in this codebase
# from Identity::User, this project's real model
validates :username, presence: true,
uniqueness: { case_sensitive: false, message: "is not available" },
length: { minimum: 3, maximum: 30 },
format: { with: /\A[a-zA-Z0-9_]+\z/ }
length: { minimum: 3, maximum: 30 } rejects a username shorter than 3 characters or longer than 30. That's two separate limits on the same attribute. Catalog::Course and Catalog::Lesson use the simpler form, with one limit:
# from Catalog::Course, this project's real model
validates :title_en, presence: true, length: { maximum: 255 }
This one has an upper limit and no lower one. Both lines use the same length: option with different keys.
Two more shapes, tried on a scratch table
Neither in: nor is: appears anywhere in this codebase today. in: takes a range that sets both limits at once, and is: demands an exact length. Here they are on a throwaway ScratchArticle model built for this lesson:
class ScratchArticle < ApplicationRecord
validates :title, length: { in: 3..20 }
validates :code, length: { is: 4 }
end
a = ScratchArticle.new(title: "ab")
a.valid?
a.errors[:title]
# => ["is too short (minimum is 3 characters)"]
b = ScratchArticle.new(title: "a" * 25, code: "abc")
b.valid?
b.errors[:title]
# => ["is too long (maximum is 20 characters)"]
b.errors[:code]
# => ["is the wrong length (should be 4 characters)"]
in: 3..20 is minimum: 3, maximum: 20 written as one range, with the same failures and the same messages. is: 4 is stricter than either. It isn't a range at all: the value has one allowed length.
Four keys, one option
minimum, maximum, in and is all belong to the one length: option; they aren't four different validations. A model picks the shape that matches its rule: an upper limit alone, like the title on Catalog::Course; both limits, like the username on Identity::User; a range written as one value; or an exact count, for something like a fixed-width code.
Rails reads whichever keys are present. The message names the limit that failed ("too short", "too long", "the wrong length") instead of a generic "invalid".
Key takeaways
- ActiveRecord: model introduction introduced
validates, and ActiveRecord: creating records showed.savereturningfalsewith.errors.full_messagesexplaining why. This course picks up from both. length: { minimum:, maximum: }onIdentity::Userandlength: { maximum: }onCatalog::Courseare the same option with different keys.in:(a range) andis:(an exact length) don't appear in this codebase. On a scratch table,in: 3..20behaves likeminimum: 3, maximum: 20, andis: 4accepts only that one length.- Each shape has its own error message ("too short", "too long", "the wrong length"), which you read from
.errors[:attribute].
Keep going with lesson 2
uniqueness: scoped, and case-insensitive on purpose
Scoping uniqueness to a group with scope:, ignoring capitalization with case_sensitive: false, and replacing the default message.
A free account also gives you the quizzes, your progress and notes, and the discussion.