Send an array of different hashes in a single POST HTTP request - ruby-on-rails

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).

Related

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

How to indicate empty array in HTTP request query params?

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.

assign params hash with javascript variable

Is it possible to assign the params hash with javascript variables in the views side.
because i would be doing a complicated structure in my params hash that involves nesting and arrays.
You can post JSON data to the rails server and Rails will make it available in params hash, provided the JSON response data has the correct headers. Refer this thread and this thread for more details.
The only way I can think of is to use JS to add new fields to your form, giving them IDs and names adhering to how Rails will parse the elements' names into params when the form is submitted (something along the lines of id=model_assoc_attributes_N_attr and name=model[assoc_attributes][N][attr], which I think is the case when Model accepts_nested_attributes_for Assoc).

RoR: POST to a page using raw form data. How?

Is there a ruby method to POST form data encoded in "x-www-form-urlencoded" as specified here? http://www.w3.org/MarkUp/html-spec/html-spec_8.html
I am aware of Net::HTTP.post_form, but because I have several values to post which share the same name I can't use a hash, which is required by that method.
To clarify, I have a string of the form "value1=x&value1=y&value1=z&value2=a&value3=b" and I want to be able to POST it to another page. How can I do this?
I think internally the params object is a parsed version of the actual raw post body in the http request. All post data is posted the same way (as raw post data), but the params hash in ActionController has already parsed this into an easy-to-use hash. If you actually need the raw post data from a form, you can access it through the raw_post method of the request object itself.
The ActionController::Request.raw_post documentation here is for rails3, but has been available since at least 2.3.8 (the only 2.3.x version I checked). I think it most likely has been available longer than that.
In a controller, try self.request.raw_post to get the raw post data as a string.
Are you able to have a hash value which is an Array? I think that this is the way parameters with the same names are usually handled.

Resources