How to indicate empty array in HTTP request query params? - ruby-on-rails

This is the way Rails accepts arrays in query parameters:
PUT /resource.json?sport_ids[]=133&sport_ids[]=64&sport_ids[]=71 ...
I tried to google this question but didn't find any explicit docs on it:
How to tell Rails that we want sport_ids to become empty (pass empty array of sport_ids via query parameters) ?

HTTP requests can have only variables on the url itself. That's a limitation feature of HTTP, not Rails.
Take a look at How Does Rack Parse Query Params? With Parse_nested_query to figure out how rails collects the variables into an array, it won't run out of the box in case of an empty array.
You can avoiding sending the params["sport_ids"] and patch your controller with:
params["sport_ids"] ||= []
The best practice to use put/post requests, is passing such data in the request body (json/xml) like:
{
"sport_ids": []
}
Or with data as:
//...
{
"sport_ids": [133, 64, 71]
}
//...
For more info about HTTP request steps, check Running a HTTP request with rails.

While #mohameddiaa27's answer has good advice on how to achieve that by passing such data in the request body as JSON I found that I cannot rely on it within my application: I found that it is not easy to combine such passing of JSON into request body within multipart forms where I want to pass user record (with user[sport_ids] in it) and user's avatar image (user[avatar]) field.
So I continued to investigate how to achieve that using default "query parameters in a request body of POST/PUT request" approach and found the reason why I was not able to reset my sport_ids on server-side: it was the lack of permission for that specific sport_ids field. Now I have the following permits (pseudocode):
current_user.update!(user_params)
where user_params is
user_attributes_to_permit = [
...
:sport_ids, # this is what was needed for just `user[sport_ids]` to work.
{ :sport_ids => [] } # this is needed for sport_ids non-empty arrays to work
...
]
params.require(:user).permit(user_attributes_to_permit)
so now I am able to reset the sport_ids array of my user by passing just user[sport_ids] (without '=' and value! i.e. ...&user[sport_ids]&...) within my query parameters.

Related

Send an array of different hashes in a single POST HTTP request

I have two different kinds of hashes:
hash1 = {"h1_k1": "h1_v1", "h1_k2": ["h1_v2"]}
hash2 = {"h2_k1": "h2_v1", "h2_k2": "h2_v2"}
I may have numerous occurences of each hash with different values, but the following issue occurs even with a single occurence of each:
I want to send the data to a Rails server in an HTTP post request, and the behavior differs when I send it in a single request for the entire data and in one request per hash.
In the controller, params will be the following:
Single request: I push both hashes into an array and Net::HTTP.post_form(uri, array).
Parameters: {"{\"h1_k1\"=>\"h1_v1\", \"h1_k2"\"=>"=>{"\"h1_v2"\""=>{"}"=>nil}, {\"h2_k1\"=>\"h2_v1\", {\"h2_k2\"=>\"h2_v2\"}"=>nil}
One request per hash: array.each {|hash| Net::HTTP.post_form(uri, hash) }
Parameters: {"h1_k1": "h1_v1", "h1_k2": "h1_v2"} # array converted to string of only the last element
Parameters: {"h2_k1": "h2_v1", "h2_k2": "h2_v2"}
What is the reason behind this, and is there any way to properly send the data in a single request?
In the definition of post_form(url, params):
The form data must be provided as a Hash mapping from String to String
In your example, you have an Array that contains two hashes. Consider passing the params as Hash.
I ended up solving it in two different ways:
I used to_json on the array and I set the header Content-Type to be application/json.
This allowed instant access to the properly formatted array and hashes in the server side params[:_json].
For example params[:_json][0]['h1_k1'] gives h1_v1.
I used to_yaml on the array and I set the header Content-Type to any of the YAML options.
The params in the backend side were empty as (I guess) Rails couldn't parse it automatically, but using request.raw_post allowed to get the data from the post body.
Thus using Psych.safe_load(request.raw_post) parsed it back into an array of hashes, which allowed the use of the data just like in method 1 (disregarding params).

How to send an array with a POST request, with a parameter name for the array that contains an empty set of square brackets []?

So, Rails normally handles parsing of incoming Arrays sent via HTTP Post requests (forms), like this:
"Normally Rails ignores duplicate parameter names. If the parameter
name contains an empty set of square brackets [] then they will be
accumulated in an array." - Rails Guides
But when using Net::HTTP.Post to send a Post request to a third party service (API), then it seems this convention of handling arrays in HTTP Post requests is not followed.
This code:
data = {:categories => [one, two, three]}
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Post.new(uri.request_uri)
request.set_form_data(data)
response = http.request(request)
Then set_form_data will serialize the Array like this:
categories=one&categories=two&categories=three
And not like this (which I thought was the conventional Rails way):
categories[]=one&categories[]=two&categories[]=three
Why?
I can see that it has to do with the recent implementation of the URI.encode_www_form method that set_form_data uses. But what is the purpose deviating from the conventional Rails way?
And, more importantly, how do I easily modify this to send it in the latter way (without overloading a bunch of inherent Ruby/Rails methods)?
I found out that the solution was as easy as changing the table name:
data = {'categories[]' => [one, two, three]}
It even works if other elements of the data hash are :symbols.
I'd still be curious to find out why Rails makes this "hack" necessary when using the Net::HTTPHeader::set_form_data method, to get Rails' otherwise conventional way of handling arrays in the url parameters.

How to access Rails params before all of the values are stringified?

I'd like to be able to inspect the params hash before all of the values are stringified by Rails. For example if I am using application/json Accept/Content-Type, and I receive:
{ "id":1, "post":"Hello" }
I want to be able to know that params[:id] was originally passed as a JSON integer, not a string.
I also want to be able to do this within a controller spec, which uses a limited set of middleware (or none at all?). Is this possible?
I believe this post has what you are looking for: How to access the raw unaltered http POST data in Rails?
request.raw_post
http://api.rubyonrails.org/classes/ActionDispatch/Request.html#method-i-raw_post

Get raw parameter data in Ruby on Rails

I have a ruby on rails api where I want to sign my request data by appending a hashed version of all passed in parameters to the request and rebuild this one at the server side as well to validate the integrity of the requests.
When I simply use the params method in the controller I have different parameters (e.g. for an update-method which is specified by this:
put 'login' => 'login#update'
I get as parameters on the server:
{"timestamp"=>"1399562324118", "secured"=>"xxx",
"login"=>{"timestamp"=>"1399562324118", "secured"=>"xxx"}}
although I only send the request from the client with
{"timestamp"=>"1399562324118", "secured"=>"xxx"}
Does any one have an idea how to get rid of this "login" parameter in the params list in a generic way? I do not want to exclude this for every single request of my api.
Thanks a lot!
Per the Rails Edge guide on ActionController:
"If you've turned on config.wrap_parameters in your initializer or calling wrap_parameters in your controller, you can safely omit the root element in the JSON parameter"
See http://guides.rubyonrails.org/action_controller_overview.html#json-parameters

InvalidAuthenticityToken from rails for POST request from openlaszlo app

InvalidAuthenticityToken from rails for POST request
Hi All
I have a rails server running to which I make a POST request.
The dataset is defined as
Now per rails documentation in order to make a POST a request I need to set the add "authenticity_token" to the query string. So if for example the authenticity_token is "xxxxxxx", the final url should look like http://mywebsite.com/doSomething?aut..._token=xxxxxxx
I get the authenticity token from the server in the flashvars.
I have a user defined canvas attribute called auth_token which I use to store the authenticity token.
Below is the openlaszlo code I use to make the request.
var d = canvas.datasets.ds;
var content = get_my_content();
d.setQueryParam('lzpostbody',content);
d.setQueryString({authenticity_token : encodeURIcomponent(canvas.auth_token) });
d.doRequest
In this code the setQueryString call seem to clear out the query params. If I change the order of the setQueryString and setQueryParam calls the opposite happens.
The question is. Is there a way to set the query string without changeing/deleting the query params.
Thanks very much
Puneet
I don't know anything about OpenLaszlo, but my guess is that setQueryParam adds or modifies one param, whereas setQueryString overwrites the whole query string with the contents of the object.
Shouldn't you want to just add the authenticity token?
d.setQueryParam('lzpostbody', content);
d.setQueryParam('authenticity_token', encodeURIcomponent(canvas.auth_token));

Resources