Update Multiple Records with a single command in Ruby - ruby-on-rails

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

Related

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)

How do I check if a record already exists in my db when posting with Ajax?

How do I check if a record already exists in my db when posting with Ajax?
Here is my Ajax code:
$.ajax({
type: "POST",
url: "team_selections#create",
data: {
team_selection: {
season_id: "1",
club_id: "1",
player_id: id,
fixture_week: "1",
position: pos
}
},
dataType: "html"
})
Here is my Rails controller code:
def create
if !TeamSelection.where(season_id: params[:season_id], club_id: params[:club_id], player_id: params[:player_id], fixture_week: params[:fixture_week], position: params[:position]).exists?
TeamSelection.create(selection_params)
end
end
private
def selection_params
params.require(:team_selection).permit(:season_id, :club_id, :player_id, :fixture_week, :position)
end
you can use find_or_create_by rails method in your controller. this will finds the first record with the given attributes, or creates a record with the attributes if one is not found.This method always returns a record, but if creation was attempted and failed due to validation errors it won’t be persisted, you get what create returns in such situation.
def create
TeamSelection.find_or_create_by(selection_params)
end
You can add a check with the help of a before_action.
before_action :check_record_exists?
def create
TeamSelection.create(selection_params)
render_something
end
private
def check_record_exists?
if TeamSelection.where(selection_params.slice(:season_id, :club_id, :player_id, :fixture_week, :position)).exists?
render json: { error: 'Record already exists' }
end
end
def selection_params
params.require(:team_selection).permit(:season_id, :club_id, :player_id, :fixture_week, :position)
end
NOTE: You definitely need to have a validation on model to prevent creation of such records. Don't just rely on checks in controller or the JS.
As #Jagdeep commented correctly: add a validation in the model if you don't want similar records to be created more than once.
But here controller is not returning any response like 'Record already exists'
Replace your create method with
def create
is_record_present = TeamSelection.where(season_id: params[:season_id], club_id: params[:club_id], player_id: params[:player_id], fixture_week: params[:fixture_week], position: params[:position]).exists?
if !is_record_present
TeamSelection.create(selection_params)
else
#return respose for example
render json: {message: 'Record already present'}, status: :bad_request
end
end

Rails 4: Escape Not Found error for nested form

I need to escape NotFound exception for nested form associations, when there is one relation isn't found.
For example, i have
class User < ActiveRecord::Base
has_many :user_selection_criteria
accepts_nested_attributes_for :user_selection_criteria, :reject_if => lambda { |t| t['brand_id'].nil? }, :allow_destroy => true
end
and
if #user.update_attributes user_params
render
else
render json: #user.errors, status: :unprocessable_entity
end
Which updates attributes, params do permit this, and everything is ok.
I use nested form, say, with those attributes.
user_selection_criteria_attributes[0][id]
user_selection_criteria_attributes[0][brand_id]
user_selection_criteria_attributes[0][_destroy]
user_selection_criteria_attributes[1][id]
user_selection_criteria_attributes[1][brand_id]
user_selection_criteria_attributes[1][_destroy]
user_selection_criteria_attributes[2][id]
user_selection_criteria_attributes[2][brand_id]
user_selection_criteria_attributes[3][_destroy]
etc...
Everything is OK, when i:
Leave id blank - a new record is created
Use id of existing record - corresponding record is updated.
But when i use non-existing record id, for example when another user already deleted this record, i get an error Couldn't find UserSelectionCriterium with ID=13 for User with ID=12
When i use
begin
if #user.update_attributes user_params
render
else
render json: #user.errors, status: :unprocessable_entity
end
escape
render
end
Error is escaped, but attributes aren't saved. But that's expected.
Question: how do i squelch/escape that error, ignore that record does not exist any more, and save any other valid relations?
I.e. when nested relation with id 13 doesn't exist, but relation with id 14 exists, relation 13 is ignored and 14 is processed normally.
You can filter these ids which's record does not exsited, like this(ideally, use your own code):
def some_params
params.require(:user).permit(user_selection_criteria_attributes: [:id, :brand_id]).tap do |white_list|
white_list[:user_selection_criteria_attributes].each do |key, value|
if value[:id].present? && UserSelectionCriteria.find_by(id: value[:id]).blank?
white_list[:user_selection_criteria_attributes].delete(key)
end
end
end
end

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.

POST/Create multiple items

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/

Resources