Статьи

Сборка Ribbit в Rails

Добро пожаловать в следующий выпуск нашей серии клонов в Twitter! В этом уроке мы создадим Ribbit с нуля не с использованием PHP , а с использованием Ruby on Rails. Давайте начнем!

пример

Одно быстрое объявление о сервисе, прежде чем мы начнем: мы не будем разрабатывать пользовательский интерфейс для приложения в этом руководстве; это было сделано в Построить клон Twitter с нуля: дизайн . Я дам вам знать, если нам нужно что-то изменить из этой статьи.


Перво-наперво: я использую Ruby 1.9.3 (p194) и Rails версии 3.2.8 для этого урока. Убедитесь, что вы используете те же версии. Неважно, как вы устанавливаете Ruby; Вы можете использовать RVM ( учебник ), rbenv или просто обычную установку Ruby . Независимо от подхода, каждый установщик предоставляет вам двоичный файл gem , который затем можно использовать для установки Rails. Просто используйте эту команду:

1
gem install rails

Это установит последнюю версию Rails, и мы можем начать сборку нашего приложения уже после его установки. Я надеюсь, вы понимаете, что 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’

Руководство по интерфейсу познакомило вас с изображениями и таблицами стилей 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’

Конечно, у нас не может быть клона 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’

Создание пользовательского интерфейса в 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’

Несмотря на то, что мы реализовали пользовательскую функцию, пользователь пока не может войти в систему. Итак, давайте добавим поддержку сеанса дальше.

Возможно, вы узнали, как мы создали учетные записи пользователей.

Я взял этот общий метод из эпизода 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.rbapp/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’

Теперь мы, наконец, готовы перейти к сути нашего приложения: созданию 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/ , но, конечно, подойдет любая страница профиля пользователя), напишите ribbit и нажмите «Ribbit!». Новый ribbit должен отображаться в списке ribbit на странице вашего профиля.

Хорошо, давайте передадим эти изменения:

1
2
git add .
git commit -m ‘ribbit functionality created’

Далее мы хотим создать общедоступную страницу, которая включает в себя все кролики, сделанные всеми пользователями. Логически, это должно быть представление индекса 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’

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

Это немного сложно на первый взгляд. Подумайте об этом: наши 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’

Есть несколько других простых страниц, которые мы хотим добавить. Во-первых, давайте создадим страницу для перечисления всех зарегистрированных пользователей. Это было бы отличным местом, чтобы найти новых друзей, увидеть их страницы и в конечном итоге следовать за ними. Логически, это должен быть маршрут /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'

Последний шаг — развернуть приложение. Мы будем использовать 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, использующих разные языки и фреймворки в конвейерах. Будьте на связи!