module ActiveRecord::Validations::ClassMethods
Открытые методы экземпляра
# File activerecord/lib/active_record/validations/absence.rb, line 20 def validates_absence_of(*attr_names) validates_with AbsenceValidator, _merge_attributes(attr_names) end
Проверяет, что указанные атрибуты отсутствуют (согласно определению Object#present?). Если атрибут является связью, связанный объект также считается отсутствующим, если он помечен для уничтожения.
Дополнительные сведения см. в ActiveModel::Validations::HelperMethods.validates_absence_of.
# File activerecord/lib/active_record/validations/associated.rb, line 60 def validates_associated(*attr_names) validates_with AssociatedValidator, _merge_attributes(attr_names) end
Проверяет, что все связанные объекты являются допустимыми. Работает со связями любого типа.
class Book < ActiveRecord::Base has_many :pages belongs_to :library validates_associated :pages, :library end
ВНИМАНИЕ: Эту проверку нельзя использовать на обоих концах связи. Это приведёт к циклической зависимости и бесконечной рекурсии.
ПРИМЕЧАНИЕ: Эта проверка не завершится ошибкой, если связь не была назначена. Если необходимо убедиться, что связь существует и является допустимой, нужно также использовать validates_presence_of.
Параметры конфигурации:
-
:message— пользовательское сообщение об ошибке (по умолчанию: «is invalid»). -
:on— задаёт контексты, в которых активна эта проверка. По умолчанию выполняется во всех контекстах проверкиnil. Можно передать символ или массив символов (например,on: :create,on: :custom_validation_contextилиon: [:create, :custom_validation_context]). -
:if— задаёт метод, proc или строку, вызываемые для определения необходимости выполнения проверки (например,if: :allow_validationилиif: Proc.new { |user| user.signup_step > 2 }). Метод, proc или строка должны возвращать или вычисляться в значениеtrueилиfalse. -
:unless— задаёт метод, proc или строку, вызываемые для определения того, не следует ли выполнять проверку (например,unless: :skip_validationилиunless: Proc.new { |user| user.signup_step <= 2 }). Метод, proc или строка должны возвращать или вычисляться в значениеtrueилиfalse.
# File activerecord/lib/active_record/validations/length.rb, line 19 def validates_length_of(*attr_names) validates_with LengthValidator, _merge_attributes(attr_names) end
Проверяет, что длина указанных атрибутов соответствует заданным ограничениям. Если атрибут является связью, записи, помеченные для уничтожения, не учитываются.
Дополнительные сведения см. в ActiveModel::Validations::HelperMethods.validates_length_of.
# File activerecord/lib/active_record/validations/numericality.rb, line 31 def validates_numericality_of(*attr_names) validates_with NumericalityValidator, _merge_attributes(attr_names) end
Проверяет, является ли значение указанного атрибута числовым, пытаясь преобразовать его в число с плавающей точкой с помощью Kernel.Float (если only_integer равно false) или сопоставляя его с регулярным выражением /\A[+\-]?\d+\z/ (если для only_integer задано значение true). Точность Kernel.Float по умолчанию равна значению точности столбца или 15.
Дополнительные сведения см. в ActiveModel::Validations::HelperMethods.validates_numericality_of.
# File activerecord/lib/active_record/validations/presence.rb, line 40 def validates_presence_of(*attr_names) validates_with PresenceValidator, _merge_attributes(attr_names) end
Проверяет, что указанные атрибуты не являются пустыми (согласно определению Object#blank?). Если атрибут является связью, связанный объект также считается пустым, если он помечен для уничтожения.
class Person < ActiveRecord::Base has_one :face validates_presence_of :face end
Атрибут face должен присутствовать в объекте и не может быть пустым или помеченным для уничтожения.
Этот валидатор делегирует проверку наличия Active Model, добавляя проверку того, что связанный объект не помечен для уничтожения. Это не позволяет родительскому объекту успешно пройти проверку и сохраниться, после чего связанный объект будет удалён, а родительский объект окажется в недопустимом состоянии.
Дополнительные сведения см. в ActiveModel::Validations::HelperMethods.validates_presence_of.
ПРИМЕЧАНИЕ: При использовании этой проверки со связью она не завершится ошибкой, если связь была назначена, но недопустима. Если необходимо убедиться, что связь существует и является допустимой, нужно также использовать validates_associated.
# File activerecord/lib/active_record/validations/uniqueness.rb, line 291 def validates_uniqueness_of(*attr_names) validates_with UniquenessValidator, _merge_attributes(attr_names) end
Проверяет, являются ли значения указанных атрибутов уникальными в системе. Это полезно, например, чтобы гарантировать, что только одного пользователя можно назвать «davidhh».
class Person < ActiveRecord::Base validates_uniqueness_of :user_name end
Также можно проверить уникальность значений указанных атрибутов с учётом параметра :scope:
class Person < ActiveRecord::Base validates_uniqueness_of :user_name, scope: :account_id end
Можно также задать несколько параметров области действия. Например, чтобы гарантировать, что учитель может быть включён в расписание определённого класса только один раз за семестр.
class TeacherSchedule < ActiveRecord::Base validates_uniqueness_of :teacher_id, scope: [:semester_id, :class_id] end
Также можно ограничить проверку уникальности набором записей, соответствующих определённым условиям. В этом примере архивные статьи не учитываются при проверке уникальности атрибута заголовка:
class Article < ActiveRecord::Base
validates_uniqueness_of :title, conditions: -> { where.not(status: 'archived') }
end
Чтобы сформировать условия на основе состояния записи, задайте вызываемый объект условий с параметром, которым будет сама запись. В этом примере проверяется уникальность заголовка в пределах года публикации:
class Article < ActiveRecord::Base
validates_uniqueness_of :title, conditions: ->(article) {
published_at = article.published_at
where(published_at: published_at.beginning_of_year..published_at.end_of_year)
}
end
При создании записи выполняется проверка, чтобы убедиться, что в базе данных нет записей с указанным значением заданного атрибута (соответствующего столбцу). При обновлении записи выполняется та же проверка, но сама запись не учитывается.
Параметры конфигурации:
-
:message— задаёт пользовательское сообщение об ошибке (по умолчанию: «has already been taken»). -
:scope— один или несколько столбцов, ограничивающих область действия ограничения уникальности. -
:conditions— задаёт условия в виде фрагмента SQLWHERE, ограничивающего поиск для проверки уникальности (например,conditions: -> { where(status: 'active') }). -
:case_sensitive— выполняет поиск точного совпадения. Игнорируется для нетекстовых столбцов. По умолчанию учитывается используемая базой данных сортировка. -
:allow_nil— если задано значениеtrue, проверка пропускается, если атрибут имеет значениеnil(по умолчанию —false). -
:allow_blank— если задано значениеtrue, проверка пропускается, если атрибут пуст (по умолчанию —false). -
:if— задаёт метод, proc или строку, вызываемые для определения необходимости выполнения проверки (например,if: :allow_validationилиif: Proc.new { |user| user.signup_step > 2 }). Метод, proc или строка должны возвращать или вычисляться в значениеtrueилиfalse. -
:unless— задаёт метод, proc или строку, вызываемые для определения того, не следует ли выполнять проверку (например,unless: :skip_validationилиunless: Proc.new { |user| user.signup_step <= 2 }). Метод, proc или строка должны возвращать или вычисляться в значениеtrueилиfalse.
Параллельное выполнение и целостность
Использование этого метода проверки вместе с ActiveRecord::Base#save не гарантирует отсутствие вставки дублирующихся записей, поскольку проверки уникальности на уровне приложения по своей природе подвержены состояниям гонки. Например, предположим, что два пользователя одновременно пытаются опубликовать комментарий, заголовок которого должен быть уникальным. На уровне базы данных действия этих пользователей могут чередоваться следующим образом:
User 1 | User 2
------------------------------------+--------------------------------------
# User 1 checks whether there's |
# already a comment with the title |
# 'My Post'. This is not the case. |
SELECT * FROM comments |
WHERE title = 'My Post' |
|
| # User 2 does the same thing and also
| # infers that their title is unique.
| SELECT * FROM comments
| WHERE title = 'My Post'
|
# User 1 inserts their comment. |
INSERT INTO comments |
(title, content) VALUES |
('My Post', 'hi!') |
|
| # User 2 does the same thing.
| INSERT INTO comments
| (title, content) VALUES
| ('My Post', 'hello!')
|
| # ^^^^^^
| # Boom! We now have a duplicate
| # title! Лучший способ решить эту проблему — добавить уникальный индекс в таблицу базы данных с помощью connection.add_index. В редком случае возникновения состояния гонки база данных гарантирует уникальность поля.
Когда база данных обнаруживает такую вставку дубликата, ActiveRecord::Base#save вызывает исключение ActiveRecord::StatementInvalid. Можно либо позволить этой ошибке распространиться (в результате будет показана стандартная страница исключения Rails), либо перехватить её и перезапустить транзакцию (например, сообщив пользователю, что такой заголовок уже существует, и попросив ввести другой). Эта техника также известна как оптимистическое управление параллельным доступом.
Встроенные адаптеры ActiveRecord::ConnectionAdapters отличают ошибки ограничения уникального индекса от других типов ошибок базы данных, вызывая исключение ActiveRecord::RecordNotUnique. Для других адаптеров необходимо анализировать текст сообщения об исключении (зависящий от базы данных), чтобы обнаружить такой случай.
Следующие встроенные адаптеры вызывают исключение ActiveRecord::RecordNotUnique:
-
ActiveRecord::ConnectionAdapters::TrilogyAdapter.
© 2004–2021 David Heinemeier Hansson
Licensed under the MIT License.