Введение
Как вы уже знаете из поста тов. Yehuda Katz об ActiveModel абстракции, в Rails 3.0, ActiveRecord отныне содержит в себе некоторые аспекты ActiveModel, среди которых модули валидации.
И прежде чем мы начнем, давайте вспомним, какие методы валидации у нас уже есть:
- validates_acceptance_of
- validates_associated
- validates_confirmation_of
- validates_each
- validates_exclusion_of
- validates_format_of
- validates_inclusion_of
- validates_length_of
- validates_numericality_of
- validates_presence_of
- validates_size_of
- validates_uniqueness_of
Все они по прежнему в строю, но Rails 3 предлагает несколько новых отличных альтернатив.Новый метод validate
Метод
validate принимает атрибут и хеш с опциями валидации. Это значит, что теперь привычную валидацию можно записать вот так:class Person < ActiveRecord::Base validates :email, :presence => true end
В качестве опций, которые можно передать, выступают следующие:
- :acceptance => Boolean
- :confirmation => Boolean
- :exclusion => { :in => Ennumerable }
- :inclusion => { :in => Ennumerable }
- :format => { :with => Regexp }
- :length => { :minimum => Fixnum, maximum => Fixnum, }
- :numericality => Boolean
- :presence => Boolean
- :uniqueness => Boolean
Что дает обширную область очень простых, кратких опций для тех или иных атрибутов предоставляя возможность писать все нужные валидации в одном месте.К примеру, если нужно проверить имя и электронную почту, можно сделать так:
Таким образом, теперь можно взглянув на модель сразу увидеть, какие валидации навешаны для каждого атрибута — что есть небольшая победа для кода и удобочитаемости :)class User < ActiveRecord::Base validates :name, :presence => true, :length => {:minimum => 1, :maximum => 254} validates :email, :presence => true, :length => {:minimum => 3, :maximum => 254}, :uniqueness => true, :format => {:with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i} end
Извлечение привычных сценариев использования
Тем не менее, записаь
:format => {:with => EmailRegexp}, немного тяжеловата, чтоб писать её в нескольких местах, что наталкивает на мысль о создании многократно используемой валидации, которую можно было бы применить в других моделях.Пойдем дальше — а что если нужно использовать гораздо более выразительное регулярно выражение, состоящ��е из более чем нескольких символов, чтобы показать, как классно вы умеете гуглить? :)
Чтож, валидации могут быть и написанными вручную. Для начала создадим файл
email_validator.rb в каталоге lib, в недрах нашего приложения:# lib/email_validator.rb class EmailValidator < ActiveModel::EachValidator EmailAddress = begin qtext = '[^\\x0d\\x22\\x5c\\x80-\\xff]' dtext = '[^\\x0d\\x5b-\\x5d\\x80-\\xff]' atom = '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-' + '\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+' quoted_pair = '\\x5c[\\x00-\\x7f]' domain_literal = "\\x5b(?:#{dtext}|#{quoted_pair})*\\x5d" quoted_string = "\\x22(?:#{qtext}|#{quoted_pair})*\\x22" domain_ref = atom sub_domain = "(?:#{domain_ref}|#{domain_literal})" word = "(?:#{atom}|#{quoted_string})" domain = "#{sub_domain}(?:\\x2e#{sub_domain})*" local_part = "#{word}(?:\\x2e#{word})*" addr_spec = "#{local_part}\\x40#{domain}" pattern = /\A#{addr_spec}\z/ end def validate_each(record, attribute, value) unless value =~ EmailAddress record.errors[attribute] << (options[:message] || "не корректный") end end end
Так как каждый файл из директории
lib загружается автоматически, и, так как наш валидатор унаследован от класса ActiveModel::EachValidator, имя нашего класса используется в качестве динамического валидатора, который можно применять в любом объекте, у которого есть доступ к ActiveModel::Validations. Тоесть, к примеру, это все объекты ActiveRecord.Название динамического валидатора — это всё что стоит левее от слова Validator, приведенное к нижнему регистру.
Таким образом наш класс
User теперь будет выглядеть вот так:# app/models/person.rb class User < ActiveRecord::Base validates :name, :presence => true, :length => {:minimum => 1, :maximum => 254} validates :email, :presence => true, :length => {:minimum => 3, :maximum => 254}, :uniqueness => true, :email => true end
Обратили внимание на
:email => true? Так гораздо проще, но что самое главное — теперь это можно использовать где угодно!А в консоли теперь мы увидим нечто следующее (с нашим собственным сообщением “не корректный”):
$ ./script/console Loading development environment (Rails 3.0.pre) ?> u = User.new(:name => 'Mikel', :email => 'bob') => #<User id: nil, name: "Mikel", email: "bob", created_at: nil, updated_at: nil> >> u.valid? => false >> u.errors => #<OrderedHash {:email=>["не корректный"]}>
Валидации для классов
А что если, скажем, существуют три различные модели (
user, visitor и customer), каждый из которых должен использовать общие валидации. В таком случае, заменив validates на validates_with, мы просто должны сделать так:# app/models/person.rb class User < ActiveRecord::Base validates_with HumanValidator end # app/models/person.rb class Visitor < ActiveRecord::Base validates_with HumanValidator end # app/models/person.rb class Customer < ActiveRecord::Base validates_with HumanValidator end
А в каталог
lib поместим файл:class HumanValidator < ActiveModel::Validator def validate(record) record.errors[:base] << "This person is dead" unless check(human) end private def check(record) (record.age < 200) && (record.age > 0) end end
И проверим на, явно притянутом за уши, примере:
$ ./script/console Loading development environment (Rails 3.0.pre) >> u = User.new => #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil> >> u.valid? => false >> u.errors => #<OrderedHash {:base=>["This person is dead"]}>
Время триггеров
Как и стоило ожидать, каждая валидация может принимать следующие под-опции:
- :on
- :if
- :unless
- :allow_blank
- :allow_nil
Каждая из которых может принимать вызов произвольного метода. Таким образом:Этого, пожалуй, будет достаточно чтобы составить первое впечатление о новом уровне гибкости.class Person < ActiveRecord::Base validates :post_code, :presence => true, :unless => :no_postcodes? def no_postcodes? true if ['TW'].include?(country_iso) end end
