[info]ru_dmitriid


О королях и капусте


Previous Entry Add to Memories Tell a Friend Next Entry
Валидация для erlyweb
happy
[info]dmitriid wrote in [info]ru_dmitriid
Накалякал тут функцию валидации для Erlyweb. Сначала описание работы, а потом, может быть, о самой функции :) Работает примерно так:

Создаем форму с четырьмя полями:
- login
- password
- pasword_repeat
- email

Стандартная форма регистрации то бишь. Естественно, нам нужно провести валидацию полей следующим образом:
- login должен быть 4-16 символов
- password должен быть 4-16 символов
- password должен быть равен password_repeat
- email должен быть правильным адресом

Стандартные функции валидации для этого не подходят. Но подойдет моя :)

Предположим, у нас есть функция process_signup, которая принимает на вход стандартную структуру yaws_arg. Тогда валидация будет выглядеть следующим образом:

process_signup(A) ->
	F = fun(A, Field) ->
		{ok, Val} = yaws_api:postvar(A, Field),
		L = string:len(Val),
		if
			L < 4 orelse L > 16 ->
				{Field, length};
			true ->
				{}
			end
	end,
	EmailCheck = fun(Args, Field2) ->
		{ok, Email} = yaws_api:postvar(Args, Field2),
		Match = regexp:match(Email, "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]+"),
		Match /= nomatch
	end,

	%% магия тут:
	buktu_form:validate(A, [
		{login, F},
		{email, EmailCheck},
		{password, [{'=', password_repeat}, F]}
	]).


Таким образом, если вообще ни одно поле введено не будет, то мы получим:
[{login,invalid_field},
 {email,invalid_field},
 {password,[{invalid_fields,[password,password_repeat]},
            invalid_field]}]


Если введем поля, не соответствующие критериям, то:
[{login,length},
 {email,invalid_value},
 {password,[{not_equal,password_repeat},
            length]}]


  • Callback-функция, которую можно передавать для валидации, может возвращать:

    • кортеж типа {FieldName, Error}

    • true, если поле прошло валидацию или false в противном случае (тогда функция валидации вернет кортеж {FieldName, invalid_value})

    • любое значение Value, которое будет трансформировано в {FieldName, Value}


  • если любое из полей в правиле не существует или не содержит значение, то для каждого такого правила функция валидации вернет:

    • invalid_field если поле сравнивается со значением

    • {invalid_fields, [field1, field2]} если поле сравнивается с другим полем


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

    • Проверить, существует ли такое поле
      {field_name}

    • Сравнить поле field_name с полем field2_name (также можно использовать '=', '/=', '<', '=<', '>', '>=' )
      {field_name, {'=', field2_name}}

    • Сравнить значение поля с любым другим значением:
      {field_name, {'=', Value}}

    • Использовать callback-функцию (function/2, первый параметр - yaws_arg, второй - название поля). Функция может быть лямбдой или любой функцией из любого модуля в виде module:function/2 или {module, function}
      {field_name, F}

    • Использовать callback-функцию с дополнительным значением (function/3, первый параметр - yaws_arg, второй - название поля, третий - значение). Функция может быть лямбдой или любой функцией из любого модуля в виде module:function/3 или {module, function}
      {field_name, {F, Value}}


  • Функция валидации возвращает proplist в виде:
    [{FieldName(), Errors()}]

    где
    FieldName() = atom()
    Errors() = Error() | [Error()]
    Error() = любое_пользовательское_значение | absent | invalid_value | 
              invalid_field | {invalid_fields, [FieldName(), FieldName()]} |
              ComparisonError()
    ComparisonError() = {not_equal, value_or_field} | {equal, value_or_field} |
                        {not_greater, value_or_field} | {not_less, value_or_field} |
                        {not_greater_or_equal, value_or_field} | {not_less_or_equal, value_or_field} |
    

(Leave a comment)
Вот любопытно. Вы там, на РСДНе, на этом примере обругали всячески отсутствие паттерн-матчинга. Но вот более-менее близкое решение на Ruby (точнее, решение близкой проблемы):

# стандартные валидации:
validate_presence_of :login
validate_pattern :email, /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]+$/

# упс, стандартных валидаций не хватило!
class LengthValidation < Validation(:length_of)
   def initialize(field, range)
      super(field) #инициализируем суперкласс
      @range = range
   end

   def valid?(value); @range.include?(value.size) end
end

#и сразу же:
validate_length_of :login, (4..16)

#ах да! произвольная валидация:
validate :login, :with => lambda{|value| value.is_so_coool?}



Причем, полагаю, "думающий на Руби" будет с пеной у рта отстаивать сентенцию "много маленьких классов лучше одной большой функции", а Эрлангист - наоборот.

Но в любом случае, необходимость в этой примитивной задаче ПМ, имхо, не доказана :)

PS:
надо ли говорить, что вариант со всего 3 разными валидациями (точное "снятие" эрланг-варианта) - делается без ПМ на раз?

validate :login, lambda{|l| (4..16).include?(l.size)}
validate :password, :=, :password_repeat
validate :email, :=~, /big ugly regexp/

но мой первый, более "разнообразный" вариант принято считать понятнее...

PPS: я не холиварю, я пытаюсь разобраццо.

Руби крут тем, что благодаря различному сахару на нем удобно писать всякие DSL'и и DSL-еподобные штуки :)

Я на РСДНе просто пытаюсь отстоять то, что ПМ позволяет использовать унифицированый подход к решению разнообразных проблем :)

Таки как он этот ErlyDB?
нормально ли с Postgres работает?
и как быть с кашированием аля hibernate?

Не помню, есть ли там драйвер для Postgre. Кэширования там нет. Это очен тонкая обертка над SQLем

(Leave a comment)

Home