Подтвердить что ты не робот

Rails reply_with - почему POST возвращает URL вместо данных?

Это вопрос "почему он работает таким образом", а не "как мне это сделать".

Мое приложение вызывает сторонний REST API, который возвращает JSON, и возвращает результат как часть моего собственного JSON API.

Я использовал методы Rails 3 respond_to и respond_with; в случае запросов GET это работает, как я ожидаю, просто проходя через JSON.

В случае POST он делает больше, включая создание URL-адреса из объекта, возвращаемого для передачи в опции :location. Но поскольку мой объект - это просто JSON (не ActiveRecord), я получаю сообщение об ошибке.

Например...

# POST /api/products.json with params id=:id
def create
  query_string = "#{user_id}&id=#{params[:id]}"
  @products = third_party_api_wrapper.products(query_string, 'POST')
  respond_with @products
end 

Моя обертка для стороннего API делает запрос POST, который возвращается хорошо, тогда Rails возвращает ошибку 500, которая регистрируется следующим образом:

NoMethodError (undefined method `{"response":{"message":"product 4e1712d9ec0f257c510013f8 selected"}}_url' for #<MyController> 

Rails хочет, чтобы мой объект @products знал, как сделать URL-адрес местоположения.

CLARIFICATION: объект @products, возвращаемый сторонним API, является чистой JSON - строкой, которую вы можете увидеть встроенной в сообщение журнала ошибок выше. Эта ошибка возникает из-за того, что Rails, похоже, хочет, чтобы объект был чем-то большим - во внутренней поддержке API Rails это объект ActiveRecord.

Если я заменил новый respond_with на sytax старым

respond_to do |format|
  format.json { render :json => @products }  # note, no :location or :status options
end

тогда все работает. И это то, что я сделал, поэтому у меня нет проблемы с "как", вместо этого возникает вопрос "почему".

Сообщение Райана Дайгла о введении объясняет, что ожидается.

Мой вопрос: почему выполняет respond_with ожидание чего-либо, кроме данных (и статуса HTTP?), и, по-видимому, только для POST.

Я не говорю, что это неправильно, просто пытается понять обоснование реализации Rails.

4b9b3361

Ответ 1

Резюме: Rails получает свое обоснование от HTTP и REST.

(Спасибо за ваш обновленный вопрос. Теперь я понимаю ваш основной вопрос: "Я не говорю это неправильно, просто пытаюсь понять логику реализации Rails".)

Теперь для объяснения. Обоснование того, как ведет себя Rails, основано на использовании протоколов HTTP и REST.

Просто чтобы перейти от того, что вы прочитали к тому, что я собираюсь рассказать, хочу упомянуть о соответствующих частях из статьи Райана Дайгла по умолчанию RESTful Rendering:

Если был запрошен формат: html:

[текст удален]

  • [после PUT или POST и без ошибок проверки] перенаправляет на местоположение ресурса (то есть user_url)

(Текст [в скобках] был добавлен мной.)

Если был запрошен другой формат (т.е.: xml или: json)

[текст удален]

  • Если это был запрос POST, вызовите метод: to_format на ресурсе и отправьте его обратно с созданным статусом и: местоположением нового созданного ресурса

Позвольте мне сказать это в своих словах о том, что Rails считает хорошей практикой:

  • Для контента для человека (например, HTML) после POST или PUT сервер должен сообщить браузеру о перенаправлении через 303 для вновь созданного ресурса. Это обычная практика - очень полезная вещь, потому что пользователь хочет видеть обновления, связанные с их изменениями.

  • Для машинного контента (например, JSON, XML) после PUT сервер должен просто отобразить 201. Клиент, в данном случае, программа, использующая API, может решить остановиться там. (В конце концов, клиент указал запрос и получил 201, так что все это честно.) Именно поэтому используется 201 (успех), а не 303 (перенаправление). Если клиент хочет запросить вновь созданный ресурс, он может искать его с помощью заголовка Location, но перенаправление не должно быть принудительно.

В любом случае обратите внимание, что требуется местоположение вновь созданного ресурса. Вот почему @products в вашем примере выше должен содержать как данные, так и местоположение.

Для фона я немного поделился с Страница W3C в 201 Создана:

10.2.2 201 Создано

Запрос выполнен и создан новый ресурс. На вновь созданный ресурс можно ссылаться на URI (ы), возвращенные в сущности ответа, с самым конкретным URI для ресурса, заданного полем заголовка Location. Ответ СЛЕДУЕТ включать объект, содержащий список характеристик и местоположения ресурсов, из которых пользователь или пользователь может выбрать наиболее подходящий. Формат сущности определяется типом носителя, указанным в поле заголовка Content-Type. Исходный сервер ДОЛЖЕН создать ресурс перед возвратом кода состояния 201. Если действие не может быть выполнено немедленно, сервер ДОЛЖЕН ответить на ответ 202 (Accepted) вместо этого.

Надеюсь, это поможет объяснить обоснование. Это мое (наивное?) Понимание, что это обоснование хорошо принято в рамках веб-фреймворка. Исторически я подозреваю, что Rails была пылкой практикой (новое предупреждение!) Для многих горячих сторонников REST и ресурсно-ориентированной архитектуры.

Ответ 2

"Почему" отлично ответил @david-james. Это всего лишь короткое "как" ответить через respond_with:

class Api::V1::UsersController < ApplicationController

  respond_to :json

  def create
    @user = User.create(...)
    respond_with @user, location: url_for([:api, :v1, @user])
  end

end

Ответ 3

Чтобы ответить на этот вопрос: "Почему API должен возвращать что-либо иное, кроме данных (и статус HTTP?). Я не говорю это неправильно, просто пытаюсь понять логическое обоснование".

Я не могу придумать ничего хорошего. Что еще более важно, я не вижу никакого способа, чтобы API мог возвращать любую структуру , кроме! (Этот вопрос для меня не имеет смысла!)

По определению вызов API должен возвращать структуру данных. (Это может быть так же просто, как строка. Это может быть JSON. Это может быть XML.) Он может использовать согласование контента для определения формата. Это может быть или не быть строгой схемой, но, по крайней мере, клиентская библиотека должна иметь возможность ее проанализировать. В любом случае, документация API должна сделать это совершенно ясно и придерживаться этого. Как еще можно ожидать взаимодействия клиентских библиотек?

Я думаю, что я упустил этот момент, это кажется слишком очевидным. (Я подозреваю, что у вас другая проблема в вашем коде выше.)