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
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 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
I have a method called is_following in the user.rb model, and it checks to see if one user is following another.
Users Controller
def show
#user = User.find(params[:id])
respond_to |format|
format.json { render json: #user, :methods => [:image_url, :is_following] }
end
User Model
def is_following params
return "yep" if params[:follower_id] == id
end
However, I don't know how to pass params into the :is_following method in the controller. Anyone have any luck or solution to this?
This may not be the answer you're looking for but it definitely a solution. I recommend you stop using render to generate your JSON.
Gems such as jbuilder or rabl make it easier and gives you more flexibility.
Ruby on Rails is a MVC framework. You should leave view generation at the view layer. Doing this will save you from much more pain you'll endure in the future.
I am building a simple json API in Rails 3.1. I created a controller that has two functions:
class Api::DogsController < ActionController::Base
respond_to :json, :xml
def index
respond_with({:msg => "success"})
end
def create
respond_with({:msg => "success"})
end
end
In routes.rb I have
namespace :api do
resources :dogs
end
When I make a get request to http://localhost:3000/api/dogs I get the correct json from above. When I make a post to the same url, I get a rails exception:
ArgumentError in Api::DogsController#create
Nil location provided. Can't build URI.
actionpack (3.1.0) lib/action_dispatch/routing/polymorphic_routes.rb:183:in `build_named_route_call`
actionpack (3.1.0) lib/action_dispatch/routing/polymorphic_routes.rb:120:in `polymorphic_url'
actionpack (3.1.0) lib/action_dispatch/routing/url_for.rb:145:in `url_for'
But if I change the create code to
def create
respond_with do |format|
format.json { render :json => {:msg => "success"}}
end
end
it returns the json just fine.
Can someone explain what is going on here?
After coming across this issue myself and overcoming it, I believe I can supply an answer.
When you simply say:
def create
respond_with({:msg => "success"})
end
What rails tries to do is "guess" a url that the newly created resource is available at, and place it in the HTTP location header. That guess fails miserably for a hash object (the location it deduces is nil, which leads to the error message you are seeing).
To overcome this issue, then, you would need to do the following:
def create
respond_with({:msg => "success"}, :location => SOME_LOCATION)
end
Assuming that you know where the new resource is located. You can even specify "nil" as "SOME_LOCATION" and that will work (somewhat absurdly).
I had the problem myself.
It is, like Aubergine says, something related with the http location header.
Actually, rails seems to build this location using by default the show route.
If you don't have a show action, -which is weird in an API, but can happen (i think)`, then you have to set a location by yourself. I have no idea what is the standard in this case.
If it happens that you need a show route, then code it, and everything should work fine.
Cheers.
I have found that
errors = {:error => #device.errors.full_messages}
respond_with( errors, :status => :bad_request, :location => nil)
works. The :location is required and setting it to nil helps when using with spec:
expect(last_response.status).not_to eql 201
expect(last_response.location).to be_nil
The problem I was having is that I was not returning the errors hash, just the status. Adding the errors hash and setting the location myself solved it.