Can't extract nested JSON data from POST in Rails controller - ruby-on-rails

I am trying to configure my controller to process the params sent through a POST from another website. My log shows that the parameters that I receive are as follows:
{"page_id"=>"8b62f4ac-8588-11e3-a094-12314000b04c", "page_name"=>"test form", "variant"=>"b", "page_url"=>"http://get.xxxxxxx.com/test-form", "data.json"=>"{\"name\":[\"Dave\"],\"email\":[\"xxxx#me.com\"],\"phone\":[\"4447177265\"],\"ip_address\":[\"64.114.175.126\"],\"time_submitted\":[\"07:34 AM UTC\"]}", "data.xml"=>"\n\n Dave\n xxxx#me.com\n 2507177265\n 64.114.175.126\n 07:34 AM UTC\n"}
Initially I thought that Rails would automatically parse the JSON in the params and I could access them in the normal way. So I wrote the Registrations Controller like this:
class Api::RegistrationsController < Devise::RegistrationsController
skip_before_filter :verify_authenticity_token
respond_to :json
def create
#user = User.new(user_params)
if #user.save
render json: #user.as_json( email: #user.email), status: 201
return
else
warden.custom_failure!
render json: #user.errors, status: 422
end
end
def user_params
params.require(:'data.json').permit(:email, :name, :phone, :comments, :residency, :qualification, :acknowledgement) if params.present?
end
end
However, it is simply not working at all. I get an error undefined method 'permit' for string. So obviously I'm not accessing the JSON correctly. Is it possible that because the JSON is escaped that it's throwing the errors?
I've been googling and asking in IRC for a couple of days but I'm not any farther ahead.
I can pass a properly formatted JSON to the controller and it works fine (with changes to the require arguments)
I'm stumped since I need to be able to create a new user with the JSON data. Any help would be HUGELY appreciated. I just don't know what direction to even go from here.

The params.require(:'data.json') returns a JSON body which is a string, however your controller does not interpret the string but expects a Hash.
You can convert the JSON string to a Hash object using the parse class method for JSON like so:
require 'json'
JSON::parse(json_string)

Related

Custom error JSON in Rails 5

How do I render custom error JSON in a Rails 5 API? Right now, if I perform a GET on this url http://localhost:3000/users/5, it returns the 404 not found code, and all the traces associated with it. How can I stop Rails from automatically rendering all the traces?
Example of the generated error response: https://pastebin.com/C1dQA5eL
Hi you can create a custom module and extend it in your controller. Create a method in that module with parameters of resource and value. And on the basis of that send response and after that you can extend it in your respective Controller
like this:
class MyController
include AppError
end
I think you should if....else.
def show
user = User.find_by(id: params[:id])
if user.present?
render json: user
else
render json: { status: :not_found }
end
end

Trying to pull data from params object in Rails API using JSON

With Rspec, I am trying to build a spec testing some basic http requests. I'm making a rookie mistake somewhere and need help finding it.
I am purposely making the spec fail with a nonsense expectation so the error message will tell me what I'm getting -- once I figure things out I'll correct the expectation:
user = create(:member)
json_data = {email: user.email, password: user.password}.to_json
post "api/v1/users/sign_in", json_data, format: :json
expect(last_response.body).to eq "foobar"
api/v1/users/sign_in routes to the following controller:
class API::V1::SessionsController < Devise::SessionsController
respond_to :json
def create
render text: params
end
end
This gives the error:
expected: "foobar"
got: "{\"{\\"email\\":\\"7abdiel_roob#smithrau.biz\\",\\"password\\":\\"12345678\\"}\"=>nil,
\"action\"=>\"create\", \"controller\"=>\"api/v1/sessions\"}"
Ok great. My data is getting to the server and the server sends it back, which is what I want. In my next step I try to grab the email. I change the controller to
class API::V1::SessionsController < Devise::SessionsController
respond_to :json
def create
render text: params[:email]
end
end
and I get
expected: "foobar"
got: " "
I looks to me that the params hash is using the JSOn data I sent in the request as the name of a key, not actually a value. Or maybe this is a strong_params thing? I've tried many things and can't seem to pull the data I want out of the params object.
What is happening is that you are double encoding the JSON data which you are sending in your spec.
json_data = {email: user.email, password: user.password}
post "api/v1/users/sign_in", json_data, format: :json
RSpec will automatically encode the request body as JSON for you.

ROR: sending an object in response

I have 2 ruby on rails apps. With app A I post app B some data (in the form of a hash). I then want app B to send a hash on this data (with some modifications) back to app A in the response.
I have tried the code below App A
response = Net::HTTP.post_form(uri, params)
quotes.push(response.body)
and in App B
details = get_details //returns a hash
respond_with details
But its not working. Is what im doing even possible? Is there a way I can place this hash in my response?
Any help would be appreciated
Solution #1
If you use respond_with you need also specify formats which your app should respond to. For this you should use respond_to method.
Example:
class TestController < ApplicationController
respond_to :json
def index
details = get_details
respond_with(details)
end
end
Also check this good article about respond_to method.
Solution #2
Just use render json: {...} in your controller action.
Example:
class TestController < ApplicationController
def index
details = get_details
render json: details
end
end
In your app A response.body will contain a string with the data from your app B. So you need to parse that string.
In your app A:
require 'json' # this is unnecessary if app A is a Rails app
response = Net::HTTP.post_form(uri, params)
parsed_response = JSON.parse(response.body)
quotes.push(parsed_response)
The rails way to do that is using JSON as an exchange format. have a look at the guides for how to use that: http://guides.rubyonrails.org/layouts_and_rendering.html#rendering-json
It is also possible to use ActiveResource for such a communication. It allows direct access to your rails API.

View for JSON errors on specific model

I have a model called Entity and the create action in the controller looks like this:
# enitities_controller.rb
def create
# loading params, etc...
#entity.save
respond_with #entity
end
I am using jbuilder for custom JSON views rather than rendering #entity.to_json, which works great. I have one last issue, which is when the model won't save due to validation errors I get the following response (with status 422 Unprocessable Entity):
{"errors":{"parent_share":["can't be blank","is not a number"]}}
I would like to override this json with my own. I am aware of he possibility to replace respond_with #entity with:
respond_with #entity do |format|
if #entity.errors.any?
format.json {
render "entities/create", :status => :unprocessable_entity
}
end
end
But shouldn't there be a more auto-magic way by defining some sort of errors view or something? This feels a bit dirty AND it makes me have to write more code each time I need this rather than allowing me to use respond_with. Is there another way?
Meanwhile I have found the answer:
You have to create the file lib/application_responder.rb and add the following:
class ApplicationResponder < ActionController::Responder
include Responders::FlashResponder
include Responders::HttpCacheResponder
def to_json
set_flash_message! if set_flash_message?
if !has_errors? || response_overridden?
default_render
else
controller.default_render( status: :unprocessable_entity )
end
end
end
And add the following to your application responder:
self.responder = ApplicationResponder
What this does is add a to_json method that will copy the behaviour of the to_js responder.

Simple respond_with in rails that avoids 204 from PUT

I want to PUT to rails and avoid getting a 204. I am using this pattern:
class SomeController < ApplicationController
respond_to :json
def update
# ...
respond_with(some_object)
end
end
However, when I do a put to update, I get a 204 back. I realize this is completely valid etc, but I explicitly want the content back. I can override it to some extent like this:
def update
respond_with(some_object) do |format|
format.json{render json: some_object}
end
end
but this seems a bit too hands-on for rails. Is there any more idiomatic way of avoiding a 204 and requesting the full content to be sent back? This is Rails 3.2.
In summary: I want maximally idiomatic rails that avoids a 204.
I made a custom responder which always returns my JSON encoded resource even on PUT/POST.
I put this file in lib/responders/json_responder.rb. Your /lib dir should be autoloaded.
module Responders::JsonResponder
protected
# simply render the resource even on POST instead of redirecting for ajax
def api_behavior(error)
if post?
display resource, :status => :created
# render resource instead of 204 no content
elsif put?
display resource, :status => :ok
else
super
end
end
end
Now, explicitly modify the controller which requires this behavior, or place it in the application controller.
class ApplicationController < ActionController::Base
protect_from_forgery
responders :json
end
You should now get JSON encoded resources back on PUT.
As a less invasive alternative, you can pass a json: option to the respond_with method invocation inside your controller update action, like this:
def update
# ...
respond_with some_object, json: some_object
end
Granted it seems a bit unDRY having to repeat the object twice in the arguments, but it'll give you what you want, the json representation of the object in the response of a PUT request, and you don't need to use the render json: way, which won't give you the benefits of responders.
However, if you have a lot of controllers with this situation, then customizing the responders, as jpfuentes2 showed in the accepted anwser, is the way to go. But for a quick single case, this alternative may be easier.
Source: https://github.com/plataformatec/responders/pull/115#issuecomment-72517532
This behavior seems intentional to fall in line with the HTTP spec, and "ideally" you should be firing off an additional GET request to see the results. However, I agree in the real world I'd rather have it return the JSON.
#jpfuentes2's solution above should do the trick (it's very similar to the pull request below), but I'm hesitant to apply anything that's patching rails internals, as it could be a real pain to upgrade between major versions, especially if you don't have tests for it (and let's face it, developers often skimp on controller tests).
References
https://github.com/rails/rails/issues/9862
https://github.com/rails/rails/pull/9887
Just to clarify, you do not need the responders gem to do this... You can just do:
config/initializers/responder_with_put_content.rb
class ResponderWithPutContent < ActionController::Responder
def api_behavior(*args, &block)
if put?
display resource, :status => :ok
else
super
end
end
end
and then either (for all updates actions to be affected):
class ApplicationController < ActionController::Base
def self.responder
ResponderWithPutContent
end
end
or in your action:
def update
foo = Foo.find(params[:id])
foo.update_attributes(params[:foo])
respond_with foo, responder: ResponderWithPutContent
end
What's wrong with simply doing:
def update
some_object = SomeObject.update()
render json: some_object
end
Not a big fan of this behavior. To get around it, I had to avoid using the respond_with method:
class SomeController < ApplicationController
respond_to :json
def update
# ...
respond_to do |format|
format.json { render(json: some_object, status: 200) }
end
end
end

Resources