Статьи

Отдых с Rails Часть 2: Обслуживание XML, JSON и Atom

В первой части REST с Rails у нас было введение в создание сервисов RestFull с Rails. В этой статье мы рассмотрим обслуживание этого контента с использованием различных представлений, включая XML, JSON и Atom.

Каждый ресурс имеет представление, фактически данный ресурс может иметь более одного представления. Пользователи, обращающиеся к нашему диспетчеру задач, захотят увидеть страницу HTML со списком всех своих задач или могут выбрать программу чтения каналов, чтобы подписаться на свой список задач; читатели фида ожидают документ Atom или RSS. Если мы пишем приложение, мы хотели бы видеть список задач в виде документа XML или объекта JSON или, возможно, перетащить его в приложение календаря в виде списка задач и событий iCal. В этом разделе мы собираемся исследовать ресурсы, рассматривая несколько представлений, начиная с HTML и добавляя представления XML, JSON и Atom для нашего списка задач.

Эта статья основана на главе 5 из Ruby in Practice Джереми МакАнелли и Ассаф Аркин. Предоставлено Manning Publications . Все права защищены.

 [img_assist | nid = 4838 | title = | desc = | link = url | url = http: //www.manning.com/mcanally/ | align = right | width = 139 | height = 174]
ПРОБЛЕМА

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

РЕШЕНИЕ

Одной из причин, по которой мы рекомендуем Rails для создания веб-сервисов, является простота добавления разных представлений для одного и того же базового ресурса. Итак, давайте начнем с простого действия, которое отображает текущий список задач в одном из нескольких форматов:

def index
@tasks = Task.for_user(@user_id)
end

Поскольку большинство примеров Rails выглядят так и поддерживают только HTML, мы не будем обвинять вас в том, что в этом примере показан только вывод HTML, но на самом деле он поддерживает столько форматов, сколько у нас есть представлений. Когда вы оставляете это для Rails для обработки ответа, он пытается найти подходящее представление на основе имени действия и ожидаемого формата. Если мы написали представление с именем index.html.erb, Rails будет использовать его для отображения HTML-ответов. Если мы добавим представление с именем index.xml.builder, Rails будет использовать его для отображения XML-ответов. Для Atom мы бы использовали index.atom.builder, а для iCal index.ics.erb.

Обратите внимание на шаблон здесь? Первая часть сообщает Rails, какое действие представляет это представление, вторая часть сообщает, к какому формату он применяется, а последняя часть сообщает, какой движок шаблонов использовать. Rails поставляется с тремя из них: ERB (eRuby), Builder и RJS. Это новая функция, представленная в Rails 2.0. Более ранние версии были менее гибкими и всегда соответствовали комбинации формата и движка шаблонов, поэтому для HTML он по умолчанию будет ERB при поиске представления index.rhtml, а для XML он будет по умолчанию для Builder при поиске представления index.rxml. , Rails 2.0 дает вам больше гибкости в смешивании и сопоставлении форматов и шаблонизаторов, а также облегчает добавление новых обработчиков шаблонов, например, для использования шаблонов Liquid или HAML.

Вскоре мы покажем вам Builder, когда мы используем его для создания канала Atom для нашего списка задач. Для XML и JSON мы не будем сталкиваться с проблемой создания и поддержки пользовательского представления, вместо этого мы позволим ActiveRecord выполнить тривиальное преобразование наших записей в документ XML или объект JSON:

def index
@tasks = Task.for_user(@user_id)
case request.format
when Mime::XML
response.content_type = Mime::XML
render :text=>@tasks.to_xml
when Mime::JSON
response.content_type = Mime::JSON
render :text=>@tasks.to_json
when Mime::HTML, Mime::ATOM
# Let Rails find the view and render it.
else
# Unsupported content format: 406
head :not_acceptable
end
end

Это долгий путь. Вы можете увидеть краткий способ ответить разными типами контента в листинге 4.

def index
@tasks = Task.for_user(@user_id)
respond_to do |format|
format.html
format.xml { render :xml=>@tasks }
format.json { render :json=>@tasks }
format.atom #5
end
end

Мы используем метод response_to для соответствия каждому формату, который мы поддерживаем, и логике для его отображения. Это похоже на приведенный выше оператор case, но проще в определении и более декларативно. Мы также позволяем методу рендера выполнить всю тяжелую работу, попросив его преобразовать массив в документ XML или объект JSON и соответствующим образом установить заголовок Content-Type. Короче писать, проще в обслуживании. Теперь пришло время обработать представление Atom, для которого мы создадим файл представления с именем index.atom.builder:

atom_feed do |feed|
feed.title "My tasks list"
feed.updated @tasks.first.created_at
@tasks.each do |task|
feed.entry task do |entry|
entry.title task.title
entry.content task.description, :type => 'html'
end
end
end

При вызове atom_feed создается документ XML с правильной упаковкой для канала, включая объявление типа документа XML, элемент канала с идентификатором и альтернативную ссылку на наш сайт. Он также создает объект AtomFeedBuilder и уступает блоку. Из этого блока мы собираемся создать заголовок канала, указать последнее обновление и добавить все записи канала.

Теперь у нас есть ресурс задач, который отвечает на GET и возвращает список задач в четырех различных типах контента, HTML для веб-браузера, Atom для читателей каналов, а также XML или JSON для клиентских приложений.

ОБСУЖДЕНИЕ

Протокол HTTP позволяет клиентам запрашивать данные в определенном формате, используя согласование содержимого. Когда клиент отправляет запрос на сервер, он использует заголовок Accept, чтобы указать все типы контента, которые он поддерживает, в порядке предпочтения. Сервер может выбрать наиболее подходящий тип контента и использовать его при ответе клиенту в формате, понятном клиенту. Если сервер не поддерживает ни один из перечисленных типов контента, он просто отвечает 406 (не приемлемо). Другой код состояния, 415 (Неподдерживаемый тип носителя), сообщает клиенту, что сервер не поддерживает тип содержимого запроса POST или PUT.

Это основная идея переговоров по содержанию. В некоторых случаях это явно правильно. Мы можем использовать один URL ресурса и отправить его всем нашим клиентам, и каждый клиент может увидеть различное представление одного и того же ресурса. Веб-браузер увидит страницу HTML, читатель ленты увидит ленту Atom, другие приложения могут увидеть XML или CSV.

Другой подход использует разные URL ресурсов для каждого представления. Некоторые люди предпочитают этот подход, поскольку он позволяет вам управлять различными представлениями, например, вы можете отправить кому-то URL-адрес XML-документа. Если вы хотите загрузить документ CSV с помощью веб-браузера, вам необходим URL-адрес, который всегда отправляет документ CSV.

Нет единственно верного способа создать эти URL, но есть два общих соглашения. Одно соглашение добавляет параметр запроса, который указывает ожидаемый тип содержимого, например, вы можете решить использовать параметр запроса формата и использовать URL-адрес, такой как / tasks? Format = xml. Другое соглашение заключается в использовании суффикса расширения в URL-пути, например, /tasks.xml. Мы рекомендуем использовать суффикс расширения по той простой причине, что при сохранении документа с помощью веб-браузера суффикс сохранится, а файл с именем tasks.xml всегда будет открываться в правильном приложении.

Как Rails справляется с этим? Когда мы используем встроенный механизм для выбора типа контента, как мы делали в этом разделе, Rails выбирает ожидаемый формат из формата параметра запроса, или, если его нет, из суффикса пути URL, или его нет из Принять заголовок. Какой способ вы используете для запроса различных типов контента, зависит от вас, приложение Rails может поддерживать все три. Вы заметите в предыдущем разделе, когда мы написали действие для создания новой задачи, мы сделали это:

Task.create!(params[:task])

Несколько представлений работают в обе стороны. Если мы сможем создать ответ и отправить обратно документ XML, мы сможем обработать запрос, приняв тот же документ XML. Когда Rails обрабатывает XML-запрос, он конвертирует XML-документ в Hash, используя имя элемента документа для имени параметра. В приведенном выше примере ожидается, что документ будет содержать элемент <task> и передаст хэш ActiveRecord.

Он работает аналогично для HTML-форм, если вы следуете простым правилам соглашения об именах, установленным Rails. В наших формах у нас будут поля, такие как task [title] и task [priority]. Rails использует это соглашение об именах, чтобы выяснить, как поля связаны друг с другом, и превратить их в параметр Hash, чтобы мы могли использовать одну и ту же строку кода для обработки XML-документа или представления HTML-формы. Помогает то, что мы используем вспомогательные методы Rails, например:

<% form_for @task do |f| %>
<%= f.text_field :title %>
<%= f.text_field :priority %>
<% end %>

Form_for создает элемент <form>, определяет URL-адрес действия и заботится о том, чтобы сопоставить имена полей от заголовка к задаче [заголовок]. Дайте ему новую запись, и он укажет форму на URL для создания нового ресурса (tasks_url); дайте ему существующую запись, и он укажет форму на URL для обновления существующего ресурса (task_url (@task)). Это, вероятно, объясняет, почему мы использовали Task.new для визуализации формы в новом действии. Rails поставляется со встроенной поддержкой форм HTML, XML, JSON и YAML, и если этого недостаточно, вы всегда можете добавить анализаторы параметров клиента, посмотрите ActionController :: Base.param_parsers для получения дополнительной информации.

В приведенном выше примере мы показали, как использовать AtomFeedBuilder, шаблонный механизм для генерации каналов Atom. Сам AtomFeedBuilder расширяет более общий механизм шаблонов XML, предоставляемый Builder :: XmlMarkup. Давайте на минутку посмотрим на Builder и что вы можете с ним сделать.
Builder — простой шаблонный механизм для создания XML-документов из кода Ruby. Поскольку он всегда создает правильно оформленные документы, некоторые разработчики даже используют его для создания страниц XHTML. Он доступен в виде Gem, который вы можете использовать в любом приложении, которое должно генерировать XML, а также входит в состав Rails. Builder очень прост для понимания и интуитивно понятен, а также является хорошим примером того, что можно сделать с помощью небольшого количества метапрограммирования.

Когда вы вызываете метод для объекта Builder, он берет имя метода и использует его для создания элемента XML с тем же именем. Это делается с помощью method_missing, нет необходимости указывать какой-либо из этих методов заранее. AtomFeedBuilder определяет только несколько методов, которые делают намного больше, чем просто генерируют элемент XML, поэтому он определяет запись, но не пытается определить заголовок или содержимое.
Как вы можете себе представить из этого примера, передача строкового аргумента будет использовать это значение для содержимого элемента; аргумент хеша определяет атрибуты элемента; и блоки используются для вложения одного элемента в другой. Помимо них, есть несколько специальных методов, которые вы можете вызвать как tag! создать элемент с заданным именем (например, для обработки специальных символов или пространств имен), text !, cdata! и комментарий! (они делают именно то, что вы думаете, что они будут), и наставлять! создать декларацию XML в верхней части документа.

METHOD_MISSING AND BLANKSLATE

Builder — интересное использование метода method_missing. Объекты Ruby используют метод передачи. Когда вы вызываете метод для объекта, Ruby сначала пытается сопоставить его с определением известного метода, и если он не находит какой-либо метод, передайте его в метод method_missing. Реализация по умолчанию выбрасывает NoMethodError. Builder использует method_missing для перехвата вызовов методов и их преобразования в элементы XML, поэтому нам не нужно объявлять схему XML или создавать какие-либо каркасные объекты, чтобы получить это простое создание документов XML из кода Ruby.

Существующие методы объекта могут конфликтовать с именами элементов XML, например, имена, такие как id и тип, обычно используются в качестве имен элементов. Чтобы решить эту проблему, Builder использует BlankSlate, класс, в котором удалены большинство стандартных методов (в Ruby 1.9 вы можете добиться того же, используя BasicObject).

Мы упоминали ранее, что вы можете использовать разные URL для различных представлений. Когда мы определили ресурс задач, Rails создал несколько именованных методов маршрута, таких как tasks_url и task_url. Кроме того, мы не показывали, что раньше создавались именованные методы маршрутизации, которые принимают формат и возвращают URL-адрес, указывающий формат вывода в виде суффикса пути. Эти имена методов начинаются с formatted_ и принимают дополнительный аргумент, который задает формат вывода, и будут отображаться при запуске задачи rake router. Итак, давайте добавим ссылку, которую пользователи могут использовать для подписки на канал Atom, используя именованный маршрут:

<%= link_to "Subscribe", formatted_tasks_url(:atom) %>

В этом разделе мы показали, как создать веб-сервис RESTful. Что если вы хотите получить доступ к этой услуге из другого приложения? В следующем разделе мы поговорим об ActiveResource, способе Rail доступа к удаленным ресурсам с помощью ActiveRecord-подобного API.