406 error when responding with json - ruby-on-rails

I'm trying to get a rails app running with the node.js framework batman.js via the batman-rails gem.
When I'm responding with json in my rails controller, I get a 406 error everytime, and I don't know why. Here's my controller:
respond_to :json
def index
respond_with Sample.all
end
This gives me a 406 no matter what. I do not think this is related to batman, but rails itself. But for good measure, here's my batman code:
index: (params) ->
TopNavTemplate.Sample.load (err) -> throw err if err
#set 'samples', TopNavTemplate.Sample.get('all')
And then my index.html.erb file simply says 'index', it isn't really doing anything with batman yet.
There is a lot of 406 JSON related questions, I haven't really been able to apply them yet to my situation. Is there something I'm doing wrong to make rails respond_with JSON?

Alright, I made a very simple app to check out your situation:
SamplesController.rb
class SamplesController < ApplicationController
respond_to :json
def show
respond_with Sample.find(params[:id])
end
def index
respond_with Sample.all
end
end
When I visited /samples.json and samples/1.json, it worked as intended. However, when I went to /samples and /samples/1 (no .json extension), I got a 406 error.
In order to have the URL's work without the .json extension, you need to modify your config/routes.rb file as follows:
resources :samples, defaults: {format: :json}
Otherwise, the Rails application will attempt to respond to the request with an HTML response.

Related

This webpage has a redirect loop - Rails

I'm trying to get my app to redirect to a custom route when it encounters the error:
Twitter::Error::TooManyRequests
However, I'm having difficulty for some reason and i keep getting this error:
This webpage has a redirect loop
Here's my controller:
#app/controllers/tweets_controller.rb
rescue_from Twitter::Error::TooManyRequests, with: :too_many_requests
def too_many_requests
redirect_to too_many_requests_path
end
Here's my routes:
#config/routes.rb
get "/too_many_requests", to: "tweets#too_many_requests", as: :too_many_requests
I have a view within app/views/tweets named too_many_requests.html.erb
I know i must be doing something incorrectly but can someone help?
Thanks
Unless I'm missing something, it looks like you redirect the action to itself:
def too_many_requests
# Error handling.....
# You should redirect this elsewhere
redirect_to some_other_path
end

respond_with ArgumentError (Nil location provided. Can't build URI.):

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

Catch Unknown action in Rails 3 for custom 404

I want to catch unknown action error in Rails 3, that shows "Unknown Action" error on development and the 404.html on production. I tried putting this rescue_from handler on my ApplicationController (and also on an actual controller, just in case) but I still see the ugly error.
I have custom stuff on the 404, and it can't be plain .html file.
My route:
match '/user/:id/:action', controller: 'users'
The URL I'm accessing: /user/elado/xxx
The rescue_from code:
rescue_from AbstractController::ActionNotFound, :with => :action_not_found
def action_not_found
render text: "action_not_found"
end
The error in the browser:
Unknown action
The action 'xxx' could not be found for UsersController
And in the console:
Started GET "/user/elado/xxx" for 127.0.0.1 at 2011-09-07 19:16:27 -0700
AbstractController::ActionNotFound (The action 'xxx' could not be found for UsersController):
Tried also rescue_from ActionController::UnknownAction.
Any suggestions?
Thanks!
rescue_from was slightly broken when Rails 3 came out (still broken in 3.1 too). Basically you can't:
rescue_from ActionController::RoutingError
anymore. See here.
The solution, for now, is what hamiltop recommends. Use a catch all route that goes to your "routing error" route. Make sure you put it at the end of your config\routes.rb file so it is processed last.
# Any routes that aren't defined above here go to the 404
match "*a", :to => "application#routing_error"
def routing_error
render "404", :status => 404
end
Note: This method has one major drawback. If you use an engine such as Jammit or Devise the catch all will route will make Rails ignore the engine's routes.
If you aren't using an engine that has it's own routes then you should be fine.
However, if you do use an engine that defines its own routes see #arikfr's answer.
Using a catch all route to handle 404 erros (as #Seth Jackson suggested) has one major drawback: if you use any Rails engines that define their own routes (such as Jammit) their routes will be ignored.
Better and more compliant solution would be to use a Rack middleware that will catch 404 errors. In one of my projects, I've implemented such Rack middleware that reports these errors to Hoptoad. I've based my implementation on this one: https://github.com/vidibus/vidibus-routing_error, but instead of invoking my Rails app again to handle the 404 error, I do it in the Rack middleware and let nginx to show the 404 page.
If you really want to rescue AbstractController::ActionNotFound in a controller, you can try something like this:
class UsersController < ApplicationController
private
def process(action, *args)
super
rescue AbstractController::ActionNotFound
respond_to do |format|
format.html { render :404, status: :not_found }
format.all { render nothing: true, status: :not_found }
end
end
public
# actions must not be private
end
This overrides the process method of AbstractController::Base that raises AbstractController::ActionNotFound (see source).
Have you tried a catch all route?
http://railscasts.com/episodes/46-catch-all-route
The wildcard route that you are current using is a Bad Idea(tm).
I would recommend defining the routes you care about, and then doing a catchall as the last line of routes.rb (routes defined first in routes.rb trump later definitions). Then you can render whatever page you want (and specify the 404 status code).
Edit: If you really want to use your current approach... (although this seems like it could be deprecated)
def rescue_action(exception)
case exception
when ActionNotFound, UnknownAction then
# Handle these exceptions here
else
super
end
end

Rails respond_with acting different in index and create method

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.

POSTing file as multipart/form-data to a Rails API

RoR noob here... :)
I need to create a Rails API that clients can call and send an XML file through a POST request.
I've create my route like this:
namespace :api do
namespace :v1 do
resource :report
end
end
and my controller like this:
class Api::V1::ReportsController < ApplicationController
respond_to :xml
def create
#report_submission = ReportSubmission.create :login => params[:login],
:status => :success
respond_with(#report_submission)
end
end
What do I need to do in the controller to receive the XML file that the client will be posting, and then read is content so I can ultimately put it in the database?
How can I test that?
I've created a sandbox project to try this out and got stuck... no idea what to do next. I've pushed it up here:
https://github.com/claudiolassala/api-samples/
Any help will be just awesome!
end
After doing some more research, I've managed to get that working. I've updated my repository on GitHub with the solution.
The main changes were to modify my controller so to read the contents of the file being posted:
class Api::V1::ReportsController < ApplicationController
respond_to :xml
def create
#report_submission = ReportSubmission.create :login => params[:login],
:status => :success,
:results => read_file_data(params[:reportxml])
respond_with(#report_submission)
end
private
def read_file_data(file)
xml_contents = ""
if file.respond_to?(:read)
xml_contents = file.read
elsif file.respond_to?(:path)
xml_contents = File.read(file.path)
else
logger.error "Bad file_data: #{file.class.name}: #{file.inspect}"
end
xml_contents
end
end
And I've fixed my Cucumber step that performs the post, changing it to this:
When /^I send a POST request containing the file$/ do
#login = "some-login"
#file_path = "#{::Rails.root.to_s}/features/step_definitions/test_report.xml"
post "api/v1/report.xml?login=#{#login}",
:reportxml => Rack::Test::UploadedFile.new(#file_path, 'text/xml')
end
Please let me know whether there's any better way to do this. Thanks!
You need to do absolutely nothing apart from making absolutely 100 % certain that any data that is posted is not harmful :)
RESTfull routes give you a params hash in your controller action in the normal way when posting xml. This is exactly how ActiveResource works
So just interrogate your params as normal.
The only potential gotcha is that Rails expects properly formatted xml for this to work.
The easiest way for you to check what happens is try posting an xml file to your action and have a close look in the log file at the params the controller receives
http://apidock.com/rails/ActiveResource/Base Shows how this works in quite good detail (No you don't have to be posting the xml via an ActiveResource object)
As far as testing is concerned Ryan Bates has a great Railscast on ActiveResource here http://railscasts.com/episodes/94-activeresource-basics and here http://railscasts.com/episodes/95-more-on-activeresource
They are old casts but still totally valid and you can see exactly how to test

Resources