Rails для начинающих
Это руководство раскрывает установку и запуск Ruby on Rails.
После его прочтения, вы узнаете:
- Как установить Rails, создать новое приложение на Rails и присоединить ваше приложение к базе данных.
- Общую структуру приложения на Rails.
- Основные принципы MVC (Model, View Controller - «Модель-представление-контроллер») и дизайна, основанного на RESTful.
- Как быстро генерировать изначальный код приложения на Rails.
Допущения в этом руководстве
Это руководство рассчитано на новичков, которые хотят запустить приложение на Rails с нуля. Оно не предполагает, что вы раньше работали с Rails.
Rails – фреймворк для веб-разработки, написанный на языке программирования Ruby. Если у вас нет опыта в Ruby, возможно вам будет тяжело сразу приступить к изучению Rails. Есть несколько хороших англоязычных ресурсов, посвященных изучению Ruby, например:
Обращайте внимание, что некоторые ресурсы, хотя все еще великолепные, но все же покрывают старые версии Ruby, такие как 1.6, и в особенности 1.8, и не включает некоторые элементы синтаксиса, которые вы увидите в повседневной разработке на Rails.
Что такое Rails?
Rails - фреймворк для веб-разработки, написанный на языке программирования Ruby. Он разработан, чтобы сделать программирование веб-приложений проще, так как использует ряд допущений о том, что нужно каждому разработчику для создания нового проекта. Он позволяет вам писать меньше кода в процессе программирования, в сравнении с другими языками и фреймворками. Профессиональные разработчики на Rails также отмечают, что с ним разработка веб-приложений более забавна =)
Rails - своевольный программный продукт. Он делает предположение, что имеется "лучший" способ что-то сделать, и он так разработан, что стимулирует этот способ - а в некоторых случаях даже препятствует альтернативам. Если изучите "The Rails Way", то, возможно, откроете в себе значительное увеличение производительности. Если будете упорствовать и переносить старые привычки с других языков в разработку на Rails, и попытаетесь использовать шаблоны, изученные где-то еще, ваш опыт разработки будет менее счастливым.
Философия Rails включает два важных ведущих принципов:
- Don't Repeat Yourself: DRY — это принцип разработки ПО, который гласит, что "Каждый кусочек информации должен иметь единственное, неизбыточное, авторитетное представление в системе." Не пишите одну и ту же информацию снова и снова, код будет легче поддерживать, и он будет более расширяемым и менее ошибочным.
- Convention Over Configuration: у Rails есть мнения о наилучших способах делать множество вещей в веб-приложении, и по умолчанию выставлены эти соглашения, вместо того, чтобы заставлять вас по мелочам править многочисленные конфигурационные файлы.
Создание нового проекта Rails
Лучший способ использования этого руководства - проходить его шаг за шагом. Все шаги существенны для запуска служащего примером приложения, кроме них больше не требуется какого-либо дополнительного кода или шагов.
Следуя этому руководству, вы создадите проект Rails с названием blog
, очень простой веб-блог. Прежде чем начнем создавать приложение, нужно убедиться, что сам Rails установлен.
TIP: Нижеследующие примеры используют $
для обозначения строки ввода терминала в UNIX-подобных операционных системах, хотя у вас он может быть настроен по другому Если вы используете Windows, ваша строка будет выглядеть наподобие c:\source_code>
Установка Rails
Перед установкой Rails необходимо проверить, чтобы в вашей системе были установлены необходимые предварительные зависимости. К ним относятся Ruby и SQLite3.
Откройте приложения для командной строки. На macOS откройте Terminal.app, на Windows выберите "Run" в меню Start и напишите 'cmd.exe'. Любые команды, начинающиеся со знака доллара $
должны быть запущены в командной строке. Убедитесь, что у вас установлена текущая версия Ruby:
$ ruby -v
ruby 2.3.1p112
Rails требует, чтобы был установлен Ruby версии 2.2.2 или новее. Если номер версии меньше этой, нужно будет установить новую копию Ruby.
TIP: Существует ряд инструментов для помощи в быстрой установке Ruby и Ruby on Rails в вашей системе. Пользователи Windows могут использовать Rails Installer, а пользователи macOS могут использовать Tokaido. Больше методов для установки для большинства операционных систем можно увидеть на ruby-lang.org.
Если работаете в Windows, необходимо установить Ruby Installer Development Kit.
Вам также понадобится установка базы данных SQLite3.
Многие популярные UNIX-подобные ОС поставляются с приемлемой версией SQLite3. На Windows, если вы устанавливали Rails с помощью Rails Installer, у вас уже установлен SQLite. Прочие пользователи могут обратиться к инструкциям по установке на сайте SQLite3. Проверьте, что он корректно установлен и содержится в вашем PATH:
$ sqlite3 --version
Программа должна сообщить свою версию.
Для установки Rails используйте команду gem install
, представленную RubyGems:
$ gem install rails
Чтобы проверить, что все установлено верно, нужно выполнить следующее:
$ rails --version
Если выводится что-то вроде "Rails 5.1.1", можно продолжать.
Создание приложения Blog
Rails поставляется с рядом скриптов, названных генераторами, разработанных для облегчения жизни разработчика, создавая все, что необходимо для начала работы над определенной задачей. Одним из них является генератор нового приложения, предоставляющий вам основу приложения Rails, таким образом, вам не нужно писать его самим.
Для использования этого генератора, откройте терминал, войдите в папку, в которой у вас есть права на создание файлов и напишите:
$ rails new blog
Это создаст приложение на Rails с именем Blog в директории blog
и установит гемы, зависимости от которых упомянуты в Gemfile
при использовании bundle install
.
NOTE: При использовании Windows Subsystem for Linux, имеются некоторые ограничения на сообщения файловой системы, означающие, что следует отключить гемы spring
и listen
, что можно сделать, запустив rails new blog --skip-spring --skip-listen
.
TIP: Можно посмотреть все возможные опции командной строки, которые принимает билдер приложения на Rails, запустив rails new -h
.
После того, как вы создали приложение blog, перейдите в его папку:
$ cd blog
В директории blog
имеется несколько автоматически сгенерированных файлов и папок, задающих структуру приложения на Rails. Большая часть работы в этом самоучителе будет происходить в папке app
, но сейчас пробежимся по функциям каждой папки, которые создает Rails в новом приложении по умолчанию:
Файл/Папка | Назначение |
---|---|
app/ | Содержит контроллеры, модели, вьюхи, хелперы, рассыльщики, каналы, задания и ассеты вашего приложения. Мы рассмотрим эту папку подробнее далее. |
bin/ | Содержит Rails скрипты которые стартуют ваше приложение, также директория может содержать другие скрипты которые вы используете для настройки, обновления, деплоя или запуска. |
config/ | Конфигурации маршрутов, базы данных вашего приложения, и т.д. Более подробно это раскрыто в Конфигурирование приложений на Rails |
config.ru | Конфигурация Rack для серверов, основанных на Rack, используемых для запуска приложения. Подробнее о Rack смотрите на сайте Rack. |
db/ | Содержит текущую схему вашей базы данных, а также миграции базы данных. |
Gemfile Gemfile.lock |
Эти файлы позволяют указать, какие зависимости от гемов нужны для вашего приложения на Rails. Эти файлы используются гемом Bundler. Подробнее о Bundler смотрите на сайте Bundler. |
lib/ | Внешние модули для вашего приложения. |
log/ | Файлы логов приложения. |
package.json | Этот файл позволяет указать, какие зависимости npm необходимы для приложения Rails. Этот файл используется Yarn. Подробнее о Yarn смотрите на сайте Yarn. |
public/ | Единственная папка, которая доступна извне как есть. Содержит статичные файлы и скомпилированные ассеты. |
Rakefile | Этот файл находит и загружает задачи, которые могут быть запущены в командной строке. Определенная задача доступна во всех компонентах Rails. Вместо изменения Rakefile , можно добавить свои собственные задачи, добавив файлы в директорию lib/tasks приложения. |
README.md | Это вводный мануал для вашего приложения. Его следует отредактировать, чтобы рассказать остальным, что ваше приложение делает, как его настроить, и т.п. |
test/ | Юнит-тесты, фикстуры и прочий аппарат тестирования. Это раскрывается в руководстве Тестирование приложений на Rails |
tmp/ | Временные файлы (такие как файлы кэша и pid) |
vendor/ | Место для кода внешних разработчиков. В типичном приложении на Rails включает внешние гемы. |
.gitignore | Этот файл сообщает git, какие файлы (явно или по шаблону) ему следует игнорировать. Подробнее об игнорировании файлов смотрите GitHub - Ignoring files. |
.ruby-version | Этот файл содержит дефолтную версию Ruby. |
Hello, Rails!
Для начала, давайте выведем на экран какой-либо текст. Для этого нужен запущенный сервер вашего приложения на Rails.
Запуск веб-сервера
Фактически у вас уже есть функциональное приложение на Rails. Чтобы убедиться, нужно запустить веб-сервер на вашей машине. Это можно осуществить, запустив следующую команду из директории blog
:
$ bin/rails server
TIP: Если вы используете Windows, вы должны передавать скрипты из папки bin
непосредственно в интерпретатор Ruby, то есть ruby bin\rails server
.
TIP: Компилирование CoffeeScript и сжатие ассетов JavaScript требует среды выполнения JavaScript в вашей системе, и его отсутствие приведет к ошибке execjs
во время компиляции ассетов. Обычно macOS и Windows поставляются с установленной средой выполнения JavaScript. Rails добавляет гем mini_racer
в генерируемый Gemfile
нового приложения в закомментированной строчке, если нужно, можете ее раскомментировать. therubyrhino
- рекомендованная среда выполнения для пользователей JRuby, она добавляется в Gemfile
, если приложение генерируется под JRuby. Можно узнать все о поддерживаемых средах выполнения в ExecJS
Это запустит Puma, веб-сервер, распространяющийся с Rails по умолчанию. Чтобы увидеть приложение в действии, откройте окно браузера и пройдите по адресу http://localhost:3000. Вы должны увидеть дефолтную информационную страницу Rails:
TIP: Для остановки веб-сервера нажмите Ctrl+C в терминале, где он запущен. Чтобы убедиться в том, что сервер был остановлен, вы должны снова увидеть курсор командной строки. Для большинства UNIX-подобных систем, включая macOS, это будет знак доллара $
. В режиме development, Rails в основном не требует остановки сервера; все изменения, которые Вы делаете в файлах, автоматически подхватываются сервером.
Страница "Welcome Aboard" это своеобразный тест для нового приложения на Rails: она показывает, что ваши программы настроены достаточно правильно для отображения страницы.
Скажите "привет", Рельсы
Чтобы Rails сказал "Привет", нужно создать, как минимум, контроллер и вьюху (представление).
Назначением контроллера является получение определенных запросов к приложению. Роутинг решает, какой контроллер получит какие запросы. Часто имеется более одного маршрута к каждому контроллеру, и различные маршруты могут быть обработаны различными экшнами. Назначением каждого экшна является сбор информации для предоставления ее во вьюху.
Назначением вьюхи является отображение этой информации в удобочитаемом формате. Необходимо отметить важное различие, что местом, в котором собирается информация, является контроллер, а не вьюха. Вьюха должна только лишь отображать эту информацию. По умолчанию шаблоны вьюх пишутся на языке, названном eRuby (Embedded Ruby), который конвертируется циклом запросов в Rails до отправки пользователю.
Для создания нового контроллера, нужно запустить генератор "controller" и сказать ему, что вы хотите контроллер с именем "Welcome" с экшном по имени "index", вот так:
$ bin/rails generate controller Welcome index
Rails создаст несколько файлов и маршрут.
create app/controllers/welcome_controller.rb
route get 'welcome/index'
invoke erb
create app/views/welcome
create app/views/welcome/index.html.erb
invoke test_unit
create test/controllers/welcome_controller_test.rb
invoke helper
create app/helpers/welcome_helper.rb
invoke test_unit
invoke assets
invoke coffee
create app/assets/javascripts/welcome.coffee
invoke scss
create app/assets/stylesheets/welcome.scss
Наиболее важными из них являются, разумеется, контроллер, расположенный в app/controllers/welcome_controller.rb
, и вьюха, расположенная в app/views/welcome/index.html.erb
.
Откройте файл app/views/welcome/index.html.erb
в текстовом редакторе. Удалите весь существующий в файле код и замените его на следующую строчку кода:
<h1>Hello, Rails!</h1>
Настройка домашней страницы приложения
Теперь, когда мы сделали контроллер и вьюху, нужно сказать Rails, что мы хотим увидеть "Hello Rails!". В нашем случае мы хотим это увидеть, когда зайдем в корневой URL нашего сайта, http://localhost:3000. Однако сейчас это место заняла тестовая страница "Welcome Aboard".
Теперь нужно сказать Rails, где находится настоящая домашняя страница.
Откройте файл config/routes.rb
в редакторе.
Rails.application.routes.draw do
get 'welcome/index'
# Подробнее о DSL, доступном в этом файле, смотрите http://rusrails.ru/rails-routing
end
Это маршрутный файл вашего приложения, который содержит варианты входа на сайт на специальном языке DSL (domain-specific language, предметно-ориентированный язык программирования), который говорит Rails, как соединять входящие запросы с контроллерами и экшнами. Отредактируйте этот файл, добавив строчку кода root 'welcome#index'
. Он должен выглядеть так:
Rails.application.routes.draw do
get 'welcome/index'
root 'welcome#index'
end
root 'welcome#index'
говорит Rails направить запросы к корню приложения в экшн index контроллера welcome, а get 'welcome/index'
говорит Rails направлять запросы к http://localhost:3000/welcome/index в экшн index контроллера welcome. Он был создан ранее при запуске генератора контроллера (bin/rails generate controller Welcome index
).
Запустите снова веб-сервер, если вы его остановили для генерации контроллера (bin/rails server
) и пройдите по адресу http://localhost:3000 в браузере. Вы увидите надпись Hello, Rails!
, которую поместили в app/views/welcome/index.html.erb
, показывающую, что этот новый маршрут действительно ведет в экшн index
в WelcomeController
, и вьюха корректно рендерится.
TIP. Чтобы узнать больше о роутинге, обратитесь к руководству Роутинг в Rails.
Разрабатываем быстро
После того, как вы увидели, как создать контроллер, экшн и вьюху, давайте создадим что-то более вещественное.
Теперь в приложении Blog, мы создадим новый ресурс. Ресурс - это термин, обозначающий коллекцию схожих объектов, таких как статьи, люди или животные. Можно создавать, читать, обновлять и уничтожать элементы для ресурса, и эти операции называются операциями CRUD (create, read, update, destroy).
Rails представляет метод resources
, который используется для объявления стандартного ресурса REST. Вам необходимо добавить ресурс article в config/routes.rb
, этот файл будет выглядеть так:
Rails.application.routes.draw do
get 'welcome/index'
resources :articles
root 'welcome#index'
end
Если запустить bin/rails routes
, можно увидеть, что он объявил все маршруты для стандартных экшнов RESTful. Значение столбца префикса (и остальных столбцов) мы рассмотрим позже, а пока обратите внимание, что Rails знает о форме единственного числа слова article
и разумно использует это различие.
$ bin/rails routes
Prefix Verb URI Pattern Controller#Action
welcome_index GET /welcome/index(.:format) welcome#index
articles GET /articles(.:format) articles#index
POST /articles(.:format) articles#create
new_article GET /articles/new(.:format) articles#new
edit_article GET /articles/:id/edit(.:format) articles#edit
article GET /articles/:id(.:format) articles#show
PATCH /articles/:id(.:format) articles#update
PUT /articles/:id(.:format) articles#update
DELETE /articles/:id(.:format) articles#destroy
root GET / welcome#index
В следующем разделе мы добавим возможность создания новых статей и сможем просматривать их. Это буквы "C" и "R" из CRUD: create и read. Форма для этого будет выглядеть так:
Она выглядит немного просто сейчас, но это нормально. Позже мы увидим, как улучшить ее внешний вид.
Основы
Сперва нам необходимо где-то создать новую статью. Наиболее подходящим местом будет /articles/new
. С уже определенным маршрутом уже можно осуществлять запросы к /articles/new
приложения. Перейдите на http://localhost:3000/articles/new и вы увидите ошибку маршрутизации:
Эта ошибка произошла, поскольку маршрут нуждается в определенном контроллере, чтобы обслужить запрос. Решение этой проблемы простое: нужно создать контроллер с именем ArticlesController
. Это будет сделано запуском команды:
$ bin/rails generate controller Articles
Если открыть только что сгенерированный app/controllers/articles_controller.rb
, можно увидеть абсолютно пустой контроллер:
class ArticlesController < ApplicationController
end
Контроллер - это просто класс, унаследованный от ApplicationController
. В этом классе вы должны определить методы, которые станут экшнами для этого контроллера. Эти экшны будут выполнять операции CRUD со статьями в вашей системе.
NOTE: В Ruby
методы бывают public
, private
и protected
, но экшнами контроллеров могут быть только public
методы. Подробнее смотрите в Programming Ruby.
Если теперь обновить http://localhost:3000/articles/new, вы увидите новую ошибку:
Эта ошибка показывает, что Rails не может найти экшн new
внутри ArticlesController
, который был только что создан. Это так, поскольку контроллеры в Rails при создании пустые по умолчанию, если вы не указали желаемые экшны при процессе генерации.
Чтобы вручную определить экшн в контроллере, все что нужно - это определить в нем новый метод. Откройте app/controllers/articles_controller.rb
и в классе ArticlesController
определите метод new
, чтобы контроллер стал выглядеть так:
class ArticlesController < ApplicationController
def new
end
end
С методом new
, определенным в ArticlesController
, если обновите http://localhost:3000/articles/new, увидите другую ошибку:
Вы получили эту ошибку, поскольку Rails ожидает, что обычные экшны, такие как этот, имеют связанные с ними вьюхи для отображения их информации. Когда нет доступных вьюх, Rails вызовет исключение.
В вышеприведенном изображении конец строчки был обрезан. Давайте посмотрим на полное сообщение об ошибке:
ArticlesController#new is missing a template for this request format and variant. request.formats: ["text/html"] request.variant: [] NOTE! For XHR/Ajax or API requests, this action would normally respond with 204 No Content: an empty white screen. Since you're loading it in a web browser, we assume that you expected to actually render a template, not… nothing, so we're showing an error to be extra-clear. If you expect 204 No Content, carry on. That's what you'll get from an XHR or API request. Give it a shot.
Как много букв! Давайте быстро пробежимся и поймем, что означает каждая часть.
Первая часть указывает, какой шаблон отсутствует. В нашем случае, шаблон articles/new
. Rails сперва ищет этот шаблон. Если не находит, он пытается загрузить шаблон с именем application/new
. Он так ищет, поскольку ArticlesController
унаследован от ApplicationController
.
Следующая часть сообщения содержит request.formats
, указывающий формат шаблона, обслуживающего отклик. Он установлен в text/html
, так как мы запросили эту страницу с помощью браузера, поэтому Rails ищет шаблон HTML. request.variant
определяет, на какой тип физических устройств будет передан отклик, и помогает Rails определить, какой шаблон использовать для отклика. Он пустой, так как не было предоставлено никакой информации.
Простейшим шаблоном, работающим в данном случае, будет расположенный в app/views/articles/new.html.erb
. Расширение этого файла важно: первое расширение это формат шаблона, а второе расширение это обработчик, который будет использован для рендеринга шаблона. Rails пытается найти шаблон с именем articles/new
в app/views
приложения. Форматом для этого шаблона может быть только html
, и обработчик по умолчанию это erb
. Rails использует другие обработчики для других форматов. Обработчик builder
используется для создания шаблонов XML, а обработчик coffee
использует CoffeeScript для создания шаблонов JavaScript. Поскольку мы хотим создать новую форму HTML, будем использовать язык ERB
, разработанный, чтобы внедрять Ruby в HTML.
Следовательно, файл должен называться articles/new.html.erb
и должен быть расположен в директории app/views
приложения.
Создайте новый файл app/views/articles/new.html.erb
и поместите в него:
<h1>New Article</h1>
Теперь при обновлении http://localhost:3000/articles/new вы увидите, что у страницы появился заголовок. Теперь маршрут, контроллер, экшн и вьюха гармонично работают. Время создать форму для новой статьи.
Первая форма
Для создания формы в этом шаблоне, мы будем использовать form builder. Основной form builder для Rails представлен методом хелпера по имени form_with
. Для использования этого метода добавьте код в app/views/articles/new.html.erb
:
<%= form_with scope: :article, local: true do |form| %>
<p>
<%= form.label :title %><br>
<%= form.text_field :title %>
</p>
<p>
<%= form.label :text %><br>
<%= form.text_area :text %>
</p>
<p>
<%= form.submit %>
</p>
<% end %>
Если теперь обновить страницу, вы увидите точно такую форму как в начале примера. Создание форм в Rails, действительно, очень простое!
При вызове form_with
, вы передали в него определяющий объект для этой формы. В нашем случае это символ :article
. Это сообщает хелперу form_with
, для чего эта форма. Внутри блока для этого метода, объект FormBuilder
- представленный как form
- используется для создания двух меток и двух текстовых полей, по одному для заголовка и текста статьи. Наконец, вызов submit
на объекте form
создаст кнопку отправки формы.
Хотя, у этой формы есть одна проблема. Если посмотрите на сгенерированный HTML, просмотрев исходник страницы, то увидите у формы атрибут action
, указывающий на /articles/new
. Это проблема, поскольку этот маршрут ведет на ту же самую страницу, и этот маршрут должен использоваться только для отображения формы для новой статьи.
Форме нужно использовать иной URL, чтобы вести куда-то еще. Это можно быстро сделать с помощью опции :url
для form_with
. Обычно в Rails, экшн, используемый для подтверждения формы new, такой как эта, называется "create", поэтому форма должна указывать на этот экшн.
Отредактируйте строчку form_with
в app/views/articles/new.html.erb
следующим образом:
<%= form_with scope: :article, url: articles_path, local: true do |form| %>
В этом примере в опцию :url
передан хелпер articles_path
. Чтобы увидеть, что с помощью него делает Rails, снова посмотрим на результат bin/rails routes
:
$ bin/rails routes
Prefix Verb URI Pattern Controller#Action
welcome_index GET /welcome/index(.:format) welcome#index
articles GET /articles(.:format) articles#index
POST /articles(.:format) articles#create
new_article GET /articles/new(.:format) articles#new
edit_article GET /articles/:id/edit(.:format) articles#edit
article GET /articles/:id(.:format) articles#show
PATCH /articles/:id(.:format) articles#update
PUT /articles/:id(.:format) articles#update
DELETE /articles/:id(.:format) articles#destroy
root GET / welcome#index
Хелпер articles_path
сообщает Rails указать форме на URI Pattern, связанный с префиксом articles
; и форма пошлет (по умолчанию) запрос POST
на этот маршрут. Он связан с экшном create
текущего контроллера, ArticlesController
.
С определенными формой и ее связанным маршрутом, можно заполнить форму и нажать на кнопку отправки, чтобы начать процесс создания новой статьи, поэтому идите и сделайте это. При отправке формы вы увидите знакомую ошибку:
Чтобы это заработало, нужно создать экшн create
в ArticlesController
.
ПРИМЕЧАНИЕ: По умолчанию form_with
отправляет формы с использованием Ajax, тем самым не осуществляя редирект всей страницы. Чтобы облегчить чтение данного руководства, мы отключили это с помощью local: true
.
Создание статей
Чтобы убрать "Unknown action", нужно определить экшн create
в классе ArticlesController
в app/controllers/articles_controller.rb
, ниже экшна new
, как показано:
class ArticlesController < ApplicationController
def new
end
def create
end
end
Если теперь еще раз отправить форму, на странице ничего не поменяется. Не беспокойтесь! Это так, потому что Rails по умолчанию возвращает отклик 204 No Content
для экшна, если мы не указали, какой должен быть отклик. Мы просто добавили экшн create
, но не указали ничего о том, каким должен быть отклик. В нашем случае, экшн create
должен сохранить нашу новую статью в базу данных.
При отправке формы, ее поля будут посланы в Rails как параметры. К этим параметрам можно обратиться из экшнов контроллера, как правило, для выполнения определенных задач. Чтобы увидеть, на что похожи эти параметры, измените экшн create
так:
def create
render plain: params[:article].inspect
end
Тут метод render
принимает очень простой хэш с ключом :plain
и значением params[:article].inspect
. Метод params
это объект, представляющий параметры (или поля), приходящие от формы. Метод params
возвращает объект ActionController::Parameters
, позволяющий получать доступ к ключам хэша с использованием или строк, или символов. В этой ситуации имеют значение только параметры, пришедшие от формы.
TIP: Убедитесь, что у вас есть твердое понимание метода params
, так как вы будете его использовать очень часто. Давайте рассмотрим в качестве примера URL: http://www.example.com/?username=dhh&[email protected]. В этом URL, params[:username]
будет равен "dhh" и params[:email]
будет равен "[email protected]".
Если еще раз отправить форму, вы увидите что-то вроде следующего:
<ActionController::Parameters {"title"=>"First Article!", "text"=>"This is my first article."} permitted: false>
Теперь этот экшн отображает параметры для статьи, пришедшие из формы. Однако, это все еще бесполезно. Да, вы видите параметры, но по сути ничего не делаете с ними.
Создание модели Article
Модели в Rails используют имя в единственном числе, а их соответствующая таблица в базе данных - имя во множественном числе. Rails предоставляет генератор для создания моделей, которым пользуются большинство разработчиков на Rails для создания новых моделей. Для создания новой модели, запустите эту команду в своем терминале:
$ bin/rails generate model Article title:string text:text
С помощью этой команды мы сообщаем Rails, что хотим модель Article
с атрибутом title строкового типа и атрибутом text текстового типа. Эти атрибуты автоматически добавятся в таблицу articles
и привяжутся к модели Article
.
Rails в ответ создаст ряд файлов. Сейчас нам интересны только app/models/article.rb
и db/migrate/20140120191729_create_articles.rb
(у вас имя может немного отличаться). Последний ответственен за создание структуры базы данных, поэтому мы и рассмотрим его далее.
TIP: Active Record достаточно сообразителен, чтобы автоматически связать имена столбцов с атрибутами модели, что означает, что внутри моделей Rails не нужно объявлять атрибуты, Active Record сделает это автоматически.
Запуск миграции
Как вы уже видели, bin/rails generate model
создал файл миграции базы данных в директории db/migrate
. Миграции - это класс Ruby, разработанный для того, чтобы было просто создавать и модифицировать таблицы базы данных. Rails использует команды rake для запуска миграций, и возможна отмена миграции после того, как она была применена к вашей базе данных. Имя файла миграции включает временную метку, чтобы быть уверенным, что они выполняются в той последовательности, в которой они создавались.
Если Вы заглянете в файл db/migrate/YYYYMMDDHHMMSS_create_articles.rb
(помните, у вас файл имеет немного другое имя), вот что там обнаружите:
class CreateArticles < ActiveRecord::Migration[5.0]
def change
create_table :articles do |t|
t.string :title
t.text :text
t.timestamps
end
end
end
Эта миграция создает метод change
, вызываемый при запуске этой миграции. Экшны, определенное в этой миграции, также является обратимым, что означает, что Rails знает, как отменить изменения, сделанные этой миграцией, в случае, если вы решите их отменить позже. Когда вы запустите эту миграцию, она создаст таблицу articles
со строковым столбцом и текстовым столбцом. Она также создаст два поля временных меток для отслеживания времени создания и обновления статьи.
TIP: Для подробностей о миграциях Rails обратитесь к руководству Миграции Active Record.
Сейчас нам нужно использовать команду bin/rails, чтобы запустить миграцию:
$ bin/rails db:migrate
Rails запустит эту команду миграции и сообщит, что он создал таблицу Articles.
== CreateArticles: migrating ==================================================
-- create_table(:articles)
-> 0.0019s
== CreateArticles: migrated (0.0020s) =========================================
NOTE: Так как вы работаете по умолчанию в среде development, эта команда будет применена к базе данных, определенной в разделе development
вашего файла config/database.yml
. Если хотите запустить миграции в другой среде, например в production, следует явно передать ее при вызове команды: bin/rails db:migrate RAILS_ENV=production
.
Сохранение данных в контроллере
Возвратимся к ArticlesController
, нам нужно изменить экшн create
, чтобы использовать новую модель Article
для сохранения данных в базе данных. Откройте app/controllers/articles_controller.rb
и измените экшн create
следующим образом:
def create
@article = Article.new(params[:article])
@article.save
redirect_to @article
end
Вот что тут происходит: каждая модель Rails может быть инициализирована с помощью соответствующих атрибутов, которые будут автоматически привязаны к соответствующим столбцам базы данных. В первой строчке мы как раз это и делаем (помните, что params[:article]
содержит интересующие нас атрибуты). Затем @article.save
ответственен за сохранение модели в базу данных. Наконец, мы перенаправляем пользователя на экшн show
, который мы определим позже.
TIP: Вы, возможно, задаетесь вопросом, почему A
в Article.new
заглавная, хотя все остальные ссылки на статьи в этом руководстве используют строчное написание. В этом контексте мы ссылаемся на класс по имени Article
, который определен в app/models/article.rb
. Имена классов в Ruby должны начинаться с заглавной буквы.
TIP: Как мы увидим далее, @article.save
возвращает булево значение, показывающее, была ли сохранена модель, или нет.
Если теперь перейти на http://localhost:3000/articles/new, вы почти сможете создать новую статью. Попробуйте! Вы должны получить ошибку, которая выглядит так:
Rails имеет ряд защитных мер, помогающих писать безопасные приложения, и вы только что столкнулись с одной из них. Она называется strong parameters и требует, чтобы мы указали Rails, какие именно параметры разрешено передавать в экшны нашего контроллера.
Почему об этом нужно беспокоиться? Возможность взять и автоматически назначить все параметры контроллера в вашу модель облегчит работу программиста, но это удобство также позволяет использование в злонамеренных целях. Что, если запрос к серверу был специально создан как отправка формы новой статьи, но также включает дополнительные поля, которые нарушают целостность вашего приложения? Они будут 'массово назначены' в вашу модель и затем в базу данных среди нормальных данных — потенциально ломая ваше приложение, или еще хуже.
Нам нужно составить белый список параметров нашего контроллера, чтобы предотвратить ошибочное массовое назначение. В этом случае, мы хотим и разрешить, и затребовать параметры title
и text
для корректного использования в create
. Синтаксис для этого представлен методами require
и permit
. Изменение затрагивает одну строчку в экшне create
:
@article = Article.new(params.require(:article).permit(:title, :text))
Это часто выносится в отдельный метод, чтобы его можно было повторно использовать в нескольких экшнах одного контроллера, например в create
и update
. Метод часто делают private
(это уже не связано с массовым назначением), чтобы убедиться, что он не может быть вызван вне предназначенного контекста. Вот результат:
def create
@article = Article.new(article_params)
@article.save
redirect_to @article
end
private
def article_params
params.require(:article).permit(:title, :text)
end
TIP: За подробностями обратитесь к этой статье по Strong Parameters.
Отображение статей
Если снова отправить форму, Rails сообщит о ненайденном экшне show
. Нам это уже не нужно, поэтому давайте добавим экшн show
до того.
Как вы уже видели в результатах выполнения bin/rails routes
, маршрут для экшна show
следующий:
article GET /articles/:id(.:format) articles#show
Специальный синтаксис :id
сообщит rails, что этот маршрут ожидает параметр :id
, который в нашем случае будет идентификатором статьи.
Как и раньше, нам необходим экшн show
в app/controllers/articles_controller.rb
и его соответствующая вьюха.
NOTE: Частой практикой является помещать стандартные экшны CRUD в каждом контроллере в следующем порядке: index
, show
, new
, edit
, create
, update
и destroy
. Можно использовать любой порядок, какой хотите, но помните, что это публичные методы; как уже упоминалось ранее в этом руководстве, они должны быть помещены перед определением видимости private
в контроллере.
С учетом этого, давайте добавим экшн show
следующим образом:
class ArticlesController < ApplicationController
def show
@article = Article.find(params[:id])
end
def new
end
# опущено для краткости
Нужно отметить несколько вещей. Мы использовали Article.find
для поиска статьи, в которой мы заинтересованы, передав params[:id]
, чтобы получить параметр :id
из запроса. Также мы использовали переменную экземпляра (с префиксом @
) для хранения ссылки на объект статьи. Мы сделали так, потому что Rails передаст все переменные экземпляра во вьюху.
Теперь создайте новый файл app/views/articles/show.html.erb
со следующим содержимым:
<p>
<strong>Title:</strong>
<%= @article.title %>
</p>
<p>
<strong>Text:</strong>
<%= @article.text %>
</p>
С таким изменением, вы, наконец, сможете создать новую статью. Посетите http://localhost:3000/articles/new и попробуйте!
Отображение всех статей
Нам все еще нужен способ для отображения списка всех наших статей, давайте сделаем его. Маршрут, выводящийся с помощью bin/rails routes
, следующий:
articles GET /articles(.:format) articles#index
Добавьте соответствующий этому маршруту экшн index
внутри ArticlesController
в файле app/controllers/articles_controller.rb
. Когда мы пишем экшн index
, обычной практикой является разместить его как первый метод в контроллере. Сделаем это:
class ArticlesController < ApplicationController
def index
@articles = Article.all
end
def show
@article = Article.find(params[:id])
end
def new
end
# опущено для краткости
И, наконец, вьюха для этого экшна, расположенная в app/views/articles/index.html.erb
:
<h1>Listing articles</h1>
<table>
<tr>
<th>Title</th>
<th>Text</th>
<th></th>
</tr>
<% @articles.each do |article| %>
<tr>
<td><%= article.title %></td>
<td><%= article.text %></td>
<td><%= link_to 'Show', article_path(article) %></td>
</tr>
<% end %>
</table>
Теперь, если перейти в http://localhost:3000/articles, можно увидеть список всех статей, которые вы уже создали.
Добавление ссылки
Теперь вы можете создавать и просматривать отдельную и все статьи. Давайте добавим несколько ссылок для навигации между страницами.
Откройте app/views/welcome/index.html.erb
и измените его следующим образом:
<h1>Hello, Rails!</h1>
<%= link_to 'My Blog', controller: 'articles' %>
Метод link_to
- один из встроенных хелперов Rails. Он создает гиперссылку, на основе текста для отображения и указания куда перейти - в нашем случае путь для контроллера articles.
Давайте добавим ссылки и в другие вьюхи, начнем с добавления ссылки "New Article" в app/views/articles/index.html.erb
, поместив ее над тегом <table>
:
<%= link_to 'New article', new_article_path %>
Эта ссылка позволит перейти на форму для создания новой статьи.
Теперь добавьте еще одну ссылку в app/views/articles/new.html.erb
, под формой, чтобы вернуться обратно в экшн index
:
<%= form_with scope: :article, url: articles_path, local: true do |form| %>
...
<% end %>
<%= link_to 'Back', articles_path %>
Наконец, добавьте ссылку в шаблон app/views/articles/show.html.erb
, чтобы также вернуться в экшн index
, чтобы просматривающие отдельную статью могли вернуться и просмотреть снова полный список:
<p>
<strong>Title:</strong>
<%= @article.title %>
</p>
<p>
<strong>Text:</strong>
<%= @article.text %>
</p>
<%= link_to 'Back', articles_path %>
TIP: Если вам нужна ссылка на экшн того же контроллера, не нужно определять опцию :controller
, так как Rails по умолчанию использует текущий контроллер.
TIP: В режиме development (с которым вы работаете по умолчанию), Rails перегружает ваше приложение с каждым запросом браузера, так что не нужно останавливать и перезапускать веб-сервер при внесении изменений.
Добавим немного валидаций
Файл модели app/models/article.rb
выглядит проще простого:
class Article < ApplicationRecord
end
Не так уж много написано в этом файле, но заметьте, что класс Article
наследован от ApplicationRecord
. ApplicationRecord
наследуется от ActiveRecord::Base
, который обеспечивает огромную функциональность для ваших моделей Rails, включая основные операции для базы данных CRUD (Create, Read, Update, Destroy - создать, читать, обновить, уничтожить), валидации данных, сложную поддержку поиска и возможность устанавливать отношения между разными моделями.
Rails включает методы, помогающие проверить данные, которые вы передаете в модель. Откройте файл app/models/article.rb
и отредактируйте:
class Article < ApplicationRecord
validates :title, presence: true,
length: { minimum: 5 }
end
Эти изменения позволят быть уверенным, что все статьи имеют заголовок длиной как минимум пять символов. Rails может проверять разные условия в модели, включая существование или уникальность полей, их формат и существование связанных объектов. Подробнее валидации раскрыты в Валидации Active Record.
Теперь, когда есть валидации, при вызове @article.save
на невалидной статье, будет возвращен false
. Если снова открыть app/controllers/articles_controller.rb
, вы увидите, что мы не проверяем результат вызова @article.save
в экшне create
. Если в этой ситуации @article.save
не удастся, нам нужно снова показать форму пользователю. Для этого замените экшны new
и create
в app/controllers/articles_controller.rb
на эти:
def new
@article = Article.new
end
def create
@article = Article.new(article_params)
if @article.save
redirect_to @article
else
render 'new'
end
end
private
def article_params
params.require(:article).permit(:title, :text)
end
Теперь экшн new
создает новую переменную экземпляра по имени @article
, и вы увидите, зачем это, через пару абзацев.
Отметьте, что в экшне create
мы использовали render
вместо redirect_to
, когда save
возвращает false
. Метод render
использован, чтобы объект @article
был передан назад в шаблон new
, когда он будет отрендерен. Этот рендеринг выполняется в рамках того же запроса, что и отправка формы, в то время как redirect_to
сообщает браузеру выполнить другой запрос.
Если перезагрузите http://localhost:3000/articles/new и попытаетесь сохранить статью без заголовка, Rails вернет вас обратно на форму, но это не очень полезно. Вам нужно сказать пользователю, что что-то пошло не так. Для этого нужно модифицировать app/views/articles/new.html.erb
для проверки на сообщения об ошибке:
<%= form_with scope: :article, url: articles_path, local: true do |form| %>
<% if @article.errors.any? %>
<div id="error_explanation">
<h2>
<%= pluralize(@article.errors.count, "error") %> prohibited
this article from being saved:
</h2>
<ul>
<% @article.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<p>
<%= form.label :title %><br>
<%= form.text_field :title %>
</p>
<p>
<%= form.label :text %><br>
<%= form.text_area :text %>
</p>
<p>
<%= form.submit %>
</p>
<% end %>
<%= link_to 'Back', articles_path %>
Несколько вещей о том, что происходит. Мы проверяем, имеются ли какие-либо ошибки с помощью @article.errors.any?
, и в этом случае показываем список всех ошибок с помощью @article.errors.full_messages
.
pluralize
это хелпер rails, принимающий число и строку как аргументы. Если число больше одного, строка будет автоматически склонено во множественном числе.
Причина, по которой мы добавили @article = Article.new
в ArticlesController
, в том, что в противном случае @article
будет nil
во вьюхе, и вызов @article.errors.any?
вызовет ошибку.
TIP: Rails автоматически оборачивает поля, содержащие ошибку, в div с классом field_with_errors
. Можно определить правило css, чтобы сделать их выделяющимися.
Теперь у нас будет прекрасное сообщение об ошибке при сохранении статьи без заголовка, если попробуете так сделать в форме новой статьи http://localhost:3000/articles/new.
Обновление статей
Мы раскрыли часть "CR" от CRUD. Теперь сфокусируемся на части "U", обновлении (updating) статей.
Первым шагом следует добавить экшн edit
в ArticlesController
, как правило между экшнами new
и create
, как показано.
def new
@article = Article.new
end
def edit
@article = Article.find(params[:id])
end
def create
@article = Article.new(article_params)
if @article.save
redirect_to @article
else
render 'new'
end
end
Вьюха будет содержать форму, схожую с той, которую мы использовали при создании новых статей. Создайте файл с именем app/views/articles/edit.html.erb
и добавьте в него следующее:
<h1>Editing article</h1>
<%= form_with(model: @article, local: true) do |form| %>
<% if @article.errors.any? %>
<div id="error_explanation">
<h2>
<%= pluralize(@article.errors.count, "error") %> prohibited
this article from being saved:
</h2>
<ul>
<% @article.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<p>
<%= form.label :title %><br>
<%= form.text_field :title %>
</p>
<p>
<%= form.label :text %><br>
<%= form.text_area :text %>
</p>
<p>
<%= form.submit %>
</p>
<% end %>
<%= link_to 'Back', articles_path %>
Сейчас мы указываем форме на экшн update
, который пока не определен, но скоро мы это сделаем.
Передача объекта статьи в метод автомагически создаст url для отправки формы отредактированной статьи. Эта опция сообщает Rails, что мы хотим, чтобы эта форма была отправлена с помощью PATCH
, метода HTTP, от которого ожидается, что он используется для обновления ресурсов в соответствии с протоколом REST.
Аргументы form_with
могут быть объектами модели, например, model: @article
, который заставит хелпер заполнить форму полями объекта. Передача в пространство имен символа (scope: :article
) просто создает поля, но ничем не заполняет их. Подробнее в документации по form_with.
Затем нужно создать экшн update
в app/controllers/articles_controller.rb
. Добавьте его между экшном create
и методом private
:
def create
@article = Article.new(article_params)
if @article.save
redirect_to @article
else
render 'new'
end
end
def update
@article = Article.find(params[:id])
if @article.update(article_params)
redirect_to @article
else
render 'edit'
end
end
private
def article_params
params.require(:article).permit(:title, :text)
end
Новый метод, update
, используется, когда хотите обновить запись, которая уже существует, и он принимает хэш, содержащий атрибуты, которые вы хотите обновить. Как и прежде, если будет ошибка обновления статьи, мы хотим опять показать форму пользователю.
Мы заново использовали метод article_params
, который определили ранее для экшна create.
TIP: Не обязательно передавать все атрибуты в update
. К примеру, если был вызван @article.update(title: 'A new title')
, Rails обновит только атрибут title
, оставив все другие атрибуты нетронутыми.
Наконец, мы хотим показать ссылку на экшн edit
в списке всех статей, так что, давайте добавим ее в app/views/articles/index.html.erb
рядом с ссылкой "Show":
<table>
<tr>
<th>Title</th>
<th>Text</th>
<th colspan="2"></th>
</tr>
<% @articles.each do |article| %>
<tr>
<td><%= article.title %></td>
<td><%= article.text %></td>
<td><%= link_to 'Show', article_path(article) %></td>
<td><%= link_to 'Edit', edit_article_path(article) %></td>
</tr>
<% end %>
</table>
И также добавим в шаблон app/views/articles/show.html.erb
, чтобы ссылка "Edit" также была на странице статьи. Добавьте следующее в конце шаблона:
...
<%= link_to 'Edit', edit_article_path(@article) %> |
<%= link_to 'Back', articles_path %>
И вот как выглядит наше приложение сейчас:
Использование партиалов для очистки повторения во вьюхах
Наша страница edit
очень похожа на страницу new
, фактически они используют один и тот же код для отображения формы. Давайте уберем это дублирование, использовав партиал вьюхи. По соглашению, файлы партиалов начинаются со знака подчеркивания.
TIP: Подробнее о партиалах можно прочитать в руководстве Макеты и рендеринг в Rails.
Создайте новый файл app/views/articles/_form.html.erb
со следующим содержимым:
<%= form_with model: @article, local: true do |form| %>
<% if @article.errors.any? %>
<div id="error_explanation">
<h2>
<%= pluralize(@article.errors.count, "error") %> prohibited
this article from being saved:
</h2>
<ul>
<% @article.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<p>
<%= form.label :title %><br>
<%= form.text_field :title %>
</p>
<p>
<%= form.label :text %><br>
<%= form.text_area :text %>
</p>
<p>
<%= form.submit %>
</p>
<% end %>
Все, за исключением объявления form_with
, осталось тем же самым. Причина, по которой можно использовать это более короткое и простое объявление form_with
по сравнению с другим формами, в том, что @article
— это ресурс, соответствующий полному набору ресурсных маршрутов, и Rails способен определить, какой использовать URI и method. Подробнее об этом использовании form_with
смотрите в Resource-oriented style.
Давайте сейчас обновим вьюху app/views/articles/new.html.erb
, чтобы использовать этот новый партиал, переписав ее полностью:
<h1>New article</h1>
<%= render 'form' %>
<%= link_to 'Back', articles_path %>
И то же самое для вьюхи app/views/articles/edit.html.erb
:
<h1>Edit article</h1>
<%= render 'form' %>
<%= link_to 'Back', articles_path %>
Удаление статей
Теперь мы готовы раскрыть часть "D" от CRUD, удаление (deleting) из базы данных. Следуя соглашению REST, маршрут для удаления статей в результатах вывода bin/rails routes
следующий:
DELETE /articles/:id(.:format) articles#destroy
Метод роутинга delete
должен быть использован для маршрутов, уничтожающих ресурсы. Если бы его оставить обычным маршрутом get
, станет возможным создавать следующие злонамеренные URL:
<a href='http://example.com/articles/1/destroy'>look at this cat!</a>
Мы используем метод delete
для уничтожения ресурсов, и этот маршрут связывается с экшном destroy
в app/controllers/articles_controller.rb
, который еще не существует. Метод destroy
обычно последний экшн CRUD в контроллере, и подобно остальным публичным экшнам CRUD, он должен быть расположен перед любыми private
или protected
методами. Давайте его добавим:
def destroy
@article = Article.find(params[:id])
@article.destroy
redirect_to articles_path
end
Полностью ArticlesController
в файле app/controllers/articles_controller.rb
должен выглядеть теперь так:
class ArticlesController < ApplicationController
def index
@articles = Article.all
end
def show
@article = Article.find(params[:id])
end
def new
@article = Article.new
end
def edit
@article = Article.find(params[:id])
end
def create
@article = Article.new(article_params)
if @article.save
redirect_to @article
else
render 'new'
end
end
def update
@article = Article.find(params[:id])
if @article.update(article_params)
redirect_to @article
else
render 'edit'
end
end
def destroy
@article = Article.find(params[:id])
@article.destroy
redirect_to articles_path
end
private
def article_params
params.require(:article).permit(:title, :text)
end
end
Можно вызывать destroy
на объектах Active Record, когда вы хотите удалить их из базы данных. Отметьте, что нам не нужно добавлять вьюху для этого экшна, так как мы перенаправляем на экшн index
.
Наконец, добавим ссылку 'Destroy' в шаблон экшна index
(app/views/articles/index.html.erb
), собрав все ссылки вместе.
<h1>Listing Articles</h1>
<%= link_to 'New article', new_article_path %>
<table>
<tr>
<th>Title</th>
<th>Text</th>
<th colspan="3"></th>
</tr>
<% @articles.each do |article| %>
<tr>
<td><%= article.title %></td>
<td><%= article.text %></td>
<td><%= link_to 'Show', article_path(article) %></td>
<td><%= link_to 'Edit', edit_article_path(article) %></td>
<td><%= link_to 'Destroy', article_path(article),
method: :delete,
data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</table>
Тут мы используем link_to
другим образом. Мы передаем именованный маршрут как второй аргумент, и опции как иной аргумент. Опции method: :delete
и data: { confirm: 'Are you sure?' }
используются как атрибуты html5, поэтому при нажатии ссылки, Rails сначала покажет пользователю диалог подтверждения, а затем отправит ссылку с помощью метода delete
. Это выполняется с помощью файла JavaScript rails-ujs
, который автоматически включается в макет приложения (app/views/layouts/application.html.erb
) при генерации приложения. Без этого файла диалог подтверждения не будет показан.
TIP: Узнайте больше про ненавязчивый JavaScript в руководстве Работа с JavaScript в Rails.
Наши поздравления, теперь вы можете создавать, просматривать все и по отдельности, обновлять и уничтожать статьи.
TIP: В основном, Rails рекомендует использовать ресурсные объекты вместо объявления маршрутов вручную. Подробнее о роутинге смотрите Роутинг в Rails.
Добавляем вторую модель
Настало время добавить вторую модель в приложение. Вторая модель будет обрабатывать комментарии к статьям.
Генерируем модель
Мы намереваемся использовать тот же генератор, что мы использовали ранее при создании модели Article
. В этот раз мы создадим модель Comment
, содержащую ссылку на статью. Запустите следующую команду в терминале:
$ bin/rails generate model Comment commenter:string body:text article:references
Эта команда генерирует четыре файла:
Файл | Назначение |
---|---|
db/migrate/20140120201010_create_comments.rb | Миграция для создания таблицы comments в вашей базе данных (ваше имя файла будет включать другую временную метку) |
app/models/comment.rb | Модель Comment |
test/models/comment_test.rb | Каркас для тестирования модели комментария |
test/fixtures/comments.yml | Образцы комментариев для использования в тестировании |
Сначала взглянем на app/models/comment.rb
:
class Comment < ApplicationRecord
belongs_to :article
end
Это очень похоже на модель Article
, которую мы видели ранее. Разница в строчке belongs_to :article
, которая устанавливает связь Active Record. Вы ознакомитесь со связями в следующем разделе руководства.
Ключевое слово (:references
), использованное в команде bash, это специальный тип данных для моделей. Он создает новый столбец в вашей базе данных с именем представленной модели с добавленным _id
, который может содержать числовые значения. Чтобы лучше понять, проанализируйте файл db/schema.rb
после выполнения миграции.
В дополнение к модели, Rails также сделал миграцию для создания соответствующей таблицы базы данных:
class CreateComments < ActiveRecord::Migration[5.0]
def change
create_table :comments do |t|
t.string :commenter
t.text :body
t.references :article, foreign_key: true
t.timestamps
end
end
end
Строчка t.references
создает числовой столбец с именем article_id
, индекс для него, и ограничение внешнего ключа, указывающего на столбец id
таблицы articles
. Далее запускаем миграцию:
$ bin/rails db:migrate
Rails достаточно сообразителен, чтобы запускать только те миграции, которые еще не были запущены для текущей базы данных, в нашем случае Вы увидите:
== CreateComments: migrating =================================================
-- create_table(:comments)
-> 0.0115s
== CreateComments: migrated (0.0119s) ========================================
Связываем модели
Связи Active Record позволяют Вам легко объявлять отношения между двумя моделями. В случае с комментариями и статьями, вы можете описать отношения следующим образом:
- Каждый комментарий принадлежит одной статье.
- Одна статья может иметь много комментариев.
Фактически, это очень близко к синтаксису, который использует Rails для объявления этой связи. Вы уже видели строчку кода в модели Comment
(app/models/comment.rb), которая делает каждый комментарий принадлежащим статье:
class Comment < ApplicationRecord
belongs_to :article
end
Вам нужно отредактировать app/models/article.rb
, добавив другую сторону связи:
class Article < ApplicationRecord
has_many :comments
validates :title, presence: true,
length: { minimum: 5 }
[...]
end
Эти два объявления автоматически делают доступным большое количество возможностей. Например, если у вас есть переменная экземпляра @article
, содержащая статью, вы можете получить все комментарии, принадлежащие этой статье, в массиве, вызвав @article.comments
.
TIP: Более подробно о связях Active Record смотрите руководство Связи Active Record.
Добавляем маршрут для комментариев
Как в случае с контроллером welcome
, нам нужно добавить маршрут, чтобы Rails знал, по какому адресу мы хотим пройти, чтобы увидеть комментарии
. Снова откройте файл config/routes.rb
и отредактируйте его следующим образом:
resources :articles do
resources :comments
end
Это создаст comments
как вложенный ресурс в articles
. Это другая сторона захвата иерархических отношений, существующих между статьями и комментариями.
TIP: Более подробно о роутинге написано в руководстве Роутинг в Rails.
Генерируем контроллер
Имея модель, обратим свое внимание на создание соответствующего контроллера. Снова будем использовать то же генератор, что использовали прежде:
$ bin/rails generate controller Comments
Создадутся пять файлов и пустая директория:
Файл/Директория | Назначение |
---|---|
app/controllers/comments_controller.rb | Контроллер Comments |
app/views/comments/ | Вьюхи контроллера хранятся здесь |
test/controllers/comments_controller_test.rb | Тест для контроллера |
app/helpers/comments_helper.rb | Хелпер для вьюх |
app/assets/javascripts/comments.coffee | CoffeeScript для контроллера |
app/assets/stylesheets/comments.scss | Каскадная таблица стилей для контроллера |
Как и в любом другом блоге, наши читатели будут создавать свои комментарии сразу после прочтения статьи, и после добавления комментария они будут направляться обратно на страницу отображения статьи и видеть, что их комментарий уже отражен. В связи с этим, наш CommentsController
служит в качестве средства создания комментариев и удаления спама, если такой будет.
Сначала мы расширим шаблон Article show (app/views/articles/show.html.erb
), чтобы он позволял добавить новый комментарий:
<p>
<strong>Title:</strong>
<%= @article.title %>
</p>
<p>
<strong>Text:</strong>
<%= @article.text %>
</p>
<h2>Add a comment:</h2>
<%= form_with(model: [ @article, @article.comments.build ], local: true) do |form| %>
<p>
<%= form.label :commenter %><br>
<%= form.text_field :commenter %>
</p>
<p>
<%= form.label :body %><br>
<%= form.text_area :body %>
</p>
<p>
<%= form.submit %>
</p>
<% end %>
<%= link_to 'Edit', edit_article_path(@article) %> |
<%= link_to 'Back', articles_path %>
Это добавит форму на страницу отображения статьи, создающую новый комментарий при вызове экшна create
в CommentsController
. Тут вызов form_with
использует массив, что создаст вложенный маршрут, такой как /articles/1/comments
.
Давайте напишем create
в app/controllers/comments_controller.rb
:
class CommentsController < ApplicationController
def create
@article = Article.find(params[:article_id])
@comment = @article.comments.create(comment_params)
redirect_to article_path(@article)
end
private
def comment_params
params.require(:comment).permit(:commenter, :body)
end
end
Тут все немного сложнее, чем вы видели в контроллере для статей. Это побочный эффект вложения, которое вы настроили. Каждый запрос к комментарию отслеживает статью, к которой комментарий присоединен, таким образом сначала решаем вопрос с получением статьи, вызвав find
на модели Article
.
Кроме того, код пользуется преимуществом некоторых методов, доступных для связей. Мы используем метод create
на @article.comments
, чтобы создать и сохранить комментарий. Это автоматически связывает комментарий так, что он принадлежит к определенной статье.
Как только мы создали новый комментарий, мы возвращаем пользователя обратно на оригинальную статью, используя хелпер article_path(@article)
. Как мы уже видели, он вызывает экшн show
в ArticlesController
, который, в свою очередь, рендерит шаблон show.html.erb
. В этом месте мы хотим отображать комментарии, поэтому давайте добавим следующее в app/views/articles/show.html.erb
.
<p>
<strong>Title:</strong>
<%= @article.title %>
</p>
<p>
<strong>Text:</strong>
<%= @article.text %>
</p>
<h2>Comments</h2>
<% @article.comments.each do |comment| %>
<p>
<strong>Commenter:</strong>
<%= comment.commenter %>
</p>
<p>
<strong>Comment:</strong>
<%= comment.body %>
</p>
<% end %>
<h2>Add a comment:</h2>
<%= form_with(model: [ @article, @article.comments.build ], local: true) do |form| %>
<p>
<%= form.label :commenter %><br>
<%= form.text_field :commenter %>
</p>
<p>
<%= form.label :body %><br>
<%= form.text_area :body %>
</p>
<p>
<%= form.submit %>
</p>
<% end %>
<%= link_to 'Edit', edit_article_path(@article) %> |
<%= link_to 'Back', articles_path %>
Теперь в вашем блоге можно добавлять статьи и комментарии и отображать их в нужных местах.
Рефакторинг
Теперь, когда у нас есть работающие статьи и комментарии, взглянем на шаблон app/views/articles/show.html.erb
. Он стал длинным и неудобным. Давайте воспользуемся партиалами, чтобы разгрузить его.
Рендеринг коллекций партиалов
Сначала сделаем партиал для комментариев, показывающий все комментарии для статьи. Создайте файл app/views/comments/_comment.html.erb
и поместите в него следующее:
<p>
<strong>Commenter:</strong>
<%= comment.commenter %>
</p>
<p>
<strong>Comment:</strong>
<%= comment.body %>
</p>
Затем можно изменить app/views/articles/show.html.erb
вот так:
<p>
<strong>Title:</strong>
<%= @article.title %>
</p>
<p>
<strong>Text:</strong>
<%= @article.text %>
</p>
<h2>Comments</h2>
<%= render @article.comments %>
<h2>Add a comment:</h2>
<%= form_with(model: [ @article, @article.comments.build ], local: true) do |form| %>
<p>
<%= form.label :commenter %><br>
<%= form.text_field :commenter %>
</p>
<p>
<%= form.label :body %><br>
<%= form.text_area :body %>
</p>
<p>
<%= form.submit %>
</p>
<% end %>
<%= link_to 'Edit', edit_article_path(@article) %> |
<%= link_to 'Back', articles_path %>
Теперь это отрендерит партиал app/views/comments/_comment.html.erb
по разу для каждого комментария в коллекции @article.comments
. Так как метод render
перебирает коллекцию @article.comments
, он назначает каждый комментарий локальной переменной с именем, как у партиала, в нашем случае comment
, которая нам доступна в партиале для отображения.
Рендеринг партиальной формы
Давайте также переместим раздел нового комментария в свой партиал. Опять же, создайте файл app/views/comments/_form.html.erb
, содержащий:
<%= form_with(model: [ @article, @article.comments.build ], local: true) do |form| %>
<p>
<%= form.label :commenter %><br>
<%= form.text_field :commenter %>
</p>
<p>
<%= form.label :body %><br>
<%= form.text_area :body %>
</p>
<p>
<%= form.submit %>
</p>
<% end %>
Затем измените app/views/articles/show.html.erb
следующим образом:
<p>
<strong>Title:</strong>
<%= @article.title %>
</p>
<p>
<strong>Text:</strong>
<%= @article.text %>
</p>
<h2>Comments</h2>
<%= render @article.comments %>
<h2>Add a comment:</h2>
<%= render 'comments/form' %>
<%= link_to 'Edit', edit_article_path(@article) %> |
<%= link_to 'Back', articles_path %>
Второй render всего лишь определяет шаблон партиала, который мы хотим рендерить, comments/form
. Rails достаточно сообразительный, чтобы подставить подчеркивание в эту строку и понять, что Вы хотели рендерить файл _form.html.erb
в директории app/views/comments
.
Объект @article
доступен в любых партиалах, рендерируемых во вьюхе, так как мы определили его как переменную экземпляра.
Удаление комментариев
Другой важной особенностью блога является возможность удаления спама. Чтобы сделать это, нужно вставить некоторую ссылку во вьюхе и экшн destroy
в CommentsController
.
Поэтому сначала добавим ссылку для удаления в партиал app/views/comments/_comment.html.erb
:
<p>
<strong>Commenter:</strong>
<%= comment.commenter %>
</p>
<p>
<strong>Comment:</strong>
<%= comment.body %>
</p>
<p>
<%= link_to 'Destroy Comment', [comment.article, comment],
method: :delete,
data: { confirm: 'Are you sure?' } %>
</p>
Нажатие этой новой ссылки "Destroy Comment" запустит DELETE /articles/:article_id/comments/:id
в нашем CommentsController
, который затем будет использоваться для нахождения комментария, который мы хотим удалить, поэтому давайте добавим экшн destroy в наш контроллер (app/controllers/comments_controller.rb
):
class CommentsController < ApplicationController
def create
@article = Article.find(params[:article_id])
@comment = @article.comments.create(comment_params)
redirect_to article_path(@article)
end
def destroy
@article = Article.find(params[:article_id])
@comment = @article.comments.find(params[:id])
@comment.destroy
redirect_to article_path(@article)
end
private
def comment_params
params.require(:comment).permit(:commenter, :body)
end
end
Экшн destroy
найдет статью, которую мы просматриваем, обнаружит комментарий в коллекции @article.comments
и затем уберет его из базы данных и вернет нас обратно на просмотр статьи.
Удаление связанных объектов
Если удаляете статью, связанные с ней комментарии также должны быть удалены, в противном случае они будут просто занимать место в базе данных. Rails позволяет использовать опцию dependent
на связи для достижения этого. Измените модель Article, app/models/article.rb
, следующим образом:
class Article < ApplicationRecord
has_many :comments, dependent: :destroy
validates :title, presence: true,
length: { minimum: 5 }
[...]
end
Безопасность
(Basic Authentication) Простая аутентификация
Если вы опубликуете свой блог онлайн, любой сможет добавлять, редактировать и удалять статьи или удалять комментарии.
Rails предоставляет очень простую аутентификационную систему HTTP, которая хорошо работает в этой ситуации.
В ArticlesController
нам нужен способ блокировать доступ к различным экшнам, если пользователь не аутентифицирован. Тут мы можем использовать метод Rails http_basic_authenticate_with
, разрешающий доступ к требуемым экшнам, если метод позволит это.
Чтобы использовать систему аутентификации, мы определим ее вверху нашего ArticlesController
в app/controllers/articles_controller.rb
. В нашем случае, мы хотим, чтобы пользователь был аутентифицирован для каждого экшна, кроме index
и show
, поэтому напишем так:
class ArticlesController < ApplicationController
http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show]
def index
@articles = Article.all
end
# пропущено для краткости
Мы также хотим позволить только аутентифицированным пользователям удалять комментарии, поэтому в CommentsController
(app/controllers/comments_controller.rb
) мы напишем:
class CommentsController < ApplicationController
http_basic_authenticate_with name: "dhh", password: "secret", only: :destroy
def create
@article = Article.find(params[:article_id])
# ...
end
# пропущено для краткости
Теперь, если попытаетесь создать новую статью, то встретитесь с простым вызовом аутентификации HTTP:
Также для приложений Rails доступны иные методы аутентификации. Двумя популярными аддонами для Rails, среди прочих, являются Devise и Authlogic.
Прочие мысли о безопасности
Безопасность, особенно в веб-приложениях, это обширная и детализированная область. Безопасность вашего приложения Rails раскрывается более детально в Руководстве по безопасности Ruby on Rails.
Что дальше?
После того, как вы создали свое первое приложение на Rails, можете свободно его модифицировать и экспериментировать на свое усмотрение.
Помните, что вы не сможете сделать без посторонней помощи все, что угодно. Так же, как вы обращались к этому руководству "Rails для начинающих", далее можете так же свободно пользоваться этими ресурсами:
- Ruby on Rails Guides
- Ruby on Rails Tutorial
- Ruby on Rails mailing list
- Канал #rubyonrails на irc.freenode.net
Отдельно хотелось бы выделить и поддержать следующие хорошие русскоязычные ресурсы по Ruby on rails:
Ошибки конфигурации
Простейший способ работы с Rails заключается в хранении всех внешних данных в UTF-8. Если не так, библиотеки Ruby и Rails часто будут способны конвертировать ваши родные данные в UTF-8, но это не всегда надежно работает, поэтому лучше быть уверенным, что все внешние данные являются UTF-8.
Если вы допускаете ошибку в этой области, наиболее обычным симптомом является черный ромбик со знаком вопроса внутри, появляющийся в браузере. Другим обычным симптомом являются символы, такие как "ü" появляющиеся вместо "ü". Rails предпринимает ряд внутренних шагов для смягчения общих случаев тех проблем, которые могут быть автоматически обнаружены и исправлены. Однако, если имеются внешние данные, не хранящиеся в UTF-8, это может привести к такого рода проблемам, которые не могут быть автоматически обнаружены Rails и исправлены.
Два наиболее обычных источника данных, которые не в UTF-8:
- Ваш текстовый редактор: Большинство текстовых редакторов (такие как TextMate), по умолчанию сохраняют файлы как UTF-8. Если ваш текстовый редактор так не делает, это может привести к тому, что специальные символы, введенные в ваши шаблоны (такие как é) появятся как ромбик с вопросительным знаком в браузере. Это также касается ваших файлов перевода i18N. Большинство редакторов, не устанавливающие по умолчанию UTF-8 (такие как некоторые версии Dreamweaver) предлагают способ изменить умолчания на UTF-8. Сделайте так.
- Ваша база данных: Rails по умолчанию преобразует данные из вашей базы данных в UTF-8 на границе. Однако, если ваша база данных не использует внутри UTF-8, она может не быть способной хранить все символы, которые введет ваш пользователь. Например, если ваша база данных внутри использует Latin-1, и ваш пользователь вводит русские, ивритские или японские символы, данные будут потеряны как только попадут в базу данных. Если возможно, используйте UTF-8 как внутреннее хранилище в своей базе данных.