I want to PUT to rails and avoid getting a 204. I am using this pattern:
class SomeController < ApplicationController
respond_to :json
def update
# ...
respond_with(some_object)
end
end
However, when I do a put to update, I get a 204 back. I realize this is completely valid etc, but I explicitly want the content back. I can override it to some extent like this:
def update
respond_with(some_object) do |format|
format.json{render json: some_object}
end
end
but this seems a bit too hands-on for rails. Is there any more idiomatic way of avoiding a 204 and requesting the full content to be sent back? This is Rails 3.2.
In summary: I want maximally idiomatic rails that avoids a 204.
I made a custom responder which always returns my JSON encoded resource even on PUT/POST.
I put this file in lib/responders/json_responder.rb. Your /lib dir should be autoloaded.
module Responders::JsonResponder
protected
# simply render the resource even on POST instead of redirecting for ajax
def api_behavior(error)
if post?
display resource, :status => :created
# render resource instead of 204 no content
elsif put?
display resource, :status => :ok
else
super
end
end
end
Now, explicitly modify the controller which requires this behavior, or place it in the application controller.
class ApplicationController < ActionController::Base
protect_from_forgery
responders :json
end
You should now get JSON encoded resources back on PUT.
As a less invasive alternative, you can pass a json: option to the respond_with method invocation inside your controller update action, like this:
def update
# ...
respond_with some_object, json: some_object
end
Granted it seems a bit unDRY having to repeat the object twice in the arguments, but it'll give you what you want, the json representation of the object in the response of a PUT request, and you don't need to use the render json: way, which won't give you the benefits of responders.
However, if you have a lot of controllers with this situation, then customizing the responders, as jpfuentes2 showed in the accepted anwser, is the way to go. But for a quick single case, this alternative may be easier.
Source: https://github.com/plataformatec/responders/pull/115#issuecomment-72517532
This behavior seems intentional to fall in line with the HTTP spec, and "ideally" you should be firing off an additional GET request to see the results. However, I agree in the real world I'd rather have it return the JSON.
#jpfuentes2's solution above should do the trick (it's very similar to the pull request below), but I'm hesitant to apply anything that's patching rails internals, as it could be a real pain to upgrade between major versions, especially if you don't have tests for it (and let's face it, developers often skimp on controller tests).
References
https://github.com/rails/rails/issues/9862
https://github.com/rails/rails/pull/9887
Just to clarify, you do not need the responders gem to do this... You can just do:
config/initializers/responder_with_put_content.rb
class ResponderWithPutContent < ActionController::Responder
def api_behavior(*args, &block)
if put?
display resource, :status => :ok
else
super
end
end
end
and then either (for all updates actions to be affected):
class ApplicationController < ActionController::Base
def self.responder
ResponderWithPutContent
end
end
or in your action:
def update
foo = Foo.find(params[:id])
foo.update_attributes(params[:foo])
respond_with foo, responder: ResponderWithPutContent
end
What's wrong with simply doing:
def update
some_object = SomeObject.update()
render json: some_object
end
Not a big fan of this behavior. To get around it, I had to avoid using the respond_with method:
class SomeController < ApplicationController
respond_to :json
def update
# ...
respond_to do |format|
format.json { render(json: some_object, status: 200) }
end
end
end
Related
I am building a simple rails tutorial on how to build APIs for some students and I am building it without the respond_to and respond_with because I just want to see if I can build an api without using a gem. This is what I have and my tests pass:
controller:
class Api::V1::SuyasController < ApplicationController
def index
render json: Suya.all
end
def create
render json: Suya.create(suyas_params)
end
private
def suyas_params
params.require(:suya).permit(:meat, :spicy)
end
end
routes:
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :vendors
resources :suyas
end
end
end
tests:
require 'test_helper'
class Api::V1::SuyasControllerTest < ActionController::TestCase
test "index can get all the suyas" do
Suya.create(meat: "beef", spicy: true)
Suya.create(meat: "kidney", spicy: false)
get :index
suyas = JSON.parse(response.body)
assert_equal "beef", suyas[0]["meat"]
assert_equal true, suyas[0]["spicy"]
assert_equal "kidney", suyas[1]["meat"]
assert_equal false, suyas[1]["spicy"]
end
test "create can create a suya" do
assert_difference("Suya.count", 1) do
create_params = { suya: { meat: "beefy", spicy: true }, format: :json }
post :create, create_params
suya = JSON.parse(response.body)
assert_equal "beefy", suya["meat"]
assert_equal true, suya["spicy"]
end
end
end
What's the difference between using render vs respond_with? I can't find any answers. Is there even something that I am doing wrong? Why are there two ways to create APIs (respond_to/respond_with AND this way?)
-Jeff
render is part of Rails and it just renders whatever you say in whatever format you say. Typically a view, possibly a string, possibly a file.
A pretty low-level function that renders whatever you say making a few assumptions per conventions, like where to look for a view.
respond_to is a micro-DSL that allows you to respond differently to different formats being requested.
I. e. in a block with |format| call to format.json requires a block that will be executed on requests for JSON, otherwise will be a no-op (no operation). Also, if respond_to didn't execute any block, it responds with a generic 406 Not Acceptable (server cannot respond in any format acceptable by the client).
While it is possible to do if request.json?, it's not so readable and needs to explicitly specify when to respond with 406.
respond_with, formerly part of Rails, now (since 4.2) in a separate gem responders (for a reason), takes an object and uses it to construct a response (making a lot of assumptions, all of them can be given at controller declaration).
It makes code much shorter in typical use cases (i. e. some APIs). In not-so-typical use cases it can be customized to suit your needs. In highly unusual use cases it's of no use.
I may be overly simplifying things, it's a general overview.
There are two things :)..render and respond_to.
Render is used to create a full response and sends it back to the browser.
So render is used in respond_to ,to make your action very responsive for every call whether it can be js/ajax call,full page load(html),json(to show autosearch dropdown,tokens) or xml.So if i want my method to work and respond to every calls from client,i will use the below block in my action.
respond_to do |format|
format.html { redirect_to(person_list_url) }
format.js {render "show_person_details"}
format.xml { render :xml => #people.to_xml }
format.json { render json: #people}
end
above controller will work on every scenario,such as js/html/json and xml without getting 403 Forbidden error which we get usually get when a js call is made to action having only format.html and not format.js
HOPE IT HELPS
I think the answer is that render only allows me to respond with JSON, whereas if I use respond_to and respond_with, I can respond in more than just one manner? Is that all?
I'd like a Rails controller (all of them, actually, it's an API) to render JSON always always.
I don't want Rails to return "route not found", or try and fail to find an HTML template, or return 406. I just want it to automatically and always render JSON, e.g. from a RABL or JBuilder view.
Is this possible? Related questions seem to have answers that have the aforementioned downsides.
You can add a before_filter in your controller to set the request format to json:
# app/controllers/foos_controller.rb
before_action :set_default_response_format
protected
def set_default_response_format
request.format = :json
end
This will set all response format to json. If you want to allow other formats, you could check for the presence of format parameter when setting request.format, for e.g:
def set_default_response_format
request.format = :json unless params[:format]
end
You can use format.any:
def action
respond_to do |format|
format.any { render json: your_json, content_type: 'application/json' }
end
end
It's just:
render formats: :json
I had similar issue but with '.js' extension. To solve I did the following in the view:
<%= params.except!(:format) %>
<%= will_paginate #posts %>
I tried the above solutions and it didn't solve my use case.
In some of the controllers of my Rails 4.2 app, there was no explicit render called. For example, a service object was called and nothing was returned. Since they are json api controllers, rails was complaining with a missing template error. To resolve I added this to our base controller.
def render(*args)
options = args.first
options.present? ? super : super(json: {}, status: :ok)
end
It's a large app I'm converting to Rails 5, so this is just a safety measure as I removed the RocketPants gem that seemed to do this automatically.
As a note, my controllers inherit from ActionController::Base
Of course:
before_filter :always_json
protected
def always_json
params[:format] = "json"
end
You should probably put this in a root controller for your API.
I have a model called Entity and the create action in the controller looks like this:
# enitities_controller.rb
def create
# loading params, etc...
#entity.save
respond_with #entity
end
I am using jbuilder for custom JSON views rather than rendering #entity.to_json, which works great. I have one last issue, which is when the model won't save due to validation errors I get the following response (with status 422 Unprocessable Entity):
{"errors":{"parent_share":["can't be blank","is not a number"]}}
I would like to override this json with my own. I am aware of he possibility to replace respond_with #entity with:
respond_with #entity do |format|
if #entity.errors.any?
format.json {
render "entities/create", :status => :unprocessable_entity
}
end
end
But shouldn't there be a more auto-magic way by defining some sort of errors view or something? This feels a bit dirty AND it makes me have to write more code each time I need this rather than allowing me to use respond_with. Is there another way?
Meanwhile I have found the answer:
You have to create the file lib/application_responder.rb and add the following:
class ApplicationResponder < ActionController::Responder
include Responders::FlashResponder
include Responders::HttpCacheResponder
def to_json
set_flash_message! if set_flash_message?
if !has_errors? || response_overridden?
default_render
else
controller.default_render( status: :unprocessable_entity )
end
end
end
And add the following to your application responder:
self.responder = ApplicationResponder
What this does is add a to_json method that will copy the behaviour of the to_js responder.
For example, I would like
/apples/123?_format=json
to act like
/apples/123.json
where it renders the *.json.* templates, executes respond_to {|format| format.json {...}}, etc.
Is this at all possible?
Thanks!
You can do the following to disable Rails' automatic handling of the .ext format:
constraints format: false do
resources :apples
# ...
end
Then, and this is a bit gross but I don't see a better way to do this at the moment, you can do the following to update ActionController on what format to serve:
class ApplicationController < ActionController::Base
before_filter :set_format_from_query_string
private
def set_format_from_query_string
request.format = params.fetch(:_format, 'json')
end
end
This will allow your respond_to block to toggle based on the _format query string parameter and uses json as the default format.
I have a controller that responds_with JSON for all of the RESTful actions, index, create, update etc,
class QuestionsController
respond_to :json
def index
respond_with Question.all
end
end
However, I also have other actions in the controller. For example, in one method, it checks whether a response was correct and then tries to return a variable with a boolean true or false
respond_with correct_response #either true or false
However, this is giving me the error
ArgumentError (Nil location provided. Can't build URI.):
There will also be other methods that I'll wish to respond with multiple values. In Sinatra, you can do this to respond with json
{:word => session[:word], :correct_guess => correct_guess, :incorrect_guesses => session[:incorrect_guesses], :win => win}.to_json
How would I do that in Rails?
So, two questions, what's the proper way to write this
respond_with correct_response
and how to respond_with multiple values in a way similar to the example I showed from a Sinatra app.
Thanks for your help.
You want ActionController::Base#render, not respond_with. The proper way to do what you're trying to achieve here is:
render json: {word: session[:word], correct_guess: correct_guess, incorrect_guesses: session[:incorrect_guesses], win: win}
respond_with is actually OK for this scenario--it just happens to do some magic for you and relies on having access to info it needs; take a look at Rails 4.1.9's actionpack/lib/action_controller/metal/responder.rb.
In your case, ArgumentError (Nil location provided. Can't build URI.) is actually telling the truth--it's trying to determine a URL to use from the location header setting but isn't able to figure it out. I'd wager you could get your code working if you gave it one:
class QuestionsController
respond_to :json
def index
respond_with Question.all, location: questions_url
end
end