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
Related
I m building my first REST API using Rails and have a simple question which I am unable to solve.
Basically i am not able to write a params hash into the database from a http PUSH request.
I have the following user controller under the Api namespace with the create action.
class Api::UserController < ApplicationController
http_basic_authenticate_with :name => "test", :password => "test"
skip_before_action :verify_authenticity_token
def create
#user = User.create(params[:user])
respond_to do |format|
if #user.save
format.json{render json: #user, status: :created, location: #user} #render create full response
else
format.json{render json: #user.errors, status: :unprocessable_entity}
end
end
end
end
The route to this action is
POST /api/user(.:format) api/user#create{:format=>"json"}
The user model has a name and a address string not contained.
To test the REST call i setup the firefox plugins RESTeasy and RESTclient
with this URL http://localhost:3000/api/user/
and this body
{
"name": "timoteo",
"address": "berlin"
}
So far so good. Now after sending this package Webrick gives me this output:
Started POST "/api/user" for ::1 at 2015-08-03 17:06:50 +0200
Processing by Api::UserController#create as JSON
Parameters: {"name"=>"timoteo", "address"=>"berlin"}
(1.2ms) BEGIN
SQL (0.6ms) INSERT INTO "users" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "id" [["created_at", "2015-08-03 15:06:50.646169"], ["updated_at", "2015-08-03 15:06:50.646169"]]
(42.8ms) COMMIT
(0.3ms) BEGIN
(0.3ms) COMMIT
Completed 201 Created in 66ms (Views: 1.4ms | ActiveRecord: 48.1ms)
neglecting the two parameter and an empty empty record like this one gets stored in the database(also the REST clients show me the same)
{"id":19,"name":null,"address":null,"created_at":"2015-08-03T15:06:50.646Z","updated_at":"2015-08-03T15:06:50.646Z"}
Can someone give me a hint what might have gone wrong during the way. I assume it is something with the params hash but i could not figure it out yet.
Add validation to your model:
class User < ActiveRecord::Base
validates_presence_of :name
validates_presence_of :address
end
That will disallow saving record with blank attributes.
Then, change your #save call to #persisted? in your controller (because record is created immediately after #create is called, it is already saved or failed)
if #user.persisted?
format.json{render json: #user, status: :created, location: #user} #render create full response
else
format.json{render json: #user.errors, status: :unprocessable_entity}
end
UPD. You could also benefit from whitelisting parameters which you accept:
#user = User.create(params.require(:name, :address))
This way Rails will respond with error, saying parameter is missing, and will not assign any other attributes to user (which you don't want to be assigned).
Your params of { "name": "timoteo", "address": "berlin" } are incorrect when your user creation is #user = User.create(params[:user])
You params should look more like:{ "user": { "name": "timoteo", "address": "berlin" } } because you're accessing params[:user] so the user attributes need to belong to the user section of your params.
Adding Validations may help if its incorrect to save a User with no name/address too.
class User < ActiveRecord::Base
validates :name, presence: true
validates :address, presence: true
# etc...
end
when I used $.ajax() to submit the form's content with the following respond_to block:
respond_to do
render :json => { :url => question_path(resource, :recent => :true),
:uploadPath => question_path(#question, :format => "json"),
:editPath => edit_question_path(#question),
:exitPath => question_path(resource)
}
end
I got a error:
Completed 406 Not Acceptable in 124690.7ms (Views: 0.4ms | ActiveRecord: 16.7ms | Sphinx: 0.0ms)
After I removed respond_to, as the above code is changed into:
render :json => { :url => question_path(resource, :recent => :true),
:uploadPath => question_path(#question, :format => "json"),
:editPath => edit_question_path(#question),
:exitPath => question_path(resource)
}
The error has gone, why this happens? what things I miss? Thank you!
respond_to is used when you want to produce different responses for the same controller action. For example perhaps you want to render HTML for browsers but JSON for an API client. You list all the responses you can generate (and how they should be generated) and rails picks the one that matches the request:
respond_to do |format|
format.html { ... }
format.json { ... }
end
In the example above rails would know how to generate HTML and JSON responses but if a request came in requesting an XML response then rails would produce a 406 error (not acceptable ) because it doesn't know how to produce such a response.
In your code snippet you are calling respond_to so you are telling rails to only use the response formats given by the block but you are not defining any response formats (you're not using the object yielded to the block), therefore rails produces a 406.
When you remove the call to respond_to Rails no longer worries about trying to pick the correct response format and just uses your call to render (and would do so no matter what format the request was asking for).
If you wanted to use respond_to you would do
respond_to do |format|
format.json { render :json => { ... }}
end
when doing so you do need to ensure that the request is requesting json - if rails though it was requesting HTML then you'd be back to your 406 error. You can use jQuery's dataType option for this or use an extension of .json (ie make your request to /foo/bar.json)
I'm trying to post a json message to a Rails 4.1.1 server, but is failing due to unpermitted parameters. I'm using Mongoid as well and submitting via POST and content type of application/json.
Here's my domain:
class Sale
include Mongoid::Document
include Mongoid::Timestamps
field :internalId, type: String
embeds_many :saleItems
accepts_nested_attributes_for :saleItems
end
Here's the controller code:
def sale_params
params.require(:sale).permit(:internalId, :parentInternalId, :externalId, :internalIdForStore, :internalIdForCustomer, :sendReceiptType, :saleItems)
end
# POST /sales
# POST /sales.json
def create
#sale = Sale.new(sale_params)
#####################
puts "parent: "
puts #sale.inspect
puts "collections: "
#sale.saleItems.each do |si|
puts "collection here"
puts si.inspect
end
respond_to do |format|
if #sale.save
format.html { redirect_to #sale, notice: 'Sale was successfully created.' }
format.json { render action: 'show', status: :created, location: #sale }
else
format.html { render action: 'new' }
format.json { render json: #sale.errors, status: :unprocessable_entity }
end
end
end
I've successfully saved the collection saleItems fine outside of rails and just using a ruby script with the collection successfully saving via Mongoid.
Here's the JSON content:
{
"sale" : {
"internalId":"77E26804-03CC-4CA9-9184-181C2D8CB02A"
"saleItems" : [
{
"inventoryName" : "inv 1"
},
{
"inventoryName" : "inv 2"
}
]
}
}
Wow I figured it out. It needs to have the {} around the collection of items.
params.require(:sale).permit(:internalId, :parentInternalId, :externalId, :internalIdForStore, :internalIdForCustomer, :sendReceiptType,
{:saleItems => [:inventoryName, :internalIdForSeller]})
Here's the post I found to help fix the issue.
Rails 4 - Strong Parameters - Nested Objects
I think the issue is the strong parameters being permitted.
You have
params.require(:sale).permit(:internalId, :parentInternalId, :externalId, :internalIdForStore, :internalIdForCustomer, :sendReceiptType, :saleItems)
But salesItems is another class. You need something like
params.require(:sale).permit(:internalId, :parentInternalId, :externalId, :internalIdForStore, :internalIdForCustomer, :sendReceiptType, :saleItems_attributes => [:inventoryName, :anotherAttribute, :stillAnotherAttribute])
Kindly edit your answer the tell that what params you are getting in.
The things is params is data structure its a request object. And permit is a method which allow to permit the specific parameter .
So put the debugger and easily you will recognize what the problem is.
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.
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.