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