Convert sent params to json - ruby-on-rails

I have controller method which perform some actions for POST sent json params, but the problem is when I try to send json params without json header.
I would like to handle this exception by converting string to json and here the problems begins.
Let's say that I have simple JSON like this:
{"menu": {
"id": "file",
"value": "File",
"popup": {
"menuitem": [
{"value": "New", "onclick": "CreateNewDoc()"},
{"value": "Open", "onclick": "OpenDoc()"},
{"value": "Close", "onclick": "CloseDoc()"}
]
}
}}
I have placed binding.pry at the begining of method and I try to get params from there. I figured out that request.body will return me object #<StringIO:0x007fbdb5cf9fe8> which I have to convert to JSON.
First thing I have tried was:
[1] pry(#<TestController>)> request.body.to_json
=> "[\"{\\\"menu\\\": {\\n\",\" \\\"id\\\": \\\"file\\\",\\n\",\" \\\"value\\\": \\\"File\\\",\\n\",\" \\\"popup\\\": {\\n\",\" \\\"menuitem\\\": [\\n\",\" {\\\"value\\\": \\\"New\\\", \\\"onclick\\\": \\\"CreateNewDoc()\\\"},\\n\",\" {\\\"value\\\": \\\"Open\\\", \\\"onclick\\\": \\\"OpenDoc()\\\"},\\n\",\" {\\\"value\\\": \\\"Close\\\", \\\"onclick\\\": \\\"CloseDoc()\\\"}\\n\",\" ]\\n\",\" }\\n\",\"}}\"]"
but it is not result I was looking for.
I have tried several more converts, like:
[2] pry(#<TestsController>)> ActiveSupport::JSON.decode(request.body.to_json)
=> []
Which also gives me nothing. I'm out of ideas.
I would like to achive something like this:
[1] pry(#<TestsController>)> params
=> {"menu"=>{"id"=>"file", "value"=>"File", "popup"=>{"menuitem"=>[{"value"=>"New", "onclick"=>"CreateNewDoc()"}, {"value"=>"Open", "onclick"=>"OpenDoc()"}, {"value"=>"Close", "onclick"=>"CloseDoc()"}]}},
"controller"=>"payments",
"action"=>"notification",
"payment"=>{"menu"=>{"id"=>"file", "value"=>"File", "popup"=>{"menuitem"=>[{"value"=>"New", "onclick"=>"CreateNewDoc()"}, {"value"=>"Open", "onclick"=>"OpenDoc()"}, {"value"=>"Close", "onclick"=>"CloseDoc()"}]}}}}
This is scenario for sending params with json header. In that can I can easily iterate through them.

You should use JSON.parse on request.body.read
request.body.rewind
JSON.parse(request.body.read)

Have you tried
request.body replaced with env['rack.input'].read
json = JSON.parse env['rack.input'].read
OR
json = JSON.parse request.body.read
as stated here

Since you are using RoR, if you are using Action Controller Parameters you can use the to_hash method and then use to_json (which is only on RoR, not part of Ruby) to convert your permitted params, like this:
params.permit(:attributes).to_hash.to_json

In my case this worked:
JSON.parse(params[:attributes].to_json)

Related

Is it possible to use the same parameter scoping from form_for, except from an AJAX call?

I'm submitting some data to my rails backend via an AJAX call (axios specifically).
My AJAX request looks something like this:
Axios({
url: "/terms/" + event.target.term_id.value,
method: "put",
data: {
"term[en_text]": event.target["term[en_text]"].value,
"term[de_text]": event.target["term[de_text]"].value,
"term[jp_text]": event.target["term[jp_text]"].value
},
headers: {
"Authorization": token
}
}).then((result) => {
console.log(result)
})
It looks as though the backend doesn't do the same kind of scoping it would do if I had submitted this via form_for:
Parameters: {"term[en_text]"=>"Dates", "term[de_text]"=>"Datteln", "term[jp_text]"=>"foobars", "id"=>"1", "term"=>{}}
What the heck happened? As you can see from looking at the parameters received, none of them end up in "term" => {}
data: {
"term[en_text]": event.target["term[en_text]"].value,
"term[de_text]": event.target["term[de_text]"].value,
"term[jp_text]": event.target["term[jp_text]"].value
},
Would be correct if you are sending application/x-www-form-urlencoded or multipart/form-data as its a simple key/value format and rack parses parameters with brackets into hashes (and arrays) when parsing form data (or query strings).
However if you are passing application/json you need to pass an actual JSON object instead:
data: {
term: {
en_text: event.target["term[en_text]"].value,
de_text: event.target["term[de_text]"].value,
jp_text: event.target["term[jp_text]"].value
}
}
A JSON parser will not expand the keys, and there really is no need for that as JSON can actually express objects and arrays without the hackyness involved with form data.

How can I embed HTML tags into a JSON object in Rails?

I have a simple hash containing a description:
simple_hash = { "description" => "<p>This is my description.</p>" }
I need write this out to a file. In the file, this hash needs to be represented as JSON:
simple_hash.to_json
But this is what I'm actually getting:
"{\"description\":\"\\u003cp\\u003eThis is my description.\\u003c/p\\u003e\"}"
How do I stop to_json from doing that to my HTML tags?
to_json is doing the safe thing encoding the HTML.
If you are certain that it is safe, you can easily decode with Rails' JSON.parse
>> JSON.parse "{\"desc\":\"\\u003cp\\u003eThis is my description.\\u003c/p\\u003e\"}"
=> {"desc"=>"<p>This is my description.</p>"}
You could try doing this:
def json_template(id)
###################
# Method to create the json templates
###################
File.open("test_dir/temp_file.json", 'w'){|i|
templ = %Q<{
"id": "#{Random.rand(20)}#{Random.rand(20)}",
"version": 1.0.1.1,
"service": "ftp",
"os": "linux"
}>
i.write(templ)
}
end

Rails: Empty params when do ajax post with content-type = application/json

I used Backbone as Javascript framework for my new project. However, When I invoke save() on a Model , Rails server just can't parse it to the params hash
Javascript
user new Project.Models.User({email : "nqtien310#gmail.com"})
user.save()
Rails server
puts params
=> {"action"=>"create", "controller"=>"users"}
puts request.raw_post
=> "{\"email\":\"nqtien310#gmail.com\"}"
Then I tried to set Backbone.emulateJSON to true
Javascript
Backbone.emulateJSON = true
and Rails can parse the post data to params now, but not the expected data
puts params
=> {"model"=>"{\"email\":\"nqtien310#gmail.com\"}", "action"=>"create", "controller"=>"users"}
Well, after a long research , I figure it out that inside of my profiles' initialize/mime_types.rb , they added
Mime::Type.register "application/json", :mobile
Dig into a bit , I can see that Rails uses the mime types to parse the request body , in this case , application/json is overriden , and Rails can't use the Json parse strategy to parse the request body into params hash, that's why it's empty
Backbone.emulateJSOn will set another content-type instead of application/json, that's why Rails still can understand this Mime Type, and has correspond parse strategy for it , but since it's not the JSON strategy , so the params is not in JSON format
Json parse strategy ( from Rails source )
when :json
data = request.deep_munge ActiveSupport::JSON.decode(request.body)
request.body.rewind if request.body.respond_to?(:rewind)
data = {:_json => data} unless data.is_a?(Hash)
data.with_indifferent_access

issue with passing array of hashes as parameter to POST in rails application with typhoeus

I have two rails services. One serving the UI and some basic functionality (UIService) and another which manages the underlying models and database interactions (MainService).
At the UIService, I have a form that collects a list of items and uses that to POST to MainService via jQuery.
I take the javascript array and call the jQuery.post to UIService first, like this -
var selected_items = new Array();
// Filled up via the form...
params={"name":$("#name_input").val(),
"items": selected_items };
jQuery.post("/items", params);
This is then converted to an array of hashes with the key "item_id" and then forwarded to the MainService via Typhoeus like this -
items = []
item = {}
params[:items].each do |i|
item[:item_id] = i
end
## Gives me this ---> items = [ {item_id: 189}, {item_id: 187} ]
req = Typhoeus::Request.new("#{my_url}/items/",
method: :POST,
headers: {"Accepts" => "application/json"})
hydra = Typhoeus::Hydra.new
hydra.queue(req)
hydra.run
At the MainService, I need the JSON schema to be in a particular format. Basically an array of items... like this -
{ "name": "test_items", "items": [ {"item_id":"189"},{"item_id": "187"} ] }
The issue is that when I collect the array from jQuery and pass it to UIService, it looks like this in the params -
[ {item_id: 189}, {item_id: 187} ]
But, when it arrives at MainService, it becomes this -
{"name"=>"test_items",
"items"=>{"0"=>{"item_id"=>"189"}, "1"=>{"item_id"=>"187"}}
So, I need the array of items to be key'ed with "item_id" and inserted into the params. I tried several ways to keep it as an array of hashes, but it always ends up in the wrong format at the destination.
I tried various workarounds, like stringifying, not stringifying, building my own array of hashes etc. I'm pretty stuck at this point. Any ideas? Anything I'm doing wrong or not doing?
I can make it work other JSON schemas, but I need to stick to this one.
The issue was with the way I was passing the parameters into typhoeus
before (with issue) --
req = Typhoeus::Request.new("#{Rails.application.config.custom_ads_url}/groups",
method: :POST,
params: parameters,
headers: {"Content-Type" => "application/json", "AUTHORIZATION" => "auth_token #{user.auth_token}"})
after (works) --
notice that I needed to convert to json and put it in the body. 'params' in typhoeus was being considered as a custom hash.
req = Typhoeus::Request.new("#{Rails.application.config.custom_ads_url}/groups",
method: :POST,
body: parameters.to_json,
headers: {"Content-Type" => "application/json", "AUTHORIZATION" => "auth_token #{user.auth_token}"})
There is also a middleware provided by Typhoeus which does the correct conversion: http://rubydoc.info/github/typhoeus/typhoeus/frames/Rack/Typhoeus/Middleware/ParamsDecoder.
If you're using the Typhoeus class method .post you can implement your request like so:
Typhoeus.post(
"#{Rails.application.config.custom_ads_url}/groups",
headers: { 'Content-Type'=> "application/json" },
body: parameters.to_json
)

How to post JSON data in rails 3 functional test

I plan to use JSON data in both request and response in my project and having some problems in testing.
After searching for a while, I find the following code which uses curl to post JSON data:
curl -H "Content-Type:application/json" -H "Accept:application/json" \
-d '{ "foo" : "bar" }' localhost:3000/api/new
In the controller I can access the JSON data simply using params[:foo] which is really easy. But for functional testing, I only find post and xhr (alias for xml_http_request).
How can I write functional test in rails to achieve the same effect as using curl? Or should I do test in other ways?
Here's what I've tried. I find the implementation for xhr in action_controller/test_case.rb, and tried to add jhr method simply changing 'Conetent-Type' and 'HTTP_ACCEPT'. (Added in test/test_helpers.rb.)
def json_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
#request.env['Content-Type'] = 'Application/json'
#request.env['HTTP_ACCEPT'] ||= [Mime::JSON, Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
__send__(request_method, action, parameters, session, flash).tap do
#request.env.delete 'Content-Type'
#request.env.delete 'HTTP_ACCEPT'
end
end
alias jhr :json_http_request
I used this in the same way as xhr, but it does not work. I inspected the #response object and sees the body is " ".
I also find one similar question on Stack Overflow but it's for rails 2 and the answer for posting raw data does not work in rails 3.
As of Rails 5, the way to do this is:
post new_widget_url, as: :json, params: { foo: "bar" }
This will also set the Content-type header correctly (to application/json).
I found that this does exactly what I want – post JSON to a controller's action.
post :create, {:format => 'json', :user => { :email => "test#test.com", :password => "foobar"}}
Just specify appropriate content type:
post :index, '{"foo":"bar", "bool":true}', "CONTENT_TYPE" => 'application/json'
Json data should go as a string, not as a Hash.
Looking at stack trace running a test you can acquire more control on request preparation:
ActionDispatch::Integration::RequestHelpers.post => ActionDispatch::Integration::Session.process =>
Rack::Test::Session.env_for
Specifying :format does not work because request go as 'application/x-www-form-urlencoded' and json isn't parsed properly processing a request body.
Assuming you have a controller named api, a method named new, and you're in the test for the api controller:
#request.env["RAW_POST_DATA"] = '{ "foo" : "bar" }'
post :new
did the trick for me.
Here is a snippet that let me post json data to test my own app. rails 3
port = Rails.env.production? ? 80 : 3000
uri = URI.parse( Rails.application.routes.url_helpers.books_url(:host => request.host, :port => port, :format => :json) )
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Post.new(uri.request_uri)
request.content_type = 'application/json'
request.body = #json_data
response = http.request( request )
#result = response.body
Hope this helps others
As #taro suggests in a comment above, the syntax that works for me in functional and integration tests is:
post :create, {param1: 'value1', param2: 'value2', format: 'json'}
(The curly braces aren't always necessary, but sometimes it doesn't work if they're missing, so I always add them.)
Here's what params and request.format look like for a post of that sort:
params:
{"param1"=>"value1", "param2"=>"value2", "format"=>"json", "controller"=>"things", "action"=>"create"}
request.format:
application/json
The best answer I can come up with to this is you don't
Whether or not it was intentional it s maybe good that rails doesn't implement this for you.
In functional tests you really want to just test your controller and not rails method of deserialization or even that routing and mime detection are all setup correctly, those all fall under an IntegrationTest.
So for your controllers, don't pass JSON just pass your params hash like you normally would. Maybe adding :format as an argument as well if you need to check that and respond differently.
If you want to test the full stack move to an IntegrationTest

Resources