I have a Ruby on Rails server application(hosted on Heroku) which is supposed to be able to receive HTTP POST requests with JSON strings and add the JSON objects to the database. There are two database models: thanksgivings and requests.
controller/request_controller.rb:
class RequestsController < ApplicationController
[...]
# POST /requests
# POST /requests.json
def create
#request = Request.new(params[:request])
respond_to do |format|
if #request.save
format.html { redirect_to #request, notice: 'Request was successfully created.' }
format.json { render json: #request, status: :created, location: #request }
else
format.html { render action: "new" }
format.json { render json: #request.errors, status: :unprocessable_entity }
end
end
end
[...]
end
The JSON keys for the different database model objects values should be "thanks" and "request" for the Thankgiving DB model and Request DB model respectively.
model/request.rb:
class Request < ActiveRecord::Base
attr_accessible :request
end
model/thankgiving.rb:
class Thanksgiving < ActiveRecord::Base
attr_accessible :thanks
end
When I post an HTTP request to http://YADA.herokuapp.com/thanksgivings.json everything works fine, but when I send it to .../requests.json, I get a 500 error. If I use a faulty key in the JSON string, the request succeeds, but the value becomes null.
Request 1: (correct request to thanksgiving.json)
POST
Host: http://YADA.herokuapp.com/thanksgivings.json
Content-Type: application/json
Body: {"thanks":"qwerty"}
Request 2: (correct request to request.json – returns 500)
POST
Host: http://YADA.herokuapp.com/requests.json
Content-Type: application/json
Body: {"request":"qwerty"}
Request 3: (faulty key - will succeed: value will be null, key will be "request" in response, database element will be added)
POST
Host: http://YADA.herokuapp.com/requests.json
Content-Type: application/json
Body: {"abc":"qwerty"}
The strange thing is that the two controllers, thanksgivings_controller.rb and requests_controller.rb, are "identical" but their behavior differs.
Anyone knows how to do this successfully?
According to the server logs, there was a NoMethodError – stringify_keys. This question's answer was the solution: undefined method `stringify_keys'
Changing
#request = Request.new(params[:request])
to
#request = Request.new(:request => params[:request])
did the trick.
Related
I'm trying to send a POST request from some client to a rails server and I'm having some problems.The full requirement is to send an image to to be processed by paperclip but it look like it's a general postman multipart POST with Rails problem.
This is what I'm getting:
Bellow my setup:
class CategoriesController < ApplicationController
def create
#category = Category.new(category_params)
respond_to do |format|
if #category.save
format.html { redirect_to #category, notice: 'Category was successfully created.' }
format.json { render :show, status: :created, location: #category }
else
format.html { render :new }
format.json { render json: #category.errors, status: :unprocessable_entity }
end
end
end
private
def category_params
params.require(:category).permit(:label, :description)
end
I'm assuming the problem is that the Request params are not encapsulated int the "categories".
Please let me know if I wasn't clear enough and if I can offer more info.
Thanks in advance.
EDIT:
As suggested by fylooi I've changed the Request Body in Postman adding an encapsulating "entity" like this:
Still I'm getting the same results
Processing by CategoriesController#create as JSON
Parameters: {"------WebKitFormBoundaryFdJXZFMuAl0fZf3Q\r\nContent-Disposition: form-data; name"=>"\"category[label]\"\r\n\r\nTraffic\r\n------WebKitFormBoundaryFdJXZFMuAl0fZf3Q\r\nContent-Disposition: form-data; name=\"category[description]\"\r\n\r\nTraffic category\r\n------WebKitFormBoundaryFdJXZFMuAl0fZf3Q--\r\n"}
Completed 400 Bad Request in 1ms (ActiveRecord: 0.0ms)
ActionController::ParameterMissing (param is missing or the value is empty: category):
app/controllers/categories_controller.rb:67:in `category_params'
app/controllers/categories_controller.rb:27:in `create'
Postman works fine with Rails, you just need to understand how Rails handles parameters in general.
Let's say you POST the following parameters to the server:
plain_param=value
nested_object[attribute]=value
This gets parsed into the following:
pry(main)> params = ActionController::Parameters.new(plain_param:"value", nested_object: { attribute: "value" } )
=> {"plain_param"=>"value", "nested_object"=>{"attribute"=>"value"}}
Let's take a look at how permit works.
params.permit(:plain_param)
pry(main)> params.permit(:plain_param)
Unpermitted parameter: nested_object
=> {"plain_param"=>"value"}
pry(main)> params.permit(:nested_object)
Unpermitted parameters: plain_param, nested_object
=> {}
pry(main)> params.permit(:nested_object => :attribute)
Unpermitted parameter: plain_param
=> {"nested_object"=>{"attribute"=>"value"}}
pry(main)> params.permit(:plain_param, :nested_object => :attribute )
=> {"plain_param"=>"value", "nested_object"=>{"attribute"=>"value"}}
So far, so good. Looks like permit returns the entire hash for top level and nested permitted keys through and prints an alert for unpermitted keys. How about require?
[33] pry(main)> params
=> {"plain_param"=>"value", "nested_object"=>{"attribute"=>"value"}}
pry(main)> params.require(:plain_param)
=> "value"
pry(main)> params.require(:nested_object)
=> {"attribute"=>"value"}
pry(main)> params.require(:nested_object => :attribute)
ActionController::ParameterMissing: param is missing or the value is empty: {:nested_object=>:attribute}
pry(main)> params.require(:plain_param, :nested_object)
ArgumentError: wrong number of arguments (2 for 1)
We can see that require returns the value for a single param key. This comes in handy to ensure the presence of objects with multiple attributes.
Wrapping up:
params.require(:category).permit(:label, :description)
expects a hash of the form
{:category=>{:label=>"value", :description=>"value"}}
which translates to HTML POST parameters of
category[label]=value
category[description]=value
Edit: Postman automagically sets the content-type header for multi part file upload, so do not set it manually. Not sure whether this is considered a bug or a feature.
https://github.com/postmanlabs/postman-app-support/issues/191
I have 2 apps. A plain Ruby app and a Rails app that listens for POSTS from the plain Ruby app and also shows data that has been posted.
I am trying to design a basic API for my Rails app. I use Faraday to POST some JSON to my app. That part is working as I can see objects are being created after the POST.
In my resp I am expecting to see something related to the object I just created. Instead I do see a status=302.
How can I get a response back that shows success and maybe some json with the created object would be nice. Really I just want an indicator that the POST went correctly.
--- code from plain Ruby app that POSTS to my Rails app
conn = Faraday.new(:url => 'http://localhost:3000/api/v1/tests') do |faraday|
faraday.request :url_encoded # form-encode POST params
faraday.response :logger # log requests to STDOUT
faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
end
resp = conn.post do |req|
req.headers['Content-Type'] = 'application/json'
req.body = data.to_json
end
When I examine resp in pry I get this
[1] pry(#<Object>)> resp
=> #<Faraday::Response:0x007fb8c3a317d0
#env=
#<struct Faraday::Env
method=:post,
body="<html><body>You are being redirected.</body></html>",
url=#<URI::HTTP:0x007fb8c34176b0 URL:http://localhost:3000/api/v1/tests>,
request=
#<struct Faraday::RequestOptions
params_encoder=nil,
proxy=nil,
bind=nil,
timeout=nil,
open_timeout=nil,
boundary=nil,
oauth=nil>,
request_headers={"User-Agent"=>"Faraday v0.9.1", "Content-Type"=>"application/json"},
ssl=
#<struct Faraday::SSLOptions
verify=nil,
ca_file=nil,
ca_path=nil,
verify_mode=nil,
cert_store=nil,
client_cert=nil,
client_key=nil,
certificate=nil,
private_key=nil,
verify_depth=nil,
version=nil>,
parallel_manager=nil,
params=nil,
response=nil,
response_headers=
{"x-frame-options"=>"SAMEORIGIN",
"x-xss-protection"=>"1; mode=block",
"x-content-type-options"=>"nosniff",
"location"=>"http://localhost:3000/tests/2",
"content-type"=>"text/html; charset=utf-8",
"cache-control"=>"no-cache",
"x-request-id"=>"62da5fdc-6f00-4f2a-9c93-c80f6e08c4f8",
"x-runtime"=>"0.219049",
"server"=>"WEBrick/1.3.1 (Ruby/2.2.0/2014-12-25)",
"date"=>"Thu, 30 Jul 2015 16:54:07 GMT",
"content-length"=>"95",
"connection"=>"close",
"set-cookie"=>
"request_method=POST; path=/, _cimportal_session=QjdTN0lOaXBMSTZYT0hvOCsyOVozeW43VTRiSHZXTTBlakJFMmllTTNmdTYvbnd5QllWZnRnSDJhN2lHSDJxM3phSHZWU1VGcElSWEY0V0N3dXd6RHdzTW5leHFtY3hoNDNaTHRCZ2l3ODVaZTVIcXc0eDBnMHdKelhhN2lZeEdIYXNlWU9ZaThETkUvYnRTL293c0lKRGU1N3FrY2liN2t6anBCNlJMaXJFPS0tZmhIQmN0VXVvVEJ4VE5rQ3JLdWphZz09--965190ef9f588d95347f2ee25044eb79f3b5d289; path=/; HttpOnly"},
status=302>,
#on_complete_callbacks=[]>
As you can see there is a status=302 at the bottom. How can I get a success here along with some json representing the object?
Here is the main part of the Rails create method
#test = Test.new(test_params)
respond_to do |format|
if #test.save
format.html { redirect_to #test, notice: 'Test was successfully created.' }
format.json { render :show, status: :created, location: #test }
else
format.html { render :new }
format.json { render json: #test.errors, status: :unprocessable_entity }
end
What am I missing? The POST is working but I would like to see a response that it is successful.
You have a line that says:
format.json { render :show, status: :created, location: #test }
This is causing the 302 you're seeing.
Instead try something like
format.json { render status: :created, json: #test }
This should return a 201 status code and respond with the JSON of the new object created.
Try adding ".json" to the URL so the controller won't think the request is a regular HTML request.
If that fixes the problem, then you can either add that to all of your URL's or configure the controller to respond_to :json (and remove the format.html lines from the controller).
I typically do this in the application controller so all controller methods will inherit it:
class ApplicationController < ActionController::Base
respond_to :json
end
You can also do some stuff in your routes to lock this down further, but my memory is a little foggy on this:
resources :users, defaults: { format: :json }
EDIT
Above I suggested remove the format.html lines, but really you can remove all of that format stuff and just let the method return some JSON without caring what the format is:
if #test.save
# assuming there is a jBuilder view for this and that you have the
# respond_to :json in the controller or application controller
render :show, status: :created, location: #test
else
render json: #test.errors, status: :unprocessable_entity
I used both answers to help. Mainly I added .json to the end of the URL and it pretty much started to work. I also changed to this format.json { render status: :created, json: #test }
Given the following controller in rails:
class AccountsController < ApplicationController
respond_to :json, :xml
def update
#account = Account.where(uuid: params[:id]).first
unless #account.nil?
if #account.update_attributes params[:account]
respond_with #account, location: account_url(#account)
else
respond_with error_hash, status: :unprocessable_entity, root: :error, location: api_account_url(#account)
end
else
respond_with error_hash, status: :not_found, root: :error, location: accounts_url
end
end
def error_hash
{ :example => "Example for this question", :parameter => 42 }
end
end
I would expect a PUT request to /accounts/update/ to do the following
If the id exists, and the update_attributes call succeeds, deliver a 204 (No Content) success message. (I have it set to return #account, which would be nice, but no big deal. 204 is fine here.)
If the id exists, but the data is bad, deliver a 422 (Unprocessable Entity) error message, along with the xml/json to represent the error.
If the id does not exist, deliver a 404 (Not Found) error message, along with the xml/json to represent the error.
What actually happens is:
Deliver a 204 with no body.
Deliver a 204 with no body.
Deliver a 204 with no body.
Why is it that it ignores both my status and my body? I've had a similar setup for GET requests that work out just fine (correct status, correct body).
Example CURL request (for an ID that does not exist):
PUT request
curl -i --header "Accept: application/xml" --header "Content-type: application/json" -X PUT -d '{"name": "whoop"}' http://localhost:3000/api/accounts/3d2cc5d0653911e2aaadc82a14fffee9
HTTP/1.1 204 No Content
Location: http://localhost:3000/api/accounts
X-Ua-Compatible: IE=Edge
Cache-Control: no-cache
X-Request-Id: bf0a02f452fbace65576aab6d2bd7c1e
X-Runtime: 0.029193
Server: WEBrick/1.3.1 (Ruby/1.9.3/2013-01-15)
Date: Thu, 24 Jan 2013 08:01:31 GMT
Connection: close
Set-Cookie: _bankshare_session=BAh7BkkiD3Nlc3Npb25faWQGOgZFRkkiJWFmNmI2MmU0MzViMmE3N2YzMDIzOTdjMDJmZDhiMzEwBjsAVA%3D%3D--133e394eb760a7fce07f1fd51349dc46c2d51626; path=/; HttpOnly
GET request
curl -i --header "Accept: application/json" --header "Content-type: application/json" -X GET http://localhost:3000/api/accounts/3d2cc5d0653911e2aaadc82a14fffee9
HTTP/1.1 404 Not Found
Content-Type: application/json; charset=utf-8
X-Ua-Compatible: IE=Edge
Cache-Control: no-cache
X-Request-Id: 9cc0d1cdfb27bb86a206cbc38cd75473
X-Runtime: 0.005118
Server: WEBrick/1.3.1 (Ruby/1.9.3/2013-01-15)
Date: Thu, 24 Jan 2013 08:19:45 GMT
Content-Length: 116
Connection: Keep-Alive
{"friendly-status":"not-found","status":404,"message":"No account with id '3d2cc5d0653911e2aaadc82a14fffee9' found"}
According to this discussion this rather non intuitive behavior is due to the desire to maintain compatibility with the scaffold.
In general we keep the responder the same implementation as the
scaffold. This allows us to say: replace respond_to by respond_with
and everything will work exactly the same.
-- josevalim
You have two choices to override the default behavior.
A) Pass a block to respond_with
unless #account.nil?
if #account.update_attributes params[:account]
respond_with #account do |format|
format.json { render json: #account.to_json, status: :ok }
format.xml { render xml: #account.to_xml, status: :ok }
end
else
respond_with error_hash do |format|
format.json { render json: error_hash.to_json(root: :error), status: :unprocessable_entity }
format.xml { render xml: error_hash.to_xml(root: :error), status: :unprocessable_entity }
end
end
else
respond_with error_hash do |format|
format.json { render json: error_hash.to_json(root: :error), status: :not_found }
format.xml { render xml: error_hash.to_xml(root: :error), status: :not_found }
end
end
It's unfortunate that we have to return to duplication for each format, but that seems to be the current recommendation up to Rails 4.0 so far; see here.
You should return 200 - OK, not 204 - No Content if you are returning the updated object, or don't return anything and have your client code 'GET' the updated object. :location is not meaningful in an api context, it's for redirecting an html response.
B) Create a custom responder
respond_with #account, status: :ok, responder: MyResponder
I've not done this myself so I can't give an example, but it seems like overkill here anyway.
Check out Railscasts Episode:224 for some discussion of respond_with including custom responders.
Did you see ActionController::Responder class ?
Here are some methods to think about
# All other formats follow the procedure below. First we try to render a
# template, if the template is not available, we verify if the resource
# responds to :to_format and display it.
#
def to_format
if get? || !has_errors? || response_overridden?
default_render
else
display_errors
end
rescue ActionView::MissingTemplate => e
api_behavior(e)
end
and
def api_behavior(error)
raise error unless resourceful?
if get?
display resource
elsif post?
display resource, :status => :created, :location => api_location
else
head :no_content
end
end
As you can see api_behavior works for post and get methods but not for put .
If an existing resource is modified, either the 200 (OK) or 204 (No Content) response codes SHOULD be sent to indicate successful completion of the request.
head :no_content is what you get.
So reason of this is that rails doesn't understand what are you trying to do. Rails thinks there is no error when you use respond_with in this case.(it's not a bug you just shouldn't use it that way)
I think respond_to is what you need.
IMHO, I would simply try this first.
The .first! will make rails emit a 404 if the record is not found.
In case of success will render 204.
In case of error while saving it will extract errors from the model errors object.
class AccountsController < ApplicationController
respond_to :json, :xml
def update
#account = Account.where(uuid: params[:id]).first!
#account.update_attributes params[:account]
respond_with #account, location: account_url(#account)
end
end
If model validation messages are not enough, then you will need to conditionally emit the result.
The success path will work as above and in case of failure you will render what you need.
class AccountsController < ApplicationController
respond_to :json, :xml
def update
#account = Account.where(uuid: params[:id]).first!
if #account.update_attributes params[:account]
respond_with #account, location: account_url(#account)
else
respond_to error_hash do |format|
format.json { render json: error_hash, status: :unprocessable_entity }
format.xml { render xml: error_hash, status: :unprocessable_entity }
end
end
end
def error_hash
{ :example => "Example for this question", :parameter => 42 }
end
end
I have a web service, that receives incoming POST requests from another server. The fields of the request map to a model in my rails application called 'message'.
When I send a JSON POST like
curl -v -H "Accept: application/json" -H "Content-type: application/json" -X POST -d
'{"message":{"content":"GUESTTEST","time":"2012-08-01 10:30:99","businessNumber":"5555","sessionId":"5555CHS1343808543654"}}'
http://localhost:3000/messages.json
The request is processed by the block:
respond_to do |format|
if #message.save
format.html { redirect_to #message, notice: 'Message was successfully created.' }
format.json { render json: #message, status: :created, location: #message }
end
And the object is saved successfully.
Unfortunately, the POST requests that I receive from the other web service are non-json and in this format:
content=GUESTTEST&time=2012-08-01+10%3A09%3A03&businessNumber=5555&sessionId=5555CHS1343808543654
How do I write my own route and method to process these requests to also map them to my message model?
Any tipps will be greatly appreciated!
=====
Update:
I've solved this by creating the the object based on top level params elements in the controller as follows:
def create
#message = Message.new(content: params[:content], command: params[:command], messageId: params[:messageId], msisdn: params[:msisdn], businessNumber: params[:businessNumber], keyword: params[:keyword], operatorCode: params[:operatorCode], sessionId: params[:sessionId], time: params[:time])
respond_to do |format|
if #message.save
format.html { redirect_to #message, notice: 'Message was successfully created.' }
format.json { render json: #message, status: :created, location: #message }
else
format.html { render action: "new" }
format.json { render json: #message.errors, status: :unprocessable_entity }
end
end
end
What would be a more elegant way to achieve this?
You should find all the parameters in the params hash. You can check that by outputting them with inserting this in an appropriate view:
<%= debug(params) if Rails.env.development? %>
although it might be possible to pass them directly to update_attributes, (or Message.new, the line is missing in your code snippet) I would write a custom method that sanitizes them and initializes the new Message from the params hash.
The respond_to method is only concerned with the format that is used to respond to the client when the creation request is finished. So here, when you post to the URI .json, it will respond in a JSON format. The same data posted to only /messages will create the same object, but will respond in HTML (or the set default).
In your case, the POST request that you receive is in parameter format and is the same as the data sent out by an HTML form element, for example. The parameters will be in the params variable, as is already present I am sure.
As the route is not concerned with the format, it shouldn't change either.
You can simulate this POST request with curl like so:
$ curl -d "content=GUESTTEST&time=2012-08-01+10%3A09%3A03&businessNumber=5555&sessionId=5555CHS1343808543654" http://localhost:3000/messages
I'm very new to Rails and web development.
I'm generating a bunch of objects in Matlab and I'd like send these objects to a database in my Rails app. Can anyone advise me on how to do this?
So far, on the Rails end, I've generated basic scaffolding for my data. I can add objects to my database using a form at '/myobjects/new'.
On the Matlab end, I've been trying to add objects using HTTP POST requests, like so:
s = urlread('http://localhost:3000/myobjects.json','POST',{'myobject','{name1:''value1''}'})
This fails and prints the following to the Rails console:
Started POST "/myobjects.json" for 127.0.0.1 at 2012-06-16 11:48:28 -0400
Processing by MyobjectsController#create as JSON
Parameters: {"myobject"=>"{name1:'value1'}"}
WARNING: Can't verify CSRF token authenticity
Completed 500 Internal Server Error in 1ms
NoMethodError (undefined method `stringify_keys' for "{name1:'value1'}":String):
app/controllers/myobjects_controller.rb:43:in `new'
app/controllers/myobjects_controller.rb:43:in `create'
This approach might be way off base, but hopefully the code above makes my goal clear. Can anyone tell me how to fix my code, or suggest a better strategy for getting my data into rails?
EDIT
At the moment my new and create methods look like this (but I can change them as required)
# GET /irs/new
# GET /irs/new.json
def new
#ir = Ir.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #ir }
end
end
# POST /irs
# POST /irs.json
def create
#ir = Ir.new(params[:ir])
respond_to do |format|
if #ir.save
format.html { redirect_to #ir, notice: 'Ir was successfully created.' }
format.json { render json: #ir, status: :created, location: #ir }
else
format.html { render action: "new" }
format.json { render json: #ir.errors, status: :unprocessable_entity }
end
end
end
In the end I gave up trying to do this with matlab's built-in functions. Instead, I imported a Java library (Apache HttpComponents). Here's the script I came up with. This did the job.
javaaddpath(['utils/httpcomponents-client-4.2/lib/httpcore-4.2.jar']);
javaaddpath(['utils/httpcomponents-client-4.2/lib/httpclient-4.2.jar']);
import org.apache.http.impl.client.DefaultHttpClient
import org.apache.http.client.methods.HttpPost
import org.apache.http.entity.StringEntity
httpclient = DefaultHttpClient();
httppost = HttpPost('http://127.0.0.1:3000/myobjects.json');
httppost.addHeader('Content-Type','application/json');
httppost.addHeader('Accept','application/json');
params = StringEntity('{"field1":"value1"}');
httppost.setEntity(params);
response = httpclient.execute(httppost);
You can avoid that specific problem by setting
class MyobjectsController < ApplicationController
protect_from_forgery :except => :create
...
end
within your controller. It disables the CSRF token validity check.