Undr

На память

Проверка данных моделей ActiveRecord и функции обратного вызова. Первая часть.

1 Star2 Stars3 Stars4 Stars5 Stars (6 голосов, средний: 4.00 из 5)
Loading ... Loading ...

with one comment

Перевод Active Record Validations and Callbacks

Это руководство описывает цикл жизни объектов Active Record. Вы узнаете, как проверить состояние объектов прежде, чем они попадут в базу данных, и как выполнить собственный код в определенных моментах цикла жизни объекта.

Прочтя это руководство, Вы сможете:

  • цикл жизни объектов Active Record
  • Использовать встроенные помощники для проверки состояния данных модели
  • Создавать свои собственные помощники для проверки состояния данных модели
  • Работать с сообщениями об ошибках, которые генерируются в результате этих проверок
  • Создавать функции обратного вызова, вызываемые на разные события происходящие в течении жизни объекта
  • Создавать специальные классы, которые будут энкапсулировать общее поведение для функций обратного вызова
  • Создавать классы-наблюдатели, которые будут отвечать за вызов соответствующих методов в ответ на события происходящие в течении жизни объекта

1 Цикл жизни объекта

Во время нормального функционирования приложения Rails объекты могут быть созданы, обновлены и удалены. Active Record обеспечивает обработчики прерываний в разные моменты цикла жизни объектов, так чтобы Вы могли управлять своим приложением и его данными.

Проверки данных позволяют Вам быть уверенным, что только достоверные данные попадут в Вашу базу данных. Функции обратного вызова и наблюдатели позволят Вам вызывать некоторый код прежде или после изменения состояния объекта.

2 Краткий обзор проверок состояния данных модели

Прежде, чем погрузиться в детали, проведем краткий обзор проверок состояния данных модели.

2.1 Зачем нужны проверки?

Проверки используются, чтобы убедиться, что только достоверные данные попали в базу данных. Например, для приложения может быть важно, чтобы каждый пользователь указывал правильный адрес электронной почты и почтовый адрес.

Есть несколько способов проверить правильность данных прежде, чем они попадут базу данных, включая родные ограничения базы данных, проверки на стороне клиента, проверки на уровне контроллера и проверки на уровне модели.

  • Ограничения базы данных и/или сохраненные процедуры, делающие зависимые от базы данных проверки правильности данных, могут сделать тестирование и обслуживание более сложным. Однако, если Ваша база данных используется другими приложениями, может быть хорошей идеей использовать некоторые ограничения на уровне базы данных. Также, проверки на уровне базы данных могут безопасно обработать некоторые случаи (такие как проверка уникальности данных в таблицах), которые сложно проверить подругому.

  • Проверки на стороне клиента могут быть полезными, но они ненадежны, если используются одни. Если они сделаны с использованием JavaScript, их легко обойти, если выключить JavaScript в браузере пользователя. Однако, если их объединить с другими методиками, они могут быть удобными, предоставляя пользователю более быстрый отчет об ошибках.

  • Проверки на уровне контроллера могут быть очень заманчивыми для использования, но, как правило, часто становятся громоздкими и сложными для тестирования и поддержки. Всегда, когда это возможно, лучше оставлять в контроллерах минимум кода, в конечном счете это сделает Вашу работу более приятной.

  • Проверки на уровне модели – это лучший способ гарантировать попадание в базу данных только достоверных данных. Их нельзя обойти и они удобны для тестирования и поддержки. Rails делает их удобными для использования, предоставляя встроенные помощники, а также позволяет Вам создавать свои собственные методы проверки.

2.2 Когда происходит проверка?

Есть два вида объектов Active Record: те, которые соответствуют строке в базе данных и те, которые еще не сохранены. Когда Вы создаете новый объект, например используя метод new, этот объект еще не принадлежит базе данных. Как только Вы вызовите метод save этого объекта, он будет сохранен в базе данных. Active Record использует метод экземпляра класса new_record?, чтобы определить, существует объект в базе данных или нет. Рассмотрим простой класс Active Record:

class Person < ActiveRecord::Base
end

Вот пример, из которого видно, как работает этот метод:

p = Person.new(:name => "John Doe")
# => #<Person id: nil, name: "John Doe", created_at: nil, :updated_at: nil>
p.new_record?
# => true
p.save
# => true
p.new_record?
# => false

Создание и сохранение нового объекта создает новый INSERT SQL запрос. Обновление существующего объекта создаст UPDATE SQL запрос. Проверки обычно выполняются прежде, чем происходит запрос к базе данных. Если какая-нибудь проверка потерпит неудачу, то объект будет помечен как недопустимый и Active Record не будет пытаться выполнять INSERT или UPDATE запросы к базе данных. Так можно избежать попадания недопустимых данных в базу данных. Вы можете выполнять разные проверки при создании, сохранении и обновлении объекта.

Есть множество способов изменить состояние объекта в базе данных. Некоторые методы вызывают проверки, а некоторые нет. Это означает что можно случайно сохранить недопустимый объект в базе данных, поэтому будте осторожны.

Эти методы вызывают проверки и сохранют объект только если он правилен:

  • create
  • create!
  • save
  • save!
  • update
  • update_attributes
  • update_attributes!

Методы с восклицательным знаком (такие как save!) вызывают исключение если данные неправильны. Методы без восклицательного знака: save and update_attributes возвращают false, create и update просто возвращают объект или объекты.

2.3 Когда проверка не происходит?

Следующие методы не производят проверок и сохраняют объект независимо не от чего. Ими следует пользоваться с осторожностью:

  • decrement!
  • decrement_counter
  • increment!
  • increment_counter
  • toggle!
  • update_all
  • update_attribute
  • update_counters

Заметьте, что метод save также может не производить проверок, если в качестве аргумента ему передать false. Будьте осторожны используя этот способ.

  • save(false)

2.4 valid? и invalid?

Чтобы проверить объект, Rails использует метод valid?. Вы можете использовать этот метод самостоятельно. Метод valid? производит проверку и возвращает true, если нет ошибок, и false если они есть.

class Person < ActiveRecord::Base
  validates_presence_of :name
end
Person.create(:name => "John Doe").valid?
# => true
Person.create(:name => nil).valid?
# => false

Когда Active Record выполняет проверку, то найденные ошибки можно посмотреть используя метод экземпляра класса errors. По сути, если после проверок этот массив пустой, то объект правилен.

При создании объекта метод errors вернет nil, даже если объект содержит ошибки, это потому что при использовании метода new проверки не выполняются.

class Person < ActiveRecord::Base
  validates_presence_of :name
end
p = Person.new
# => #<Person id: nil, name: nil>
p.errors
# => #<ActiveRecord::Errors..., @errors={}>
p.valid?
# => false
p.errors
# => #<ActiveRecord::Errors..., @errors={"name"=>["can't be blank"]}>
p = Person.create
# => #<Person id: nil, name: nil>
p.errors
# => #<ActiveRecord::Errors..., @errors={"name"=>["can't be blank"]}>
p.save
# => false
p.save!
# => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
Person.create!
# => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank

Метод invalid? это метод обратный valid?. invalid? производит проверки и возвращает true если есть ошибки и false если их нет.

2.5 errors.invalid?

Чтобы проверить определенный атрибут объекта нужно использовать метод errors.invalid?. Этот метод имеет смысл использовать только после того, как были выполнены проверки правильности объекта, потому что он метод errors и не производит проверок самостоятельно. Он отличается от метода ActiveRecord:: Base.invalid? тем, что он не проверяет объект в целом. Он только выясняет, есть ли ошибки в конкретном атрибуте объекта.

class Person < ActiveRecord::Base
  validates_presence_of :name
end

Person.new.errors.invalid?(:name)
# => false
 Person.create.errors.invalid?(:name)
# => true

Более подробно об ошибках проверки мы расскажем в разделе “Работа с ошибками проверки”. А пока, рассмотрим встроенным помощники используемые для проверки, Rails предоставляет их по-умолчанию.

3 Помощники используемые для проверок

Active Record предлагает встроенные помощники для проверок, которые можно использовать непосредственно в собственных моделях. Эти помощники обеспечивают общие правила для проверок. Каждый раз, когда бывает обнаружена ошибка, сообщение о ней добавляется в массив ошибок объекта, это сообщение связанно полем в котором находятся ошибочные данные.

Каждый помощник может принимать множество названий атрибута, таким образом в одной строчке кода можно произвести проверку по нескольким атрибутам.

Все они принимают :on и :message параметры, которые определяют, когда должна быть выполнена проверка и какое сообщение должно быть добавлено в массив ошибок. Параметр :on может содержать одно из значений: :save (значение по умолчанию), :create или :update. Есть и заданное по умолчанию сообщение об ошибках для каждого из помощников. Эти сообщения используются, когда параметр :message не определен. Давайте рассмотрим этих помощников.

3.1 validates_acceptance_of

Проверяет был ли включен определенный checkbox при отправке формы. Это обычно используется, когда пользователь должен принять условия использования приложения или подтвердите прочтение некоторого текста (соглашение, лицензия и т. д.). Эта проверка является достаточно специфичной для веб-приложений, и это ‘принятие’ не должно сохраняться в базе данных (если у Вас нет для этого поля в базе данных, то помощник создаст виртуальный атрибут).

class Person < ActiveRecord::Base
  validates_acceptance_of :terms_of_service
end

По умолчанию, помощник validates_acceptance_of использует “must be accepted” в качестве сообщения об ошибке.

validates_acceptance_of может получать параметр :accept, который определяет значение, являющееся признаком принятия условий. По умолчанию это “1″.

class Person < ActiveRecord::Base
  validates_acceptance_of :terms_of_service, :accept => 'yes'
end

3.2 validates_associated

Используйте этого помощника, когда у модели есть связи с другими моделями, и они также должны быть проверены. Когда Вы сохраняете свой объект, метод valid? будет назван для каждого из связанных объектов.

class Library < ActiveRecord::Base
  has_many :books
  validates_associated :books
end

Этот помошник работает со всеми типами связей

Не используйте validates_associated в обоих объектах, участвующих в связи, это приведет к бесконечному циклу.

По умолчанию, помощник validates_associated использует “is invalid” в качестве сообщения об ошибке. Заметьте что, каждый связанный объект будет содержать свой собственный массив ошибок, и они не добавляются в основной объект.

3.3 validates_confirmation_of

Используйте этого помощника у Вас есть два текстовых поля, которые должны содержать одинаковый текст. Например, Вы хотите подтвердить адрес электронной почты или пароль. Этот помошник создает виртуальный атрибут, название которого составляет название проверяемого поля с добавлением "_confirmation".

class Person < ActiveRecord::Base
  validates_confirmation_of :email
end

В шаблоне можете написать так:

<%= text_field :person, :email %> <%= text_field :person, :email_confirmation %>

Эта проверка выполняется только если email_confirmation не nil. Чтобы использовать validates_confirmation_of добавьте проверку на наличие проверяемого атрибута (мы рассмотрим validates_presence_of чуть позже):

class Person < ActiveRecord::Base
  validates_confirmation_of :email
  validates_presence_of :email_confirmation
end

По умолчанию, помощник validates_confirmation_of использует “doesn`t match confirmation” в качестве сообщения об ошибке.

3.4 validates_exclusion_of

Этот помощник проверяет, что значение атрибута не содержится в определенном наборе данных. Фактически, этот набор может быть любым enumerable объектом.

class Account < ActiveRecord::Base
  validates_exclusion_of :subdomain, :in => %w(www), :message => "Subdomain {{value}} is reserved."
end

Помощник validates_exclusion_of имеет параметр :in, который определяет набор данных, используемых для проверки. Вместо параметра :in можно использовать параметр :within, они совершенно одинаковые. Пример выше использует параметр :message чтобы показать как можно включить значение атрибута в сообщение об ошибке.

По умолчанию, помощник validates_exclusion_of использует сообщение "is not included in the list" в качестве сообщения об ошибке.

3.5 validates_format_of

Этот помощник проверяет значение атрибута с помощью регулярного выражения, которое определяется параметром :with.

class Product < ActiveRecord::Base
  validates_format_of :legacy_code, :with => /\A[a-zA-Z]+\z/, :message => "Only letters allowed"
end

По умолчанию, помощник validates_format_of использует сообщение "is invalid" в качестве сообщения об ошибке.

3.6 validates_inclusion_of

Этот помощник проверяет, что значение атрибута содержится в определенном наборе данных. Фактически, этот набор может быть любым enumerable объектом.

class Coffee < ActiveRecord::Base
  validates_inclusion_of :size, :in => %w(small medium large), :message => "{{value}} is not a valid size"
end

Помощник validates_inclusion_of имеет параметр :in, который определяет набор данных, используемых для проверки. Вместо параметра :in можно использовать параметр :within, они совершенно одинаковые. Пример выше использует параметр :message чтобы показать как можно включить значение атрибута в сообщение об ошибке.

По умолчанию, помощник validates_inclusion_of использует сообщение "is not included in the list" в качестве сообщения об ошибке.

3.7 validates_length_of

Этот помощник проверяет длину значений атрибутов. Он поддерживает множество параметров, таким образом можно по-разному определять ограничения для длины:

class Person < ActiveRecord::Base
  validates_length_of :name, :minimum => 2
  validates_length_of :bio, :maximum => 500
  validates_length_of :password, :in => 6..20
  validates_length_of :registration_number, :is => 6
end

Возможные параметры для ограничения длинны:

  • :minimum – Значение атрибута не может иметь длинну меньше, чем указанное число.
  • :maximum – Значение атрибута не может иметь длинну больше, чем указанное число.
  • :in (или :within) – Значение атрибута должно иметь длинну укладывающуюся в зпданный диапазон. Значение этого параметра должно быть интервалом.
  • :is – Значение атрибута должно иметь длинну равную указанному значению.

Заданные по умолчанию сообщения об ошибках для помощника зависят от длинны значения атрибута. Вы можете изменить их для себя используя параметры: :wrong_length, :too_long и :too_short и маркер {{count}} для обозначения ограничения длинны. Вы можете также использовать параметр :message для того чтобы задать сообщение об ошибке.

class Person < ActiveRecord::Base
  validates_length_of :bio, :maximum => 1000, :too_long => "{{count}} characters is the maximum allowed"
end

По умолчанию помощник считает каждый символ, Вы можете изменить это поведение используя параметр :tokenizer:

class Essay < ActiveRecord::Base
  validates_length_of :content, :minimum => 300,
                                :maximum => 400,
                                :tokenizer => lambda { |str| str.scan(/\w+/) },
                                :too_short => "must have at least {{count}} words",
                                :too_long => "must have at most {{count}} words"
end

Помощник validates_size_of копия помощника validates_length_of.

3.8 validates_numericality_of

Этот помощник проверяет, что атрибуты имеют только числовые значения. По умолчанию, происходит проверка являются ли значения атрибутав integer или float. Используйте параметр :only_integer, чтобы указать помощнику что разрешены только integer числа.

Когда Вы устанавливаете параметр :only_integer равным true, то Вы используете это регулярное выражение для проверки значения атрибута

/\A[+-]?\d+\Z/

Иначе, помощник попытается преобразовать значение в float число.

Заметьте, что это регулярное выражение выше позволяет завершающий символ новой стоки.

class Player < ActiveRecord::Base
  validates_numericality_of :points
  validates_numericality_of :games_played, :only_integer => true
end

Кроме :only_integer, помощник validates_numericality_of для указания дополнительных ограничений принимает следующие параметры:

  • :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 or equal to {{count}}".
  • :odd – Определяет, что значение должно быть нечетным. Сообщение об ошибке по умолчанию: "must be odd".
  • :even – Определяет, что значение должно быть четным. Сообщение об ошибке по умолчанию: "must be even".

По умолчанию, помощник validates_numericality_of использует сообщение "is not a number" в качестве сообщения об ошибке.

3.9 validates_presence_of

Этот помощник проверяет что указанные атрибуты не пусты. Он использует метод blank?, чтобы проверить, является ли значение атрибута нолем или пустой строкой, то есть, строкой, которая либо пуста, либо состоит из пробелов.

class Person < ActiveRecord::Base
  validates_presence_of :name, :login, :email
end

Если Вы хотите убедиться, что ассоциация присутствует, то Вы должны проверить, присутствует ли внешний ключ, используемый для связи, а не непосредственно связанный объект.

class LineItem < ActiveRecord::Base
  belongs_to :order
  validates_presence_of :order_id
end

Так как false.blank?, то если Вам надо проверить наличие булева атрибута, то Вы должны использовать: validates_inclusion_of :field_name, :in => [true, false].

По умолчанию, помощник validates_presence_of использует сообщение "can’t be empty" в качестве сообщения об ошибке.

3.10 validates_uniqueness_of

Этот помощник проверяет атрибуты на уникальность, прежде чем объект будет сохранен. Он не создает ограничение уникальности в базе данных, поэтому может получиться так, что два разных соединения с базой данных создадут две записи с тем же самым значением для атрибутов, которые должны быть уникальными. Чтобы избежать этого, Вы должны создать в базе данных уникальный индекс.

class Account < ActiveRecord::Base
  validates_uniqueness_of :email
end

Проверка осуществляется с помощью SQL запроса к базе данных, который ищет существующие записи с тем же значением что и у проверяемого атрибута.

Существует параметр :scope который указывает на атрибуты, используемые для ограничения проверки уникальности:

class Holiday < ActiveRecord::Base
  validates_uniqueness_of :name, :scope => :year, :message => "should happen once per year"
end

Так же существует параметр :case_sensitive предназначенный для того, чтобы указать, будет ли учитываться регистр букв. По умолчанию параметр установлен в true, то есть заглавные и прописные буквы различаются.

class Person < ActiveRecord::Base
  validates_uniqueness_of :name, :case_sensitive => false
end

Заметьте, что некоторые базы данных должны быть настроены для того чтобы выполнять поиск независящий от регистра.

По умолчанию, помощник validates_uniqueness_of использует сообщение "has already been taken" в качестве сообщения об ошибке.

3.11 validates_each

Этот помощник проверяет атрибуты в переданном блоке. У него нет предопределенной функции для проверки правильности. Вы должны создать ее, используя блок, в который передается модель, имя атрибута и значение атрибута. В приведенном примере происходит проверка на то что значения атрибутов :name, :surname являются стоками начинающимся с заглавной буквы.

class Person < ActiveRecord::Base
  validates_each :name, :surname do |model, attr, value|
    model.errors.add(attr, 'must start with upper case') if value =~ /\A[a-z]/
  end
end

В блоке Вы можете делать какие угодно проверки, если Ваша проверка завершилась неудачно, Вы можете добавить в модель сообщение об ошибке. Это сделает модель недопустимой для сохранения в базе.

4 Общие параметры для помощников

Есть параметры общие для всех помощников. Все они, за исключением :if и :unless, которые обсуждаются позже в разделе Условная проверка, описываются ниже.

4.1 :allow_nil

Параметр :allow_nil позволяет пропустить проверку атрибута, если его значение nil. Используйте параметр :allow_nil с помощником validates_presence_of, чтобы сделать исключение для nil, тогда как все другие значения для которых метод blank? вернет true не пройдут проверку.

class Coffee < ActiveRecord::Base
  validates_inclusion_of :size, :in => %w(small medium large), :message => "{{value}} is not a valid size", :allow_nil => true
end

4.2 :allow_blank

Этот параметр похож на параметр :allow_nil. Он позволяет пропустить проверку атрибута, если его значение для которого метод blank? вернет true, то есть равно nil или пустой строки.

class Topic < ActiveRecord::Base
  validates_length_of :title, :is => 5, :allow_blank => true
end
Topic.create("title" => "").valid?
# => true
Topic.create("title" => nil).valid?
# => true

4.3 :message

Как Вы уже знаете, параметр :message позволяет указать сообщение об ошибке, которое будет добавлено в массив ошибок модели. Если параметр не используется, то Active Record будет использовать соответствующее сообщение по умолчанию.

4.4 :on

Параметр :on определяет когда должна проводиться проверка. По умолчанию все помощники производят проверку при создании и при изменении объекта в базе данных. Вы можете изменить это поведение, используйте :on => :create, чтобы проверку производить только при создании, и :on => :update, чтобы произвести проверку только при изменении существующего объекта.

class Person < ActiveRecord::Base
  # it will be possible to update email with a duplicated value
  validates_uniqueness_of :email, :on => :create

  # it will be possible to create the record with a non-numerical age
  validates_numericality_of :age, :on => :update

  # the default (validates on both create and update)
  validates_presence_of :name, :on => :save
end

Перевод: Лепешкин Андрей.

Написал undr ()

11 ноября 2009 в 17:42

One Response to 'Проверка данных моделей ActiveRecord и функции обратного вызова. Первая часть.'

Подписаться на комментарии or TrackBack to 'Проверка данных моделей ActiveRecord и функции обратного вызова. Первая часть.'.

  1. снова перевод. Супер! почитаем-с.. Спасибо!

    Alex

    11 Ноя 09 at 22:45

Оставьте комментарий