POST/Create multiple items - ruby-on-rails

I'm currently using the below to add one product that has a name and a brand via API call. I would like to be able to submit an array of 'products' and then add then to my DB.
Could anyone suggest:
1) How would I do this in the controller?
2) How would I structure the API POST body?
Current call looks like:
http://localhost:3000/api/v1/products?brand=brand&name=name
My Controller:
def create
#newProduct = Product.create(product_params)
if #newProduct.save
render json: {message: "Product created"}
else
render json: {error: "Failed to create product"}
end
end
private
def product_params
params.permit(:name, :brand)
end
Thanks

Add a new route in routes file with line below
get 'create_multiple_products'
Send data in an array
{"products":[
{"name":"playstation"},
{"name":"xbox"},
{"name":"blueray"}
]}
then add a new method in controller and call the create in a loop
def create_multiple_products
response["products"].each do |p|
Product.create( p )
end
end
The above is pseudocode, you might want to try a test driven approach setting up expected api and matching with returned data with rspec. http://matthewlehner.net/rails-api-testing-guidelines/

Related

Update Multiple Records with a single command in Ruby

Basically I want to update an array of objects that my api recieves in a single command. I have done it when I was inserting but I couldn't find a way to do update it.
Here is m create method for multiple insertions:
def create_all
if Attendance.create attendance_params
render json: { message: "attendance added" }, status: :ok
else
render json: { message: "error in creation" }, status: :bad_request
end
end
Params:
def attendance_params
params.require(:attendance).map do |p|
p.permit(
:student_id,
:id,
:attendance
)
end
end
I tried to do similar thing with update but it generates this error:
Completed 500 Internal Server Error in 11ms (ActiveRecord: 2.7ms)
Argument Error (When assigning attributes, you must pass a hash as an argument.)
my update method is this:
def update_attendance
if Attendance.update attendance_params
render json: { message: "attendance updated" }, status: :ok
end
end
ActiveRecord Create can take an array of hashes and create multiple records simultaneously.
However, ActiveRecord Update cannot.
You could potentially create an "update_batch" method on your model that allows for an array of hashes. You would have to send an array of hashes and each hash would have to include the id of the record you are updating (and allow that in your strong parameters definition). Then in your update_batch method you would have to grab the id from each hash and update each:
class Attendance < ActiveRecord
def update_batch(attendance_records)
attendance_records.each do |record|
Attendance.find(record[:id]).update(record.except(:id))
end
end
end
Please check this example and try applying it:
Attendance.where(:student_id => [23,45,68,123]).update_all(:attendance => true)
Or if you're trying to update all Attendance records:
Attendance.update_all(:attendance => true)
Also, please check this link:
https://apidock.com/rails/ActiveRecord/Relation/update_all

Make params required in a rails api

I have a controller that accepts three params, title, users and project_type. I want to make all the params required
I have seen people do things like
def project_params
params.require(:title,:project_type, :users)
.permit(:title, :project_type, :users)
end
And then do Project.new(project_params), but I need to work a little with the params first. How can I make this possible?
I make a post request in postman like this:
module Api
module V1
class ProjectsController < ApplicationController
def create
admins = params[:admins]
users = get_user_array()
project_type = ProjectCategory.find_by(name: params[:project_type])
project = Project.new(
title: params[:title],
project_category: project_type,
project_users: users)
if project.save
render json: {data:project}, status: :ok
else
render json: {data:project.errors}, status: :unprocessable_entity
end
end
...
end
end
end
{
"title": "Tennis",
"project_type": "Sports",
"users": [{"name": "john Dow", "email": "johnDoe#gmail.com"}],
}
I would say that you are using ActionController::Parameters#require wrong. Its not meant to validate that the all the required attributes are present - thats what model validations are for. Rather you should just use params.require to ensure that the general structure of the parameters is processable.
For example if you used the rails scaffold you would get the following whitelist:
params.require(:project)
.permit(:title, :project_type)
This is because there is no point in continuing execution if the project key is missing from the params hash since this would give you an empty hash or nil.
ActionController::Parameters#require will raise a ActionController::ParameterMissing error which will return a 400 - Bad Request response which is the wrong response code for what you are doing. You also should not use exceptions for normal application flow. A missing attribute is not an exceptional event.
Instead if you want to use a flat params hash you should whitelist it with:
def project_params
params.permit(:title, :project_type, users: [:name, :email])
end
I think that if you don't have to get anything from the frontend to run get_user_array(), you could only allow and require title and project_type.
def create
users = get_user_array()
project = Project.new(project_params)
project.users = users
if project.save
render json: {data:project}, status: :ok
else
render json: {data:project.errors}, status: :unprocessable_entity
end
end
private
def project_params
params.require(:project).permit(:title, :project_type).tap do |project_params|
project_params.require(:title, :project_type)
end
end
If you need to process something before creating the project, you can do this:
project_category = ProjectCategory.find_by(name: project.project_type)

Add children to parent from form constructed via ajax Rails 4

I have the following form:
When the user selects a product from the dropdown, a ajax is triggered to find the inventory of the single product to append the details to a table.
The user can attach a product detail to the order.
Finally I get something like that:
{"utf8"=>"✓", "authenticity_token"=>"xmlzMouWp0QGUnpKeawQ8OCPJ/GlF2bp0kn97ra2Qyb7TgsCkXmJEGD1l/oZitn+VPVJRc8x79/kTUtgbbDr0A==", "order"=>{"customer_search"=>"", "customer_id"=>"2", "product_search"=>"", "order_detail"=>[{"product_id"=>"10", "product_detail_id"=>"13", "price_id"=>"12"}, {"product_id"=>"1", "product_detail_id"=>"8", "price_id"=>"11"}], "subtotal"=>"111990", "tax"=>"0", "comission"=>"0", "total"=>"111990"}, "product_list"=>"1", "button"=>""}
My code to create the order is working, but I can not add the details.
Orders controller
def create
# Creates the order removing the order details from the hash
#order = Order.create(order_params.except!(:order_detail))
# Set all the details into an empty array
order_details_attributes = order_params[:order_detail]
order_details_attributes.each do |order_detail_attributes|
# Fill the params with order_id and creates the detail
order_detail_attributes["order_id"] = #order.id
#order_detail = OrderDetail.create(order_detail_attributes)
end
respond_to do |format|
if #order.save
format.html { redirect_to #order, notice: 'Order was successfully created.' }
format.json { render :show, status: :created, location: #order }
else
format.html { render :new }
format.json { render json: #order.errors, status: :unprocessable_entity }
end
end
end
def order_params
params.require(:order).permit(:customer_id, :subtotal, :tax, :comission, :total, :invoice, :shipping_id, :order_detail, order_details_attributes: [:product_id, :product_detail_id, :price_id])
end
I'm getting this error:
undefined method `delete' for nil:NilClass
order_details_attributes = order_params[:order].delete(:order_detail)
What could be bad? I really need help :(
Thanks!!
order_params doesn't have key :order, it only has keys you specified in permit method when defined order_params. Actually, you don't need to manually create children records, as Rails can do this automatically. Check this out: http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
You just need to add accepts_nested_attributes_for :order_details in Order model, fill order_details_attributes param when creating an order (currently you fill order_detail, you need to fix you form for it to be order_details_attributes, because this is one of Rails conventions, or you can use fields_for helper for this). Then you just create Order in standard way, like #order = Order.new(order_params) and #order.save, and you'll get order and order details created together.
This is a really messy thing in Rails. I can only recommend you to read the link I posted and use Google to find some tutorials.
As to the error you get:
undefined method `delete' for nil:NilClass
There is no :order key in order_params. It is in params, but not in order_params, because you called require(:order). So order_params returns only the keys you specified in permit method. But :order_detail will be empty, as you didn't describe it as an array with certain keys (like you did for order_details_attributes).
So, your problem is that you tried to implement nested attributes, but you pass :order_detail instead of :order_details_attributes (hm, but you still have it in strong params) and try to create children relations manually. Don't do this, just use what Rails provides to you.
There are 2 ways:
You continue to use order_detail param. In this case you need to change order_params in controller to look like so:
params.require(:order).permit(:customer_id, :subtotal, :tax, :comission, :total, :invoice, :shipping_id, order_detail: [:product_id, :product_detail_id, :price_id])
(just replace order_details_attributes with order_detail)
Then instead of
order_details_attributes = order_params[:order].delete(:order_detail)
you do
order_details_attributes = order_params[:order_detail]
(you don't need delete here as order_params is a method that returns a hash)
And leave rest controller code as it is now. And you don't need nested attributes, as you don't use it (bad way).
You fully use nested attributes. I described in a comment below how to do this. You also need to tweak you jquery code to generate order_details_attributes instead of order_detail.

ActiveRecord serialize two different models in at once

I have a controller action (favorites) in my Rails app that returns a JSON object with two keys (companies and jobs). Each key represents a collection of Company or JobDescription objects. What I want to know is if there is a clean way I can serialize both #companies and #jobs. Here is my code:
def favorites
#companies = current_user.companies
#jobs = current_user.job_descriptions
respond_to do |format|
format.html
format.json { render json: {companies: #companies, jobs: #jobs}, root: false }
end
end
I could always refactor my code into two separate JSON calls (one for jobs, one for companies), but I'd prefer to stick with a single call to favorites.
You can use Rails Presenters here!
So, you can have two presenters: CompaniesPresenter and JobsPresenter which will be responsible for building the #companies and jobs objects respectively.
So, in your controller, you would have something like:
#companies = CompaniesPresenter.new(current_user).companies
#jobs = JobsPresenter.new(current_user).job_descriptions
For example, your CompaniesPresenter would look like this:
class CompaniesPresenter
attr_reader :current_user
def initialize(current_user)
#current_user = current_user
end
def companies
# build the companies JSON here
end
end
Here is a tutorial with Rails Presenter Pattern that might be useful.
And, here is an useful video. Hope this helps.
This example works, are you just trying to change the json format? If so...
In the company or job model, you can add an as_json method and format the output as you want.
def as_json(options = {})
{ :name => name }
end

How to write a duplicate record method in Ruby on Rails?

In my Rails app I have an invoices_controller.rb with these actions:
def new
#invoice = current_user.invoices.build(:project_id => params[:project_id])
#invoice.build_item(current_user)
#invoice.set_number(current_user)
end
def create
#invoice = current_user.invoices.build(params[:invoice])
if #invoice.save
flash[:success] = "Invoice created."
redirect_to edit_invoice_path(#invoice)
else
render :new
end
end
Essentially, the new method instantiates a new invoice record plus one associated item record.
Now, what sort of method do I need if I want to duplicate an existing invoice?
I am a big fan of Rails's RESTful approach, so I wonder if I should add a new method like
def duplicate
end
or if I can use the existing new method and pass in the values of the invoice to be duplicated there?
What is the best approach and what might that method look like?
Naturally, you can extend RESTful routes and controllers.
To be rally RESTful, it is important to look exactly, what you want.
i.e. if you want a new invoice and use an existing one as a kind of template, then it is comparable to a new action, and the verb should be GET (get the input form). As is it based on an existing invoice, it should reference that object. After that you would create the new invoice in the usual way.
So in you routes:
resources :invoices do
member do
get 'duplicate'
end
end
giving you a route duplicate_invoice GET /invoices/:id/duplicate(.format) invoices#duplicate
So in your view you can say
<%= link_to 'duplicate this', duplicate_invoice_path(#invoice) %>
and in your controller
def duplicate
template = Invoice.find(params[:id])
#invoice= template.duplicate # define in Invoice.duplicate how to create a dup
render action: 'new'
end
If I understand correctly your question you can:
resources :invoices do
collection do
get 'duplicate'
end
end
and with this you can do:
def duplicate
# #invoice = [get the invoice]
#invoice.clone_invoice
render 'edit' # or 'new', depends on your needs
end
clone_invoice could be a custom method which should have a invoice.clone call in your custom method.
If you question if you can use additional methods except REST, you absolutely can. Google, for example, encourage developers to use something, what they call "extended RESTful" on GoogleIO, http://www.youtube.com/watch?v=nyu5ZxGUfgs
So use additional method duplicate, but don't forget about "Thin controllers, fat models" approach to incapsulate your duplicating logic inside model.

Resources