Здравствуйте, записался на курс. При этом ставил галочку на "обучаться с тьютором". На email пришло письмо, о том, что записался на самостоятельное изучение курса. Как выбрать тьютора? |
Войти, выйти
Рабочий метод sign_in
Теперь мы готовы к написанию первого элемента входа - самой sign_in функции. Как было отмечено выше, выбранный нами метод аутентификации заключается в помещении (вновь созданного) remember token в качестве куки в браузер пользователя и последующем использовании токена для поиска записи пользователя в базе данных при перемещении пользователя от страницы к странице (реализовано в Разделе 8.2.3). Результирующий Листинг 8.19 вводит новый для нас метод current_user который мы будем реализовывать в Разделе 8.2.3.
module SessionsHelper def sign_in(user) remember_token = User.new_remember_token cookies.permanent[:remember_token] = remember_token user.update_attribute(:remember_token, User.encrypt(remember_token)) self.current_user = user end endЛистинг 8.19. Завершенная (но пока-еще-не-работающая) функция sign_in. app/helpers/sessions_helper.rb
Здесь мы следуем избранной тактике: во-первых, создаем новый токен; во-вторых, помещаем зашифрованный токен в куки браузера; в-третьих, сохраняем зашифрованный токен в базе данных; в-четвертых, устанавливаем текущего пользователя равным данному пользователю (Раздел 8.2.3). Как мы увидим в Разделе 8.2.3, установка текущего пользователя равным user в данный момент не нужна из-за незамедлительного редиректа в create действии (Листинг 8.13), но все же это хорошая идея - на тот случай если мы когда-нибудь захотим использовать sign_in без редиректа.
В Листинге 8.19 обратите внимание на использование update_attribute для сохранения токена. Как вкратце упоминалось в Разделе 6.1.5), этот метод позволяет обновлять один атрибут в обход валидаций — в данном случае это необходимо так как у нас нет пароля пользователя. Листинг 8.19 также вводит утилиту cookies которая позволяет нам манипулировать куками браузера как если бы они были хэшем; каждый элемент в куки представляет из себя хэш из двух элементов: value и (необязательный) expires дата (# дата истечения). Например, мы могли бы осуществить вход пользователя путем размещения куки со значением, равным пользовательскому токену, которая истекает через 20 лет:
cookies[:remember_token] = { value: remember_token, expires: 20.years.from_now.utc }
(Этот код использует один из удобных Rails помощников, о чем говорится в Блоке 8.1.)
______________________________________________________________
Блок 8.1.Куки истекают через 20.years.from_now
Вы можете вспомнить из Раздела 4.4.2, что Ruby позволяет добавлять методы к любому, даже встроенному классу. В том разделе мы добавляли palindrome? метод к String классу (и в результате обнаружили, что "deified" является палиндромом), и мы также видели, как Rails добавляет blank? метод к классу Object (таким образом, "".blank?, " ".blank?, и nil.blank? все являются true). Код куки в Листинге 8.19 (который внутренне устанавливает срок действия cookie в 20.years.from_now) дает еще один пример из этой практики, посредством одного из Rails’ временных хелперов, которые являются методами добавленными к Fixnum (базовый класс для чисел):
$ rails console >> 1.year.from_now => Sun, 13 Mar 2011 03:38:55 UTC +00:00 >> 10.weeks.ago => Sat, 02 Jan 2010 03:39:14 UTC +00:00
Rails добавляет и другие помощники:
>> 1.kilobyte => 1024 >> 5.megabytes => 5242880
Они полезны для валидации загрузки, что позволяет легко ограничить, например, загрузку изображений размером в 5.megabytes.
Хотя она должна использоваться с осторожностью, возможность добавлять методы к встроенным классам позволяет создавать черезвычайно естественные добавления к обычному Ruby. Действительно, большая часть элегантности Rails в конечном счете, является производной от податливости лежащего в его основе языка Ruby.
______________________________________________________________
Паттерн установки куки истекающей через 20 лет стал настолько общепринятым, что Rails добавил специальный метод permanent для его реализации, так что мы можем просто написать
cookies.permanent[:remember_token] = remember_token
Под капотом, применение permanent приводит к автоматической установке даты истечения куки через 20 лет (20.years.from_now).
После того как куки установлены, на последующих представлениях страниц мы можем извлекать пользователя с кодом вроде
User.find_by(remember_token: remember_token)
(Как мы увидим в Листинге 8.22, на самом деле мы вначале должны захэшировать токен.) Конечно, cookies это на самом деле не хэш, поскольку назначение cookies действительно сохраняет кусочек текста в браузере, но частью красоты Rails является то, что он позволяет вам забыть о деталях и сконцентрироваться на написании приложения.
Текущий пользователь
Обсудив способ хранения пользовательского remember token в куки для последующего использования, теперь нам необходимо узнать как извлекать пользователя при последующем просмотре страниц. Давайте еще раз взглянем на функцию sign_in для того чтобы понять где мы находимся:
module SessionsHelper def sign_in(user) remember_token = User.new_remember_token cookies.permanent[:remember_token] = remember_token user.update_attribute(:remember_token, User.encrypt(remember_token)) self.current_user = user end end
Единственный участок кода который в данный момент не работает это:
self.current_user = user
Как было отмечено сразу после Листинга 8.19, этот код никогда не будет использоваться в данном приложении из-за немедленного редиректа в Листинге 8.13, но для метода sign_in было бы опасным полагаться на это.
Целью current_user, доступного и в контроллерах и в представлениях является возможность создания конструкции подобные этой:
<%= current_user.name %<
и
redirect_to current_user
Использование self в назначении является необходимым по тем же причинам что были отмечены в обсуждении приведшем к Листингу 8.18: без self Ruby будет просто создавать локальную переменную с названием current_user.
Для того, чтобы начать писать код для current_user, обратите внимание, что строка
self.current_user = user
это назначение, которое мы должны определить. В Ruby есть специальный синтаксис для определения таких назначаемых функций, показанный в Листинге 8.20.
module SessionsHelper def sign_in(user) . . . end def current_user=(user) @current_user = user end endЛистинг 8.20. Определение назначения current_user. app/helpers/sessions_helper.rb
Это может выглядеть сбивающим с толку — большинство языков не позволит вам использовать знак равенства в определении метода, но это просто определение метода current_user= специально разработанного для обработки назначения current_user. Другими словами, код
self.current_user = ...
автоматически конвертируется в
current_user=(...)
тем самым вызывая метод current_user=. Его единственный аргумент это то, что находится справа от назначения, в данном случае - пользователь который войдет. Однострочный метод в теле просто устанавливает переменную экземпляра @current_user, эффективно хранящую пользователя для дальнейшего использования.
В обычном Ruby, мы могли бы определить второй метод, current_user, предназначенный для возвращения значения @current_user, как это показано в Листинге 8.21.
module SessionsHelper def sign_in(user) . . . end def current_user=(user) @current_user = user end def current_user @current_user # Useless! Don't use this line. end endЛистинг 8.21. Заманчивое, но бесполезное определение current_user.
Если бы мы сделали это, мы бы фактически повторили функциональность attr_accessor, который мы видели в Разделе 4.4.5.5На самом деле, эти двое абсолютно эквивалентны; attr_accessor это просто удобный способ создавать такие getter/setter методы автоматически. Проблема в том, что он совершенно не в состоянии решить наши проблемы: с кодом в Листинге 8.21, статус вошедшего пользователя будет забыт: как только пользователь перейдет на другую страницу — poof! — сессия закончится и пользователь автоматически выйдет. Это связано с тем что в HTTP отсутствует сохранение промежуточного состояния между парами "запрос-ответ" (Раздел 8.2.1) — когда пользователь делает второй запрос, все переменные устанавливаются к своим дефолтным значениям, в случае переменных экземпляра вроде @current_user это nil. Таким образом, когда пользователь обратится к еще одной странице, даже находясь в том же приложении, Rails установит @current_user равным nil и код в Листинге 8.21 не сделает то чего вы от него ожидали.
Для того чтобы избежать этой проблемы мы можем искать пользователя соответствующего remember token созданному кодом в Листинге 8.19, как это показано в Листинге 8.22. Обратите внимание: поскольку токен хранимый в базе данных зашифрован, нам нужно зашифровать токен полученный из куки прежде чем использовать его для поиска пользователя в базе данных. Мы достигним этого с помощью метода User.encrypt определенного в Листинге 8.18.
module SessionsHelper . . . def current_user=(user) @current_user = user end def current_user remember_token = User.encrypt(cookies[:remember_token]) @current_user ||= User.find_by(remember_token: remember_token) end endЛистинг 8.22. Поиск текущего пользователя с помощью remember_token. app/helpers/sessions_helper.rb
Листинг 8.22 использует общепринятый, но изначально обескураживающий ||= ("или равно") оператор присваиваивания (Блок 8.2). Его эффект заключается в установке переменной экземпляра @current_user пользователю, соответствующему remember token, но только если @current_user не определен.6Как правило, это означает присвоение переменных, которые изначально nil, но обратите внимание - ложные (false) значения также будут переписаны оператором ||= Иными словами, конструкция
@current_user ||= User.find_by(remember_token: remember_token)
вызывает метод find_by при первом вызове которого вызывается current_user, но при последующих вызовах возвращается @current_user без обращения к базе данных.7то является примером мемоизации, которая обсуждалась ранее в Блоке 6.3. Это полезно лишь в случае если current_user используется чаще чем один раз для запроса отдельно взятого пользователя; в любом случае, find_by будет вызван по крайней мере один раз при каждом посещении страницы на этом сайте.
______________________________________________________________
Блок 8.2.Что за *$@! этот ваш ||= ?
Конструкция ||= - очень Рубишная — то есть, она очень характерна для языка Ruby — и, следовательно, важно ее знать, если вы планируете много программировать на Ruby. Хотя на первый взгляд она может показаться таинственной, или равно легко понять по аналогии.
Начнем с общепринятой идиомы для изменения определенной в настоящее время переменной. Многие компьютерные программы включают приращение переменной, как в
x = x + 1
Большинство языков обеспечивают синтаксическое сокращение для этой операции; в Ruby (и в C, C++, Perl, Python, Java, и т.д.), это выглядит следующим образом:
x += 1
Аналогичные конструкции существуют и для других операторов:
$ rails console >> x = 1 => 1 >> x += 1 => 2 >> x *= 3 => 6 >> x -= 7 => -1
В каждом случае, паттерном является то, что x = x O y и x O= y эквивалентны для любого оператора O.
Другим распространенным Ruby паттерном является назначение переменной, если она nil но в противном случае оставляя ее в покое. Вспоминая or оператор || из Раздела 4.2.3, мы можем записать это следующим образом:
>> @user => nil >> @user = @user || "the user" => "the user" >> @user = @user || "another user" => "the user"
Поскольку nil ложно в булевом контексте, первое присвоение это nil || "the user", что оценивается как "the user"; аналогично, второе присвоение является "the user" || "another user", которое также оценивается как "the user" — так как строки true в булевом контексте, серия || выражений прекращается после оценки первого выражения. (Эта практика оценки выражений || слева направо и остановки на первом истинном значении, известна как оценка короткого замыкания (short-circuit evaluation).)
Сравнивая в консольной сессии различные операторы, мы видим, что @user = @user || value следует x = x O y паттерну с || вместо O, что позволяет предположить следующую эквивалентную конструкцию:
>> @user ||= "the user" => "the user"
Вуаля!
______________________________________________________________