Статьи

Как Построить Ненавязчивую Систему Входа в Rails

Ненавязчивая система входа в систему — это та, которая мешает пользователю. Это сделает ваше приложение более приятным и изысканным. Эта статья проведет вас через процесс настройки пользовательских логинов, а затем упростит процесс, переместив форму в модальное окно, которое обменивается данными с сервером. Кроме того, эта статья покажет вам, как создать установку, используя jQuery и Prototype, чтобы вы могли выбрать свою любимую библиотеку. В этой статье предполагается, что у вас есть опыт работы с Rails и jQuery или Prototype.


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


Последний проект

Мы начнем с создания фиктивного приложения, имеющего открытую и закрытую страницу. Корневой URL является общедоступной страницей. На общедоступной странице есть ссылка для входа. Если пользователь успешно входит в систему, он перенаправляется на личную страницу. Если нет, они перенаправляются обратно в форму входа. Личная страница показывает имя пользователя. Мы будем использовать это как отправную точку для адаптации сайта.

Первый шаг — использование команды rails для создания нового приложения, затем установка и настройка authlogic.

1
2
$ cd into-a-directory
$ rails unobtrusive-login

Добавьте authlogic.

1
2
# /config/environment.rb
config.gem ‘authlogic’

Теперь установите драгоценные камни.

1
2
3
$ sudo gem install gemcutter
$ sudo gem tumble
$ sudo rake gems:install

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

1
2
3
4
5
6
7
8
9
$ ./script/generate model User
exists app/models/
exists test/unit/
exists test/fixtures/
create app/models/user.rb
create test/unit/user_test.rb
create test/fixtures/users.yml
create db/migrate
create db/migrate/20100102082657_create_users.rb

Теперь добавьте столбцы к новой миграции.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
class CreateUsers < ActiveRecord::Migration
  def self.up
    create_table :users do |t|
      t.string :login, :null => false
      t.string :crypted_password, :null => false
      t.string :password_salt, :null => false
      t.string :persistence_token, :null => false
      t.timestamps
    end
  end
 
  def self.down
    drop_table :users
  end
end

Перенос базы данных.

1
$ rake db:migrate

Включите authlogic в пользовательскую модель.

1
2
3
4
# /app/models/user.rb
class User < ActiveRecord::Base
  acts_as_authentic
end

Теперь мы можем создать пользователя. Поскольку это демонстрационное приложение, веб-функциональность для регистрации не требуется. Итак, откройте консоль и создайте пользователя:

1
2
$ ./script/console
>> me = User.create(:login => ‘Adman65’, :password => ‘nettuts’, :password_confirmation => ‘nettuts’)

Теперь у нас есть пользователь в системе, но у нас нет возможности войти или выйти. Для этого нам нужно создать модели, контроллеры и представления. Authlogic имеет свой собственный класс для отслеживания логинов. Мы можем использовать генератор для этого:

1
2
# create the user session
$ ./script/generate UserSession

Далее нам нужно сгенерировать контроллер, который будет входить / выходить из системы пользователей. Вы можете создавать сессии, как и любой другой ресурс в Rails.

1
2
# create the session controller
$ ./script/generate controller UserSessions

Теперь установите его содержимое:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
# /app/controllers/user_sessions_controller.rb
class UserSessionsController < ApplicationController
  def new
    @user_session = UserSession.new
  end
 
  def create
    @user_session = UserSession.new(params[:user_session])
    if @user_session.save
      flash[:notice] = «Login successful!»
      redirect_back_or_default user_path
    else
      render :action => :new
    end
  end
end

Он выглядит точно так же, как контроллер, созданный с помощью скаффолдинга. Теперь создайте контроллер пользователя, который имеет публичный и приватный контент. Создайте пользовательский контроллер. Внутри контроллера мы будем использовать фильтр before для ограничения доступа к закрытым областям. Действие индекса является общедоступным, а показ — закрытым.

1
2
# create the users controller
$ ./script/generate controller users

Обновите его содержимое:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
# /app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_filter :login_required, :only => :show
 
  def index
  end
 
  def show
    @user = current_user
  end
 
  private
  def login_required
    unless current_user
      flash[:error] = ‘You must be logged in to view this page.’
      redirect_to new_user_session_path
    end
  end
end

Вы должны заметить, что current_user — неопределенный метод в этой точке. Определите эти методы в ApplicationController. Откройте application_controller.rb и обновите его содержимое:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
# application controller
class ApplicationController < ActionController::Base
  helper :all # include all helpers, all the time
  protect_from_forgery # See ActionController::RequestForgeryProtection for details
 
  # From authlogic
  filter_parameter_logging :password, :password_confirmation
  helper_method :current_user_session, :current_user
 
  private
  def current_user_session
    @current_user_session ||= UserSession.find
  end
 
  def current_user
    @current_user ||= current_user_session && current_user_session.user
  end
end

На данный момент модели и контроллеры завершены, но представления нет. Нам нужно создать представления для формы входа в систему и публичного и частного контента. Мы будем использовать гем nifty-generators, чтобы создать базовый макет.

1
2
$ sudo gem install nifty-generators
$ ./script/generate nifty_layout

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
# create the login views
# /app/views/user_sessions/_form.html.erb
<% form_for(@user_session, :url => user_session_path) do |form|
  <%= form.error_messages %>
 
  <p>
    <%= form.label :login %>
    <%= form.text_field :login %>
  </p>
 
  <p>
    <%= form.label :password %>
    <%= form.password_field :password %>
  </p>
 
  <%= form.submit ‘Login’ %>
<% end %>

Отобразите частичное в новом представлении:

1
2
3
# /app/views/user_sessions/new.html.erb
<% title ‘Login Please’ %>
<%= render :partial => ‘form’ %>

Создайте несколько основных страниц для публичного и частного контента. Действие index показывает общедоступный контент, а show показывает частный контент.

1
# create the dummy public page # /app/views/users/index.html.erb <% title ‘Unobtrusive Login’ %> <p>Public Facing Content</p> <%= link_to ‘Login’, new_user_session_path %>

И для личной страницы:

1
2
3
4
5
6
# create the dummy private page
# /app/views/users/show.html.erb
<% title ‘Welcome’ %>
<h2>Hello <%=h @user.login %></h2>
 
<%= link_to ‘Logout’, user_session_path, :method => :delete %>

Удалите файл /public/index.html и запустите сервер. Теперь вы можете войти и выйти из приложения.

1
$ ./script/server

Вот несколько скриншотов демонстрационного приложения. Первая — это общедоступная страница.

Public Page

Теперь форма входа

Login Page

И личная страница

Private Page

И, наконец, доступ запрещен при попытке зайти на http: // localhost: 3000 / user

Access Denied

Прежде чем продолжить, нам нужно понять, как сервер и браузер будут работать вместе, чтобы завершить этот процесс. Мы знаем, что нам нужно будет использовать JavaScript для модального поля и сервера для проверки логинов. Давайте выясним, как это будет работать. Пользователь нажимает на ссылку для входа, затем появляется модальное поле с формой для входа. Пользователь заполняет форму и либо перенаправляется на личную страницу, либо модальное поле обновляется новой формой входа. Следующий вопрос: как обновить модальное окно или сказать браузеру, что делать после того, как пользователь отправит форму? В Rails есть блоки response_to. С помощью response_to вы можете указать контроллеру визуализировать различное содержимое, если пользователь запросил XML, HTML, JavaScript, YAML и т. Д. Поэтому, когда пользователь отправляет форму, сервер может возвратить некоторый JavaScript для выполнения в браузере. Мы будем использовать эту визуализацию для новой формы или перенаправления. Прежде чем углубляться, давайте пройдемся по порядку.

  1. Пользователь заходит на публичную страницу
  2. Пользователь нажимает на ссылку для входа
  3. Появляется модальное окно
  4. Пользователь заполняет форму
  5. Форма отправлена ​​на сервер
  6. Сервер возвращает JavaScript для выполнения
  7. Браузер выполняет JavaScript, который либо перенаправляет, либо обновляет модальное окно.

Это высокий уровень. Вот реализация низкого уровня.

  1. Пользователь заходит на публичную страницу
  2. На общедоступной странице есть некоторый JavaScript, который запускается, когда DOM готов, который присоединяет JavaScript к ссылке для входа. Этот javscript выполняет XMLHTTPRequest (с этого момента XHR) на сервере для некоторого JavaScript. JavaScript устанавливает содержимое модального поля в форму HTML. JavaScript также делает что-то очень важное. Он связывает действие отправки формы с XHR с данными POST и действием формы. Это позволяет пользователю продолжать заполнять форму авторизации внутри модального поля.
  3. Модальное поле теперь имеет форму и требуется JavaScript
  4. Пользователь нажимает «Войти»
  5. Вызывается функция submit (), которая выполняет POST XHR для действия формы с ее данными.
  6. Сервер генерирует JavaScript для формы или перенаправления
  7. Браузер получает JavaScript и выполняет его. Браузер либо обновит модальное окно, либо перенаправит пользователя через window.location.

Давайте посмотрим на новую структуру для контроллера UserSessions.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class UserSessionsController < ApplicationController
  layout :choose_layout
 
  def new
    @user_session = UserSession.new
  end
 
  def create
    @user_session = UserSession.new(params[:user_session])
 
    if @user_session.save
      respond_to do |wants|
        wants.html { redirect_to user_path(@user_session.user) }
        wants.js { render :action => :redirect } # JavaScript to do the redirect
      end
    else
      respond_to do |wants|
        wants.html { render :new }
        wants.js # defaults to create.js.erb
      end
    end
  end
 
  private
  def choose_layout
    (request.xhr?) ?
  end
end

Как видите, структура другая. Внутри условия if save, иначе условного, response_to используется для визуализации правильного содержимого. want.xx где xx — тип содержимого По умолчанию Prototype и jQuery запрашивают текст / JavaScript. Это соответствует want.js. Мы готовы начать работу с AJAX. Мы не будем использовать какие-либо плагины, за исключением модальных блоков. Мы будем использовать Facebox для jQuery и ModalBox для Prototype.

Rails имеет встроенную поддержку Prototype. JavaScript-помощники Rail — это функции Ruby, которые генерируют JavaScript, использующий Prototype. Этот метод известен как RJS (Ruby JavaScript). Одним из примеров является remote_form_for, который работает как стандартный for_for и добавляет JS, связанный с onsubmit, который отправляет в форму свое действие, используя метод с данными. Я не буду использовать RJS в этой статье, так как хочу продемонстрировать ванильный JS. Я думаю, что используя чистый JS и исключая JS-помощников, эта статья станет более доступной для менее опытных разработчиков. При этом вы можете легко выполнить эти шаги, используя помощники RJS / Prototype, если вы выберете.

Добавить Prototype в приложение очень просто. Когда вы используете команду rails, она создает файлы Prototype и scriptaculous в / public / JavaScripts. Включать их легко. Откройте /app/views/layouts/application.erb и добавьте эту строку в тег head:

1
<%= JavaScript_include_tag :defaults %>

JavaScript_include_tag создает теги сценариев для файлов по умолчанию в / public / JavaScripts, наиболее важно prototype.js, effect.js и application.js. Effects.js является сценарием. application.js — это файл, который вы можете использовать для хранения JS для конкретного приложения. Теперь нам нужен модальный плагин. Мы собираемся использовать это . Это очень хороший модальный плагин, вдохновленный OSX. Исходный код размещен на GitHub, поэтому вам придется клонировать и перемещать файлы в каталоге вашего проекта. Например:

1
2
3
4
5
6
7
$ cd code
$ git clone git://github.com/okonet/modalbox.git
$ cd modalbox
# move the files in the correct directories.
# move modalbox.css into /public/stylesheets
# move modalbox.js into /public/JavaScripts
# move spinner.gif into /public/images

Теперь включите таблицы стилей и JavaScript в ваше приложение.

1
2
3
4
<%= stylesheet_link_tag ‘application’ %>
<%= stylesheet_link_tag ‘modalbox’ %>
<%= JavaScript_include_tag :defaults %>
<%= JavaScript_include_tag ‘modalbox’%>

Теперь давайте получим ссылку для входа в систему, чтобы открыть модалбокс. Для этого нам нужно добавить JavaScript, который запускается, когда DOM готов, который присоединяет modalbox к нашей ссылке. Когда пользователь нажимает ссылку для входа в систему, браузер выполняет GET для / user_sessions / new, которая содержит форму для входа. Ссылка для входа в систему использует селектор # login-link. Обновите ссылку для входа в систему, чтобы использовать новый идентификатор в /app/views/users/index.html.erb. Измените функцию link_to следующим образом:

1
<%= link_to ‘Login’, new_user_session_path, :id => ‘login-link’ %>

Это дает нам # логин-ссылку. Теперь для JavaScript, чтобы прикрепить модалбокс. Добавьте этот JS в /public/JavaScripts/application.js

1
2
3
4
5
6
7
8
9
document.observe(‘dom:loaded’, function() {
    $(‘login-link’).observe(‘click’, function(event) {
        event.stop();
        Modalbox.show(this.href,
            {title: ‘Login’,
            width: 500}
        );
    });
})

Существует несколько простых JS для того, когда пользователь нажимает на ссылку, открывается модальное окно с ссылкой на ссылку. Обратитесь к документации modalbox, если вы хотите больше настроек. Вот скриншот:

Initial Modalbox

Обратите внимание, что внутри модальной рамки выглядит очень похоже на нашу стандартную страницу. Rails использует макет нашего приложения для всех ответов HTML. Поскольку наши XHR хотят фрагменты HTML, имеет смысл отображать их без макетов. Вернитесь к примеру контроллера. Я ввел метод определения макета. Добавьте это в UserSessionsController, чтобы отключить макет для XHR.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class UserSessionsController < ApplicationController
  layout :choose_layout
 
  def new
    @user_session = UserSession.new
  end
 
  def create
    @user_session = UserSession.new(params[:user_session])
    if @user_session.save
      flash[:notice] = «Login successful!»
      redirect_to user_path
    else
      render :action => :new
    end
  end
 
  def destroy
    current_user_session.destroy
    flash[:notice] = «Logout successful!»
    redirect_to root_path
  end
 
  private
  def choose_layout
    (request.xhr?) ?
  end
end

Обновите страницу и нажмите на ссылку, вы должны получить что-то вроде этого:

Without Layout

Заполните форму и посмотрите, что получится. Если вы заполняете поле с плохой информацией, вы будете перенаправлены за пределы модального поля. Если вы залогинены правильно, вы будете перенаправлены в обычном режиме. В соответствии с требованиями пользователь должен иметь возможность снова и снова заполнять форму внутри модального поля, пока он не войдет в систему правильно. Как мы можем сделать это? Как описано выше, нам нужно использовать AJAX для отправки данных на сервер, а затем использовать JavaScript, чтобы обновить модальное поле с помощью формы или выполнить перенаправление. Мы знаем, что modalbox выполняет GET для HTML. После отображения исходного modalbox нам нужно написать JS, который заставляет форму представлять себя в стиле AJAX. Это позволяет форме отправлять себя внутри модального поля. Простое добавление этого кода после вызова модального окна не сработает, потому что XHR может не завершиться. Нам нужно использовать обратный вызов Modalbox afterLoad. Вот новый код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
document.observe(‘dom:loaded’, function() {
    $(‘login-link’).observe(‘click’, function(event) {
        event.stop();
        Modalbox.show(this.href,
            {title: ‘Login’,
            width: 500,
            afterLoad: function() {
                $(‘new_user_session’).observe(‘submit’, function(event) {
                    event.stop();
                    this.request();
                })
            }}
        );
    });
})

Запрос формы # — это удобный метод для сериализации и отправки формы с помощью Ajax.Request на URL-адрес атрибута действия формы — это именно то, что нам нужно. Теперь вы можете заполнить форму внутри модала без ее закрытия. Клиентская часть теперь завершена. Как насчет серверной части? Клиент отправляет POST, желая вернуть JS. Сервер должен принять решение либо вернуть JavaScript для обновления формы, либо выполнить перенаправление. В UserSessionsController мы будем использовать response_to для обработки запроса JS и условное выражение для возврата правильного JS. Давайте начнем с обработки неудачного входа в систему. Сервер должен вернуть JS, который обновляет форму и сообщает новой форме о передаче через ajax. Мы разместим этот шаблон в /app/views/users_sessions/create.js.erb. Вот структура для нового действия создания:

01
02
03
04
05
06
07
08
09
10
11
12
def create
  @user_session = UserSession.new(params[:user_session])
  if @user_session.save
    flash[:notice] = «Login successful!»
    redirect_to user_path
  else
    respond_to do |wants|
      wants.html { render :new }
      wants.js # create.js.erb
    end
  end
end

Теперь давайте заполним create.js.erb:

1
2
3
4
5
6
$(‘MB_content’).update(«<%= escape_JavaScript(render :partial => ‘form’) %>»);
Modalbox.resizeToContent();
$(‘new_user_session’).observe(‘submit’, function(event) {
    event.stop();
    this.request();
});

Сначала мы обновляем содержимое, чтобы включить новую форму. Затем мы изменяем размер модальной рамки. Затем мы как-то изменили форму. Вуаля, вы можете заполнить форму столько раз, сколько захотите.

Bad Info
Updated Form

Далее нам нужно обработать случай перенаправления. Создайте новый файл в /app/views/users_sessions/redirect.js.erb:

1
window.location=”<%= user_path %>”;

Теперь обновите действие create для обработки процесса перенаправления:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
def create
  @user_session = UserSession.new(params[:user_session])
  if @user_session.save
    respond_to do |wants|
      wants.html do
        flash[:notice] = «Login successful!»
        redirect_to user_path
      end
 
      wants.js { render :redirect }
    end
  else
    respond_to do |wants|
      wants.html { render :new }
      wants.js # create.js.erb
    end
  end
end

Вот и все! Теперь попробуйте войти с правильными учетными данными, и вы будете перенаправлены на личную страницу. Для дальнейшего изучения, попробуйте добавить счетчик и уведомление о том, что форма отправляется или они перенаправлены. Приложение все еще работает, если у пользователя отключен JavaScript.

JQuery

Поскольку я уже рассмотрел процесс создания прототипа, я не буду вдаваться в те же детали, что и раньше. Вместо этого я быстро перейду к описанию альтернативного JavaScript для добавления в приложение. Версия jQuery будет иметь ту же структуру, что и версия Prototype. Все, что нам нужно изменить — это то, что находится в application.js, create.js.erb и JavaScript / css.

Первое, что нам нужно сделать, это загрузить jQuery и Facebox . Переместите jQuery в / public / JavaScripts как jquery.js. Для facebox переместите изображения в / public / images /, таблицы стилей в / public / stylesheets и, наконец, JS в / public / JavaScripts. Теперь обновите /app/views/layouts/application.html.erb, чтобы отразить изменения:

1
2
3
4
5
6
7
8
<head>
  <title><%= h(yield(:title) || «Untitled») %></title>
  <%= stylesheet_link_tag ‘facebox’ %>
  <%= stylesheet_link_tag ‘application’ %>
  <%= JavaScript_include_tag ‘jquery’ %>
  <%= JavaScript_include_tag ‘facebox’ %>
  <%= JavaScript_include_tag ‘application’ %>
</head>

Facebox поставляется с таблицей стилей по умолчанию, которая предполагает, что у вас есть ваши изображения в / facebox. Вам нужно обновить эти селекторы в facebox.css следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
#facebox .b {
  background:url(/images/b.png);
}
 
#facebox .tl {
  background:url(/images/tl.png);
}
 
#facebox .tr {
  background:url(/images/tr.png);
}
 
#facebox .bl {
  background:url(/images/bl.png);
}
 
#facebox .br {
  background:url(/images/br.png);
}

Теперь мы прикрепляем фейсбокс к ссылке для входа. Откройте /public/JavaScripts/application.js и используйте это:

1
2
3
4
5
6
$(document).ready(function() {
    $(‘#login-link’).facebox({
        loadingImage : ‘/images/loading.gif’,
    closeImage : ‘/images/closelabel.gif’,
    });
});

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

Facebox

Следующее, что нам нужно сделать, это настроить форму для отправки через AJAX. Как и раньше, мы должны будем использовать обратные вызовы для выполнения кода после того, как модальное окно будет готово. Мы будем использовать метод post jQuery для запроса XHR. Facebox имеет крючок после открытия, который мы можем использовать. application.js:

01
02
03
04
05
06
07
08
09
10
11
12
13
$(document).ready(function() {
    $(‘#login-link’).facebox({
        loadingImage : ‘/images/loading.gif’,
        closeImage : ‘/images/closelabel.gif’,
    });
 
    $(document).bind(‘reveal.facebox’, function() {
        $(‘#new_user_session’).submit(function() {
            $.post(this.action, $(this).serialize(), null, «script»);
            return false;
        });
    });
});

Обновление create.js.erb должно быть достаточно простым. Мы должны обновить содержимое фейсбокса и повторно изменить форму. Вот код:

1
2
3
4
5
$(‘#facebox .content’).html(«<%= escape_JavaScript(render :partial => ‘form’) %>»);
$(‘#new_user_session’).submit(function() {
    $.post(this.action, $(this).serialize(), null, «script»);
    return false;
});

Вот и все! Вот конечный продукт:

Logging In
Bad Login
Redirected

Вы можете получить код здесь . Есть ветви для каждой библиотеки, так что вы можете проверить версии Prototype или jQuery. Есть вопросы, комментарии, проблемы? Еще раз спасибо за чтение!

  • Подпишитесь на нас в Твиттере или подпишитесь на ленту Nettuts + RSS для получения лучших учебных материалов по веб-разработке.