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.
Related
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
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.
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
I would like to create entries in a Track (as in music) database in a Rails application by sending the track data information from a client-side Ruby script. I only need to create and destroy tracks from the script, I don't need to have any web interface, and I'm not worrying about authentication/authorization at the moment. Could someone please walk me through (a) how to properly set up the Rails app (using Rails 2.3.8) and (b) how to send the data from a Ruby script?
Here's the approach I have taken so far:
I have created a Track model and Tracks controller. Here is the Track controller code:
class TracksController < ApplicationController
def create
#track = Track.new(params[:track])
respond_to do |format|
if track.save
format.any(:xml, :json) { head :ok }
else
format.xml { render :xml => #track.errors, :status => :unprocessable_entity}
format.json { render :json => #track.errors, :status => :unprocessable_entity}
end
end
end
def destroy
#track = Track.find(params[:id])
#track.destroy
respond_to do |format|
format.any(:xml, :json) { head :ok }
end
end
end
I have set up the routes as follows:
map.resources :tracks, :only => [:create, :destroy]
To send the information from the Ruby script, I have tried (1) using ActiveResource and (2) using net/http with the track information in xml format. For the latter, I'm not sure how to make the post request with net/http and also I'm unclear on how to properly format the xml. For example, can I just use to_xml on a track object?
Thank you in advance for your help.
I don't see any particular problems with your API, or how you are going about scripting it with an HTTP client. However, to get it to fit to the RESTful standard, your create call should return the object as XML or JSON. You can, indeed, simply call to_xml or to_json on the #track object. These functions accept options to further control the output. For instance, if you wish to exclude some piece of data from your API, you can pass the :except option. See the docs linked for more info.
As for your script, I personally prefer HTTParty over ActiveResource - very simple, easy to understand, and doesn't require that you fit your API exactly to the ActiveResource way of doing things. The examples are a good place to start, or have a look at the Chargify gem to see a longer example. HTTParty simply takes a Hash and converts it to XML or JSON. You don't need to have a Track object in your script (unless you really want to). Your script would be something like this:
require 'httparty'
class TrackPoster
include HTTParty
base_uri 'http://hostname.com'
def self.create_track(artist, song)
post('/tracks', :body => {
:track => { :artist => artist, :song => song }})
end
end
TrackPoster.create_track('The Beatles', 'Let It Be')
This call will return the parsed XML/JSON as a hash.
I'm using Inherited Resources for my Rails 2.3 web service app.
It's a great library which is part of Rails 3.
I'm trying to figure out the best practice for outputting the result.
class Api::ItemsController < InheritedResources::Base
respond_to :xml, :json
def create
#error = nil
#error = not_authorized if !#user
#error = not_enough_data("item") if params[:item].nil?
#item = Item.new(params[:item])
#item.user_id = #user.id
if !#item.save
#error = validation_error(#item.errors)
end
if !#error.nil?
respond_with(#error)
else
respond_with(#swarm)
end
end
end
It works well when the request is successful. However, when there's any error, I get a "Template is missing" error. #error is basically a hash of message and status, e.g. {:message => "Not authorized", :status => 401}. It seems respond_with only calls to_xml or to_json with the particular model the controller is associated with.
What is an elegant way to handle this?
I want to avoid creating a template file for each action and each format (create.xml.erb and create.json.erb in this case)
Basically I want:
/create.json [POST] => {"name": "my name", "id":1} # when successful
/create.json [POST] => {"message" => "Not authorized", "status" => 401} # when not authorized
Thanks in advance.
Few things before we start:
First off. This is Ruby. You know there's an unless command. You can stop doing if !
Also, you don't have to do the double negative of if !*.nil? – Do if *.present?
You do not need to initiate a variable by making it nil. Unless you are setting it in a before_chain, which you would just be overwriting it in future calls anyway.
What you will want to do is use the render :json method. Check the API but it looks something like this:
render :json => { :success => true, :user => #user.to_json(:only => [:name]) }
authorization should be implemented as callback (before_filter), and rest of code should be removed and used as inherited. Only output should be parametrized.Too many custom code here...