How can I get my rails controller (CREATE) to accept/POST multiples JSON records? - ruby-on-rails

I have a backend Rails JSON API. Right now, I am only able to create one record per POST based on the way I've coded my controller.
Let's say we have an application where Users can create To-Do Lists and in those lists they can create items.
3 models for our example, User, UserList, and UserListItems
User has_many UserLists
UserLists has_many UserListItems
Now right now, I'm updating UserListItems with a POST, but I can only add one item at a time. The JSON looks like...
{
"user_list_item":
{
"item_title": "Buy Milk"
}
}
And using Active Model Serializers, I am returning the record that it creates, and it looks as follows...
respond_with :api, :v1, #current_user, user_list, user_list_item, location: nil, serializer: Api::V1::UserListItemSerializer
{
"user_list_item": {
"id": 11,
"user_list_id": 2,
"item_title": "Buy Milk"
}
}
There is a serious flaw with this. If a User creates a 'Grocery To-Do List' and we POST 1 UserList record... that's fine. But then they might begin to fill the grocery list and add 3 things at once. "Buy Milk", "Buy Eggs", "Get Gas". We now need to POST to UserItemList 3 times, and that's far from ideal. What if they added 20 items. We should aim to add all 20 in 1 POST.
How can I do this?!?
1) Can someone show me the entire sample code for the CREATE in the controller to do this, as I learn best by seeing/doing syntax. I want to be able to pass in...
{
"user_list_item":
[
{
"item_title": "Buy Milk"
},
{
"item_title": "Buy Eggs"
},
{
"item_title": "Get Gas"
}
]
}
and have my controller parse, loop, and create them. 2) And I also want to be able to return the record(s) to the creator via Active Model Serializers. So for example, all 3 of these newly added records in one return (mostly I'm just interested in the first thing right now though).
EDIT: Adding my original controller#create code
if authenticate_user
user_list = #current_user.user_lists.find_by_id(params[:user_list_id])
user_list_item = user_list.user_list_items.new(user_list_item_params)
if (user_list_item.save!)
respond_with :api, :v1, #current_user, user_list, user_list_item, location: nil, serializer: Api::V1::UserListItemSerializer
else
render json: { error: "Could not create new User List Item."}, status: :unprocessable_entity
end
else
render json: { error: "User is not signed in." }, status: :unauthorized
end
private
def user_list_item_params
params.require(:user_list_item).permit(:user_list_id, :title, :due_date)
end
Now, with #Arvoreniad's suggestion below, I now have the following...
if authenticate_user
#user_list = #current_user.user_lists.find_by_id(params[:user_list_id])
#error = false
user_list_item_params[:user_list_item].each do |attribute|
item = #user_list.user_list_items.new
item.title = attribute.title
item.due_date = attribute.due_date
#error = true unless item.save
end
if (error)
????????????????
else
render json: { error: "Could not create new User List Item(s)."}, status: :unprocessable_entity
end
private
def user_list_item_params
params.require(:user_list_item).permit(:user_list_id, :title, :due_date)
end
My new questions are
1) Where all the ?'s are... where I was previously using ActiveModelSerializers to show the newly added record, how can I return something showing all the newly added records? Or this that not typical of an API? Is it just common to return something empty and just go off of whether it was successful or not?
2) Can-I/Should-I create both things in 1 POST (UserList and Multiple UserListItem's all in 1 POST)? If so, how?
3) How can I rollback all saves if one does not work. Let's say I'm trying to add 10 items, and 9 succeed but 1 fails, how do I roll them all back?

Your question is a little difficult to answer without your previous controller code, but this should give you a starting point based on the info you supplied. EDIT: Added the code for points 1 and 3.
If you want to create a UserList in the same action, just post the required data and create one from the parameters, rather than finding it in the database. I can help you with this if you'd like, but it shouldn't be too hard.
def create
#user_list = UserList.find(params[:id)
#error = false # Use this to check before rendering if there has been an error.
params[:user_list_item].each do |attributes|
item = UserListItem.new
item.item_title = attributes.item_title
item.user_list_id = #user_list.id
#error = true unless item.valid?
#items << item
end
if(#error)
render json: { error: "Could not create new User List Item(s)."}, status: :unprocessable_entity
else
#items.each do |item|
item.save
end
serialized = ActiveModel::ArraySerializer.new(#items, each_serializer: UserListItemSerializer)
render json: serialized
end
end

Related

Rails API get params to a custom method

Am trying to filter some data from database but its not getting params. This is my method:
def user_orders
orders = Order.select { | item | item[:user_id] == params[:id] }
if orders
render json: orders, status: :ok
else
render json: {error: "No orders available"}
end
end
This is the custom routing
get "/orders/user/:id", to: "orders#user_orders"
and the response is an empty array. However if I pass in a number in the method like so:
orders = Order.select { | item | item[:user_id] == 27 }
I get back the filtered array as expected. How can I pass in a dynamic ID from the routing?
EDIT: if your parameters look like this:
Parameters: { … "order"=>{…"user_id"=>27}}
… then you need to access the user_id as params[:order][:user_id].
Otherwise your conditional is comparing nil to 27, which will return false, and so nothing is ever selected.
If you’re querying an activerecord model, I also recommend that you use Order.where(item: {user_id: params[:id]}) to find your list of orders, so that and rails can cast to the right type as well.
What does the line in the rails log say? It should start with GET /user/orders and it should list what parameters have actually been received. Alternatively you can use puts to check the content of params.

Problem with selecting elements with the same params

What i do wrong? I want to return every products which pass condition:
def show
products = Product.select{|x| x[:category_id] == params[:id]}
render json: products
end
When i write
def show
products = Product.select{|x| x[:category_id] == 1}
render json: products
end
it works why the first example doesn't work?
I am pretty sure that there is mismatch in data type.
1=='1' #will be always false
1==1 #will be true
'1'=='1' #will be true as well
And also check for nil value from params[:id]
Please make sure to change as follows
def show
products = Product.select{|x| x.category_id == params[:id].to_i}
render json: products
end
OR
The best solution as suggested by #Eyeslandic is to use .where as it will not check for mismatch in data type. And also you don't have to take care of nil value from params[:id].
You should really be using a where to stop sql from loading all your products.
#products = Product.where('category_is = ?', params[:id])
The being said, if you are sticking to rails restful conventions, the fact you have a param called :id that is the category_id suggests you are on the category controller. So maybe consider changing your logic to:
#category = Category.includes(:products).find(params[:id])
you can then access products via
#category.products
or if your not interested in the category too much maybe
#products = Category.includes(:products).find(params[:id])&.products

How to create/update record if some variables are not set through strong params?

Some of the variables are required for a record but not included in the params. How to then create/update such a record? Would the code below be correct? I belief the new method works, but have doubts about the update method.
New record:
connect = Connect.new(update_params(connection))
connect.first_id = comp1.id
connect.second_id = comp2.id
connect.save
Update existing record:
#If record exists, do this:
connect.first_id = comp1.id
connect.second_id = comp2.id
if connect.save
unless connect.update_attributes(update_connection_params(connection))
return render json: #organization, message: connect.errors.full_messages, status: :bad_request
end
end
Just write validations for these fields, on validation error(s) the record will not be saved and save will return false.
Common pattern is to render the form again, with errors, asking the user to correct
Your code for new record is correct, just check return value of save like for existing:
connect.assign_attributes update_connection_params(connection)
connect.first_id = comp1.id
connect.second_id = comp2.id
if connect.save
redirect_to somewhere_path
else
return render json: #organization, message: connect.errors.full_messages, status: :bad_request
end

Printing error when using PARAMS in Rails

For my API in RAILS I have programmed a code that basically does the following.
class Api::V1::NameController < ApplicationController
skip_before_filter :verify_authenticity_token
def index
end
def create
# Loading data
data_1_W = params[:data1]
data_2_W = params[:data2]
while len > i
# -Here I do some calculations with data_1_W and data_2_W.
# Its not important to show the code here
end
# -Organizing outputs to obtain only one JSON-
# Its not important to show the code here
# Finally HTTP responses
if check_error == 1
render status: 200, json: {
message: "Succesful data calculation",
data_output: response_hash
}.to_json
end
end
end
To test that everything is working I use the cURL command. I notice that loading the data could be a problem and therefore the code would break.
I want to tell the user that it was an error loading the data for some reason (HTTP response), but I don't know where to put it. If I put and else under my success status it would not print it because the code breaks just starting (instead of sending the correct name - d '#data.json' of the data in cURL I send -d '#dat.json').
The data I am loading is a JSON data {"data1":[{"name1":"value1"},{"name2":number2}...],"data2":[{"name1":"value1"},{"name2":number2...}]}. (This data has 70080 rows with 2 columns if we see it as a table, which I divided into two in my CODE for calculations purposes data_1_W and data_2_W)
Could anyone help me where to put it? more or less like this:
render status: 500, json: {
message: "Error loading the data",
}.to_json
Put it in a rescue block around the code that throws the error.
E.g.
def func
# code that raises exception
rescue SomeException => e
# render 422
end
Since you are working in Rails I'd recommend going the rails way. This means that I would create some kind of service and initialize it in the create action.
Now, within the service you do all you funky stuff (which also allows you to clean this controller and make i look prettier) and the moment a condition is not fulfilled in that service return false. So...
# controllers/api/v1/name_controller.rb
...
def create
meaningful_variable_name = YourFunkyService.new(args)
if meaningful_variable_name.perform # since you are in create then I assume you're creating some kind of resource
#do something
else
render json: {
error: "Your error",
status: error_code, # I don't think you want to return 500. Since you're the one handling it
}
end
end
# services/api/v1/your_funky_service.rb
class Api::V1::YourFunkyService
def initiliaze(params)
#params = params
end
def perfom #call it save if you wish
....
return false if check_error == 1
end
end

Take random ids, then store those random ids into the db

so I'm working on a code snippet that essentially takes out 35 random ids from the table List.
What I would like to do to find the ids that got randomly generated, store them into a database called Status.
The purpose is to avoid duplication the next time I get a new 35 random ids from the List. So I never get the same random id twice.
Here's what I've tried, but been unsuccessful to get working.
#schedule = current_user.schedules.new
if #schedule.save
#user = User.find(params[:id])
Resque.enqueue(ScheduleTweets, #user.token)
#schedule.update_attribute(:trial, true)
flash[:notice] = "success"
redirect_to :back
else
flash[:alert] = "Try again."
redirect_to :back
end
and the worker:
def self.perform(user_token)
list = List.first(6)
#status = list.statuses.create
list.each do |list|
Status.create(list_id: "#{list}")
if list.avatar.present?
client.create_update(body: {text: "#{list.text}", profile_ids: profile_ids, media: { 'thumbnail' => 'http://png-1.findicons.com/files/icons/85/kids/128/thumbnail.png', 'photo' => 'http://png-1.findicons.com/files/icons/85/kids/128/thumbnail.png' } })
end
end
end
however the Status.create(list_id: #list) doesn't work.
Does anybody have any idea what is going on, and how I can make the list_ids get saved successfully to Status?
It's also associated:
list has many statuses, and status belongs to list
The following line of code is wrong:
Status.create(list_id: "#{list}") # WRONG
In your case the list variable is a List instance. And you're passing its string version to list_id which expects an integer.
Do this:
Status.create(list_id: list.id)
Or this:
list.statuses.create
I think the following will also work:
Status.create(list: list)

Resources