Статьи

Быстрые приложения RESTful Rails — нет, правда!

Как вы, возможно, читали в моем блоге, часть I RESTful Rails, RESTful Rails — это способ отображения HTTP-глаголов ( GET , POST , PUT и DELETE ) в действия CRUD ( Create , Read , Update и Delete ) в ваших Rails Программы. Если это предложение было для вас полным бредом, найдите время, чтобы прочитать сообщение в блоге, и оно может иметь немного больше смысла.

Хорошо, я предполагаю, что у вас было достаточно времени, чтобы поглотить мой пост, и вы полны CRUD и REST. Теперь пришло время построить реальный пример.

REST Tumblelog

Для этого примера мы создадим (очень) базовое приложение типа tumblelog. Что такое Tumblelog? Ну, это как блог, но без рефератов. Tumblelog — это путаница частых, но коротких сообщений, которые обычно представляют собой ссылку, цитату, быстрый комментарий и так далее.

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

 rails rest_tumble  cd rest_tumble  script/generate model post  script/generate controller posts 

Откройте db/migrate/001_create_posts.rb и отредактируйте его, чтобы он выглядел так:

class CreatePosts def up create_table do |t| t.string :message, :null => false, :limit => 140 t.timestamps end end def self.down drop_table :posts end end class CreatePosts def up create_table do |t| t.string :message, :null => false, :limit => 140 t.timestamps end end def self.down drop_table :posts end end

Затем запустите:

 rake db:migrate 

Наконец, добавьте некоторую проверку в модель post ( app/models/post.rb ):

class Post < ActiveRecord::Base validates_presence_of :message validates_length_of :message, :maximum => 140 end

Хорошо, теперь, когда мы создали приложение Rails, давайте настроим контроллер и маршруты. Если вы вернетесь к последней статье, вы помните, что есть четыре различных глагола RESTful. Давайте добавим эти четыре метода в наш контроллер ( app/controllers/posts_controller.rb ):

class PostsController < ApplicationController # GET - displays all posts def index end # GET - shows one post (based on the supplied id) def show end # POST - creates a new post def create end # PUT - updates an existing post def update end # DELETE - deletes a post def destroy end end

Из приведенных выше комментариев видно, как каждый глагол REST отображается на методы контроллера. Вы также заметите, что, хотя есть четыре глагола, нам нужно пять методов. У глагола GET есть два соответствующих метода: один показывает все сообщения, а другой показывает определенный пост. На самом деле нам нужно еще два метода - новый и редактировать - для завершения контроллера.

Мы добавляем эти дополнительные методы по двум причинам. Прежде всего, Rails использует одну и ту же конечную точку (плюс, может быть, id) для всех глаголов, поэтому, если мы будем ссылаться на /posts (метод GET ), Rails вернет список записей. Добавляя эти методы, мы получаем способ отображения форм.

Давайте добавим дополнительные методы и немного кода:

class PostsController < ApplicationController # GET - displays all posts def index @posts = Post.find :all, :order => 'created_at ASC' end # GET - shows one post (based on the supplied id) def show @post = Post.find(params[:id]) end # GET - displays a form which can be used to create a post def new @post = Post.new end # POST - create a new post def create @post = Post.new(params[:post]) @post.save! redirect_to post_path(@post) rescue ActiveRecord::RecordInvalid render :action => 'new' end # GET - displays a form allowing us to edit an existing post def edit @post = Post.find(params[:id]) end # PUT - update an existing post def update @post = Post.find(params[:id]) @post.attributes = params[:post] @post.save! redirect_to post_path(@post) rescue ActiveRecord::RecordInvalid render :action => 'edit' end # DELETE - delete a post def destroy @post = Post.find(params[:id]) @post.destroy redirect_to posts_path end end

Если вы когда-либо делали какое-либо кодирование на Rails ранее, здесь не будет никаких сюрпризов, за исключением возможных методов posts_path , post_path , edit_post_path и delete_post_path . Эти специальные методы сгенерируют путь к ресурсу на основе записей в routes.rb Если вы не помните синтаксис (и он может немного усложнить такие вещи, как вложенные маршруты), вы можете запустить очень полезную задачу по рейку маршрутов, например:

rake routes

Но чтобы эти специальные методы работали, нам нужно сообщить Rails, что у нас есть ресурс REST. Откройте config/routes.rb и добавьте следующую строку (примерно в середине кода вы должны увидеть похожую примерную строку, которая была закомментирована; поместите эту строку под ней):

map.resources :posts

Все, что это делает, это сообщает Rails, что у нас есть posts_controller который хочет быть RESTful, поэтому он должен ответить соответствующим образом. Испытайте грабли на маршруте, чтобы увидеть результаты своей ручной работы.

Чтобы завершить сайт, нам нужно добавить представления - каждому действию GET необходимо соответствующее представление: index.html.erb , show.html.erb , new.html.erb и edit.html.erb . Я также создал частичное _form.html.erb , потому что код формы для методов new и edit в основном одинаков.

Вот index представление. В этом файле нет ничего особенного - опять же, мы используем post_path , на этот раз в качестве параметра тега link_to :

 # app/views/posts/index.html.erb  <%= link_to 'New message', new_post_path %>  <hr />  <% if @posts.empty? -%>  <p>There are no posts</p>  <% else -%>  <% @posts.each do |post| -%>    <p>      <%= link_to h(post.message), post_path(post) -%>    </p>  <% end -%>  <% end -%> 

Вот вид show . Для этой страницы мы показываем кнопку удаления и редактируем ссылку:

# show.html.erb <p><%= h(@post.message) -%></p> <% form_tag post_path(@post), :method => :delete do -%> <%= link_to 'Edit', edit_post_path(@post) -%> <button type="submit">Delete</button> <% end -%>

В приведенном выше коде вы заметите, что у нас есть вызов form_tag включающий post_path и метод delete. Это указывает форме выполнить действие DELETE при нажатии кнопки. Однако при нажатии на ссылку редактирования мы попадаем в специальный вид редактирования.

И new и edit представления в основном одинаковы - настолько, что мы используем частичное для общего кода:

# app/views/posts/new.html.erb <%= error_messages_for('post') %> <% form_tag posts_path do -%> <%= render :partial => 'posts/form' %> <fieldset> <button type="submit">Save</button> <%= link_to 'Cancel', posts_path %> </fieldset> <% end -%> # app/views/posts/edit.html.erb <%= error_messages_for('post') %> <% form_tag post_path(@post), :method => :put do -%> <%= render :partial => 'posts/form' %> <fieldset> <button type="submit">Update</button> <%= link_to 'Cancel', posts_path %> </fieldset> <% end -%>

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

Наконец, давайте посмотрим на частичную форму, которая очень проста для форм Rails:

# app/views/posts/_form.html.erb <fieldset> <legend>Enter your message</legend> <label for="post_message">Message</label> <%= text_area 'post', 'message' %> </fieldset>

Давайте запустим его и посмотрим, что получится! Бегать:

ruby script/server

И указав ваш браузер на http://localhost:3000/posts . Если повезет, вы должны увидеть что-то похожее на это:

Приложение в действии

Поздравляем! Вы только что создали приложение RESTful Rails!

XML Goodness

Теперь я буду первым, кто признает, что эта демонстрация не очень впечатляет - мы могли бы сделать то же самое, используя обычные CRUD Rails. Но когда RESTful Rails становится классным, это когда вы можете публиковать свои собственные веб-сервисы бесплатно (ну, почти бесплатно - нам нужно добавить пару строк кода, но это все).

Для этого нам нужно сообщить Rails, что он должен по-разному реагировать на XML-запрос. К счастью, метод respond_to делает всю тяжелую работу за нас. Итак, давайте изменим контроллер поста, чтобы он выглядел так:

class PostsController < ApplicationController # GET - displays all posts def index @posts = Post.find :all, :order => 'created_at ASC' respond_to do |format| format.xml { render :xml => @posts.to_xml } format.html { } end end # GET - shows one post (based on the supplied id) def show @post = Post.find(params[:id]) respond_to do |format| format.xml { render :xml => @post.to_xml } format.html { } end end # GET - displays a form which can be used to create a post def new @post = Post.new end # POST - create a new post def create @post = Post.new(params[:post]) @post.save! respond_to do |format| format.html { redirect_to post_path(@post) } format.xml { render :xml => @post.to_xml, :status => :created } end rescue ActiveRecord::RecordInvalid respond_to do |format| format.html { render :action => 'new' } format.xml { render :xml => @post.errors.to_xml, :status => 500 } end end # GET - displays a form allowing us to edit an existing post def edit @post = Post.find(params[:id]) end # PUT - update an existing post def update @post = Post.find(params[:id]) @post.attributes = params[:post] @post.save! respond_to do |format| format.html { redirect_to post_path(@post) } format.xml { render :xml => @post.to_xml, :status => :ok } end rescue ActiveRecord::RecordInvalid respond_to do |format| format.html { render :action => 'edit' } format.xml { render :xml => @post.errors.to_xml, :status => 500 } end end # DELETE - delete a post def destroy @post = Post.find(params[:id]) @post.destroy respond_to do |format| format.html { redirect_to posts_path } format.xml { head :ok } end end end

Давайте посмотрим на то, что мы сделали здесь. Мы ввели новый блок respond_to , который позволяет разделять запросы xml и html . Мы также представили приложению различные типы рендеринга с использованием встроенного типа XML, а также с помощью to_xml для преобразования объектов в сериализованный XML для вывода. Наконец, этот пример показывает нам, как выводить различные коды состояния HTTP.

Методы index и show очень просты: если запрос представляет собой XML, он сериализует объект (или объекты) и разбрасывает их по строке. Если вы установили curl , вы можете проверить это очень легко. Сначала добавьте пару сообщений с помощью веб-интерфейса, затем введите в терминал следующее:

curl -H 'Accept: application/xml' http://localhost:3000/posts.xml

Эта команда должна вернуть список сообщений, которые вы сделали.

Следующие функции - создание, обновление и удаление - являются наиболее интересными. Если вы сообщите Rails, что контент, который вы публикуете, представляет собой XML, он автоматически преобразует (или, по крайней мере, пытается преобразовать) данные в массив, из которого мы можем создать объект.

После успешного завершения публикации в Tumblelog мы отправляем HTTP-код состояния 201 Created вместе с копией нового объекта сообщения. Отправка нового объекта в ответ означает, что потребитель веб-службы не должен делать еще один запрос, чтобы найти его, что дает им удобную контрольную точку.

Команда curl будет выглядеть примерно так:

curl -H 'Accept: application/xml' -H 'Content-type: application/xml' http://localhost:3000/posts.xml -d '<post><message>A RESTful message</message></post>' -X POST

Когда мы успешно обновляем существующее сообщение, мы возвращаем обычный код состояния 200 OK , снова с копией объекта. Например, если бы у нас был объект с id 4, мы могли бы обновить его следующим кодом:

curl -H 'Accept: application/xml' -H 'Content-type: application/xml' http://localhost:3000/posts/4.xml -d '<post><message>An updated RESTful message</message></post>' -X PUT

В обоих приведенных выше примерах, если представленные данные не полны, Rails вернет сообщения об ошибках в виде XML с кодом состояния 500 Internal Server Error .

Последний пример - удаление, которое просто возвращает ответ 200 OK :

 curl -H 'Accept: application/xml' -H 'Content-type: application/xml' http://localhost:3000/posts/4.xml -X DELETE 
Упражнения для тебя дома

Возможно, вы заметили, что обработка ошибок для XML-версии сайта довольно проста. Если вы попытаетесь обновить несуществующее сообщение, вы получите ошибку HTML. Попробуйте выработать способ сделать это с XML - и сделайте то же самое с другими исключениями. Вот подсказка: посмотрите на rescue_from класса rescue_from .

Посмотрите, сможете ли вы разработать способ создания JSON-версии сайта, которая бы подходила для ответа на запросы AJAX. Как только вы начнете, вам будет легче, чем вы думаете!

Связывая некоторые свободные концы
  1. Эти инструкции выполняются на Rails 2.0.2 с использованием базы данных SQLite3 по умолчанию, поэтому нам не нужно настраивать MySQL.
  2. Я знаю, что Rails предлагает генератор ресурсов, который будет автоматически генерировать модель, контроллер и набор шаблонов для вашего приложения RESTful, но вы ничего не узнали бы, если бы я использовал это, не так ли?
  3. Как я упоминал в моем предыдущем посте, ни один браузер не может выполнять запросы HTTP PUT или DELETE . Rails эмулирует их, вводя секретное скрытое поле ввода с именем _method . Попробуйте и посмотрите источник на странице редактирования. Вы увидите, что метод формы все еще является верным старым POST , и в нем есть дополнительное поле ввода.
  4. Я ненавижу тот факт, что по умолчанию нет специального метода удаления, который позволял бы вам добавлять представление подтверждения удаления. Я обычно добавляю его с помощью атрибута :member в файле маршрутов ( config/routes.rb ), поэтому наше предыдущее добавление к этому файлу стало бы:
    map.resources :posts, :member => { :delete => :get } 

    Это позволяет мне добавить представление delete ( delete.html.erb ), а также заменить форму и кнопку удаления ссылкой в show представления ( show.html.erb ).

Вывод

Это завершает очень простой пример сайта RESTful Rails. Очевидно, есть некоторые другие вещи, которые вам нужно добавить для работающего веб-сайта (аутентификация для одного), но то, что мы достигли, все еще довольно внушительно. Как и было обещано, вы можете скачать полный исходный код для собственного использования .