1 Validations 개요
다음은 매우 간단한 validation의 예시입니다:
class Person < ApplicationRecord
validates :name, presence: true
end
irb> Person.new(name: "John Doe").valid? # name에 값 있음
=> true
irb> Person.new(name: nil).valid? # name이 nil
=> false
보시다시피, Person
은 name
attribute 없이는 유효하지 않습니다.
더 자세한 내용을 살펴보기 전에, validation이 애플리케이션의 전체적인 그림에서 어떻게 들어맞는지 이야기해보겠습니다.
1.1 왜 Validation을 사용하나요?
Validation은 유효한 데이터만 데이터베이스에 저장되도록 보장하기 위해 사용됩니다. 예를 들어, 애플리케이션에서 모든 사용자가 유효한 이메일 주소와 우편 주소를 제공하도록 보장하는 것이 중요할 수 있습니다. Model-level validation은 유효한 데이터만 데이터베이스에 저장되도록 보장하는 가장 좋은 방법입니다. 이는 모든 데이터베이스에서 사용할 수 있고, 최종 사용자가 우회할 수 없으며, 테스트와 유지보수가 용이합니다. Rails는 일반적인 요구사항에 대한 내장 헬퍼를 제공하고, 사용자가 직접 validation 메서드를 만들 수도 있게 해줍니다.
1.2 데이터 검증의 다른 방법들
데이터베이스에 데이터를 저장하기 전에 검증하는 여러 가지 방법이 있습니다. 여기에는 데이터베이스 자체의 제약 조건, client-side 검증, controller-level 검증이 포함됩니다. 각각의 장단점은 다음과 같습니다:
- 데이터베이스 제약 조건과 stored procedure는 검증 메커니즘이 데이터베이스에 종속되며 테스트와 유지보수를 더 어렵게 만들 수 있습니다. 하지만 데이터베이스가 다른 애플리케이션에서도 사용된다면 데이터베이스 수준에서 일부 제약 조건을 사용하는 것이 좋을 수 있습니다. 또한 데이터베이스 수준의 검증은 다른 방식으로는 구현하기 어려운 일부 작업(예: 사용량이 많은 테이블에서의 고유성)을 안전하게 처리할 수 있습니다.
- Client-side 검증은 유용할 수 있지만, 단독으로 사용하면 일반적으로 신뢰성이 떨어집니다. JavaScript로 구현된 경우 사용자의 브라우저에서 JavaScript가 꺼져 있으면 우회될 수 있습니다. 하지만 다른 기술과 결합하면 사이트 사용 중에 사용자에게 즉각적인 피드백을 제공하는 편리한 방법이 될 수 있습니다.
- Controller-level 검증은 사용하고 싶은 유혹이 있을 수 있지만, 종종 다루기 어렵고 테스트와 유지보수가 어려워집니다. 가능한 한 controller를 단순하게 유지하는 것이 좋습니다. 이는 장기적으로 애플리케이션 작업을 더 쉽게 만들 것입니다.
Rails는 대부분의 경우 model-level 검증을 사용하는 것을 권장하지만, 이를 다른 검증 방법으로 보완하고 싶은 특정 사례가 있을 수 있습니다.
1.3 Validation Triggers
Active Record 객체에는 두 가지 종류가 있습니다 - 데이터베이스 내의 행에 해당하는 것과 그렇지 않은 것입니다. new
메서드를 사용하여 새로운 객체를 인스턴스화할 때, 해당 객체는 아직 데이터베이스에 저장되지 않습니다. 해당 객체에 대해 save
를 호출하면 그때 적절한 데이터베이스 테이블에 저장됩니다. Active Record는 객체가 이미 데이터베이스에 있는지 여부를 판단하기 위해 persisted?
(그리고 그 반대인 new_record?
)라는 인스턴스 메서드를 사용합니다. 다음의 Active Record 클래스를 살펴보세요:
class Person < ApplicationRecord
end
bin/rails console
출력을 살펴보면 어떻게 동작하는지 알 수 있습니다:
irb> p = Person.new(name: "Jane Doe")
=> #<Person id: nil, name: "Jane Doe", created_at: nil, updated_at: nil>
irb> p.new_record? # 새 레코드인지 확인
=> true
irb> p.persisted? # 저장되어 있는지 확인
=> false
irb> p.save # 저장
=> true
irb> p.new_record? # 이제 더 이상 새 레코드가 아님
=> false
irb> p.persisted? # 이제 저장되어 있음
=> true
새로운 레코드를 저장하면 데이터베이스에 SQL INSERT
연산을 보내고, 기존 레코드를 업데이트하면 SQL UPDATE
연산을 보냅니다. 유효성 검사는 일반적으로 이러한 명령이 데이터베이스로 전송되기 전에 실행됩니다. 유효성 검사에 실패하면 객체는 유효하지 않은 것으로 표시되고 Active Record는 INSERT
또는 UPDATE
연산을 수행하지 않습니다. 이를 통해 유효하지 않은 객체가 데이터베이스에 저장되는 것을 방지할 수 있습니다. 객체가 생성, 저장 또는 업데이트될 때 특정 유효성 검사가 실행되도록 선택할 수 있습니다.
유효성 검사는 일반적으로 유효하지 않은 데이터가 데이터베이스에 저장되는 것을 방지하지만, Rails의 모든 메서드가 유효성 검사를 트리거하지는 않는다는 점을 인지하는 것이 중요합니다. 일부 메서드는 유효성 검사를 수행하지 않고 데이터베이스에 직접 변경을 할 수 있습니다. 결과적으로, 주의하지 않으면 유효성 검사를 우회하여 유효하지 않은 상태의 객체를 저장할 수 있습니다.
다음 메서드들은 유효성 검사를 트리거하며, 객체가 유효한 경우에만 데이터베이스에 객체를 저장합니다:
뱅 버전(느낌표로 끝나는 메서드, 예: save!
)은 레코드가 유효하지 않을 경우 예외를 발생시킵니다. 비뱅 버전인 save
와 update
는 false
를 반환하고, create
는 객체를 반환합니다.
1.4 Validation 건너뛰기
다음 메서드들은 validation을 건너뛰고, 유효성과 관계없이 객체를 데이터베이스에 저장합니다. 이들은 주의해서 사용해야 합니다. 자세한 내용은 메서드 문서를 참조하세요.
decrement!
decrement_counter
increment!
increment_counter
insert
insert!
insert_all
insert_all!
toggle!
- [
touch
][] touch_all
update_all
update_attribute
update_attribute!
update_column
update_columns
update_counters
upsert
upsert_all
save(validate: false)
save
는 인자로 validate: false
를 전달하면 validation을 건너뛸 수 있습니다. 이 기술은 주의해서 사용해야 합니다.
[touch
]:
1.5 Validity 확인하기
Active Record 객체를 저장하기 전에 Rails는 validation을 실행하고, validation에서 오류가 발생하면 Rails는 해당 객체를 저장하지 않습니다.
validation을 직접 실행할 수도 있습니다. valid?
는 validation을 실행하고 객체에 오류가 없으면 true를, 그렇지 않으면 false를 반환합니다. 위에서 본 것처럼:
class Person < ApplicationRecord
validates :name, presence: true
end
irb> Person.new(name: "John Doe").valid?
=> true
irb> Person.new(name: nil).valid?
=> false
Active Record가 유효성 검사를 수행한 후에는 errors
인스턴스 메서드를 통해 실패한 내용에 접근할 수 있으며, 이는 에러 컬렉션을 반환합니다. 정의에 따르면, 유효성 검사를 실행한 후 컬렉션이 비어있다면 해당 객체는 유효한 것입니다.
new
로 인스턴스화된 객체는 기술적으로 유효하지 않더라도 에러를 보고하지 않습니다. 이는 유효성 검사가 create
나 save
메서드와 같이 객체가 저장될 때만 자동으로 실행되기 때문입니다.
class Person < ApplicationRecord
validates :name, presence: true
end
irb> person = Person.new
=> #<Person id: nil, name: nil, created_at: nil, updated_at: nil>
irb> person.errors.size
=> 0
irb> person.valid?
=> false
irb> person.errors.objects.first.full_message
=> "Name은 비워둘 수 없습니다"
irb> person.save
=> false
irb> person.save!
ActiveRecord::RecordInvalid: 유효성 검증 실패: Name은 비워둘 수 없습니다
irb> Person.create!
ActiveRecord::RecordInvalid: 유효성 검증 실패: Name은 비워둘 수 없습니다
invalid?
는 valid?
의 반대입니다. 검증을 실행하고, 객체에서 오류가 발견되면 true를, 그렇지 않으면 false를 반환합니다.
1.6 에러 검사와 처리
객체의 특정 attribute가 유효한지 여부를 확인하려면 errors[:attribute]
를 사용할 수 있습니다. 이는 :attribute
에 대한 모든 에러 메시지를 배열로 반환합니다. 지정된 attribute에 에러가 없는 경우 빈 배열이 반환됩니다. 이를 통해 특정 attribute에 유효성 검증 문제가 있는지 쉽게 확인할 수 있습니다.
다음은 attribute의 에러를 확인하는 방법을 보여주는 예시입니다:
class Person < ApplicationRecord
validates :name, presence: true
end
irb> new_person = Person.new
irb> new_person.errors[:name]
=> [] # validation이 저장될 때까지 실행되지 않으므로 오류 없음
irb> new_person.errors[:name].any?
=> false
irb> create_person = Person.create
irb> create_person.errors[:name]
=> ["비어 있을 수 없습니다"] # `name`이 필수이므로 validation 오류 발생
irb> create_person.errors[:name].any?
=> true
추가적으로, errors.add
메서드를 사용하여 특정 attributes에 대한 error message를 수동으로 추가할 수 있습니다. 이는 커스텀 validation 시나리오를 정의할 때 특히 유용합니다.
class Person < ApplicationRecord
validate do |person|
errors.add :name, :too_short, message: "길이가 충분하지 않습니다"
end
end
validation 에러에 대해 더 자세히 알아보려면 Validation 에러 다루기 섹션을 참고하세요.
2 Validations
Active Record는 클래스 정의 내에서 직접 사용할 수 있는 많은 사전 정의된 validation들을 제공합니다. 이러한 사전 정의된 validation들은 일반적인 validation 규칙을 제공합니다. validation이 실패할 때마다 에러 메시지가 객체의 errors
컬렉션에 추가되며, 이 에러는 validation이 수행되는 특정 attribute와 연결됩니다.
validation이 실패하면 에러 메시지는 validation을 트리거한 attribute 이름 아래의 errors
컬렉션에 저장됩니다. 이는 특정 attribute와 관련된 에러에 쉽게 접근할 수 있다는 것을 의미합니다. 예를 들어, :name
attribute를 검증하고 validation이 실패하면 errors[:name]
아래에서 에러 메시지를 찾을 수 있습니다.
최신 Rails 애플리케이션에서는 더 간결한 validate 구문이 일반적으로 사용됩니다. 예를 들면:
validate :name, presence: true
하지만 이전 버전의 Rails는 다음과 같은 "helper" 메서드를 사용했습니다:
validates_presence_of :name
:name의 존재 여부를 검증합니다.
두 표기법은 동일한 기능을 수행하지만, 가독성과 Rails 규칙과의 일관성을 위해 새로운 형식을 권장합니다.
각 validation은 임의의 개수의 attribute 이름을 허용하므로, 한 줄의 코드로 여러 attribute에 동일한 유형의 validation을 적용할 수 있습니다.
또한, 모든 validation은 :on
과 :message
옵션을 받습니다. :on
옵션은 validation이 실행될 시점을 지정하며, :create
또는 :update
값을 사용할 수 있습니다. :message
옵션을 사용하면 validation이 실패할 경우 errors 컬렉션에 추가될 사용자 지정 오류 메시지를 정의할 수 있습니다. 메시지를 지정하지 않으면 Rails는 해당 validation에 대한 기본 오류 메시지를 사용합니다.
사용 가능한 기본 헬퍼의 목록을 보려면 ActiveModel::Validations::HelperMethods
를 참조하세요. 이 API 섹션에서는 위에서 설명한 이전 표기법을 사용합니다.
아래에서 가장 일반적으로 사용되는 validation들을 설명합니다.
2.1 absence
이 validator는 지정된 속성이 존재하지 않는지를 검증합니다. 값이 nil도 아니고 빈 문자열도 아닌지 (즉, 비어있거나 공백만으로 이루어진 문자열) 확인하기 위해 Object#present?
메서드를 사용합니다.
#absence
는 조건부 validation에서 흔히 사용됩니다. 예를 들면:
class Person < ApplicationRecord
validates :phone_number, :address, absence: true, if: :invited?
end
Person 모델의 phone_number와 address 속성에 대해, invited? 메서드가 true를 반환할 경우 값이 없어야 함을 검증합니다.
irb> person = Person.new(name: "Jane Doe", invitation_sent_at: Time.current)
irb> person.valid?
=> true # absence 유효성 검사 통과
만약 연관관계가 없음을 확실히 하고 싶다면, 연관관계를 매핑하기 위해 사용된 foreign key가 아닌 연관된 객체 자체가 없는지를 테스트해야 합니다.
class LineItem < ApplicationRecord
belongs_to :order, optional: true
validates :order, absence: true # order가 없어야(null)만 한다고 검증합니다
end
irb> line_item = LineItem.new
irb> line_item.valid?
=> true # 부재 검증 통과
order = Order.create
irb> line_item_with_order = LineItem.new(order: order)
irb> line_item_with_order.valid?
=> false # 부재 검증 실패
belongs_to
의 경우 association presence는 기본적으로 검증됩니다. Association presence 검증을 원하지 않는다면 optional: true
를 사용하세요.
Rails는 보통 inverse association을 자동으로 추론합니다. 커스텀 :foreign_key
나 :through
association을 사용하는 경우, association 조회를 최적화하기 위해 :inverse_of
옵션을 명시적으로 설정하는 것이 중요합니다. 이는 검증 중 불필요한 데이터베이스 쿼리를 피하는 데 도움이 됩니다.
자세한 내용은 양방향 Association 문서를 확인하세요.
Association이 존재하면서 유효한지 확인하려면, validates_associated
도 함께 사용해야 합니다. 자세한 내용은 validates_associated 섹션을 참조하세요.
has_one
이나 has_many
관계를 통해 연결된 객체의 부재를 검증하는 경우, 해당 객체가 present?
도 아니고 marked_for_destruction?
도 아닌지 확인합니다.
false.present?
는 false이기 때문에, boolean 필드의 부재를 검증하고 싶다면 다음을 사용해야 합니다:
validates :field_name, exclusion: { in: [true, false] }
field_name이 true나 false로 설정되지 않도록 검증합니다.
기본 에러 메시지는 "must be blank" 입니다.
2.2 acceptance
이 메서드는 폼이 제출될 때 사용자 인터페이스의 checkbox가 체크되었는지를 검증합니다. 이는 일반적으로 사용자가 애플리케이션의 이용약관에 동의하거나, 특정 텍스트를 읽었음을 확인하거나, 이와 유사한 개념을 확인해야 할 때 사용됩니다.
class Person < ApplicationRecord
validates :terms_of_service, acceptance: true
end
이것은 person이 웹 응용 프로그램의 이용약관에 동의했는지 검증할 때 유용합니다.
이 검증은 terms_of_service
가 nil
이 아닌 경우에만 수행됩니다. 이 검증의 기본 에러 메시지는 "must be accepted" 입니다. message
옵션을 통해 커스텀 메시지를 전달할 수도 있습니다.
class Person < ApplicationRecord
validates :terms_of_service, acceptance: { message: "에 동의해야 합니다" }
end
허용 가능한 값을 결정하는 :accept
옵션도 받을 수 있습니다. 기본값은 ['1', true]
이며 쉽게 변경할 수 있습니다.
class Person < ApplicationRecord
validates :terms_of_service, acceptance: { accept: "yes" }
validates :eula, acceptance: { accept: ["TRUE", "accepted"] }
end
이 예제는 terms_of_service가 "yes"로 제한되고 eula가 "TRUE" 또는 "accepted" 중 하나로 제한되는 것을 보여줍니다.
이 validation은 웹 애플리케이션에 매우 특화되어 있으며 이 'acceptance'는 데이터베이스 어디에도 기록될 필요가 없습니다. 만약 이를 위한 필드가 없다면, validator는 virtual attribute를 생성할 것입니다. 만약 필드가 데이터베이스에 존재한다면, accept
옵션은 반드시 true
로 설정되거나 true
를 포함해야 합니다. 그렇지 않으면 validation이 실행되지 않습니다.
2.3 confirmation
두 개의 텍스트 필드가 정확히 동일한 내용을 받아야 할 때 이 validator를 사용해야 합니다. 예를 들어, email 주소나 password를 확인하고자 할 때 사용할 수 있습니다. 이 validation은 확인이 필요한 필드의 이름 뒤에 "_confirmation"이 붙은 가상 attribute를 생성합니다.
class Person < ApplicationRecord
validates :email, confirmation: true
end
confirmation
validation helper는 두 개의 text field가 정확히 동일한 내용을 담고 있는지 검증할 때 사용됩니다. 예를 들어, 이메일이나 비밀번호를 확인할 때 사용합니다. 이 validation은 _confirmation
이라는 접미사가 붙은 가상의 attribute를 생성합니다.
view template에서 아래와 같이 사용할 수 있습니다
<%= text_field :person, :email %>
<%= text_field :person, :email_confirmation %>
이 검사는 email_confirmation
이 nil
이 아닌 경우에만 수행됩니다. confirmation을 필수로 하려면, confirmation 속성에 presence 검사를 추가해야 합니다(이 가이드의 뒷부분에서 presence
검사에 대해 살펴보겠습니다):
class Person < ApplicationRecord
validates :email, confirmation: true
validates :email_confirmation, presence: true
end
또한 confirmation 제약이 대소문자를 구분할지 여부를 정의하는 :case_sensitive
옵션이 있습니다. 이 옵션의 기본값은 true입니다.
class Person < ApplicationRecord
validates :email, confirmation: { case_sensitive: false }
end
문자열의 대소문자를 구분하지 않고 attribute 확인 검증을 원할 경우 :case_sensitive
옵션을 추가할 수 있습니다.
이 validator의 기본 오류 메시지는 "doesn't match confirmation" 입니다.
message
옵션을 통해 사용자 정의 메시지를 전달할 수도 있습니다.
일반적으로 이 validator를 사용할 때는 :if
옵션과 함께 사용하여 레코드를 저장할 때마다가 아닌 초기 필드가 변경되었을 때만 "_confirmation" 필드를 검증하도록 하는 것이 좋습니다. 조건부 검증에 대해서는 나중에 더 자세히 알아보겠습니다.
class Person < ApplicationRecord
validates :email, confirmation: true
validates :email_confirmation, presence: true, if: :email_changed?
end
위 코드에서는 두번째 email 필드가 첫번째 email 필드와 정확히 일치하는지 확인합니다. email_confirmation
attribute를 모델에 추가하고 (데이터베이스에 저장되지 않더라도) email이 변경될 때만 validation을 실행합니다.
2.4 comparison
이 validator는 비교 가능한 두 값 사이의 비교를 검증합니다.
class Promotion < ApplicationRecord
validates :end_date, comparison: { greater_than: :start_date }
end
이 validator의 기본 에러 메시지는 "failed comparison" 입니다. message
옵션을 통해 커스텀 메시지를 전달할 수도 있습니다.
다음 옵션들이 지원됩니다:
옵션 | 설명 | 기본 에러 메시지 |
---|---|---|
:greater_than |
값이 제공된 값보다 커야 함을 지정합니다. | "must be greater than %{count}" |
:greater_than_or_equal_to |
값이 제공된 값보다 크거나 같아야 함을 지정합니다. | "must be greater than or equal to %{count}" |
:equal_to |
값이 제공된 값과 같아야 함을 지정합니다. | "must be equal to %{count}" |
:less_than |
값이 제공된 값보다 작아야 함을 지정합니다. | "must be less than %{count}" |
:less_than_or_equal_to |
값이 제공된 값보다 작거나 같아야 함을 지정합니다. | "must be less than or equal to %{count}" |
:other_than |
값이 제공된 값과 달라야 함을 지정합니다. | "must be other than %{count}" |
이 validator는 비교 옵션이 제공되어야 합니다. 각 옵션은 값, proc, 또는 symbol을 받습니다. Comparable을 포함하는 모든 클래스는 비교가 가능합니다.
2.5 format
이 validator는 :with
옵션에 지정된 정규표현식과 일치하는지 테스트하여 속성의 값을 검증합니다.
class Product < ApplicationRecord
validates :legacy_code, format: { with: /\A[a-zA-Z]+\z/,
message: "문자만 허용됩니다" }
end
반대로, :without
옵션을 사용하면 지정된 속성이 정규 표현식과 일치하지 않도록 요구할 수 있습니다.
어느 경우든, 제공된 :with
또는 :without
옵션은 정규 표현식이거나 정규 표현식을 반환하는 proc 또는 lambda여야 합니다.
기본 에러 메시지는 "is invalid" 입니다.
문자열의 시작과 끝을 매칭하려면 \A
와 \z
를 사용하세요. ^
와 $
는 행의 시작/끝을 매칭합니다. ^
와 $
의 잦은 오용으로 인해, 제공된 정규 표현식에서 이 두 앵커를 사용하는 경우 multiline: true
옵션을 전달해야 합니다. 대부분의 경우 \A
와 \z
를 사용해야 합니다.
2.6 inclusion
과 exclusion
이 두 validator는 속성의 값이 주어진 집합에 포함되어 있는지 또는 제외되어 있는지를 검증합니다. 이 집합은 배열, 범위, 또는 proc, lambda, symbol을 사용하여 동적으로 생성된 컬렉션과 같은 모든 열거 가능한 객체가 될 수 있습니다.
inclusion
은 값이 집합에 존재하는지 확인합니다.exclusion
은 값이 집합에 존재하지 않는지 확인합니다.
두 경우 모두, :in
옵션은 값의 집합을 받으며, :within
을 별칭으로 사용할 수 있습니다. 오류 메시지 사용자 정의에 대한 전체 옵션은 message 문서를 참조하세요.
열거 가능한 객체가 숫자, 시간, 또는 datetime 범위인 경우, 검증은 Range#cover?
를 사용하여 수행되며, 그렇지 않은 경우 include?
를 사용합니다. proc이나 lambda를 사용할 때는 검증 중인 인스턴스가 인자로 전달되어 동적 검증이 가능합니다.
2.6.1 예시
inclusion
의 경우:
class Coffee < ApplicationRecord
validates :size, inclusion: { in: %w(small medium large),
message: "%{value}는 유효한 크기가 아닙니다" }
end
For exclusion
:
exclusion
헬퍼는 주어진 집합에 attribute가 포함되지 않는지 검증합니다. 사용법은 inclusion
헬퍼와 동일합니다:
class Account < ApplicationRecord
validates :subdomain, exclusion: { in: ['www', 'us', 'ca', 'jp'],
message: "Subdomain %{value} is reserved." }
end
exclusion
헬퍼는 :in
옵션에 대해 별칭으로 :within
을 가집니다. 이 헬퍼는 :message
옵션에서 %{value}
를 사용하여 실패한 attribute의 값을 받아들일 수 있습니다.
기본 에러 메시지는 "is reserved"입니다.
class Account < ApplicationRecord
validates :subdomain, exclusion: { in: %w(www us ca jp),
message: "%{value}는 예약되어 있습니다." }
end
두 validator 모두 enumerable을 반환하는 메서드를 통한 동적 validation을 허용합니다. 다음은 inclusion
에 proc을 사용하는 예시입니다:
class Coffee < ApplicationRecord
validates :size, inclusion: { in: ->(coffee) { coffee.available_sizes } }
def available_sizes
%w(small medium large extra_large) # small, medium, large, extra_large 값들을 사용할 수 있습니다
end
end
exclusion
의 경우도 마찬가지입니다:
class Account < ApplicationRecord
validates :subdomain, exclusion: { in: ->(account) { account.reserved_subdomains } }
def reserved_subdomains
%w(www us ca jp admin) # 사용할 수 없는 subdomain 목록
end
end
2.7 length
이 validator는 속성 값의 길이를 검증합니다. 다양한 옵션을 제공하므로 여러 가지 방식으로 길이 제약조건을 지정할 수 있습니다:
class Person < ApplicationRecord
validates :name, length: { minimum: 2 } # 이름은 최소 2자 이상이어야 함
validates :bio, length: { maximum: 500 } # 자기소개는 최대 500자까지 허용됨
validates :password, length: { in: 6..20 } # 비밀번호는 6-20자 사이여야 함
validates :registration_number, length: { is: 6 } # 등록번호는 6자여야 함
end
가능한 길이 제약 조건 옵션은 다음과 같습니다:
옵션 | 설명 |
---|---|
:minimum |
속성은 지정된 길이보다 작을 수 없습니다. |
:maximum |
속성은 지정된 길이보다 클 수 없습니다. |
:in |
속성 길이는 주어진 범위 내에 있어야 합니다. 이 옵션의 값은 range여야 합니다. |
:is |
속성 길이는 주어진 값과 정확히 일치해야 합니다. |
기본 에러 메시지는 수행되는 길이 validation의 유형에 따라 다릅니다. :wrong_length
, :too_long
, :too_short
옵션을 사용하고 사용 중인 길이 제약 조건에 해당하는 숫자의 자리 표시자로 %{count}
를 사용하여 이러한 메시지를 커스터마이즈할 수 있습니다. 에러 메시지를 지정하기 위해 :message
옵션을 계속 사용할 수 있습니다.
class Person < ApplicationRecord
validates :bio, length: { maximum: 1000,
too_long: "최대 %{count}자까지 입력 가능합니다" }
end
기본 오류 메시지는 복수형입니다(예: "is too short (minimum is %{count} characters)"). 이러한 이유로, :minimum
이 1일 때는 사용자 정의 메시지를 제공하거나 대신 presence: true
를 사용해야 합니다. 마찬가지로, :in
또는 :within
의 하한값이 1일 때는 사용자 정의 메시지를 제공하거나 length
이전에 presence
를 호출해야 합니다. :minimum
과 :maximum
옵션은 함께 조합할 수 있지만, 그 외에는 한 번에 하나의 제약 조건 옵션만 사용할 수 있습니다.
2.8 numericality
이 validator는 attributes가 숫자 값만 가지도록 검증합니다. 기본적으로 선택적 부호가 붙고 그 뒤에 정수나 부동 소수점 숫자가 오는 형태를 허용합니다.
정수만 허용하도록 지정하려면 :only_integer
를 true로 설정하세요.
그러면 attribute의 값을 검증하기 위해 다음의 정규 표현식을 사용합니다.
/\A[+-]?\d+\z/
이 패턴은 선행/후행 공백이 없는 정수만 매치합니다. 예를 들어 "+1234"나 "1234"나 "-1234"는 매치되지만 "+1,234", "12.34" 또는 "1234 " 등은 매치되지 않습니다.
그렇지 않으면 Float
를 사용하여 값을 숫자로 변환하려고 시도합니다. Float
는 column의 precision 값 또는 최대 15자리를 사용하여 BigDecimal
로 변환됩니다.
class Player < ApplicationRecord
validates :points, numericality: true
validates :games_played, numericality: { only_integer: true }
end
:only_integer
의 기본 오류 메시지는 "must be an integer" 입니다.
:only_integer
외에도 이 validator는 :only_numeric
옵션을 받아들입니다. 이는 값이 Numeric
인스턴스여야 하며 값이 String
인 경우 파싱을 시도합니다.
기본적으로 numericality
는 nil
값을 허용하지 않습니다. allow_nil: true
옵션을 사용하여 이를 허용할 수 있습니다. Integer
와 Float
컬럼의 경우 빈 문자열은 nil
로 변환됩니다.
옵션이 지정되지 않은 경우의 기본 오류 메시지는 "is not a number" 입니다.
허용 가능한 값에 제약을 추가하는 데 사용할 수 있는 많은 옵션들이 있습니다:
옵션 | 설명 | 기본 오류 메시지 |
---|---|---|
:greater_than |
값이 제공된 값보다 커야 함을 지정합니다. | "must be greater than %{count}" |
:greater_than_or_equal_to |
값이 제공된 값보다 크거나 같아야 함을 지정합니다. | "must be greater than or equal to %{count}" |
:equal_to |
값이 제공된 값과 같아야 함을 지정합니다. | "must be equal to %{count}" |
:less_than |
값이 제공된 값보다 작아야 함을 지정합니다. | "must be less than %{count}" |
:less_than_or_equal_to |
값이 제공된 값보다 작거나 같아야 함을 지정합니다. | "must be less than or equal to %{count}" |
:other_than |
값이 제공된 값과 달라야 함을 지정합니다. | "must be other than %{count}" |
:in |
값이 제공된 범위 내에 있어야 함을 지정합니다. | "must be in %{count}" |
:odd |
값이 홀수여야 함을 지정합니다. | "must be odd" |
:even |
값이 짝수여야 함을 지정합니다. | "must be even" |
2.9 presence
이 validator는 지정된 attributes가 비어있지 않은지 검증합니다. 값이 nil
인지 빈 문자열인지 확인하기 위해 Object#blank?
메서드를 사용합니다 - 즉, 문자열이 비어있거나 공백으로만 이루어져 있는지 확인합니다.
class Person < ApplicationRecord
validates :name, :login, :email, presence: true
end
위 모델은 name, login, email의 presence(존재여부) validation을 합니다.
person = Person.new(name: "Alice", login: "alice123", email: "alice@example.com")
person.valid?
=> true # presence validation 통과
invalid_person = Person.new(name: "", login: nil, email: "bob@example.com")
invalid_person.valid?
=> false # presence validation 실패
association의 존재여부를 확인하기 위해서는, foreign key 매핑을 사용하는 것이 아닌 연관된 객체가 존재하는지 테스트해야 합니다. association을 테스트하면 foreign key가 비어있지 않은지와 참조된 객체가 존재하는지를 확인하는데 도움이 됩니다.
class Supplier < ApplicationRecord
has_one :account
validates :account, presence: true
end
irb> account = Account.create(name: "Account A")
irb> supplier = Supplier.new(account: account)
irb> supplier.valid?
=> true # presence 유효성 검사 통과
irb> invalid_supplier = Supplier.new
irb> invalid_supplier.valid?
=> false # presence 유효성 검사 실패
사용자 정의 :foreign_key
나 :through
association을 사용하는 경우, association lookup을 최적화하기 위해 :inverse_of
옵션을 명시적으로 설정하는 것이 중요합니다. 이는 유효성 검사 중에 불필요한 데이터베이스 쿼리를 피하는데 도움이 됩니다.
자세한 내용은 양방향 Association 문서를 확인하세요.
association이 존재하고 유효한지 모두 확인하고 싶다면, validates_associated
도 함께 사용해야 합니다. 자세한 내용은 아래를 참조하세요.
has_one
이나 has_many
관계를 통해 연결된 객체의 존재 여부를 검증하는 경우, 해당 객체가 blank?
도 아니고 marked_for_destruction?
도 아닌지 확인합니다.
false.blank?
가 true이기 때문에, boolean 필드의 존재 여부를 검증하고 싶다면 다음 유효성 검사 중 하나를 사용해야 합니다:
# 값은 _반드시_ true 또는 false여야 합니다
validates :boolean_field_name, inclusion: [true, false]
# 값은 _절대_ nil이 되면 안됩니다, 즉 true 또는 false여야 합니다
validates :boolean_field_name, exclusion: [nil]
이러한 validation들 중 하나를 사용하면 값이 nil
이 되지 않도록 보장할 수 있으며, 대부분의 경우 NULL
값이 되는 것을 방지할 수 있습니다.
기본 에러 메시지는 "can't be blank" 입니다.
2.10 uniqueness
이 validator는 객체가 저장되기 직전에 속성 값이 고유한지 검증합니다.
class Account < ApplicationRecord
validates :email, uniqueness: true
end
validation은 model의 테이블에 SQL 쿼리를 수행하여, 해당 attribute에 동일한 값을 가진 기존 레코드를 검색하여 이루어집니다.
uniqueness 검사를 제한하는데 사용할 하나 이상의 attribute를 지정하는데 사용할 수 있는 :scope
옵션이 있습니다:
class Holiday < ApplicationRecord
validates :name, uniqueness: { scope: :year,
message: "은 연간 한 번만 등록할 수 있습니다" }
end
이 validation은 데이터베이스에 uniqueness constraint를 생성하지 않으므로, 서로 다른 두 데이터베이스 연결이 unique하도록 의도한 컬럼에 대해 동일한 값을 가진 두 개의 레코드를 생성하는 시나리오가 발생할 수 있습니다. 이를 방지하려면 데이터베이스의 해당 컬럼에 unique index를 생성해야 합니다.
데이터베이스에 uniqueness database constraint를 추가하기 위해서는 migration에서 add_index
구문을 사용하고 unique: true
옵션을 포함시키세요.
uniqueness validation에서 :scope
옵션을 사용하고 있고, uniqueness validation의 위반 가능성을 방지하기 위한 database constraint를 생성하고자 한다면, 데이터베이스의 두 컬럼 모두에 대해 unique index를 생성해야 합니다. 다중 컬럼 인덱스에 대한 자세한 내용은 the MySQL manual과 the MariaDB manual을 참조하거나, 컬럼 그룹을 참조하는 unique constraint의 예시는 the PostgreSQL manual을 참조하세요.
또한 uniqueness constraint가 대소문자를 구분할지, 구분하지 않을지, 또는 기본 데이터베이스 collation을 따를지를 정의하는 데 사용할 수 있는 :case_sensitive
옵션이 있습니다. 이 옵션은 기본적으로 기본 데이터베이스 collation을 따르도록 설정되어 있습니다.
class Person < ApplicationRecord
validates :name, uniqueness: { case_sensitive: false }
end
위 코드는 name이 대소문자를 구분하지 않고 고유해야 함을 지정합니다.
일부 데이터베이스는 기본적으로 대소문자를 구분하지 않는 검색을 수행하도록 설정되어 있습니다.
:conditions
옵션을 사용하여 고유성 제약 조건 조회를 제한하기 위한 추가 조건을 WHERE
SQL 프래그먼트로 지정할 수 있습니다:
validates :name, uniqueness: { conditions: -> { where(status: "active") } }
name이 status가 "active"인 레코드들 사이에서만 유일해야 함을 검증합니다.
기본 에러 메시지는 "has already been taken" 입니다.
자세한 정보는 validates_uniqueness_of
를 참고하세요.
2.11 validates_associated
모델이 항상 검증이 필요한 associations을 가지고 있을 때 이 validator를 사용해야 합니다. 객체를 저장하려고 할 때마다 연관된 각 객체들에 대해 valid?
가 호출됩니다.
class Library < ApplicationRecord
has_many :books
validates_associated :books
end
이 validator는 모델과 연결된 객체들이 저장될 때 모두 유효한지를 확인합니다. 객체가 여러 개인 경우 각각 유효성 검사를 수행합니다. 하나라도 실패하면 에러 메시지가 추가됩니다.
이 유효성 검사는 연결된 객체를 자동으로 저장하지는 않습니다.
이 validation은 모든 association 타입에서 작동합니다.
association의 양쪽 끝에서 validates_associated
를 사용하지 마세요.
무한 루프로 서로를 호출하게 될 것입니다.
validates_associated
의 기본 에러 메시지는 "is invalid" 입니다. 각각의 연관된 객체가 자신만의 errors
컬렉션을 가지며, 에러는 호출하는 모델로 전파되지 않는다는 점에 유의하세요.
validates_associated
는 ActiveRecord 객체에서만 사용할 수 있으며, 지금까지 설명한 나머지는 ActiveModel::Validations
를 포함하는 모든 객체에서도 사용할 수 있습니다.
2.12 validates_each
이 validator는 블록을 사용하여 속성을 검증합니다. 미리 정의된 검증 함수는 없습니다. 블록을 사용하여 검증 함수를 직접 만들어야 하며, validates_each
에 전달된 모든 속성이 해당 블록으로 테스트됩니다.
다음 예제에서는 소문자로 시작하는 이름과 성을 거부하도록 하겠습니다.
class Person < ApplicationRecord
validates_each :name, :surname do |record, attr, value|
record.errors.add(attr, "대문자로 시작해야 합니다") if /\A[[:lower:]]/.match?(value)
end
end
block은 레코드, 속성명, 속성값을 받습니다.
block 내에서 유효한 데이터를 확인하기 위해 원하는 작업을 수행할 수 있습니다. 만약 validation이 실패한다면, model에 에러를 추가해야 하며, 이로 인해 invalid 상태가 됩니다.
2.13 validates_with
이 validator는 validation을 위해 별도의 class로 record를 전달합니다.
class AddressValidator < ActiveModel::Validator
def validate(record)
if record.house_number.blank?
record.errors.add :house_number, "필수 필드입니다"
end
if record.street.blank?
record.errors.add :street, "필수 필드입니다"
end
if record.postcode.blank?
record.errors.add :postcode, "필수 필드입니다"
end
end
end
class Invoice < ApplicationRecord
validates_with AddressValidator
end
validates_with
에 대한 기본 에러 메시지는 없습니다. validator 클래스 내에서 record의 errors 컬렉션에 수동으로 에러를 추가해야 합니다.
record.errors[:base]
에 추가된 에러는 record 전체 상태와 관련이 있습니다.
validate 메서드를 구현하려면, 메서드 정의에서 검증할 record를 나타내는 record
파라미터를 받아야 합니다.
특정 attribute에 에러를 추가하려면, add
메서드의 첫 번째 인자로 해당 attribute를 전달할 수 있습니다.
def validate(record)
if record.some_field != "acceptable"
record.errors.add :some_field, "이 필드는 허용되지 않습니다"
end
end
우리는 validation error에 대해서 나중에 더 자세히 다룰 것입니다.
validates_with
validator는 validation을 위해 사용할 클래스 또는 클래스 목록을 받습니다.
class Person < ApplicationRecord
validates_with MyValidator, MyOtherValidator, on: :create
end
클래스 사용자 정의 validator들을 차례로 수행하기 위해서는 위와 같이 여러 validator들을 콤마로 구분해서 나열할 수 있습니다.
다른 모든 validation과 마찬가지로 validates_with
는 :if
, :unless
, :on
옵션을 사용할 수 있습니다. 만약 다른 옵션을 전달하면 그 옵션들은 validator 클래스에 options
로 전달됩니다:
class AddressValidator < ActiveModel::Validator
def validate(record)
options[:fields].each do |field|
if record.send(field).blank?
record.errors.add field, "가 필요합니다"
end
end
end
end
class Invoice < ApplicationRecord
validates_with AddressValidator, fields: [:house_number, :street, :postcode, :country]
end
validator는 각각의 validation 실행마다가 아닌 전체 애플리케이션 생명주기에서 단 한 번만 초기화됩니다. 따라서 validator 내에서 인스턴스 변수를 사용할 때는 주의해야 합니다.
만약 validator가 인스턴스 변수가 필요할 만큼 복잡하다면, 일반적인 Ruby 객체를 대신 사용하면 됩니다:
class Invoice < ApplicationRecord
validate do |invoice|
AddressValidator.new(invoice).validate
end
end
class AddressValidator
def initialize(invoice)
@invoice = invoice
end
def validate
validate_field(:house_number)
validate_field(:street)
validate_field(:postcode)
end
private
def validate_field(field)
if @invoice.send(field).blank?
@invoice.errors.add field, "#{field.to_s.humanize}는 필수입니다"
end
end
end
custom validations에 대해서는 나중에 다루겠습니다.
3 Validation 옵션
validator들이 지원하는 여러 공통 옵션들이 있습니다. 이 옵션들은 다음과 같습니다:
:allow_nil
: 속성이nil
인 경우 validation을 건너뜁니다.:allow_blank
: 속성이 blank인 경우 validation을 건너뜁니다.:message
: 사용자 정의 에러 메시지를 지정합니다.:on
: 이 validation이 활성화되는 컨텍스트를 지정합니다.:strict
: validation이 실패할 때 예외를 발생시킵니다.:if
와:unless
: validation이 실행되어야 하거나 실행되지 말아야 할 때를 지정합니다.
모든 validator가 이러한 옵션들을 전부 지원하지는 않습니다. ActiveModel::Validations
API 문서를 참조하시기 바랍니다.
3.1 :allow_nil
:allow_nil
옵션은 검증되는 값이 nil
일 때 validation을 건너뜁니다.
class Coffee < ApplicationRecord
validates :size, inclusion: { in: %w(small medium large),
message: "%{value}는 유효한 크기가 아닙니다" }, allow_nil: true
end
irb> Coffee.create(size: nil).valid?
=> true
irb> Coffee.create(size: "mega").valid?
=> false
메시지 인자에 대한 모든 옵션은 message 문서를 참조하세요.
3.2 :allow_blank
:allow_blank
옵션은 :allow_nil
옵션과 비슷합니다. 이 옵션은 attribute의 값이 nil
이나 빈 문자열과 같이 blank?
인 경우에 validation을 통과시킵니다.
class Topic < ApplicationRecord
validates :title, length: { is: 5 }, allow_blank: true
end
이 예시는 title이 정확히 5 문자라면 blank 값을 허용합니다.
irb> Topic.create(title: "").valid?
=> true
irb> Topic.create(title: nil).valid?
=> true
irb> Topic.create(title: "short").valid?
=> false # 'short'의 길이가 5가 아니므로, blank가 아니더라도 validation이 실패합니다
3.3 :message
이미 보셨듯이, :message
옵션을 사용하면 validation이 실패했을 때 errors
컬렉션에 추가될 메시지를 지정할 수 있습니다. 이 옵션이 사용되지 않으면 Active Record는 각 validation에 대한 기본 오류 메시지를 사용합니다.
:message
옵션은 String
또는 Proc
을 값으로 받습니다.
String
:message
값은 선택적으로 %{value}
, %{attribute}
, %{model}
을 포함할 수 있으며, validation이 실패했을 때 동적으로 대체됩니다. 이 대체는 i18n gem을 사용하여 수행되며, 플레이스홀더는 정확히 일치해야 하고 공백은 허용되지 않습니다.
class Person < ApplicationRecord
# 하드코딩된 메시지
validates :name, presence: { message: "must be given please" }
# 동적 속성값을 가진 메시지. %{value}는 속성의 실제값으로 대체됩니다.
# %{attribute}와 %{model}도 사용 가능합니다.
validates :age, numericality: { message: "%{value} seems wrong" }
end
Proc
:message
값은 두 개의 인자를 받습니다: 검증되는 객체와 :model
, :attribute
, :value
키-값 쌍을 가진 해시입니다.
class Person < ApplicationRecord
validates :username,
uniqueness: {
# object = 검증되는 person 객체
# data = { model: "Person", attribute: "Username", value: <username> }
message: ->(object, data) do
"안녕하세요 #{object.name}님, #{data[:value]} 은/는 이미 사용 중입니다."
end
}
end
에러 메시지를 번역하려면 I18n 가이드를 참조하세요.
:on
옵션을 사용하면 validation이 실행되어야 하는 시점을 지정할 수 있습니다. 모든 내장 validation의 기본 동작은 저장 시점(새 record 생성과 업데이트 모두)에 실행됩니다. 이를 변경하려면 on: :create
를 사용하여 새 record가 생성될 때만 validation을 실행하거나, on: :update
를 사용하여 record가 업데이트될 때만 validation이 실행되도록 할 수 있습니다.
class Person < ApplicationRecord
# 중복된 값으로 email을 업데이트하는 것이 가능합니다
validates :email, uniqueness: true, on: :create
# 숫자가 아닌 age로 레코드를 생성하는 것이 가능합니다
validates :age, numericality: true, on: :update
# 기본값 (create와 update 모두에서 검증)
validates :name, presence: true
end
:on
을 사용하여 custom context를 정의할 수도 있습니다. Custom context는 valid?
, invalid?
, 또는 save
메서드에 context 이름을 명시적으로 전달하여 트리거해야 합니다.
class Person < ApplicationRecord
validates :email, uniqueness: true, on: :account_setup
validates :age, numericality: true, on: :account_setup
end
irb> person = Person.new(age: 'thirty-three')
irb> person.valid?
=> true
irb> person.valid?(:account_setup)
=> false
irb> person.errors.messages
=> {:email=>["이미 사용중입니다"], :age=>["숫자가 아닙니다"]}
person.valid?(:account_setup)
은 모델을 저장하지 않고 두 개의 validation을 모두 실행합니다. person.save(context: :account_setup)
은 저장하기 전에 account_setup
context에서 person
을 검증합니다.
심볼의 배열을 전달하는 것도 가능합니다.
class Book
include ActiveModel::Validations
validates :title, presence: true, on: [:update, :ensure_title]
end
irb> book = Book.new(title: nil)
irb> book.valid?
=> true
irb> book.valid?(:ensure_title)
=> false
irb> book.errors.messages
=> {:title=>["비어있을 수 없습니다"]}
명시적 context에 의해 유발되면 해당 context와 context가 없는 모든 validation이 실행됩니다.
class Person < ApplicationRecord
validates :email, uniqueness: true, on: :account_setup
validates :age, numericality: true, on: :account_setup
validates :name, presence: true
end
irb> person = Person.new
irb> person.valid?(:account_setup)
=> false
irb> person.errors.messages
=> {:email=>["이미 사용중입니다"], :age=>["숫자가 아닙니다"], :name=>["비워둘 수 없습니다"]}
:on
에 대한 추가적인 활용 사례는 Custom Contexts 섹션에서 확인할 수 있습니다.
4 조건부 Validations
때로는 주어진 조건이 충족될 때만 객체를 검증하는 것이 합리적일 수 있습니다. symbol, Proc
또는 Array
를 사용할 수 있는 :if
와 :unless
옵션을 사용하여 이를 수행할 수 있습니다. validation이 실행되어야 할 때를 지정하고 싶다면 :if
옵션을 사용할 수 있습니다. 반대로 validation이 실행되지 말아야 할 때를 지정하고 싶다면 :unless
옵션을 사용할 수 있습니다.
4.1 :if
와 :unless
에 Symbol 사용하기
:if
와 :unless
옵션을 validation이 발생하기 직전에 호출될 메서드의 이름에 해당하는 symbol과 연결할 수 있습니다. 이것이 가장 일반적으로 사용되는 옵션입니다.
class Order < ApplicationRecord
validates :card_number, presence: true, if: :paid_with_card?
def paid_with_card?
payment_type == "card"
end
end
4.2 :if
와 :unless
에서 Proc 사용하기
:if
와 :unless
에 호출될 Proc
객체를 연결할 수 있습니다. Proc
객체를 사용하면 별도의 메서드 대신 인라인 조건을 작성할 수 있습니다. 이 옵션은 한 줄짜리 코드에 가장 적합합니다.
class Account < ApplicationRecord
validates :password, confirmation: true,
unless: Proc.new { |a| a.password.blank? }
end
lambda
는 Proc
의 한 종류이므로, 축약된 문법을 활용하여 인라인 조건을 작성하는 데에도 사용될 수 있습니다.
validates :password, confirmation: true, unless: -> { password.blank? }
password가 비어있지 않은 경우에만 password의 confirmation을 검증합니다.
4.3 Conditional Validation 그룹화하기
때로는 여러 validation들이 하나의 조건을 사용하도록 하는 것이 유용할 수 있습니다. 이는 with_options
를 사용하여 쉽게 달성할 수 있습니다.
class User < ApplicationRecord
with_options if: :is_admin? do |admin|
admin.validates :password, length: { minimum: 10 }
admin.validates :email, presence: true
end
end
with_options
블록 내부의 모든 validation은 자동으로 if: :is_admin?
가 옵션에 병합됩니다.
4.4 Validation 조건 결합하기
여러 조건들이 validation이 발생해야 하는지 여부를 결정할 때는 Array
를 사용할 수 있습니다. 또한 동일한 validation에 :if
와 :unless
모두를 적용할 수 있습니다.
class Computer < ApplicationRecord
validates :mouse, presence: true,
if: [Proc.new { |c| c.market.retail? }, :desktop?],
unless: Proc.new { |c| c.trackpad.present? }
end
Computer 클래스는 다음과 같이 유효성 검증을 수행합니다: - mouse의 존재 여부를 검증합니다 - market이 retail인 경우와 desktop?이 참인 경우에만 검증이 이루어집니다 - trackpad가 존재하는 경우에는 검증을 하지 않습니다
validation은 모든 :if
조건이 true
로 평가되고 모든 :unless
조건이 false
로 평가될 때만 실행됩니다.
5 Strict Validations
객체가 유효하지 않을 때 ActiveModel::StrictValidationFailed
를 발생시키는 strict validation을 지정할 수도 있습니다.
class Person < ApplicationRecord
validates :name, presence: { strict: true }
end
위 validator는 name이 없을 때 ActiveModel::StrictValidationFailed 예외를 발생시킵니다.
irb> Person.new.valid?
=> ActiveModel::StrictValidationFailed: Name은 비어있을 수 없습니다
엄격한 validation은 validation이 실패할 때 즉시 exception을 발생시켜, 즉각적인 피드백이 필요하거나 유효하지 않은 데이터가 발견될 때 처리를 중단하고 싶은 상황에서 유용할 수 있습니다. 예를 들어, 중요한 트랜잭션을 처리하거나 데이터 무결성 검사를 수행할 때와 같이 유효하지 않은 입력이 추가 작업을 방해해야 하는 시나리오에서 엄격한 validation을 사용할 수 있습니다.
:strict
옵션에 사용자 정의 exception을 전달할 수도 있습니다.
class Person < ApplicationRecord
validates :token, presence: true, uniqueness: true, strict: TokenGenerationException
end
irb> Person.new.valid?
=> TokenGenerationException: Token은 비워둘 수 없습니다
6 Validators 나열하기
특정 객체의 모든 validator를 찾고 싶다면 validators
를 사용할 수 있습니다.
예를 들어 커스텀 validator와 내장 validator를 사용하는 다음과 같은 모델이 있다면:
class Person < ApplicationRecord
validates :name, presence: true, on: :create
validates :email, format: URI::MailTo::EMAIL_REGEXP
validates_with MyOtherValidator, strict: true
end
이제 모든 validator들을 나열하거나 validators_on
을 사용하여 특정 필드를 확인하기 위해 "Person" 모델에서 validators
를 사용할 수 있습니다.
irb> Person.validators
#=> [#<ActiveRecord::Validations::PresenceValidator:0x10b2f2158
@attributes=[:name], @options={:on=>:create}>,
#<MyOtherValidatorValidator:0x10b2f17d0
@attributes=[:name], @options={:strict=>true}>,
#<ActiveModel::Validations::FormatValidator:0x10b2f0f10
@attributes=[:email],
@options={:with=>URI::MailTo::EMAIL_REGEXP}>]
#<MyOtherValidator:0x10b2f0948 @options={:strict=>true}>]
irb> Person.validators_on(:name)
#=> [#<ActiveModel::Validations::PresenceValidator:0x10b2f2158
@attributes=[:name], @options={on: :create}>]
7 Performing Custom Validations
내장된 validation들이 당신의 필요를 충족시키지 못할 때, 선호하는 방식으로 직접 validator나 validation 메서드를 작성할 수 있습니다.
7.1 Custom Validators
Custom validator는 ActiveModel::Validator
를 상속하는 클래스입니다.
이러한 클래스들은 record를 인자로 받아 유효성 검사를 수행하는 validate
메서드를 구현해야 합니다. Custom validator는 validates_with
메서드를 사용하여 호출됩니다.
class MyValidator < ActiveModel::Validator
def validate(record)
unless record.name.start_with? "X"
record.errors.add :name, "X로 시작하는 이름을 입력해주세요!"
end
end
end
class Person < ApplicationRecord
validates_with MyValidator
end
각 속성을 검증하는 커스텀 validator를 추가하는 가장 쉬운 방법은 편리한 ActiveModel::EachValidator
를 사용하는 것입니다. 이 경우 커스텀 validator 클래스는 세 가지 인자를 받는 validate_each
메소드를 구현해야 합니다: record, attribute, value입니다. 이것들은 각각 인스턴스, 검증할 속성, 전달된 인스턴스의 속성 값에 대응됩니다.
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless URI::MailTo::EMAIL_REGEXP.match?(value)
record.errors.add attribute, (options[:message] || "이메일이 아닙니다")
end
end
end
class Person < ApplicationRecord
validates :email, presence: true, email: true
end
예시에서 볼 수 있듯이, 표준 validation과 사용자 정의 validator를 함께 조합할 수 있습니다.
7.2 커스텀 메서드
모델의 상태를 검증하고 유효하지 않을 때 errors
컬렉션에 에러를 추가하는 메서드를 생성할 수 있습니다. 이때 [validate
][] 클래스 메서드를 사용하여 validation 메서드 이름을 심볼로 전달하여 등록해야 합니다.
각 클래스 메서드에 대해 하나 이상의 심볼을 전달할 수 있으며, 해당 validation들은 등록된 순서대로 실행됩니다.
valid?
메서드는 errors
컬렉션이 비어있는지 확인하므로, validation이 실패하기를 원할 때는 커스텀 validation 메서드가 이 컬렉션에 에러를 추가해야 합니다:
class Invoice < ApplicationRecord
validate :expiration_date_cannot_be_in_the_past,
:discount_cannot_be_greater_than_total_value
def expiration_date_cannot_be_in_the_past
if expiration_date.present? && expiration_date < Date.today
errors.add(:expiration_date, "과거의 날짜는 사용할 수 없습니다")
end
end
def discount_cannot_be_greater_than_total_value
if discount > total_value
errors.add(:discount, "총액보다 클 수 없습니다")
end
end
end
기본적으로 이러한 validation은 valid?
를 호출하거나 객체를 저장할 때마다 실행됩니다. 하지만 validate
메소드에 :on
옵션을 :create
또는 :update
로 지정하여 이러한 custom validation이 실행되는 시점을 제어할 수도 있습니다.
class Invoice < ApplicationRecord
validate :active_customer, on: :create
def active_customer
errors.add(:customer_id, "활성화되어 있지 않습니다") unless customer.active?
end
end
:on
에 대한 자세한 내용은 위 섹션을 참조하세요.
7.3 Custom Contexts
콜백을 위한 사용자 정의 유효성 검사 context를 정의할 수 있습니다. 이는 특정 시나리오에 기반하여 유효성 검사를 수행하거나 특정 콜백들을 함께 그룹화하여 특정 context에서 실행하고 싶을 때 유용합니다. custom context의 일반적인 시나리오는 multi-step form이 있고 각 단계별로 유효성 검사를 수행하고 싶을 때입니다.
예를 들어, form의 각 단계에 대한 custom context를 다음과 같이 정의할 수 있습니다:
class User < ApplicationRecord
validate :personal_information, on: :personal_info
validate :contact_information, on: :contact_info
validate :location_information, on: :location_info
private
def personal_information
errors.add(:base, "이름을 입력해야 합니다") if first_name.blank?
errors.add(:base, "나이는 18세 이상이어야 합니다") if age && age < 18
end
def contact_information
errors.add(:base, "이메일을 입력해야 합니다") if email.blank?
errors.add(:base, "전화번호를 입력해야 합니다") if phone.blank?
end
def location_information
errors.add(:base, "주소를 입력해야 합니다") if address.blank?
errors.add(:base, "도시를 입력해야 합니다") if city.blank?
end
end
이러한 경우, callback을 건너뛰는 것을 고려할 수 있지만, custom context를 정의하는 것이 더 체계적인 접근 방식이 될 수 있습니다. callback에 대한 custom context를 정의하려면 context와 :on
옵션을 함께 사용해야 합니다.
custom context를 정의하고 나면, 이를 사용하여 validation을 실행할 수 있습니다:
irb> user = User.new(name: "John Doe", age: 17, email: "jane@example.com", phone: "1234567890", address: "123 Main St")
irb> user.valid?(:personal_info) # => false
irb> user.valid?(:contact_info) # => true
irb> user.valid?(:location_info) # => false
커스텀 context를 사용하여 callback을 지원하는 모든 메서드에서 validation을 트리거할 수도 있습니다. 예를 들어 커스텀 context를 사용하여 save
에서 validation을 트리거할 수 있습니다:
irb> user = User.new(name: "John Doe", age: 17, email: "jane@example.com", phone: "1234567890", address: "123 Main St")
irb> user.save(context: :personal_info) # => false
irb> user.save(context: :contact_info) # => true
irb> user.save(context: :location_info) # => false
8 Validation Error 다루기
valid?
와 invalid?
메서드는 유효성에 대한 요약 상태만을 제공합니다. 하지만 errors
컬렉션의 다양한 메서드를 사용하여 각각의 오류를 더 자세히 파악할 수 있습니다.
다음은 가장 일반적으로 사용되는 메서드들의 목록입니다. 사용 가능한 모든 메서드의 목록은 ActiveModel::Errors
문서를 참조하세요.
8.1 errors
errors
메서드는 각 에러의 다양한 세부 정보를 파악할 수 있는 시작점입니다.
이는 모든 에러를 포함하는 ActiveModel::Errors
클래스의 인스턴스를 반환하며, 각 에러는 ActiveModel::Error
객체로 표현됩니다.
class Person < ApplicationRecord
validates :name, presence: true, length: { minimum: 3 }
end
irb> person = Person.new
irb> person.valid?
=> false
irb> person.errors.full_messages
=> ["이름을 입력해주세요", "이름이 너무 짧습니다 (최소 3자 이상)"]
irb> person = Person.new(name: "John Doe")
irb> person.valid?
=> true
irb> person.errors.full_messages
=> []
irb> person = Person.new
irb> person.valid?
=> false
irb> person.errors.first.details
=> {:error=>:too_short, :count=>3}
8.2 errors[]
errors[]
는 특정 attribute의 에러 메시지를 확인하고 싶을 때 사용됩니다. 이는 주어진 attribute에 대한 모든 에러 메시지를 포함하는 문자열 배열을 반환하며, 각 문자열은 하나의 에러 메시지를 담고 있습니다. 만약 해당 attribute와 관련된 에러가 없다면 빈 배열을 반환합니다.
이 메서드는 validation이 실행된 후에만 유용합니다. 왜냐하면 이는 단순히 errors
컬렉션만을 검사할 뿐 validation 자체를 실행하지는 않기 때문입니다. 이는 위에서 설명한 ActiveRecord::Base#invalid?
메서드와는 다른데, 객체 전체의 유효성을 검증하지 않기 때문입니다. errors[]
는 객체의 개별 attribute에서 발견된 에러가 있는지만 확인합니다.
class Person < ApplicationRecord
validates :name, presence: true, length: { minimum: 3 }
end
irb> person = Person.new(name: "John Doe")
irb> person.valid?
=> true
irb> person.errors[:name]
=> []
irb> person = Person.new(name: "JD")
irb> person.valid?
=> false
irb> person.errors[:name]
=> ["너무 짧습니다 (최소 3자 이상이어야 합니다)"]
irb> person = Person.new
irb> person.valid?
=> false
irb> person.errors[:name]
=> ["비어있을 수 없습니다", "너무 짧습니다 (최소 3자 이상이어야 합니다)"]
8.3 errors.where
와 Error Object
때때로 에러의 메시지 외에도 각 에러에 대한 더 많은 정보가 필요할 수 있습니다.
각 에러는 ActiveModel::Error
객체로 캡슐화되어 있으며,
where
메서드가 접근하는 가장 일반적인 방법입니다.
where
는 다양한 조건으로 필터링된 error 객체들의 배열을 반환합니다.
다음과 같은 validation이 있다고 가정해봅시다:
class Person < ApplicationRecord
validates :name, presence: true, length: { minimum: 3 }
end
errors.where(:attr)
에 첫번째 파라미터로 attribute
를 전달하여 해당 속성만 필터링할 수 있습니다. 두번째 파라미터는 errors.where(:attr, :type)
을 호출하여 우리가 원하는 error의 type
을 필터링하는데 사용됩니다.
irb> person = Person.new
irb> person.valid?
=> false
irb> person.errors.where(:name)
=> [ ... ] # :name 속성의 모든 에러
irb> person.errors.where(:name, :too_short)
=> [ ... ] # :name 속성의 :too_short 에러
마지막으로, 주어진 error 객체 타입의 options
에 있는 모든 조건으로 필터링할 수 있습니다.
irb> person = Person.new
irb> person.valid?
=> false
irb> person.errors.where(:name, :too_short, minimum: 3)
=> [ ... ] # name이 너무 짧고 최소 길이가 3인 모든 에러
이러한 error 객체들로부터 다양한 정보를 읽을 수 있습니다:
irb> error = person.errors.where(:name).last
irb> error.attribute
=> :name
irb> error.type
=> :too_short
irb> error.options[:count]
=> 3
다음과 같이 error message를 생성할 수도 있습니다:
irb> error.message
=> "너무 짧습니다 (최소 3글자 이상이어야 합니다)"
irb> error.full_message
=> "Name이 너무 짧습니다 (최소 3글자 이상이어야 합니다)"
full_message
메서드는 속성 이름을 대문자로 시작하여 앞에 붙인 더 사용자 친화적인 메시지를 생성합니다. (full_message
가 사용하는 형식을 커스터마이즈하려면 I18n 가이드를 참조하세요.)
8.4 errors.add
[add
][] 메서드는 attribute
, 에러 type
, 그리고 추가적인 options hash를 받아서 error 객체를 생성합니다. 이는 매우 구체적인 에러 상황을 정의할 수 있게 해주기 때문에 사용자 정의 validator를 작성할 때 유용합니다.
class Person < ApplicationRecord
validate do |person|
errors.add :name, :too_plain, message: "충분히 멋지지 않습니다"
end
end
irb> person = Person.new
irb> person.errors.where(:name).first.type
=> :too_plain
irb> person.errors.where(:name).first.full_message
=> "이름이 충분히 멋지지 않습니다"
다음 Rails 가이드 문서를 한국어로 번역해주세요가 번역할 원본 문서가 보이지 않네요. 번역할 내용을 공유해주시면 도와드리도록 하겠습니다.
8.5 errors[:base]
특정 attribute와 관련된 것이 아니라 객체의 전반적인 상태와 관련된 error를 추가할 수 있습니다. 이를 위해서는 새로운 error를 추가할 때 attribute로 :base
를 사용해야 합니다.
class Person < ApplicationRecord
validate do |person|
errors.add :base, :invalid, message: "유효하지 않은 사람입니다. 이유는..."
end
end
irb> person = Person.new
irb> person.errors.where(:base).first.full_message
=> "이 사람은 다음 이유로 유효하지 않습니다 ..."
8.6 errors.size
size
메서드는 객체에 대한 전체 error 개수를 반환합니다.
class Person < ApplicationRecord
validates :name, presence: true, length: { minimum: 3 }
end
irb> person = Person.new
irb> person.valid?
=> false
irb> person.errors.size
=> 2
irb> person = Person.new(name: "Andrea", email: "andrea@example.com")
irb> person.valid?
=> true
irb> person.errors.size
=> 0
8.7 errors.clear
clear
메서드는 의도적으로 errors
컬렉션을 지우고 싶을 때 사용됩니다. 물론, 유효하지 않은 객체에서 errors.clear
를 호출한다고 해서 실제로 그 객체가 유효해지는 것은 아닙니다. errors
컬렉션은 비워지겠지만, 다음번에 valid?
를 호출하거나 이 객체를 데이터베이스에 저장하려고 시도하는 메서드를 호출하면 validation이 다시 실행됩니다. validation 중 하나라도 실패하면 errors
컬렉션은 다시 채워질 것입니다.
class Person < ApplicationRecord
validates :name, presence: true, length: { minimum: 3 }
end
irb> person = Person.new
irb> person.valid?
=> false
irb> person.errors.empty?
=> false
irb> person.errors.clear
irb> person.errors.empty?
=> true
irb> person.save
=> false
irb> person.errors.empty?
=> false
9 Views에서 Validation 오류 표시하기
모델을 정의하고 validation을 추가한 후에는, 웹 폼을 통해 모델을 생성하는 동안 validation이 실패했을 때 오류 메시지를 표시하고 싶을 것입니다.
모든 애플리케이션이 validation 오류를 표시하는 방식이 다르기 때문에, Rails는 이러한 메시지를 생성하기 위한 view 헬퍼를 포함하지 않습니다. 하지만 Rails는 여러분이 직접 만들 수 있도록 validation과 상호 작용하는 많은 메서드를 제공합니다. 또한, scaffold를 생성할 때 Rails는 _form.html.erb
에 해당 모델의 전체 오류 목록을 표시하는 ERB를 생성해 넣습니다.
@article
이라는 인스턴스 변수에 저장된 모델이 있다고 가정하면, 다음과 같습니다:
<% if @article.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@article.errors.count, "error") %> 때문에 이 article을 저장할 수 없습니다:</h2>
<ul>
<% @article.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
또한 Rails form helper를 사용하여 form을 생성할 경우, field에서 validation 오류가 발생하면 해당 입력 필드 주변에 추가적인 <div>
가 생성됩니다.
<div class="field_with_errors">
<input id="article_title" name="article[title]" size="30" type="text" value="">
</div>
그런 다음 이 div를 원하는 대로 스타일링할 수 있습니다. 예를 들어 Rails가 생성하는 기본 scaffold는 다음과 같은 CSS 규칙을 추가합니다:
.field_with_errors {
padding: 2px;
background-color: red;
display: table;
}
이는 오류가 있는 모든 field가 2픽셀 빨간색 테두리를 가지게 된다는 것을 의미합니다.
9.1 Error Field Wrapper 커스터마이징
Rails는 field_error_proc
설정 옵션을 사용하여 error가 있는 field를 HTML로 감쌉니다. 기본적으로 이 옵션은 error가 있는 form field를 위 예시에서 볼 수 있듯이 field_with_errors
클래스를 가진 <div>
로 감쌉니다:
config.action_view.field_error_proc = Proc.new { |html_tag, instance| content_tag :div, html_tag, class: "field_with_errors" }
유효성 검사에 실패한 form inputs를 래핑하는데 사용되는 HTML generator입니다. field_with_errors
div로 감싸는 것이 기본값입니다.
애플리케이션 설정의 field_error_proc 설정을 수정하여 이 동작을 커스터마이징할 수 있으며, 폼에서 에러가 표시되는 방식을 변경할 수 있습니다. 자세한 내용은 field_error_proc 설정 가이드를 참조하세요.