Добро пожаловать в следующий выпуск нашей серии клонов в Twitter! В этом уроке мы создадим Ribbit с нуля не с использованием PHP , а с использованием Ruby on Rails. Давайте начнем!
Одно быстрое объявление о сервисе, прежде чем мы начнем: мы не будем разрабатывать пользовательский интерфейс для приложения в этом руководстве; это было сделано в Построить клон Twitter с нуля: дизайн . Я дам вам знать, если нам нужно что-то изменить из этой статьи.
Шаг 0: настройка среды
Перво-наперво: я использую Ruby 1.9.3 (p194) и Rails версии 3.2.8 для этого урока. Убедитесь, что вы используете те же версии. Неважно, как вы устанавливаете Ruby; Вы можете использовать RVM ( учебник ), rbenv или просто обычную установку Ruby . Независимо от подхода, каждый установщик предоставляет вам двоичный файл gem
, который затем можно использовать для установки Rails. Просто используйте эту команду:
1
|
gem install rails
|
Это установит последнюю версию Rails, и мы можем начать сборку нашего приложения уже после его установки. Я надеюсь, вы понимаете, что Rails — это инструмент командной строки; в этом уроке вам должно быть удобно в терминале.
Шаг 1: Создание приложения Rails
Мы начинаем с создания проекта. В командной строке перейдите в любой каталог, в котором вы хотите разместить новый проект. Затем запустите:
1
|
rails new ribbitApp
|
Эта единственная команда генерирует несколько файлов внутри папки, которая называется ribbitApp
. Это то, что Rails дает нам для начала; он даже установил драгоценные камни, необходимые для проекта.
Давайте cd
в этот каталог и инициализируем git-репо.
1
2
|
cd ribbitApp
git init
|
Один из сгенерированных Rail-файлов — это .gitignore
. Если вы работаете на Mac, вы, вероятно, захотите добавить следующую строку в этот файл — просто, чтобы все было чисто:
1
|
**.DS_Store
|
Теперь мы готовы сделать наш первый коммит!
1
2
|
git add .
git commit -m ‘initial rails app’
|
Шаг 2. Подготовка интерфейса
Руководство по интерфейсу познакомило вас с изображениями и таблицами стилей Ribbit. Загрузите эти ресурсы и скопируйте папку less.js
и файлы less.js
и style.less
в public
каталог вашего приложения.
Давайте добавим правило в style.less
: стиль для наших флеш-сообщений. Вставьте это внизу файла:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
.flash {
padding: 10px;
margin: 20px 0;
&.error {
background: #ffefef;
color: #4c1717;
border: 1px solid #4c1717;
}
&.warning {
background: #ffe4c1;
color: #79420d;
border: 1px solid #79420d;
}
&.notice {
background: #efffd7;
color: #8ba015;
border: 1px solid #8ba015;
}
}
|
Это все! Давайте сделаем еще один коммит:
1
2
|
git add .
git commit -m ‘Add flash styling’
|
Теперь давайте создадим макет. Это HTML, который оборачивает основное содержимое каждой страницы — по сути, верхний и нижний колонтитулы. Приложение Rails хранит это в app/views/layouts/application.html.erb
. Избавьтесь от всего в этом файле и добавьте следующий код:
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
|
<!DOCTYPE html>
<html>
<head>
<link rel=»stylesheet/less» href=»/style.less»>
<script src=»/less.js»></script>
</head>
<body>
<header>
<div class=»wrapper»>
<img src=»/gfx/logo.png»>
<span>Twitter Clone
</div>
</header>
<div id=»content»>
<div class=»wrapper»>
<% flash.each do |name, msg|
<%= content_tag :div, msg, class: «flash #{name}» %>
<% end %>
<%= yield %>
</div>
</div>
<footer>
<div class=»wrapper»>
Ribbit — A Twitter Clone Tutorial<img src=»/gfx/logo-nettuts.png»>
</div>
</footer>
</body>
</html>
|
Есть три вещи, которые вы должны заметить по этому поводу. Во-первых, каждый URL-адрес общедоступного ресурса (изображения, таблица стилей, JavaScript) начинается с косой черты ( /
). Это сделано для того, чтобы мы могли загружать активы, когда мы находимся на «более глубоких» маршрутах. Во-вторых, обратите внимание на разметку для отображения флэш-сообщений. Это отображает флэш-сообщения, когда они существуют. И в-третьих, обратите внимание на <%= yield %>
; это где мы вставляем другие «sub» -шаблоны.
Хорошо, давайте передадим это:
1
2
|
git add .
git commit -m ‘Edit application.html.erb’
|
Шаг 3: Создание пользователей
Конечно, у нас не может быть клона Twitter без пользователей, поэтому давайте добавим эту функциональность. Это может быть сложной функцией, но Rails делает ее немного проще для нас.
Естественно, мы не хотим хранить пароли наших пользователей в виде простого текста; это огромный риск для безопасности. Вместо этого мы будем полагаться на Rails для автоматизации процесса шифрования наших паролей. Мы начнем с открытия нашего Gemfile
и поиска этих строк:
1
2
|
# To use ActiveModel has_secure_password
# gem ‘bcrypt-ruby’, ‘~> 3.0.0’
|
Использование has_secure_password
— это именно то, что мы хотим сделать, поэтому снимите комментарий со второй строки. Сохраните файл и вернитесь в командную строку. Теперь нам нужно запустить bundle install
чтобы установить гем bcrypt.
Мы можем создать наш пользовательский ресурс после установки гема.
Ресурс Rails — это, в основном, модель, связанный с ней контроллер и несколько других файлов.
Мы создаем ресурс с помощью команды rails generate
(или rails g
):
1
|
rails generate resource user username name email password_digest avatar_url
|
Этой команде мы передаем несколько параметров, в результате чего получается resource
, называемый user
; его модель имеет следующие пять полей:
-
username
: уникальное имя пользователя, эквивалент дескриптора Twitter. -
name
: фактическое имя пользователя. -
email
: их адрес электронной почты. -
password_digest
: зашифрованная версия их пароля. -
avatar_url
: путь к ихavatar_url
изображению.
Теперь нам нужно перенести нашу базу данных, чтобы она была настроена для хранения пользователей. Мы выполняем это с помощью следующей команды:
1
|
rake db:migrate
|
Это создает нашу таблицу users
, но мы напрямую не взаимодействуем с базой данных с помощью Rails. Вместо этого мы используем ActiveRecord ORM . Нам нужно добавить код в app/models/user.rb
Откройте этот файл.
Когда Rails сгенерировал этот файл, он добавил вызов метода attr_accessible
. Этот метод определяет, какие свойства доступны для чтения и записи в экземплярах этого класса. По умолчанию все вышеупомянутые свойства доступны, но мы хотим изменить это:
1
|
attr_accessible :avatar_url, :email, :name, :password, :password_confirmation, :username
|
Большинство из них должны иметь смысл, но что с password
и password_confirmation
подтверждение? В конце концов, у нас есть только поле password_digest
в нашей базе данных. Это часть магии Rails, о которой я упоминал ранее. Мы можем установить поля password
и password_confirmation
в экземпляре User
. Если они совпадают, пароль будет зашифрован и сохранен в базе данных. Но чтобы включить эту функцию, нам нужно добавить еще одну строку в наш класс модели User
:
1
|
has_secure_password
|
Далее мы хотим включить валидацию в нашу модель. Вызов has_secure_password
позаботится о полях пароля, поэтому мы будем иметь дело с полями email
, username
и name
.
Поле name
простое: мы просто хотим убедиться, что оно присутствует.
1
|
validates :name, presence: true
|
Для поля username
мы хотим убедиться, что оно существует и является уникальным; никакие два пользователя не могут иметь одно и то же имя:
1
|
validates :username, uniqueness: true, presence: true
|
Наконец, поле email
должно не только существовать и быть уникальным, но и соответствовать регулярному выражению:
1
|
validates :email, uniqueness: true, presence: true, format: { with: /^[\w.+-]+@([\w]+.)+\w+$/ }
|
Это очень упрощенное регулярное выражение электронной почты, но оно должно быть сделано для наших целей.
А что насчет этого поля avatar_url
? Мы хотим использовать адрес электронной почты пользователя и вытащить связанный с ним Gravatar , поэтому мы должны сгенерировать этот URL. Мы могли бы сделать это на лету, но будет более эффективно хранить это в базе данных. Во-первых, нам нужно убедиться, что у нас есть чистый адрес электронной почты. Давайте добавим метод в наш класс User
:
1
2
3
4
5
|
private
def prep_email
self.email = self.email.strip.downcase if self.email
end
|
Это private
ключевое слово означает, что все методы, определенные после ключевого слова, определены как частные методы; к ним нельзя получить доступ извне класса (в случаях).
Этот метод
prep_email
обрезает пробелы в начале и конце строки, а затем преобразует все символы в нижний регистр.
Это необходимо, потому что мы собираемся сгенерировать хэш для этого значения.
Мы хотим, чтобы этот метод выполнялся непосредственно перед процессом проверки; добавьте следующую строку кода в верхней части класса.
1
|
before_validation :prep_email
|
Далее, давайте сгенерируем URL для аватара, написав другой метод (поместите его под приведенным выше):
1
2
3
|
def create_avatar_url
self.avatar_url = «http://www.gravatar.com/avatar/#{Digest::MD5.hexdigest(self.email)}?s=50»
end
|
Нам нужно вызвать этот метод, прежде чем мы сохраним пользователя в базе данных. Добавьте этот вызов в начало файла:
1
|
before_save :create_avatar_url
|
Вот и все! Теперь у нас есть механика наших пользовательских функций. Прежде чем писать пользовательский интерфейс для создания пользователей, давайте передадим нашу работу:
1
2
|
git add .
git commit -m ‘Create user resource’
|
Шаг 5: Написание пользовательского интерфейса
Создание пользовательского интерфейса в Rails означает, что мы должны знать о маршрутах, которые отображают наши представления. Если вы откроете config/routes.rb
, вы найдете строку, подобную этой:
1
|
resources :users
|
Он был добавлен в файл routes.rb
при создании пользовательского ресурса, и он устанавливает маршруты REST по умолчанию. Прямо сейчас интересующий нас маршрут — это маршрут, который отображает форму для создания новых пользователей: /users/new
. Когда кто-то пойдет по этому маршруту, будет запущен new
метод на контроллере пользователей, поэтому мы и начнем.
Контроллер пользователей находится в app/controllers/users_controller.rb
. По умолчанию у него нет методов, поэтому давайте добавим new
метод в класс UsersController
.
1
2
3
|
def new
@user = User.new
end
|
Как вы знаете, переменные экземпляра Ruby начинаются с @
— делая @user
доступным из нашего представления. Давайте new.html.erb
к представлению, создав файл с именем new.html.erb
в new.html.erb
app/views/users
. Вот что происходит в этом представлении:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<img src=»/gfx/frog.jpg»>
<div class=»panel right»>
<h1>New to Ribbit?</h1>
<%= form_for @user do |f|
<% if @user.errors.any?
<ul>
<% @user.errors.full_messages.each do |message|
<li><%= message %></li>
<% end %>
</ul>
<% end %>
<%= f.text_field :email, placeholder: «email» %>
<%= f.text_field :username, placeholder: «username» %>
<%= f.text_field :name, placeholder: «name» %>
<%= f.password_field :password, placeholder: «password» %>
<%= f.password_field :password_confirmation, placeholder: «password» %>
<%= f.submit «Create Account» %>
<% end %>
</div>
|
На самом деле это представление, которое служит представлением домашней страницы, когда пользователь не вошел в систему. Мы используем вспомогательный метод form_for
для создания формы и передачи ей переменной @user
в качестве параметра. Затем внутри формы (которая находится внутри блока Ruby) мы сначала выводим ошибки. Конечно, не будет никаких ошибок на странице в первый раз.
Однако любой ввод, который не соответствует нашим правилам проверки, приводит к сообщению об ошибке, отображаемому в элементе списка.
Затем у нас есть поля для наших пользовательских свойств. Поскольку в этом дизайне нет надписей для текстовых полей, я поместил то, что было бы надписью, в качестве заполнителя полей (используя атрибут placeholder
). Они не будут отображаться в старых браузерах, но это не относится к нашей главной цели.
Теперь, что происходит, когда пользователь нажимает кнопку «Создать учетную запись»? Эта форма отправит POST
к маршруту /users
, что приведет к выполнению метода create
в контроллере users. Вернемся к этому контроллеру, затем:
1
2
3
4
5
6
7
8
9
|
def create
@user = User.new(params[:user])
if @user.save
redirect_to @user, notice: «Thank you for signing up for Ribbit!»
else
render ‘new’
end
end
|
Мы начинаем с создания нового пользователя, передавая new
метод значения из нашей формы. Затем мы вызываем метод save
. Этот метод сначала проверяет ввод; если данные имеют правильный формат, метод вставляет запись в базу данных и возвращает true
. В противном случае возвращается false
.
Если
@user.save
возвращаетtrue
, мы перенаправляем зрителя на …@user
объект@user
?
Это фактически перенаправляет на путь для этого пользователя, который будет /users/
@user.save
возвращает false, мы повторно отображаем путь /users/new
и отображаем любые ошибки проверки. Мы также предварительно заполняем поля формы ранее предоставленной пользователем информацией. Умно, а?
Что ж, если мы направим зрителя к его новому профилю пользователя, нам нужно создать эту страницу дальше. Это вызывает метод show
в контроллере пользователей, поэтому мы сначала добавим его:
1
2
3
|
def show
@user = User.find(params[:id])
end
|
Этот метод просматривает идентификационный номер в маршруте (например, /users/4
) и находит связанного пользователя в базе данных. Как и раньше, теперь мы можем использовать эту переменную @user
из представления.
Создайте файл app/views/users/show.html.erb
и добавьте следующий код:
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
|
<div id=»createRibbit» class=»panel right»>
<h1>Create a Ribbit</h1>
<p>
<form>
<textarea name=»text» class=»ribbitText»></textarea>
<input type=»submit» value=»Ribbit!»>
</form>
</p>
</div>
<div id=»ribbits» class=»panel left»>
<h1>Your Ribbit Profile</h1>
<div class=»ribbitWrapper»>
<img class=»avatar» src=»<%= @user.avatar_url %>»>
<span class=»name»><%= @user.name %>
<p>
XX Ribbits
<span class=»spacing»>XX Followers
<span class=»spacing»>XX Following
</p>
</div>
</div>
<div class=»panel left»>
<h1>Your Ribbits</h1>
<div class=»ribbitWrapper»>
Ribbits coming .
</div>
</div>
|
Вы заметите, что у нас есть несколько заполнителей в этом представлении. Во-первых, есть форма для создания нового ребра. Тогда есть последователь, следующие цифры и список ваших ребер. Мы скоро к этому придем.
На этом шаге нужно сделать еще одну вещь: мы хотим, чтобы корневой маршрут ( /
) пока отображал новую форму пользователя. Снова откройте файл config/routes.rb
и добавьте следующую строку:
1
|
root to: ‘users#new’
|
Это просто заставляет корневой маршрут вызывать new
метод в контроллере пользователей. Теперь нам просто нужно удалить файл public/index.html
который переопределяет эту конфигурацию. После удаления запустите в командной строке следующее:
1
|
rails server
|
Вы также можете запустить rails s
для достижения тех же результатов. Как и следовало ожидать, запускается сервер rails. Теперь вы можете указать вашему браузеру localhost:3000/
, и вы должны увидеть следующее:
Теперь заполните форму и нажмите «Создать аккаунт». Вы должны быть отправлены в профиль пользователя, как это:
Большой! Теперь у нас работают учетные записи пользователей. Давайте совершим это:
1
2
|
git add .
git commit -m ‘User form and profile pages’
|
Шаг 6: Добавление поддержки сеанса
Несмотря на то, что мы реализовали пользовательскую функцию, пользователь пока не может войти в систему. Итак, давайте добавим поддержку сеанса дальше.
Возможно, вы узнали, как мы создали учетные записи пользователей.
Я взял этот общий метод из эпизода 250 Railscast . Этот эпизод также демонстрирует, как создать поддержку сеанса, и я буду использовать этот подход для Ribbit.
Мы начинаем с создания контроллера для управления нашими сессиями. На самом деле мы не будем хранить сессии в базе данных, но нам нужно иметь возможность устанавливать и сбрасывать переменные сессии. Контроллер — правильный способ сделать это.
1
|
rails generate controller sessions new create destroy
|
Здесь мы создаем новый контроллер, называемый sessions
. Мы также говорим, чтобы генерировать new
, create
и destroy
методы. Конечно, он не заполнит эти методы, но создаст для нас их «оболочку».
Теперь давайте откроем файл app/controllers/sessions_controller.rb
. new
метод хорош как есть, но метод create
требует некоторого внимания. Этот метод выполняется после того, как пользователь вводит свои учетные данные и нажимает кнопку «Войти». Добавьте следующий код:
01
02
03
04
05
06
07
08
09
10
|
def create
user = User.find_by_username(params[:username])
if user && user.authenticate(params[:password])
session[:userid] = user.id
redirect_to rooturl, notice: «Logged in!»
else
flash[:error] = «Wrong Username or Password.»
redirect_to root_url
end
end
|
Мы используем метод find_by_username
в классе User
чтобы получить пользователя с указанным именем пользователя. Затем мы вызываем метод authenticate
, передавая ему пароль. Этот метод был добавлен как часть функции use_secure_password
. Если учетные данные пользователя проходят проверку, мы можем установить user_id
сеанса user_id
в идентификатор пользователя. Наконец, мы перенаправляем на корневой маршрут с сообщением «Logged in!».
Если учетные данные пользователя не проходят проверку подлинности, мы просто перенаправляем на корневой маршрут и устанавливаем во флэш-сообщении об ошибке « Имя пользователя или пароль неверны ».
Выход из системы запускает метод destroy
. Это действительно простой метод:
1
2
3
4
|
def destroy
session[:userid] = nil
redirect_to root_url, notice: «Logged out.»
end
|
Этот код довольно понятен; просто избавьтесь от этой переменной сеанса и перенаправьте в корень.
Rails помог нам еще раз и добавил три маршрута для этих методов, которые можно найти в config/routes.rb
:
1
2
3
|
get «sessions/new»
get «sessions/create»
get «sessions/destroy»
|
Мы хотим изменить sessions/create
маршрут к POST, например так:
1
|
post «sessions/create»
|
Мы почти готовы добавить форму логина к нашим представлениям. Но сначала давайте создадим вспомогательный метод, который позволит нам быстро получить зарегистрированного пользователя. Мы поместим это в контроллер приложения, чтобы мы могли получить к нему доступ из любого файла представления. Путь к контроллеру app/controllers/application_controller.rb
— app/controllers/application_controller.rb
.
1
2
3
4
5
6
7
|
private
def current_user
@current_user ||= User.find(session[:user_id]) if session[:user_id]
end
helper_method :current_user
|
Мы передаем User.find
session[:user_id]
которую мы установили в контроллере сессий. Вызов helper_method
делает это вспомогательным методом, который мы можем вызвать из представления.
Теперь мы можем открыть наш файл app/views/layouts/application.html.erb
и добавить форму для входа. Смотрите <span>Twitter Clone</span>
в элементе <header>
? Следующий код идет сразу после этого:
1
2
3
4
5
6
7
8
9
|
<% if current_user %>
<%= link_to «Log Out», sessions_destroy_path %>
<% else %>
<%= form_tag sessions_create_path do %>
<%= text_field_tag :username, nil, placeholder: «username» %>
<%= password_field_tag :password, nil, placeholder: «password» %>
<%= submit_tag «Log In» %>
<% end %>
<% end %>
|
Если пользователь вошел в систему (или, если значение current_user
вернуло current_user
), мы отобразим ссылку выхода из системы, но позже мы добавим дополнительные ссылки. Возможно, вы раньше не видели метод link_to
; он берет предоставленный текст и URL и генерирует гиперссылку.
Если ни один пользователь не вошел в систему, мы используем метод
form_tag
для создания формы, котораяseesions_create_path
сообщения вseesions_create_path
.
Обратите внимание, что мы не можем использовать метод form_for
потому что у нас нет объекта экземпляра для этой формы (как с нашим пользовательским объектом). text_field_tag
и password_field_tag
принимают одинаковые параметры: имя поля в качестве символа, значение для поля (в данном случае nil
), а затем объект параметров. Мы просто устанавливаем значение заполнителя здесь.
В нашей поддержке сеансов есть небольшая ошибка: пользователь не входит автоматически после создания новой учетной записи пользователя. Мы можем исправить это, добавив одну строку в метод create
в классе UserController
. Сразу после строки if @user.save
добавьте:
1
|
session[:user_id] = @user.id
|
Верьте или нет, приведенная выше строка кода завершает функцию сеанса. Теперь у вас должна быть возможность перезапустить сервер Rails и войти в систему. Попробуйте выйти и снова войти в систему. Единственная разница между этими двумя функциями заключается в отсутствии формы входа в систему, когда вы вошли в систему. Но мы добавим больше позже!
Давайте совершим это:
1
2
|
git add .
git commit -m ‘users are now logged in upon creation’
|
Шаг 7: Создание кроликов
Теперь мы, наконец, готовы перейти к сути нашего приложения: созданию Ribbits (нашей версии твитов). Начнем с создания ресурса ribbit:
1
2
|
rails g resource ribbit content:text user_id:integer
rake db:migrate
|
Этому ресурсу нужны только два поля: фактическое содержимое ленты и идентификатор пользователя, который его создал. Мы перенесем базу данных, чтобы создать новую таблицу. Затем мы внесем несколько изменений в новую модель Ribbit. Откройте app/models/ribbit.rb
и добавьте следующее:
1
2
3
4
5
6
7
|
class Ribbit < ActiveRecord::Base
default_scope order: ‘createdat DESC’
attr_accessible :content, :userid
belongs_to :user
validates :content, length: { maximum: 140 }
end
|
Вызов default_scope
важен; он упорядочивает список ребер от самых последних до самых последних. Метод belongs_to
создает связь между этим классом Ribbit и классом User, в результате чего наши пользовательские объекты имеют массив tweets
в качестве свойства.
Наконец, у нас есть вызов validates
, который гарантирует, что наши ребра не превышают 140 символов.
О да: обратная сторона утверждения об принадлежащем. В классе User ( app/models/user.rb
) мы хотим добавить эту строку:
1
|
has_many :ribbits
|
Это завершает ассоциацию; теперь у каждого пользователя может быть много кроликов.
Мы хотим, чтобы пользователи имели возможность создавать кроликов со страницы своего профиля. Как вы помните, у нас есть форма в этом шаблоне. Итак, давайте заменим эту форму в app/view/users/show.html.erb
, а также app/view/users/show.html.erb
несколько других изменений. Вот что вы должны получить в итоге:
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
29
30
31
32
33
34
35
|
<% if current_user %>
<div id=»createRibbit» class=»panel right»>
<h1>Create a Ribbit</h1>
<p>
<%= form_for @ribbit do |f|
<%= f.textarea :content, class: ‘ribbitText’ %>
<%= f.submit «Ribbit!»
<% end %>
</p>
</div>
<% end %>
<div id=»ribbits» class=»panel left»>
<h1>Your Ribbit Profile</h1>
<div class=»ribbitWrapper»>
<img class=»avatar» src=»<%= @user.avatar_url %>»>
<span class=»name»><%= @user.name %>
<p>
<%= @user.ribbits.size %> Ribbits
<span class=»spacing»>XX Followers
<span class=»spacing»>XX Following
</p>
</div>
</div>
<div class=»panel left»>
<h1>Your Ribbits</h1>
<% @user.ribbits.each do |ribbit|
<div class=»ribbitWrapper»>
<img class=»avatar» src=»<%= @user.avatar_url %>»>
<span class=»name»><%= @user.name %>
@<%= @user.username %>
<span class=»time»><%= time_ago_in_words(ribbit.created_at) %>
<p> <%= ribbit.content %> </p>
</div>
<% end %>
</div>
|
Есть три области, которые мы изменяем с помощью этого кода. Сначала мы удаляем HTML-форму сверху и заменяем ее вызовом вспомогательной функции form_for
. Конечно, имеет смысл, что нам нужна только текстовая область для контента (мы уже знаем идентификатор текущего пользователя). Обратите внимание, что form_for
принимает @ribbit
в качестве параметра, и нам нужно добавить этот объект в метод users_controller#show
( app/controllers/users_controller.rb
):
1
2
3
4
|
def show
@user = User.find(params[:id])
@ribbit = Ribbit.new
end
|
Обратите внимание, что мы оборачиваем весь раздел формы ( <div id="createRibbit">
) оператором if
. Если нет текущего пользователя (то есть никто не вошел в систему), мы не будем показывать форму ribbit.
Далее мы хотим отобразить количество ребер пользователя. Это число появляется чуть выше их числа подписчиков. Помните, что наш пользовательский экземпляр имеет свойство ribbits
. Итак, мы можем заменить наш текст наполнителя на это:
1
|
<%= @user.ribbits.size %> Ribbits
|
Нам нужно показать ребра пользователя. Мы можем ribbits
этот же массив ribbits
и отобразить каждый ribbit по очереди. Это последняя часть кода выше.
Наконец, (по крайней мере, что касается создания ribbit), нам нужно изменить метод create в контроллере ribbits ( app/controllers/ribbits_controller.rb
. Метод выполняется, когда пользователь нажимает кнопку «Ribbit!»).
01
02
03
04
05
06
07
08
09
10
11
|
def create
@ribbit = Ribbit.new(params[:ribbit])
@ribbit.userid = current_user.id</p>
if @ribbit.save
redirect_to current_user
else
flash[:error] = «Problem!»
redirect_to current_user
end
end
|
Я знаю «Проблема!» сообщение об ошибке не очень наглядно, но оно подойдет для нашего простого приложения. Действительно, единственная ошибка, которая может произойти, это ребро длиннее 140 символов.
Итак, попробуйте: запустите сервер ( rails server
), войдите в систему, перейдите на страницу своего профиля ( http://localhost:3000/users/
Хорошо, давайте передадим эти изменения:
1
2
|
git add .
git commit -m ‘ribbit functionality created’
|
Шаг 8: Создание публичной страницы твитов
Далее мы хотим создать общедоступную страницу, которая включает в себя все кролики, сделанные всеми пользователями. Логически, это должно быть представление индекса ribbits, найденное в /ribbits
. Метод контроллера для этого — ribbits_controler#index
. На самом деле это очень простой метод:
1
2
3
4
|
def index
@ribbits = Ribbit.all include: :user
@ribbit = Ribbit.new
end
|
Первая строка выбирает всех ribbits и связанных с ними пользователей, а вторая строка создает новый экземпляр ribbit (эта страница будет иметь форму ribbit).
Другим шагом, конечно же, является шаблон ( app/view/ribbits/index.html.erb
). Это похоже на шаблон профиля пользователя:
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
|
<% if current_user %>
<div id=»createRibbit» class=»panel right»>
<h1>Create a Ribbit</h1>
<p>
<%= form_for @ribbit do |f|
<%= f.textarea :content, class: ‘ribbitText’ %>
<%= f.submit «Ribbit!»
<% end %>
</p>
</div>
<% end %>
<div class=»panel left»>
<h1>Public Ribbits</h1>
<% @ribbits.each do |ribbit|
<div class=»ribbitWrapper»>
<a href=»<%= user_path ribbit.user %>»>
<img class=»avatar» src=»<%= ribbit.user.avatar_url %>»>
<span class=»name»><%= ribbit.user.name %>
</a>
@<%= ribbit.user.username %>
<span class=»time»><%= time_ago_in_words(ribbit.created_at) %>
<p> <%= ribbit.content %> </p>
</div>
<% end %>
</div>
|
В этом шаблоне изображение аватара и имя пользователя окружены ссылкой, которая указывает на страницу профиля пользователя. Давайте также добавим ссылку на страницу общедоступных твитов. Перед тем как выйти из ссылки в app/view/layouts/application.html.erb
, добавьте это:
1
|
<%= link_to «Public Ribbits», ribbits_path %>
|
В заключение:
1
2
|
git add .
git commit -m ‘added the public ribbits page’
|
Шаг 9: Следование за другими пользователями
Это не был бы клон Твиттера, если бы мы не могли следить за другими пользователями, поэтому давайте поработаем над этой функцией дальше.
Это немного сложно на первый взгляд. Подумайте об этом: наши User
записи должны следовать за другими User
записями и сопровождаться другими User
записями. Это ассоциация самодостаточных «многие ко многим». Это означает, что нам понадобится ассоциативный класс, и мы назовем его «Отношения». Начните с создания этого ресурса:
1
2
|
rails g resource relationship follower_id:integer followed_id:integer
rake db:migrate
|
Эта модель нуждается только в двух полях: идентификатор подписчика и идентификатор подписчика (примечание: терминология здесь может немного запутать. В нашем случае, я использую термин «следуемый», чтобы обозначить пользователя, которого следуют) ,
Далее мы хотим связать модель User
с этой моделью Relationship
, которую мы делаем с обеих сторон. Во-первых, в классе Relationship
( app/models/relationship.rb
Relationship
) мы хотим добавить эти две строки:
1
2
|
belongs_to :follower, classname: «User»
belongs_to :followed, classname: «User»
|
Первая строка связывает запись User
с полем follower_id
, а вторая строка связывает запись User
с полем follow_id. Важно включить имя класса, потому что Rails не может вывести класс из имен свойств («follower» и «follow»). Однако он может определить правильные поля базы данных ( follower_id
и followed_id
) из этих имен.
Теперь в классе User
( app/model/user.rb
) мы должны сначала подключить каждую пользовательскую модель к связанным с ней отношениям:
1
2
|
has_many :follower_relationships, classname: «Relationship», foreign_key: «followed_id»
has_many :followed_relationships, classname: «Relationship», foreign_key: «follower_id»
|
Нам нужно создать две ассоциации, потому что у нас есть два набора отношений на пользователя: все люди, которые следуют за ним, и все люди, за которыми они следуют. И нет, эти внешние ключи не должны переключаться. Ассоциация follower_relationship
отвечает за всех ваших подписчиков. Следовательно, ему нужен внешний ключ follow_id.
Затем мы можем использовать эти отношения, чтобы перейти к подписчикам по другую сторону от них:
1
2
|
has_many :followers, through: :follower_relationships
has_many :followeds, through: :followed_relationships
|
Они дают нашим пользовательским записям методы followers
и followers
. Оба эти метода возвращают массивы наших подписчиков или людей, за которыми мы следим, соответственно.
Наконец, давайте добавим два метода в нашу пользовательскую модель, которая помогает нам с пользовательским интерфейсом:
1
2
3
4
5
6
7
|
def following?
self.followeds.include?
end
def follow user
Relationship.create follower_id: self.id, followed_id: user.id
end
|
Давайте передадим эти изменения перед настройкой пользовательского интерфейса.
1
2
|
git add .
git commit -m ‘created user relationships infrastructure’
|
Теперь пользовательский интерфейс находится на страницах профиля пользователя, это app/views/users/show.html.erb
. Начнем с чего-то простого: подписчик и подсчет. Видите где у нас это?
1
2
|
<span class=»spacing»>XX Followers
<span class=»spacing»>XX Following
|
Мы заменим эти значения заполнителей следующим образом:
1
2
|
<span class=»spacing»><%= @user.followers.count %> Followers
<span class=»spacing»><%= @user.followeds.count %> Following
|
У нас есть кнопка отслеживания / отмены подписки под этими подсчетами, но есть несколько состояний, которые мы должны рассмотреть. Во-первых, мы не хотим показывать какие-либо кнопки, если пользователь либо просматривает свой собственный профиль, либо если они не вошли в систему. Во-вторых, мы хотим отобразить кнопку «Отписаться», если пользователь уже следует за владельцем этого профиля.
01
02
03
04
05
06
07
08
09
10
11
12
|
<% if current_user and @user != current_user %>
<% if current_user.following?
<%= form_tag relationship_path, method: :delete do %>
<%= submit_tag «Unfollow» %>
<% end %>
<% else %>
<%= form_for @relationship do %>
<%= hidden_field_tag :followed_id, @user.id %>
<%= submit_tag «Follow» %>
<% end %>
<% end %>
<% end %>
|
Ресурс Rails — это, в основном, модель, связанный с ней контроллер и несколько других файлов.
Поместите этот код прямо под абзацем, который содержит вышеуказанные промежутки.
Формы являются более сложными частями здесь. Во-первых, если текущий пользователь уже следует за просматриваемым пользователем, мы будем использовать form_tag
чтобы создать форму, которая переходит в relationship_path
. Конечно, мы не можем забыть установить method
как delete
потому что мы delete
отношение.
Если текущий пользователь не следует за просматриваемым пользователем, мы создадим form_for
для текущих отношений. Мы просто используем скрытое поле, чтобы определить, за кем следовать.
Если вы обратите внимание, вы поймете, что чего-то не хватает: способность манипулировать экземпляром отношений с этой точки зрения. Нам нужен экземпляр Relationship
. Если текущий пользователь еще не подписан на этого пользователя, нам нужно создать пустое отношение. В противном случае нам нужно иметь отношения под рукой, чтобы удалить! Вернитесь в app/controllers/users_controller.rb
и добавьте следующее в метод show
:
1
2
3
4
|
@relationship = Relationship.where(
follower_id: current_user.id,
followed_id: @user.id
).first_or_initialize if current_user
|
Это немного отличается от обычного способа поиска или создания записи. Это инициализирует пустой экземпляр Relationship
если не найдено записей, соответствующих параметрам where
. Конечно, мы хотим сделать это только при наличии current_user
.
Маршруты для этой модели активируются линией resources :relationship
в config/routes.rb
, поэтому нам не нужно об этом беспокоиться.
Теперь в app/controllers/relationships_controller.rb
мы начнем с new
метода:
01
02
03
04
05
06
07
08
09
10
11
12
|
def create
@relationship = Relationship.new
@relationship.followed_id = params[:followed_id]
@relationship.follower_id = current_user.id</p>
if @relationship.save
redirect_to User.find params[:followed_id]
else
flash[:error] = «Couldn’t Follow»
redirect_to root_url
end
end
|
Довольно стандартные вещи сейчас, верно? Мы создадим отношения, сохраним их и перенаправим обратно в профиль пользователя.
Метод destroy
также прост:
1
2
3
4
5
|
def destroy
@relationship = Relationship.find(params[:id])
@relationship.destroy
redirect_to user_path params[:id]
end
|
Теперь создайте другого пользователя (или четырех) и попросите нескольких пользователей следовать за другими пользователями. Вы должны увидеть текст смены следующих кнопок, а также количество подписчиков / подписчиков.
Большой! Теперь мы можем зафиксировать эту функцию:
1
2
|
git add .
git commit -m ‘Following other users is now working’
|
Шаг 10: Создание нескольких других страниц
Есть несколько других простых страниц, которые мы хотим добавить. Во-первых, давайте создадим страницу для перечисления всех зарегистрированных пользователей. Это было бы отличным местом, чтобы найти новых друзей, увидеть их страницы и в конечном итоге следовать за ними. Логически, это должен быть маршрут /users
, поэтому мы будем использовать метод UsersController#index
:
1
2
3
|
def index
@users = User.all
end
|
Теперь для app/views/users/index.html.erb
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
<div id=»ribbits» class=»panel left»>
<h1>Public Profile</h1>
<% @users.each do |user|
<div class=»ribbitWrapper»>
<a href=»<%= user_path user %>»>
<img class=»avatar» src=»<%= user.avatar_url %>»>
<span class=»name»><%= user.name %>
</a>
@<%= user.username %>
<p>
<%= user.ribbits.size %> Ribbits
<span class=»spacing»><%= user.followers.count %> Followers
<span class=»spacing»><%= user.followeds.count %> Following
</p>
<% if user.ribbits.first %>
<p><%= user.ribbits.first.content %></p>
<% end %>
</div>
<% end %>
</div>
|
Наконец, давайте добавим ссылку на эту страницу в начало нашего шаблона. Давайте также добавим ссылку на профиль авторизованного пользователя. Прямо рядом со ссылкой «Public Ribbits» добавьте:
1
2
|
<%= link_to «Public Profiles», users_path %>
<%= link_to «My Profile», current_user %>
|
Далее идет страница друзей. Это где пользователь идет, чтобы увидеть ребра людей, за которыми он следует; мы также перенаправим пользователей на эту страницу, когда они войдут в систему и будут просматривать домашнюю страницу.
Как ни странно, найти правильное место в коде для этой страницы немного сложно. В конце концов, каждая страница в нашем приложении Rails должна основываться на методе одного из наших контроллеров. В соответствии с передовой практикой каждый контроллер имеет шесть методов REST, которые контролируют ресурс. В этом случае мы хотим посмотреть на ребрышки подмножества пользователей, что, по крайней мере для меня, кажется чем-то вроде крайнего случая. Вот как мы с этим справимся: давайте создадим метод buddies
в UsersController
:
1
2
3
4
5
6
7
8
9
|
def buddies
if current_user
@ribbit = Ribbit.new
buddies_ids = current_user.followeds.map(&:id).push(current_user.id)
@ribbits = Ribbit.find_all_by_user_id buddies_ids
else
redirect_to root_url
end
end
|
Очевидно, что нечего показывать, если пользователь не вошел в систему, поэтому мы проверим current_user
. Если мы не вошли в систему, мы перенаправим на корневой URL ( /
). В противном случае мы создаем новый ribbit (для нашей новой формы ribbit).
Затем нам нужно найти всех кроликов от текущего пользователя и людей, за которыми они следуют.
Мы можем использовать свойство массива followeds
и отобразить его, чтобы получить только идентификаторы пользователя. Затем мы также вводим идентификаторы текущего пользователя и, наконец, извлекаем ребра из этих пользователей.
Давайте сохраним шаблон в app/views/users/buddies.html.erb
; это очень похоже на наш публичный шаблон ribbits:
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
|
<% if current_user %>
<div id=»createRibbit» class=»panel right»>
<h1>Create a Ribbit</h1>
<p>
<%= form_for @ribbit do |f|
<%= f.textarea :content, class: ‘ribbitText’ %>
<%= f.submit «Ribbit!»
<% end %>
</p>
</div>
<% end %>
<div class=»panel left»>
<h1>Buddies’ Ribbits</h1>
<% @ribbits.each do |ribbit|
<div class=»ribbitWrapper»>
<a href=»<%= user_path ribbit.user %>»>
<img class=»avatar» src=»<%= ribbit.user.avatar_url %>»>
< span class = "name" ><%= ribbit.user.name %></ span > </a>
@<%= ribbit.user.username %> < span class = "time" ><%= time_ago_in_words(ribbit.created_at) %></ span > < p > <%= ribbit.content %> </ p > </div>
<% end %>
</div>
|
Нам нужно сделать маршрут для этого метода, чтобы использовать его. Откройте config/routes.rb
и добавьте следующее:
1
|
get 'buddies' , to: 'users#buddies' , as: 'buddies' |
Теперь мы можем пойти /buddies
и посмотреть страницу!
Однако есть кое-что еще, что мы хотим сделать с этим. Если вошедший в систему пользователь переходит на корневой URL-адрес, мы должны перенаправить его на /buddies
. Помните, этот маршрут в настоящее время:
1
2
3
|
def new
@user = User. new end
|
Давайте изменим это на это:
1
2
3
4
5
6
7
|
def new
if current_user
redirect_to buddies_path else
@user = User. new end
end
|
Мы также должны добавить ссылку на страницу друзей в app/views/layouts/application.html.erb
:
1
|
<%= link_to "Buddies' Ribbits", buddies_path %> |
А теперь мы внесем эти изменения:
1
2
|
git add .
git commit -m 'added buddies page' |
Шаг 11 Развертывание в Heroku
Последний шаг — развернуть приложение. Мы будем использовать Heroku.
Последний шаг — развернуть приложение. Мы будем использовать Heroku. Я собираюсь предположить, что у вас есть учетная запись Heroku, и что вы установили инструментальный пояс Heroku (инструменты командной строки).
Мы сталкиваемся с проблемой еще до того, как начнем! Мы использовали базу данных SQLite, потому что Rails по умолчанию использует SQLite. Однако Heroku не использует SQLite; он использует PostgreSQL для базы данных. Мы должны внести изменения в наш Gemfile, который фактически нарушает нашу локальную копию приложения (если вы не установите и не настроите сервер PostgreSQL). Вот мой компромисс: я покажу вам, как это сделать, и вы можете поиграть с моей развернутой версией. Но вы не должны вносить изменения в свой местный проект.
К счастью, переключить Rails на PostgreSQL очень просто. В нашем Gemfile
есть строка, которая выглядит так:
1
|
gem "sqlite" |
Измените эту строку на эту:
1
|
gem "pg" |
Теперь мы должны установить этот драгоценный камень локально, чтобы обновить Gemfile.lock
. Мы делаем это, запустив:
1
|
bundle install
|
И мы обязуемся:
1
2
|
git add .
git commit -m 'updated Gemfile with Postgres' |
Теперь мы можем создать наше приложение Heroku. В каталоге нашего проекта запустите:
1
2
|
heroku create git push heroku master
|
И наконец:
1
|
heroku open
|
Это открывает ваш браузер с вашим развернутым приложением Heroku. Вы можете играть с моей развернутой копией .
Это оно!
И вот, пожалуйста!Мы только что создали действительно простой клон Twitter. Конечно, есть десятки функций, которые мы могли бы добавить к этому, но мы собрали самые важные части: пользователи, риббиты и подписчики.
Помните, если Rails не ваша ракетка, ознакомьтесь с другими уроками этой серии! У нас есть целый ряд учебных пособий по Ribbit, использующих разные языки и фреймворки в конвейерах. Будьте на связи!