Why am I getting a 405 on a POST request for JSON? - ruby

So I've got a Padrino app with a controller that looks roughly like this (simplified for brevity):
App.controllers :questions do
get :index, :with => :id, :provides => [:html, :json] do
# Stuff
end
post :index, :with => :id, :provides => [:html, :json] do
# Other stuff
end
end
If I hit "questions/1" in my browser, I see the HTML page for the given question. If I hit "questions/1.json", I see the JSON representation of the question. Exactly like I'd expect.
Now, if I submit a POST request to "questions/1" via a web form, it works. But if I try to send a POST request to "questions/1.json" (signaling that I want the response in JSON format—or at least that's how I thought it worked), I get a 405 Method Not Allowed.
I'm guessing there's something basic I'm misunderstanding here. What am I missing? More importantly, how should I define a route to accept POST requests and provide either HTML or JSON responses?

Well, I'm not really sure why this was happening; but for now I've gotten around the issue by setting the "ACCEPT" header in my POST request to "application/json" instead of tacking ".json" onto the end of the URL (and upon my limited internet research, this may be the preferred approach anyway).

Related

Supporting multiple JSON content types in Grape

I’ve been using Grape to write an Evernote-like API, and have been using Collection+JSON (MIME type "application/vnd.collection+json"). I now want to also support JSON API (mime type "application/vnd.api+json").
(The reason I want to do this is so it will be easier to write an Ember client, since Ember Data has support for JSON API built in. It might make more sense to write client-side JavaScript to solve my problem, but I’m stronger in Ruby than JavaScript.)
My goal is to have the server support either format, and choose which one based on the Accept header. Is this a reasonable thing to do, or does it not make sense to have multiple JSON types? It appears that Grape doesn’t support this. If I just have the wrong idea, then the code below is probably not relevant to that answer.
Here are some relevant pieces of my server code:
class NotesServer < Grape::API
content_type :json, ‘application/json’
content_type :json_api, 'application/vnd.api+json'
formatter :json_api, lambda { |object, env| object.to_json_api }
content_type :collection_json, 'application/vnd.collection+json'
formatter :collection_json, lambda { |object, env| object.to_collection_json }
resource :notes do
desc 'Get a note.'
params do
requires :id, type: Integer, desc: 'Note ID.'
end
route_param :id do
get do
NoteRepresenter.new Note.find(params[:id])
end
end
end
…where NoteRepresenter defines both to_json_api and to_collection_json.
The idea is to use the same Representer in either case, but to call the appropriate method depending on the Accept header.
When I run curl against this with any Accept header, the response has Content-Type: application/json, and “to_json” is called on the representer. If I leave out the content_type :json, ‘application/json’ line, I get a 406 Not Acceptable response every time.
I figured out that in the Grape library, in lib/grape/middleware/formatter.rb, the "format" of a request is considered JSON for any of these MIME types, since the vnd.collection+ or vnd.api+ parts are parsed out of the header. So my question is, is this a matter of Grape not supporting what I’m trying to do, or am I trying to do something that makes no sense?
I found a reasonable way to do this that I’mr reasonably happy with, but I don’t think it totally aligns with how Grape expects you to do things.
I created a custom formatter called JsonFormatter, so my server code looks like this:
class NotesServer < Grape::API
formatter :json, JsonFormatter
# …
end
And the formatter itself:
module JsonFormatter
def self.call(object, env)
case env['HTTP_ACCEPT']
when 'application/vnd.api+json'
object.to_json_api
when 'application/vnd.collection+json'
object.to_collection_json
else
object.to_json
end
end
end
So now either to_json_api or to_collection_api (or to_json) will be called on the presenter object, depending on the Accept header. One issue I haven’t solved yet is that the Content-Type is set to application/json in all cases.

rails not routing https

The following rails routes.rb file
resources :aggregators, :constraints => { :protocol => "https"} do
collection do
get :confirm
post :send_sms
end
member do
post :confirmed
post :failed
post :cancelled
end
end
is not being called. When a form calls one of these actions,
<%= form_for(#aggregator) do |f| %>
the user gets bounced to the root page of the application. In comparing with and without the constraints, the non-https activity picks up
Started POST [...]
Processing by AggregatorsController#create as HTML
whereas the https version
Started POST [...]
Processing by AggregatorsController#index as HTML
So clearly the routing is missing the create action. (the bouncing back to home is an applicationController method which aplies a policy on the index action) I assumed it would be an integral part of resources. What am I doing wrong? Better yet I only want to invoke https on specific actions and would like to keep the focus on new, create and confirm...

Testing an AJAX POST using Rack::Test - how to pass in data?

I'm using Rack::Test to test my app and need to test the posting of data via AJAX.
My test looks like:
describe 'POST /user/' do
include Rack::Test::Methods
it 'must allow user registration with valid information' do
post '/user', {
username: 'test_reg',
password: 'test_pass',
email: 'test#testreg.co'
}.to_json, {"CONTENT_TYPE" => 'application/json', "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest"}
last_response.must_be :ok?
last_response.body.must_match 'test_reg has been saved'
end
end
But at the server end it's not receiving the POSTed data.
I also tried just passing in the params hash without the to_json but that made no difference.
Any idea how to do this?
Your post endpoint must parse the posted JSON body itself, which I assume you already do. Can you post how your end point works, also the rack-test, rack,ruby and sinatra version numbers? Please mention also how you test whether the server's receiving anything -- namely test mockup may confuse your detection.
post '/user' do
json_data = JSON.parse(request.body.read.to_s)
# or # json_data = JSON.parse(request.env["rack.input"].read)
...
end
Okay so my solution is a little weird and specific to the way I am triggering my JSON request in the first place, namely using jQuery Validation and jQuery Forms plugins on the client end. jQuery Forms doesn't bundle the form fields into a stringified Hash as I'd expected, but sends the form fields via AJAX but as a classic URI encoded params string. So by changing my test to the following, it now works fine.
describe 'POST /user/' do
include Rack::Test::Methods
it 'must allow user registration with valid information' do
fields = {
username: 'test_reg',
password: 'test_pass',
email: 'test#testreg.co'
}
post '/user', fields, {"HTTP_X_REQUESTED_WITH" => "XMLHttpRequest"}
last_response.must_be :ok?
last_response.body.must_match 'test_reg has been saved'
end
end
Of course this is specific to the way the jQuery Forms plugin works and not at all how one would normally go about testing the POSTing of JSON data via AJAX. I hope this helps others.

Internal redirection in Padrino

Is there a way to internally redirect in Padrino? I am writing a RESTful service, no HTML response of browser client.
I have a resource, lets say, xyz.
MyApp.controllers :xyz
I have two routes in a controller:
put :index, :with => :xyz_id do...end
and
get :show, :map => '/xyz/:xyz_id' do...end
Now to simplify (and centralize) view (which is a JSON document) creation, I want to just internally redirect the control so that it processes the :show method after creating the resource. Hence, for a client of the service, PUT /xyz/1234 will create a new resource and return the same and GET /xyz/1234 will return the resource if it exists.
Is there a way to INTERNALLY (not a 302 response sent to the client) redirect to get :show method from the put :index method (after creating the resource)? Something like:
redirect (:xyz, :index, {:xyz_id => '1234'})
First of, you can put logic behind the showing data into separate function that you can call from both GET and PUT routes. If you really want to pass processing to a different route, you can do it with rack's call method:
put '/foo' do
# putting related stuff
call env.merge('REQUEST_METHOD' => 'GET')
end

Creating a route with Sinatra to only accept a certain Content-type

I'm trying to create a route with Sinatra that only accepts POST with an Content-type: application/json without success.
My approach is as follows:
post '/dogs', :provides => :json do
# returns here a json response
end
Testing with curl, I have seen that :provides => :json configures the route to respond with an Content-Type: application/json.
That's right because I want also to respond with a JSON message to the POST request but I really need that this route only respond to POST requests with a Content-Type: application/json and not, for example, to others (e.g. Content-Type: application/xml).
Is there any way in Sinatra to restrict the route to only accept requests with a certain Content-Type?
Requests do not contain "Content-Type" header, but rather have "Accept". Sinatra should basically only respond to requests with "Accept" containing "application/json". Just to make sure:
post '/gods', :provides => :json do
pass unless request.accept? 'application/json'
...
end
Read this
http://rack.rubyforge.org/doc/classes/Rack/Request.html
request.content_type will tell you
Phil might be right regarding RFC but in reality many things put a content-type in a POST request, therefore it is useful to know what it is.
i would think it is something like:
pass unless request.accept? == 'application/json'

Resources