Разрабатываем быстро

После того, как вы увидели, как создать контроллер, экшн и вьюху, давайте создадим что-то более вещественное.

Теперь в приложении Blog, мы создадим новый ресурс. Ресурс — это термин, обозначающий коллекцию схожих объектов, таких как публикации, люди или животные. Можно создавать, читать, обновлять и уничтожать элементы для ресурса, и эти операции называются операциями CRUD (create, read, update, destroy).

В этом разделе мы добавим возможность создания новых публикаций, и сможем просматривать их. Это буквы “C” и “R” из CRUD: creation и reading. Форма для этого будет выглядеть так:

Форма новой публикации

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

Основы

Первая вещь, которую нам нужно сделать, это создать новую публикацию. Наиболее подходящим местом будет /posts/new. Если попытаться перейти туда сейчас — посетив http://localhost:3000/posts/new — Rails выдаст ошибку роутинга:

Ошибка роутинга, no route matches /posts/new

Это так, поскольку ничто в маршрутах приложения — определяемых в config/routes.rb — не определяет этот маршрут. По умолчанию в Rails вообще нет настроенных маршрутов, кроме корневого маршрута, определенного ранее, поэтому необходимо определять маршруты, как только они станут вам нужны.

Для этого необходимо создать маршрут в файле config/routes.rb, в новой строчке между do и end метода draw:

get "posts/new"

Это очень простой маршрут: он определяет новый маршрут, откликающийся на запросы GET, и этот маршрут posts/new. Но как он узнает, куда идти без использования опции :to? Тут Rails использует разумные значения по умолчанию: Rails полагает, что вы хотите, чтобы этот маршрут вел в экшн new контроллера posts.

С определенным маршрутом можно делать запрос /posts/new к приложению. Перейдите к http://localhost:3000/posts/new, и вы увидите другую ошибку роутинга:

Другая ошибка роутинга, uninitialized constant PostsController

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

$ rails g controller posts

Если открыть только что созданный app/controllers/posts_controller.rb, можно увидеть абсолютно пустой контроллер:

class PostsController < ApplicationController
end

Контроллер — это просто класс, унаследованный от ApplicationController. В этом классе вы определить методы, которые станут экшнами для этого контроллера. Эти экшны будут выполнять операции CRUD с публикациями в вашей системе.

Если теперь обновить http://localhost:3000/posts/new, вы увидите новую ошибку:

Unknown action new for PostsController!

Эта ошибка показывает, что Rails не может найти экшн new внутри PostsController, который был только что создан. Это так, поскольку контроллеры в Rails при создании пустые по умолчанию, если вы не указали желаемые экшны при процессе генерации.

Чтобы вручную определить экшн в контроллере, все что нужно — это определить в нем новый метод. Откройте app/controllers/posts_controller.rb и в классе PostsController определите метод new:

def new
end

С методом new, определенным в PostsController, если обновите http://localhost:3000/posts/new, увидите другую ошибку:

Template is missing for posts/new

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

В вышеприведенном изображении конец строки был обрезан. Давайте посмотрим на полный текст:

Missing template posts/new, application/new with {:locale=>[:en], :formats=>[:html], :handlers=>[:erb, :builder, :coffee]}. Searched in: * “/path/to/blog/app/views”

Как много букв! Давайте быстро пробежимся и поймем, что означает каждая часть.

Первая часть указывает, какой шаблон отсутствует. В нашем случае, шаблон posts/new. Rails сперва ищет этот шаблон. Если не находит, он пытается загрузить шаблон с именем application/new. Он так ищет, поскольку PostsController унаследован от ApplicationController.

Следующая часть сообщения содержит хэш. Ключ :locale в этом хэше просто показывает, на каком языке должен быть получен шаблон. По умолчанию это английский шаблон — или “en”. Следующий ключ :formats определяет формат шаблона для отдачи в отклик. Формат по умолчанию :html, таким образом, Rails ищет шаблон HTML. Последний ключ, :handlers, говорит нам, что для рендеринга нашего шаблона могут быть использованы обработчики шаблон. :erb в основном используется для шаблонов HTML, :builder используется для шаблонов XML, и :coffee использует CoffeeScript для создания шаблонов JavaScript.

Заключительная часть этого сообщения говорит нам, где Rails искал шаблоны. Шаблоны в простом приложении Rails, таком как наше, содержатся в одном месте, но в более сложных приложениях, они могут находиться в разных местах.

Простейшим шаблоном, работающим в данном случае, будет расположенный в app/views/posts/new.html.erb. Расширение этого файла является ключом: первое расширение это формат шаблона, а второе расширение это обработчик, который будет использован. Rails пытается найти шаблон с именем posts/new в app/views приложения. Форматом для этого шаблона может быть только html, а обработчиком должен быть один из erb, builder или coffee. Поскольу мы хотим создать новую форму HTML, будем использовать язык ERB. Следовательно, файл должен называться posts/new.html.erb, и должен быть расположен в директории app/views приложения.

Создайте новый файл app/views/posts/new.html.erb и поместите в него:

<h1>New Post</h1>

Теперь при обновлении http://localhost:3000/posts/new вы увидите, что у страницы появился заголовок. Теперь маршрут, контроллер, экшн и вьюха гармонично работают. Время создать форму для новой публикации.

Первая форма

Для создания формы в этом шаблоне, мы будем использовать form builder. Основной form builder для Rails представлен методом хелпера по имени form_for. Для использования этого метода добавьте код в app/views/posts/new.html.erb:

<%= form_for :post do |f| %>
  <p>
    <%= f.label :title %><br>
    <%= f.text_field :title %>
  </p>

  <p>
    <%= f.label :text %><br>
    <%= f.text_area :text %>
  </p>

  <p>
    <%= f.submit %>
  </p>
<% end %>

Если теперь обновить страницу, вы увидите точно такую форму как в начале примера. Создание форм в Rails, действительно, очень простое!

При вызове form_for, вы передали в него определяющий объект для этой формы. В нашем случае это символ :post. Это сообщает хелперу form_for, для чего эта форма. Внутри блока для этого метода, объект FormBuilder — представленный как f — используется для создания двух меток и двух текстовых полей, по одному для заголовка и текста публикации. Наконец, вызов submit на объекте f создаст кнопку отправки формы.

Хотя, у этой формы есть одна проблема. Если посмотрите на созданный HTML, просмотрев исходник страницы, то увидите у формы атрибут action, указывающий на /posts/new. Это проблема, поскольку этот маршрут ведет на ту же самую страницу, и этот маршрут должен использоваться только для отображения формы для новой публикации.

Форме нужно использовать иной URL, чтобы вести куда-то еще. Это можно быстро сделать с помощью опции :url для form_for. Обычно в Rails, экшн, используемый для подтверждения формы new, такой как эта, называется “create”, поэтому форма должна указывать на этот экшн.

Отредактируйте строчку form_for в app/views/posts/new.html.erb следующим образом:

<%= form_for :post, :url => { :action => :create } do |f| %>

В этом примере объект Hash передан в опцию :url. Rails его обработает так, что форма будет указывать на экшн create текущего контроллера PostsController, и пошлет запрос POST к этому маршруту. Чтобы это сработало, нужно добавить маршрут в config/routes.rb, сразу за “posts/new”:

post "posts/create"

При использовании метода post вместо метода get, Rails определит маршрут, отвечающий только на методы POST. Метод POST является обычным методом, используемым формами во всем вебе.

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

Unknown action create for PostsController

Чтобы это заработало, нужно создать экшн create в PostsController.

Создание публикаций

Чтобы убрать “Unknown action”, нужно определить экшн create в классе PostsController в app/controllers/posts_controller.rb, ниже экшна new:

class PostsController < ApplicationController
  def new
  end

  def create
  end

end

Если теперь переотправить форму, вы увидите другую знакомую ошибку: отсутствует шаблон. Это нормально, сейчас это можно проигнорировать. Экшн create должен только сохранять нашу публикацию в базу данных.

При отправке формы, ее поля будут посланы в Rails как параметры. К этим параметрам можно обратиться из экшнов контроллера, как правило, для выполнения определенных задач. Чтобы увидеть, на что похожи эти параметры, измените экшн create так:

def create
  render :text => params[:post].inspect
end

Тут метод render принимает очень простой хэш с ключом text и значением params[:post].inspect. Метод params это объект, представляющий параметры (или поля), приходящие от формы. Метод params возвращает объект HashWithIndifferentAccess, позволяющий получать доступ к ключам хэша с использованием или строк, или символов. В этой ситуации имеют значение только параметры, пришедшие от формы.

Если еще раз переотправить форму, вы больше не увидите ошибку об отсутствующем шаблоне. Вместо этого вы увидите что-то вроде следующего:

{"title"=>"First post!", "text"=>"This is my first post."}

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

Создание модели Post

Модели в Rails используют имя в единственном числе, а их соответствующая таблица в базе данных — имя во множественном числе. Rails предоставляет генератор для создания моделей, которым пользуются большинство разработчиков на Rails для создания новых моделей. Для создания новой модели, запустите эту команду в своем терминале:

$ rails generate model Post title:string text:text

С помощью этой команды мы сообщаем Rails что хотим модель Post с атрибутом title строкового типа и атрибутом text текстового типа. Эти атрибуты автоматически добавятся в таблицу posts и привяжутся к модели Post.

Rails в ответ создаст ряд файлов. Сейчас нам интересны только app/models/post.rb и db/migrate/20120419084633_create_posts.rb (у вас имя может немного отличаться). Последний ответственен за создание структуры базы данных, поэтому мы и рассмотрим его далее.

Active Record достаточно сообразителен, чтобы автоматически связать имена столбцов с атрибутами модели, что означает, что внутри моделей Rails не нужно объявлять атрибуты, Active Record сделает это автоматически.

Запуск миграции

Как вы уже видели, rails generate model создал файл миграции базы данных в директории db/migrate. Миграции – это класс Ruby, разработанный для того, чтобы было просто создавать и модифицировать таблицы базы данных. Rails использует команды rake для запуска миграций, и возможна отмена миграции после того, как она была применена к вашей базе данных. Имя файла миграции включает временную метку, чтобы быть уверенным, что они выполняются в той последовательности, в которой они создавались.

Если Вы заглянете в файл db/migrate/20120419084633_create_posts.rb (помните, у вас файл имеет немного другое имя), вот что там обнаружите:

class CreatePosts < ActiveRecord::Migration
  def change
    create_table :posts do |t|
      t.string :title
      t.text :text

      t.timestamps
    end
  end
end

Эта миграция создает метод change, вызываемый при запуске этой миграции. Действие, определенное в этой миграции, также является обратимым, что означает, что Rails знает, как отменить изменения, сделанные этой миграцией, в случае, если вы решите их отменить позже. Когда вы запустите эту миграцию, она создаст таблицу posts со строковым столбцом и текстовым столбцом. Она также создаст два поля временных меток для отслеживания времени создания и обновления публикации. Подробнее о миграциях Rails можно прочесть в руководстве Миграции базы данных Rails.

Сейчас нам нужно использовать команду rake, чтобы запустить миграцию:

$ rake db:migrate

Rails запустит эту команду миграции и сообщит, что он создал таблицу Posts.

==  CreatePosts: migrating ====================================================
-- create_table(:posts)
   -> 0.0019s
==  CreatePosts: migrated (0.0020s) ===========================================

Так как вы работаете по умолчанию в среде development, эта команда будет применена к базе данных, определенной в секции development вашего файла config/database.yml. Если хотите запустить миграции в другой среде, например в production, следует явно передать ее при вызове команды: rake db:migrate RAILS_ENV=production.

Сохранение данных в контроллере

Возвратимся к posts_controller, нам нужно изменить экшн create, чтобы использовать новую модель Post для сохранения данных в базе данных. Откройте этот файл и измените экшн create следующим образом:

def create
  @post = Post.new(params[:post])

  @post.save
  redirect_to :action => :show, :id => @post.id
end

Вот что тут происходит: каждая модель Rails может быть инициализирована с помощью соответствующих атрибутов, которые будут автоматически привязаны к соответствующим столбцам базы данных. В первой строчке мы как раз это и делаем (помните, что params[:post] содержит интересующие нас атрибуты). Затем @post.save ответственен за сохранение модели в базу данных. Наконец, мы перенаправляем пользователя на экшн show, который мы определим позже.

Как мы увидим далее, @post.save возвращает булево значение, показывающее, была ли сохранена модель, или нет.

Отображение публикаций

Если снова отправить форму, Rails сообщит о не найденом экшне show. Нам это уже не нужно, поэтому давайте добавим экшн show до того. Откройте config/routes.rb и добавьте следующий маршрут:

get "posts/:id" => "posts#show"

Специальный синтаксис :id сообщит rails, что этот маршрут ожидает параметр :id, который в нашем случае будет идентификатором публикации. Отметьте, что в этот раз мы определили фактическую привязку, posts#show, поскольку в ином случае Rails не узнает, какой рендерить экшн.

Как и раньше, нам необходим экшн show в posts_controller и его соответствующая вьюха.

def show
  @post = Post.find(params[:id])
end

Нужно отметить несколько вещей. Мы использовали Post.find для поиска публикации, в которой мы заинтересованы. Также мы использовали переменную экземпляра (с префиксом @) для хранения ссылки на объект публикации. Мы сделали так, потому что Rails передаст все переменные экземпляра во вьюху.

Теперь создайте новый файл app/view/posts/show.html.erb со следующим содержимым:

<p>
  <strong>Title:</strong>
  <%= @post.title %>
</p>

<p>
  <strong>Text:</strong>
  <%= @post.text %>
</p>

Наконец, если перейти на http://localhost:3000/posts/new, вы сможете создать новую публикацию. Попробуйте!

Show action for posts

Отображение всех публикаций

Нам все еще нужен способ для отображения списка всех наших публикаций, давайте сделаем его. Как обычно, нам нужен маршрут, помещенный в config/routes.rb:

get "posts" => "posts#index"

И экшн для этого маршрута внутри PostsController в файле app/controllers/posts_controller.rb:

def index
  @posts = Post.all
end

И, наконец, вьюха для этого экшна, расположенная в app/views/posts/index.html.erb:

<h1>Listing posts</h1>

<table>
  <tr>
    <th>Title</th>
    <th>Text</th>
  </tr>

  <% @posts.each do |post| %>
    <tr>
      <td><%= post.title %></td>
      <td><%= post.text %></td>
    </tr>
  <% end %>
</table>

Теперь, если перейти в http://localhost:3000/posts, можно увидеть список всех публикаций, которые вы уже создали.

Добавление ссылки

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

Откройте app/views/welcome/index.html.erb и измените его следующим образом:

<h1>Hello, Rails!</h1>
<%= link_to "My Blog", :controller => "posts" %>

Метод link_to – один из встроенных хелперов Rails. Он создает гиперссылку, на основе текста для отображения и указания куда перейти – в нашем случае путь для контроллера posts.

Давайте добавим ссылки и в другие вьюхи, начнем с добавления ссылки “New Post” в app/views/posts/index.html.erb, поместив ее над тегом <table>:

<%= link_to 'New post', :action => :new %>

Эта ссылка позволит перейти на форму для создания новой публикации. Также следует добавить ссылку в этот шаблон — app/views/posts/new.html.erb — чтобы вернуться обратно в экшн index. Добавьте ее под формой в этом шаблоне:

<%= form_for :post do |f| %>
  ...
<% end %>

<%= link_to 'Back', :action => :index %>

Наконец, добавьте также ссылку в шаблон app/views/posts/show.html.erb, чтобы также вернуться в экшн index, чтобы просматривающие отдельную публикацию могли вернуться и просмотреть снова полный список:

<p>
  <strong>Title:</strong>
  <%= @post.title %>
</p>

<p>
  <strong>Text:</strong>
  <%= @post.text %>
</p>

<%= link_to 'Back', :action => :index %>

Если вам нужна ссылка на экшн того же контроллера, не нужно определять опцию :controller, так как Rails по умолчанию использует текущий контроллер.

В режиме development (с которым вы работаете по умолчанию), Rails перегружает ваше приложение с каждым запросом браузера, так что не нужно останавливать и перезапускать веб-сервер при внесении изменений.

Обновление полей

Файл модели app/models/post.rb выглядит проще простого:

class Post < ActiveRecord::Base
end

Не так уж много написано в этом файле, но заметьте, что класс Post наследован от ActiveRecord::Base. Active Record обеспечивает огромную функциональность для Ваших моделей Rails, включая основные операции для базы данных CRUD (Create, Read, Update, Destroy – создать, читать, обновить, уничтожить), валидации данных, сложную поддержку поиска и возможность устанавливать отношения между разными моделями.

Rails включает методы, помогающие обезопасить некоторые из полей модели. Откройте файл app/models/post.rb и отредактируйте:

class Post < ActiveRecord::Base
  attr_accessible :text, :title
end

Это изменение позволяет быть уверенным, что все изменения, сделанные с помощью формы HTML, смогут отредактировать содержимое полей text и title. Будет невозможно определить иные поля через форму. Конечно, их все еще можно опредить, вызвав метод `field=`. Открытые атрибуты и проблема массового назначения подробно раскрыты в Руководстве по безопасности

Добавим немного валидации

Rails включает методы, помогающие проверить данные, которые вы передаете в модель. Откройте файл app/models/post.rb и отредактируйте:

class Post < ActiveRecord::Base
  attr_accessible :text, :title

  validates :title, :presence => true,
                    :length => { :minimum => 5 }
end

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

Теперь, когда есть валидации, при вызове @post.save на невалидной публикации, будет возвращен false. Если снова открыть app/controllers/posts_controller.rb, вы увидите, что мы не проверяем результат вызова @post.save в экшне create. Если в этой ситуации @post.save не удастся, нам нужно снова показать форму пользователю. Для этого замените экшны new и create в app/controllers/posts_controller.rb на эти:

def new
  @post = Post.new
end

def create
  @post = Post.new(params[:post])

  if @post.save
    redirect_to :action => :show, :id => @post.id
  else
    render 'new'
  end
end

Теперь экшн new создает новую переменную экземпляра по имени @post, и вы увидите, зачем это, через пару абзацев.

Отметьте, что в экшне create мы использовали render вместо redirect_to, когда save возвращает false. Метод render использован, чтобы объект @post был передан назад в шаблон new, когда он будет отрендерен. Этот рендеринг выполняется в рамках того же запроса, что и отправка формы, в то время как redirect_to сообщает браузеру выполнить другой запрос.

Если перезагрузите http://localhost:3000/posts/new и попытаетесь сохранить публикацию без заголовка, Rails вернет вас обратно на форму, но это не очень полезно. Вам нужно сказать пользователю, что что-то пошло не так. Для этого нужно модифицировать app/views/posts/new.html.erb для проверки на сообщения об ошибке:

<%= form_for :post, :url => { :action => :create } do |f| %>
  <% if @post.errors.any? %>
  <div id="errorExplanation">
    <h2><%= pluralize(@post.errors.count, "error") %> prohibited
        this post from being saved:</h2>
    <ul>
    <% @post.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
  <% end %>
  <p>
    <%= f.label :title %><br>
    <%= f.text_field :title %>
  </p>

  <p>
    <%= f.label :text %><br>
    <%= f.text_area :text %>
  </p>

  <p>
    <%= f.submit %>
  </p>
<% end %>

<%= link_to 'Back', :action => :index %>

Несколько вещей о том, что происходит. Мы проверяем, имеются ли какие-либо ошибки с помощью @post.errors.any?, и в этом случае показываем список всех ошибок с помощью @post.errors.full_messages.

pluralize это хелпер rails, принимающий число и строку как аргументы. Если число больше одного, строка будет автоматически склонено во множественном числе.

Причина, по которой мы добавили @post = Post.new в posts_controller, в том, что в противном случае @post будет nil во вьюхе, и вызов @post.errors.any? вызовет ошибку.

Rails автоматически оборачивает поля, содержащие ошибку, в div с классом field_with_errors. Можно определить правило css, чтобы сделать их выделяющимися.

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

Форма с ошибками

Обновление публикаций

Мы раскрыли часть “CR” от CRUD. Теперь сфокусируемся на части “U”, обновлении (updating) публикаций.

Первым шагом следует добавить экшн edit в posts_controller.

Начнем с добавления маршрута в config/routes.rb:

get "posts/:id/edit" => "posts#edit"

А затем добавим экшн контроллера:

def edit
  @post = Post.find(params[:id])
end

Вьюха будет содержать форму, схожую с той, которую мы использовали ри создании новых публикаций. Создайте файл с именем app/views/posts/edit.html.erb и добавьте в него следующее:

<h1>Editing post</h1>

<%= form_for :post, :url => { :action => :update, :id => @post.id },
:method => :put do |f| %>
  <% if @post.errors.any? %>
  <div id="errorExplanation">
    <h2><%= pluralize(@post.errors.count, "error") %> prohibited
        this post from being saved:</h2>
    <ul>
    <% @post.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
  <% end %>
  <p>
    <%= f.label :title %><br>
    <%= f.text_field :title %>
  </p>

  <p>
    <%= f.label :text %><br>
    <%= f.text_area :text %>
  </p>

  <p>
    <%= f.submit %>
  </p>
<% end %>

<%= link_to 'Back', :action => :index %>

Сейчас мы указываем форме на экшн update, который пока не определен, но скоро мы это сделаем.

Опция :method => :put говорит Rails, что мы хотим, чтобы эта форма была отправлена с помощью PUT, метода HTTP, от которого ожидается, что он используется для обновления ресурсов в соответствии с протоколом REST.

По умолчанию, формы, созданные с помощью хелпера form_for_, отсылаются через POST+.

Затем нам нужен экшн update. В файле config/routes.rb нам нужна лишь одна строчка:

put "posts/:id" => "posts#update"

А затем создайте экшн update в app/controllers/posts_controller.rb:

def update
  @post = Post.find(params[:id])

  if @post.update_attributes(params[:post])
    redirect_to :action => :show, :id => @post.id
  else
    render 'edit'
  end
end

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

Вам не нужно передавать все атрибуты в update_attributes. к примеру, если вызовите @post.update_attributes(:title => 'A new title') Rails обновит только атрибут title, оставив все другие атрибуты нетронутыми.

Наконец, мы хотим показать ссылку на экшн edit в списке всех публикаций, так что, давайте добавим ее в app/views/posts/index.html.erb рядом с ссылкой “Show”:

<table>
  <tr>
    <th>Title</th>
    <th>Text</th>
    <th></th>
    <th></th>
  </tr>

<% @posts.each do |post| %>
  <tr>
    <td><%= post.title %></td>
    <td><%= post.text %></td>
    <td><%= link_to 'Show', :action => :show, :id => post.id %></td>
    <td><%= link_to 'Edit', :action => :edit, :id => post.id %></td>
  </tr>
<% end %>
</table>

И также добавим в шаблон app/views/posts/show.html.erb, чтобы ссылка “Edit” также была на странице публикации. Добавьте следующее в конце шаблона:

...


<%= link_to 'Back', :action => :index %>
| <%= link_to 'Edit', :action => :edit, :id => @post.id %>

И вот как выглядит наше приложение сейчас:

Экшн index с ссылкой на редактирование

Использование партиалов для очистки повторения во вьюхах

Партиалы это то, что использует Rails для устранения повторения во вьюхах. Вот простой пример:

# app/views/user/show.html.erb

<h1><%= @user.name %></h1>

<%= render 'user_details' %>

# app/views/user/_user_details.html.erb

<%= @user.location %>

<%= @user.about_me %>

Шаблон users/show автоматически включит содержимое шаблона users/_user_details. Отметьте, что партиалы начинаются со знака подчеркивания, чтобы их не спутать с обычными вьюхами. Однако, не нужно включать подчеркивание при включении их с помощью метода хелпера.

Подробнее о партиалах можно прочитать в руководстве Макеты и рендеринг в Rails.

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

Создайте новый файл app/views/posts/_form.html.erb со следующим содержимым:

<%= form_for @post do |f| %>
  <% if @post.errors.any? %>
  <div id="errorExplanation">
    <h2><%= pluralize(@post.errors.count, "error") %> prohibited
      this post from being saved:</h2>
    <ul>
    <% @post.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
  <% end %>
  <p>
    <%= f.label :title %><br>
    <%= f.text_field :title %>
  </p>

  <p>
    <%= f.label :text %><br>
    <%= f.text_area :text %>
  </p>

  <p>
    <%= f.submit %>
  </p>
<% end %>

Все, за исключением объявления form_for, осталось тем же самым. Как form_for может определить правильные атрибуты action и method при создании формы будет объяснено немного позже. А сейчас давайте обновим вьюху app/views/posts/new.html.erb, чтобы использовать этот новый партиал, переписав ее полностью:

<h1>New post</h1>

<%= render 'form' %>

<%= link_to 'Back', :action => :index %>

И то же самое для вьюхи app/views/posts/edit.html.erb:

<h1>Edit post</h1>

<%= render 'form' %>

<%= link_to 'Back', :action => :index %>

Направьте свой браузер на http://localhost:3000/posts/new и попытайтесь создать новую публикацию. Все работает. Теперь попытайтесь отредактировать публикацию, и вы получите следующую ошибку:

Undefined method post_path

Чтобы понять эту ошибку, следует понять, как работает form_for. При передаче объекта в form_for, если не указать опцию :url, Rails попытается угадать опции action и method, проверив, является ли переданный объект новой записью или нет. Rails следует соглашниям REST, поэтому для создания нового объекта Post он ищет маршрут с именем posts_path, а для обновления объекта Post он ищет маршрут с именем post_path, и передает текущий объект. Подобным образом, rails знает, что он должен создавать новые объекты через POST и обновлять их через PUT.

Если запустите rake routes из консоли, то увидите, что уже имеется маршрут posts_path, который был создан автоматически Rails при определении маршрута для экшна index. Однако, у нас нет еще post_path, по этой причине мы и получили прежнюю ошибку.

# rake routes

       posts GET  /posts(.:format)            posts#index
   posts_new GET  /posts/new(.:format)        posts#new
posts_create POST /posts/create(.:format)     posts#create
             GET  /posts/:id(.:format)        posts#show
             GET  /posts/:id/edit(.:format)   posts#edit
             PUT  /posts/:id(.:format)        posts#update
        root      /                           welcome#index

Чтобы это исправить, откройте config/routes.rb и измените строчку get "posts/:id" следующим образом:

get "posts/:id" => "posts#show", :as => :post

Опция :as говорит методу get, что мы хотим сделать доступными в нашем приложении маршрутные хелперы с именем post_url и post_path. Это как раз те методы, в которых нуждается form_for при редактировании публикации, таким образом вы можете снова обновлять публикации.

Опция :as доступна также для маршрутных методов post, put, delete и match.

Удаление публикаций

Теперь мы готовы раскрыть часть “D” от CRUD, удаление (deleting) из базы данных. Следуя соглашению REST, мы собираемся добавить маршрут для удаления публикаций в config/routes.rb:

delete "posts/:id" => "posts#destroy"

Метод роутинга delete должен быть использован для маршрутов, уничтожающих ресурсы. Если бы его оставить обычным маршрутом get, станет возможным создавать следующие злонамеренные URL:

<a href='http://yoursite.com/posts/1/destroy'>посмотри на этого котенка!</a>

Мы используем метод delete для уничтожения ресурсов, и этот маршрут связывается с экшном destroy в app/controllers/posts_controller.rb, который еще не существует, но представлен ниже:

def destroy
  @post = Post.find(params[:id])
  @post.destroy

  redirect_to :action => :index
end

Можно вызывать destroy на объектах Active Record, когда вы хотите удалить их из базы данных. Отметьте, что нам не нужно добавлять вьюху для этого экшна, так как мы перенаправляем на экшн index.

Наконец, дабавим ссылку ‘destroy’ в шаблон экшна index (+app/views/posts/index.html.erb), собрав все ссылки вместе.

<h1>Listing Posts</h1>
<table>
  <tr>
    <th>Title</th>
    <th>Text</th>
    <th></th>
    <th></th>
    <th></th>
  </tr>

<% @posts.each do |post| %>
  <tr>
    <td><%= post.title %></td>
    <td><%= post.text %></td>
    <td><%= link_to 'Show', :action => :show, :id => post.id %></td>
    <td><%= link_to 'Edit', :action => :edit, :id => post.id %></td>
    <td><%= link_to 'Destroy', { :action => :destroy, :id => post.id }, :method => :delete, :confirm => 'Are you sure?' %></td>
  </tr>
<% end %>
</table>

Тут мы используем link_to другим образом. мы обернули атрибуты :action и :id в хэш, таким образом мы можем передать эти два ключа как первый аргумент, а оследние два ключа как другой аргумент. Опции :method и :confirm используются как атрибуты html5, поэтому при нажатии ссылки, Rails сначала покажет пользователю диалог подтверждения, а затем отправит ссылку с помощью метода delete. Это выполняется с помощью файла JavaScript jquery_ujs, который автоматически включается в макет пиложения (app/views/layouts/application.html.erb) при создании приложени. Без этого файла диалог подтверждения не будет показан.

Confirm Dialog

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

Погружение в REST

Теперь мы раскрыли все действия CRUD в приложении REST. Мы это осуществили, объявив отдельные маршруты с подходящими методами в config/routes.rb. Вот как этот файл сейчас выглядит:

get "posts" => "posts#index"
get "posts/new"
post "posts/create"
get "posts/:id" => "posts#show", :as => :post
get "posts/:id/edit" => "posts#edit"
put "posts/:id" => "posts#update"
delete "posts/:id" => "posts#destroy"

Слишком много записей для описания единственного ресурса. К счастью, Rails предоставляет метод resources, который может быть использован для объявления стандартного ресурса REST. Вот как выглядит config/routes.rb после чистки:

Blog::Application.routes.draw do

  resources :posts

  root :to => "welcome#index"
end

Если запустите rake routes, то увидите, что все маршруты, которые мы объявили ранее, все еще доступны:

# rake routes
    posts GET    /posts(.:format)          posts#index
          POST   /posts(.:format)          posts#create
 new_post GET    /posts/new(.:format)      posts#new
edit_post GET    /posts/:id/edit(.:format) posts#edit
     post GET    /posts/:id(.:format)      posts#show
          PUT    /posts/:id(.:format)      posts#update
          DELETE /posts/:id(.:format)      posts#destroy
     root        /                         welcome#index

Также, если вы еще раз пройдете через этапы создания, обновления и удаления публикаций, то увидите, что приложение работает так же, как и прежде.

В основном, Rails рекомендует использовать ресурсные объекты вместо объявления маршрутов вручную. В этом руководстве было так сделано исключительно с целью обучения. Подробнее о роутинге смотрите Роутинг в Rails.