Remove attribute from Json response - ruby-on-rails

I have a REST service whose response structure differs on certain attributes based in request. From my code I return different class objects(not ActiveRecord objects) based on response to be sent to controller. From response that controller gets, certain attribute need to be passed as header in response. But that should be deleted from response body. How can I delete that from class object?
Suppose my response to controller could be class1 object with attributes : attr1, attr2, attr3
Now attr3 should actually be in response header, so this works:
response.headers["Headertitle"]=attr3
But my json response should only have attr1, attr2.
Another situation could be that response to controllers is class2 object with attr1, attr3, attr4, attr5. attr3 from this also goes to response headers. And again response should not have attr3.
Value of attr3 depends on some business rules and differs on request. But every response needs to have attr3 in response headers and not in body.
How do I remove attr3 from response?
I have already tried :
format.json { render :json => json_response.to_json(except: ["attr3"])}
And this does not work.

You can use Ruby Hash slice method:
all = {name: "Zulh", email: "zulh#example.com", phone: "0123456789"}
=> {:name=>"Zulh", :email=>"zulh#example.com", :phone=>"0123456789"}
selected = all.slice(:name, :email)
=> {:name=>"Zulh", :email=>"zulh#example.com"}
selected.to_json
=> "{\"name\":\"Zulh\",\"email\":\"zulh#example.com\"}"
Or, if you prefer except, here is the correct way to do it:
all = {name: "Zulh", email: "zulh#example.com", phone: "0123456789"}
=> {:name=>"Zulh", :email=>"zulh#example.com", :phone=>"0123456789"}
selected = all.except(:phone)
=> {:name=>"Zulh", :email=>"zulh#example.com"}
selected.to_json
=> "{\"name\":\"Zulh\",\"email\":\"zulh#example.com\"}"

If you are using Rails 5.
You can do the following:
def send_auth_token_for_valid_login_of(user)
render json: { user: user }, :except => [:password_digest, :token_created_at, :reset_password_token, :reset_password_sent_at]
end

Related

Rails API JSON response, change name of method

I have a Rails 5 API that renders an object with some of it's methods to JSON.
render json: { rides: #rides }.to_json( :methods => [ :is_preferred ]), status: 200
So this returns something like:
{
id: 123,
is_preferred: true
}
But I would like to change the name of the attribute that refers to the is_preferred method.
The output I would like id:
{
id: 123,
preferred: true
}
I tried
render json: { rides: #rides }.to_json( :methods => [ preferred: :is_preferred ]), status: 200
But this does not work. Easiest would be to change the method name in the model, but that's not possible in this case.
Is there any way I can manipulate the name inside the response?
You can try with an ActiveModel::Serializer, then you can define the attribute key as you want
class RideSerializer < ActiveModel::Serializer
attribute :preferred, key: :is_preferred
end
or use a method to retrieve the attribute value
class RideSerializer < ActiveModel::Serializer
attribute :is_preferred
def is_preferred
object.preferred
end
end
Using serializers has a lot of bennefits and is so powerfull if you want to create custom responses.

Rails API: Cannot whitelist JSON field attribute

I'm building a rails API with a model containing an attribute data of JSON type. (PSQL)
But when I try to post something like this
{ model: { name: 'Hello', data: { a: 1, b: 2 } } }
Rails thinks a and b are the attributes of a nested data association... It considers then they are unpermitted params.
The thing is, { a: 1, b: 2 } is the value of my field data.
How to provide JSON value to an attribute ?
-
Edit:
The error displayed is:
Unpermitted parameters: name, provider, confidence, location_type, formatted_address, place_id, types, locality, ...
The value of the data attribute is { name: 'Name', provider: 'Provider', ... }
Like I said Rails thinks they are the attributes of a nested data association.
-
Log:
Pastebin
if the keys are unknown in advance this could be a workaround:
def model_params
data_keys = params[:model].try(:fetch, :data, {}).keys
params.require(:model).permit(data: data_keys)
end
Credit goes to aliibrahim, read the discussion https://github.com/rails/rails/issues/9454 (P.S seems like strong parameters will support this use case in Rails 5.1)
When you post something, you have to make sure that your json have the same parameters that your controller.
Example rails api:
def example
#model = Model.new(params)
#model.save
render(json: model.to_json, status: :ok)
end
def params
params.permit(:name, :provider, {:data => [:a, :b]})
end
Example front-end json for post:
var body = {
name: 'myName',
provider: 'provider',
data: {
a: 'something',
b: 'otherthing',
}
};
For some reason rails doesnt recognize a nested json, so you need to write into params.permit that data will be a json with that syntax, if where a array, the [] should be empty.

Rails, correct JSON body for a serialized field?

If I have a Rails model with a serialized field,
class Tournament < ActiveRecord::Base
serialize :prizes, Array
end
and I have the model available through a REST API, what is the correct format of the POST body?
I've tried the following in a Rspec test,
post :create, {
format: :json,
tournament: {
prizes: [
'z2000',
'z1000',
'z500',
'z250'
]
}
}
but this results in object with prizes set to blank.
Figure it out. The fix is completely unrelated to my JSON request body.
I had assigned my strong params in my controller as,
params.permit(:prizes)
But due to it being an array, I need the following
params.permit(prizes: [])
From https://github.com/rails/strong_parameters,
To declare that the value in params must be an array of permitted
scalar values map the key to an empty array:
params.permit(:id => [])

Rails model to hash, exclude attributes

I am trying to form a json response that looks like this:
{
"user": {
"birthday": "2013-03-13",
"email": "example#example",
"id": 1,
"name": null,
"username": "example"
},
"other_data": "foo"
}
Before, when I was just returning the user, I used
render :json => #user, :except => [:hashed_password, :created_at, :updated_at]
to keep the hashed_password, created_at, and updated_at attributes from being sent. Is there a way to do this, but also allow additional data to be sent along with the user? Right now I'm just adding the attributes I want to send to the hash one by one, but this is obviously not ideal.
Rendering json data first automagically calls 'as_json' on your model, which returns a ruby hash. After that, 'to_json' is called on that to get a string representation of your hash.
To achieve what you wanted, you can call something like this:
render :json => {
:user => #user.as_json(:except => [:hashed_password]),
:some_other_data => {}
}
In this case, there is no object which responds to 'as_json', so the controller just calls 'to_json' to turn your hash to a string.
I would recommend to use this gem: https://github.com/fabrik42/acts_as_api

How to send raw post data in a Rails functional test?

I'm looking to send raw post data (e.g. unparamaterized JSON) to one of my controllers for testing:
class LegacyOrderUpdateControllerTest < ActionController::TestCase
test "sending json" do
post :index, '{"foo":"bar", "bool":true}'
end
end
but this gives me a NoMethodError: undefined method `symbolize_keys' for #<String:0x00000102cb6080> error.
What is the correct way to send raw post data in ActionController::TestCase?
Here is some controller code:
def index
post_data = request.body.read
req = JSON.parse(post_data)
end
I ran across the same issue today and found a solution.
In your test_helper.rb define the following method inside of ActiveSupport::TestCase:
def raw_post(action, params, body)
#request.env['RAW_POST_DATA'] = body
response = post(action, params)
#request.env.delete('RAW_POST_DATA')
response
end
In your functional test, use it just like the post method but pass the raw post body as the third argument.
class LegacyOrderUpdateControllerTest < ActionController::TestCase
test "sending json" do
raw_post :index, {}, {:foo => "bar", :bool => true}.to_json
end
end
I tested this on Rails 2.3.4 when reading the raw post body using
request.raw_post
instead of
request.body.read
If you look at the source code you'll see that raw_post just wraps request.body.read with a check for this RAW_POST_DATA in the request env hash.
Version for Rails 5:
post :create, body: '{"foo": "bar", "bool": true}'
See here - body string parameter is treated as raw request body.
I actually solved the same issues just adding one line
before simulating the rspec post request. What you do
is to populate the "RAW_POST_DATA". I tried to remove
the attributes var on the post :create, but if I do so,
it do not find the action.
Here my solution.
def do_create(attributes)
request.env['RAW_POST_DATA'] = attributes.to_json
post :create, attributes
end
In the controller the code you need to read the JSON is
something similar to this
#property = Property.new(JSON.parse(request.body.read))
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
You can pass json string as :params AND specify a content type "application/json". In other case content type will be set to "application/x-www-form-urlencoded" and your json will be parsed properly.
So all you need is to specify "CONTENT_TYPE":
post :index, '{"foo":"bar", "bool":true}', "CONTENT_TYPE" => 'application/json'
For those using Rails5+ integration tests, the (undocumented) way to do this is to pass a string in the params argument, so:
post '/path', params: raw_body, headers: { 'Content-Type' => 'application/json' }
I was searching very long for how to post raw JSON content in a integration test (Rails 5.1). I guess my solution could also help in this case.
I looked up the documentation and source code for the post method: https://api.rubyonrails.org/v5.1/classes/ActionDispatch/Integration/RequestHelpers.html#method-i-post
This directed me to the process method for more details: https://api.rubyonrails.org/v5.1/classes/ActionDispatch/Integration/Session.html#method-i-process
Thanks to this, I finally found out what parameters are accepted by the process and thus post method.
Here's what my final solution looked like:
post my_url, params: nil, headers: nil, env: {'RAW_POST_DATA' => my_body_content}, as: :json
If you are using RSpec (>= 2.12.0) and writing Request specs, the module that is included is ActionDispatch::Integration::Runner. If you take a look at the source code you can notice that the post method calls process which accepts a rack_env parameter.
All this means that you can simply do the following in your spec:
#spec/requests/articles_spec.rb
post '/articles', {}, {'RAW_POST_DATA' => 'something'}
And in the controller:
#app/controllers/articles_controller.rb
def create
puts request.body.read
end
Using Rails 4, I was looking to do this to test the processing of raw xml that was being posted to the controller. I was able to do it by just providing the string to the post:
raw_xml = File.read("my_raw.xml")
post :message, raw_xml, format: :xml
I believe if the parameter provided is a string, it just gets passed along to the controller as the body.
In rails, 5.1 the following work for me when doing a delete request that needed data in the body:
delete your_app_url, as: :json, env: {
"RAW_POST_DATA" => {"a_key" => "a_value"}.to_json
}
NOTE: This only works when doing an Integration test.
The post method expects a hash of name-value pairs, so you'll need to do something like this:
post :index, :data => '{"foo":"bar", "bool":true}'
Then, in your controller, get the data to be parsed like this:
post_data = params[:data]
As of Rails 4.1.5, this was the only thing that worked for me:
class LegacyOrderUpdateControllerTest < ActionController::TestCase
def setup
#request.headers["Content-Type"] = 'application/json'
end
test "sending json" do
post :index, '{"foo":"bar", "bool":true}'.to_json, { account_id: 5, order_id: 10 }
end
end
for a url at /accounts/5/orders/10/items. This gets the url params conveyed as well as the JSON body. Of course, if orders is not embedded then you can leave off the params hash.
class LegacyOrderUpdateControllerTest < ActionController::TestCase
def setup
#request.headers["Content-Type"] = 'application/json'
end
test "sending json" do
post :index, '{"foo":"bar", "bool":true}'.to_json
end
end
In Rails 4 (at least in 4.2.11.3) there's no easy way to test your controllers that consume json (functional tests). For parsing json in a running server the ActionDispatch::ParamsParser middleware is responsible. Controller tests though rely on Rack, which can't parse json to this day (not that it should).
You can do:
post :create, body_params.to_json
or:
post :update, body_parmas.to_json, url_params
But body_params won't be accessible in the controller via params. You've got to do JSON.parse(request.body.read). So the only thing that comes to mind is:
post :update, url_params.merge(body_params)
That is, in tests pass everything via parameters (application/x-www-form-urlencoded). In production the body will be parsed by ActionDispatch::ParamsParser to the same effect. Except that your numbers become strings (and possibly more):
# test/controllers/post_controller_test.rb
post :update, {id: 1, n: 2}
# app/controller/posts_controller.rb
def update
p params # tests:
# {"id"=>"1", "n" => "2", "controller"=>"posts", "action"=>"update"}
# production
# {"id"=>"1", "n" => 2, "controller"=>"posts", "action"=>"update"}
end
If you're willing to parse json in controllers yourself though you can do:
# test/controllers/post_controller_test.rb
post_json :update, {n: 2}.to_json, {id: 1}
# app/controller/posts_controller.rb
def update
p JSON.parse(request.body.read) # {"id"=>"1", "n" => 2, "controller"=>"posts", "action"=>"update"}
end
post :index, {:foo=> 'bar', :bool => 'true'}

Resources