Why should POST data be nested? - ruby-on-rails

I am using Rails as an API server, and I wonder why the data being sent to the server needs to be nested. This seems to be the preferred way of defining params:
def user_params
params.require(:user).permit(:first_name, :last_name, :password, :username, :email)
end
And this would be the corresponding JSON sent to the create route:
{
"user": {
"username": "lorem",
"first_name": "ipsum",
"last_name": "dolor",
"password": "sit",
"email": "amet"
}
}
Why is this the preferred way of posting data? Why could not the JSON be:
{
"username": "lorem",
"first_name": "ipsum",
"last_name": "dolor",
"password": "sit",
"email": "amet"
}

These aren't the only parameters that are sent while creating a resource for you, others are:
utf8 with value ✓
authenticity_token with a random string
commit with value either Save or Update
So the logic is pretty obvious: Rails groups all the user-belonging-parameters inside user key, and thus, it's easier to read, easier to interpret by the code, and easier to whitelist the related parameters.
Not only this, sometimes you will try to create multiple resources through one request, like a user has many books, so you would like to create a user, and at the same time, books - something called Nested Resources, and in that case, it will be like this:
{
"user":
{
"username": "john_don",
"books":
{
"0":
{
"author_id": 1
}
}
}
}
I hope you get the idea.

Related

Nested parameters on GET request

Question of the day: how to encode URL to be able to pass complex data on a GET request?
# data to pass
{
"main_key": {
"other_key": {
"main_array": [{
"name": "Bob",
"nickname": "bobby"
},
{
"name": "Tom",
"nickname": "Tommy"
}
]
}
}
}
Here is the current solution I got with Postman
Here is the current Rails interpretation of such a query, which is correct.
# Rails server side
Parameters: {"main_key"=>{"other_key"=>{"main_array"=>[{"name"=>"Bob", "nickname"=>"bobby"}, {"name"=>"Tom", "nickname"=>"tommy"}]}}, "default"=>{"format"=>:json}}
Can anybody have a better way to achieve a request with such a complex nested array object?
The other solution I got is to pass JSON directly as value of a query parameter and then parse it from the controller.
**Edit: ** I can pass this json on the body of the request but as it's a GET method, it does not respect XHR requirements.
If you have jquery, you can use .param() method of it.
let myParams = {
"main_key": {
"other_key": {
"main_array": [{
"name": "Bob",
"nickname": "bobby"
},
{
"name": "Tom",
"nickname": "Tommy"
}
]
}
}
}
console.log($.param(myParams));
This will give you your desired string.
"main_key%5Bother_key%5D%5Bmain_array%5D%5B0%5D%5Bname%5D=Bob&main_key%5Bother_key%5D%5Bmain_array%5D%5B0%5D%5Bnickname%5D=bobby&main_key%5Bother_key%5D%5Bmain_array%5D%5B1%5D%5Bname%5D=Tom&main_key%5Bother_key%5D%5Bmain_array%5D%5B1%5D%5Bnickname%5D=Tommy"

Devise Responding With 422 On Sign Up of User

Not sure why this is happening..I'm running against route /users.json and passing in the body as:
{
"email": "register#register.com",
"password": "ssssssssss"
}
I'm getting back: {"errors":{"email":["can't be blank"],"password":["can't be blank"]}}
uhmmm...what?
If you didn't change your controllers from the default scaffolds, the body should have "user" as the root key:
{
"user": {
"email": "register#register.com",
"password": "ssssssssss"
}
}
Also check your server logs that the parameters are actually accepted and parsed correctly.
Rolling back to version 3.3.2 of Devise fixed...and using #Ivan's req body of:
{
"user": {
"email": "register#register.com",
"password": "ssssssssss"
}
}

Ruby/Rails - interate over complex (nested) JSON elements to create objects

I'm parsing some JSON from a mixed content source, and with it trying to store it with ActiveRecord.
At the moment I'm using a ton of variables:
json['settings']['newsletters']['weekly']
json['info']['address']['city']
Or trying to make things a little easier:
newsletters = json['settings']['newsletters']
newsletters['weekly']
address = json['info']['address']
address['city']
But this is all getting very messy, and not DRY.
I think the better way to do this would be to iterate over each element that is a hash (and therefore 'complex'), and assign it it's own object. This way, I don't have to declare a trillion variables, they can instead be assigned from the context of the JSON input.
So, I can do something like this:
user = json['input']
user.settings.newsletters.weekly
user.info.address.city
This is inspired by what ActiveResource documents:
# Any complex element (one that contains other elements) becomes its own object:
#
# {"id":1,"first":"Tyler","address":{"street":"Paper St.","state":"CA"}}
tyler = Person.find(1)
tyler.address # => <Person::Address::xxxxx>
tyler.address.street # => 'Paper St.'
Here is the JSON, reduced for brevity's sake:
{
"username": "robert_fitzsimmonds",
"emails": [{
"id_number": 1,
"address": "robert_fitzsimmonds#yahoo.com",
"confirmed": false
}, {
"id_number": 2,
"address": "robert_fitzsimmonds#gmail.com",
"confirmed": true
}],
"settings": {
"marketing": {
"main": true,
"weekly": false,
"daily": false
},
"language": "English"
},
"info": {
"address": {
"line_1": "31 Mole Road",
"line_2": "",
"city": "London",
"post_code": "NE4 5RJ"
},
"shared_account": false
}
}
Would such an iteration be the most efficient solution, or is it best to stick to long, messy variables?
Use the hash_dot gem if you can https://github.com/adsteel/hash_dot

Rails errors - Define Position or Object in which the error occured

I have two models. For example Person and Address.
Because I want to add or update addresses to the person within one request, the person model looks like:
has_many :addresses
accepts_nested_attributes_for :addresses
In the address controller is only one validation
validates :city, presence: true
When I now update the user via json api it works like a charm:
{
"user": {
"addresses_attributes": [
{"street": "bla", "zip": "12345", "city": "blubb"},
{"street": "blu", "zip": "98765", "city": "blebb"}
]
}
}
Now I delete the city of the second record:
{
"user": {
"addresses_attributes": [
{"street": "bla", "zip": "12345", "city": "blubb"},
{"street": "blu", "zip": "98765"}
]
}
}
and in the Users controller I can render a json response, something like:
render json: #user.errors
which gives me the correct error.
I am missing, that I don't know which of the addresses threw the error (In this example the second).
Any Ideas?
You can return the entire user object with its nested attributes and errors. I.e.
render json: #user.as_json(
include: [{addresses: {methods: [:errors]}],
methods: [:errors]
)
The result should look like this:
{
"user": {
"errors": {...},
"addresses_attributes": [
{"street": "bla", "zip": "12345", "city": "blubb", "errors": {...}},
{"street": "blu", "zip": "98765", "errors": {...}}
]
}
}
The #user that you tried to create will still contain the addresses that it tried to create with the nested attributes.
I don't know exactly how you want to render the fact that an address failed the validation but you can identify the one(s) that did fail by iterating over the #user.addresses.
For example, this will return all the invalid addresses:
#user.addresses.select { |address| !address.valid? }
You can still render these objects, or the json representation of them, even though they haven't been saved to the database.

Rails way for creating an endpoint accepting multiple objects at once

I need to create an endpoint that accepts multiple objects at once. Example request (json body) below:
{
"obd_bluetooth_events" : [
{
"car_id": 1,
"obd_plugin_id": "1",
"kind": "CONNECT",
"date": 1422369149
},
{
"car_id": 1,
"obd_plugin_id": "1",
"kind": "DISCONNECT",
"date": 1422369149
},
{
"car_id": 1,
"obd_plugin_id": "1",
"kind": "CONNECT",
"date": 1422369149
}
]
}
So in order to be able to pass an array to create method: #obd_bluetooth_event.create(obd_bluetooth_events_params)
I need to define obd_bluetooth_events_params method like this:
def obd_bluetooth_events_params
params.permit(
obd_bluetooth_events: [
:car_id,
:obd_plugin_id,
:kind,
:date
]
)[:obd_bluetooth_events]
end
After calling which i get:
Unpermitted parameters: obd_bluetooth_event
=> [{"car_id"=>1, "obd_plugin_id"=>"1", "kind"=>"CONNECT", "date"=>1422369149},
{"car_id"=>1, "obd_plugin_id"=>"1", "kind"=>"DISCONNECT", "date"=>1422369149},
{"car_id"=>1, "obd_plugin_id"=>"1", "kind"=>"CONNECT", "date"=>1422369149}]
Im wondering wether there is a more railsy way to permit an array of objects?
def obd_params
params.require(:myparams).permit(:myarray => [])
end
Works fine for me to permit arrays.
Hope this helps ;)

Resources