Creating multiple objects with one http request - ruby-on-rails

I have an rails app with json api. So far I can create single objects via POST request.
It's fairly simple:
def create
customer = Customer.new(customer_params)
if customer.save
render json: customer, status: 201
else
render json: customer.errors, status: 422
end
end
and:
private
def customer_params
params.require(:customer).permit(:name, :city)
end
Now I want to create multiple customers by passing an array in my http request. Like this:
{
"customer": [
{
"name": "foo",
"city": "New York"
},
{
"name": "bar",
"city": "Chicago"
}
]
}
However, I don't know how to approach this. The first issue is that my strong parameters function doesn't accept arrays.
Is there a way to use strong parameters and let me loop through the array?

I would see it as a new controller method
something like:
def multi_create
render json: customer.errors, status: 422 and return unless params[:customers]
all_created = true
customers = []
params[:customers].each do |customer_params|
customer = Customer.create(name: customer_params[:name], city: customer_params[:city])
customers << customer
all_created &&= customer.valid?
end
if all_created
render json: customers, status: 201
else
render json: customers.map(&:errors), status: 422
end
end
You also need to add the route. then you could post your json to that route with the change that the outermost key should be customers.
I would not run this code without any changes but you get the general idea. And you can refactor it to your liking.

Related

How to filter a table based on attributes a column using ActiveRecords Scope in Rails

I am trying to setup a Rails API backend for a bookstore project, and the frontend will be react. The backend has only one model for now which is the Book model. In the at the frontend I will like to filter the books based on their category. I want to setup the filtering at the backend so that when I select a dropdown at the frontend, only books that fit a particular category will be displayed. So far here is what I have at the backend:
Book model
class Book < ApplicationRecord
validates :title, :category, presence: true
scope :categorized, -> (category) { where("category LIKE ?", "#{category}" ) }
end
my book controller looks like this:
Book controller
class Api::V1::BooksController < Api::V1::ApiController
before_action :set_book, only: %i[show update destroy]
def index
#books = Book.all
render json: #books
end
def categorized
#category = params[:book]
#books = Book.categorized(#category)
render json: #books
end
def show
render json: #book
end
def create
#book = Book.new(book_params)
if #book.save
render json: #book, status: :created
else
render json: #book.errors, status: :unprocessable_entity
end
end
def update
if #book.update(book_params)
render json: #book
else
render json: #book.errors, status: :unprocessable_entity
end
end
def destroy
#book.destroy
end
private
def set_book
#book = Book.find(params[:id])
end
def book_params
params.require(:book).permit(:title, :category)
end
end
Routes.rb
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :books do
collection do
get :categorized
end
end
end
end
end
Sample Data
Here is a sample data I get from the API when I query for all books in the database:
[
{
"id": 1,
"title": "I Know Why the Caged Bird Sings",
"category": "Fable"
},
{
"id": 2,
"title": "His Dark Materials",
"category": "Speech"
},
{
"id": 3,
"title": "To Say Nothing of the Dog",
"category": "Fable"
},
{
"id": 4,
"title": "An Acceptable Time",
"category": "Science fiction"
},
{
"id": 5,
"title": "A Scanner Darkly",
"category": "Suspense/Thriller"
},
{
"id": 6,
"title": "The Golden Bowl",
"category": "Science fiction"
}
]
My interest is I want to see only "Science fiction" books or any other category I choose only
using scopes. The categories are not limited to what is shown in the sample data; infact they
are likely to increase. The configuration I have above in the model is not getting what I
need. When I use postman, I do not get any result. The best I get is an empty array.
scope :categorized, -> (category) { where("category LIKE ?", "#{category}" ) }
Are you sure you need this scope for only one place?
Why don’t just filter in controller?
Second thought is using Book.where(category: category) which will produce “WHERE CATEGORY = ?” instead of “WHERE category LIKE”
Third - may be you can get rid of categorized route and just use index with optional param? You can also add pagination later in index controller - where...page(page).per(per)
Fourth, there is a good practice of using instance variable accessor methods instead of variable itself:
def set_book
#book = Book.find(params[:id])
end
To
def book
#book ||= Book.find(params[:id])
end
Also find method raises exception. You might want to rescue from it on class level. You can also use book.update! And book.save! in other methods and rescue from them as well - then you won’t need those if/else branches.

How to handle a JSON Array POST request with Rails?

I am trying to create multiple database entries using only one JSON request.
Each entry consists only of two values, a type (of action) and a time (when the action happened). To get multiple of those into one request, I am using a JSON Array.
This is what my create action in the controller looks like:
def create
respond_to do |format|
#actions = []
save_succeeded = true
params[:action].each do |action|
new_action = Action.new(type: action.type, time: action.time)
save_succeeded = false unless new_action.save
#actions << new_action
end
if save_succeeded
format.json { render json: #actions, status: :created }
else
format.json { render json: #actions.errors, status: 501 }
end
end
end
When I send a post request to the controller (/actions.json) like this:
[{ "type": 0, "time": 1234567890 },{ "type": 0, "time": 1234567891 }]
I get back an empty array [] and a status code of 201 Created.
This means, the save_succeeded variable is still true, but the actions did not get added to the array. Furthermore, the actions are not in my database.
What am I doing wrong? What am I overlooking?
I would refactor the code a bit:
def create
actions = params[:action].inject([]) do |memo, action|
memo << Action.create!(type: action[:type], time: action[:time])
end
render json: #actions, status: :created
rescue ActiveRecord::RecordInvalid => e
render json: e.message, status: 501
end
end
Couple of notable changes:
use create! and rescue ActiveRecord::RecordInvalid - create! will raise a ActiveRecord::RecordInvalid if the save fails. Then, the rescue block will rescue the exception and you can render a nice error message.
you cannot use action.time, because params is a Hash, not an object.
if you want to build an array to render later, you can use inject.
if you would like to have some atomicity to this (either everything is created or nothing!), you can wrap the whole thing in a transaction block.
It's worth mentioning that I haven't tested the code above, but it should give you a direction and (maybe) it will be a drop-in replacement.
Hope that helps!

Saving an array of params in Rails

I have in my Rails 5 api project two models: Places and Beacons (Place has_many Beacons, foreign key: place_id). This API accepts JSON, such as this one:
{
"place":{
"name": "bedroom"
},
"beacon":{
"SSID": "My Wi-Fi",
"BSSID": "00:11:22:33:44:55",
"RSSI": "-55"
}
}
This JSON works just fine, with these classes:
def create
#place = Place.new(place_params)
#beacon = Beacon.new(beacon_params)
if #place.save
#beacon.place_id=#place.id
if #beacon.save
render :json => {:place => #place, :beacon => #beacon}, status: :created, location: #places
else
render json: #beacon.errors, status: :unprocessable_entity
end
else
render json: #place.errors, status: :unprocessable_entity
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_place
#place = Place.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def place_params
params.require(:place).permit(:name)
end
def beacon_params
params.require(:beacon).permit(:SSID, :BSSID, :RSSI)
end
However, I want that to pass multiple Beacons objects in the same JSON in an array (even no beacons at all). How can I save then all beacons in the parameter array and generate a response containing all of them?
I'm assuming Place has_many :beacons. If so, you can use nested attributes. This lets you assign and update a nested resource, in this case Beacons. First, in your Place model:
accepts_nested_attributes_for :beacons
Then, change your place_params method in your controller to permit the beacon's nested attributes:
def place_params
params.require(:place).permit(:name, beacons_attributes: [:id, :SSID, :BSSID, :RSSI])
end
And your json:
{
"place":{
"name": "bedroom",
"beacons_attributes": [
{
"SSID": "My Wi-Fi",
"BSSID": "00:11:22:33:44:55",
"RSSI": "-55"
}, {
//more beacons here
}
]
},
}
You can have zero, one, or more beacons, and can do the same when updating as well if you include the beacons' id's.
You also won't have to create the #beacon manually, and can just do this: #place = Place.new(place_params), and it'll automatically create the beacons and associate them to the Place.
You can read more here, if you'd like more information or clarification: http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
Edit: I missed where you asked how to include the beacons in the response. The quickest way is to just include it in the json rendering:
render :json => #place.to_json(:include => :beacons)
You can use Active model serializers (https://github.com/rails-api/active_model_serializers), jbuilder (https://github.com/rails/jbuilder), or more simply just override the to_json method on your model.

#<ActionController::ParameterMissing: param is missing or the value is empty: book>

this is my controller
class BooksController < ApplicationController
def create
#book = Book.new(book_params)
if #book.save
render json: #book, status: 201
else
render json: { error: "check attributes again", status: 400 }, status: 400
end
end
private
def book_params
params.require(:book).permit(:author, :categories, :publisher, :title)
end
end
i am passing prams like this
{ "book":{
"author": "some one",
"categories": "some thing",
"publisher": "some publisher",
"title": "some thing my own"
}
}
I am getting the above error what is the wrong in it. Any ideas? I am using sqlite3 data base and webric server.
Check below log file.
"your rails directory"/logs/development.log
And one more point, you should write 'puts' debug like below, because 'puts' results display above log file.
def create
puts 'params => ' + params
#book = Book.new(book_params)
#Nani, you have to find in your logs something similar to this:
Started POST "/books" for 127.0.0.1 at 2016-02-18 18:58:17 +0200
Processing by BooksController#create as JS
Parameters: {"utf8"=>"✓", "authenticity_token"=>"....", "book"=>{....}}
And here you need to check if you expecting correct parameters in your controller.

Json post hasMany to Rails 4 fails with Unpermitted parameters

I'm trying to post a json message to a Rails 4.1.1 server, but is failing due to unpermitted parameters. I'm using Mongoid as well and submitting via POST and content type of application/json.
Here's my domain:
class Sale
include Mongoid::Document
include Mongoid::Timestamps
field :internalId, type: String
embeds_many :saleItems
accepts_nested_attributes_for :saleItems
end
Here's the controller code:
def sale_params
params.require(:sale).permit(:internalId, :parentInternalId, :externalId, :internalIdForStore, :internalIdForCustomer, :sendReceiptType, :saleItems)
end
# POST /sales
# POST /sales.json
def create
#sale = Sale.new(sale_params)
#####################
puts "parent: "
puts #sale.inspect
puts "collections: "
#sale.saleItems.each do |si|
puts "collection here"
puts si.inspect
end
respond_to do |format|
if #sale.save
format.html { redirect_to #sale, notice: 'Sale was successfully created.' }
format.json { render action: 'show', status: :created, location: #sale }
else
format.html { render action: 'new' }
format.json { render json: #sale.errors, status: :unprocessable_entity }
end
end
end
I've successfully saved the collection saleItems fine outside of rails and just using a ruby script with the collection successfully saving via Mongoid.
Here's the JSON content:
{
"sale" : {
"internalId":"77E26804-03CC-4CA9-9184-181C2D8CB02A"
"saleItems" : [
{
"inventoryName" : "inv 1"
},
{
"inventoryName" : "inv 2"
}
]
}
}
Wow I figured it out. It needs to have the {} around the collection of items.
params.require(:sale).permit(:internalId, :parentInternalId, :externalId, :internalIdForStore, :internalIdForCustomer, :sendReceiptType,
{:saleItems => [:inventoryName, :internalIdForSeller]})
Here's the post I found to help fix the issue.
Rails 4 - Strong Parameters - Nested Objects
I think the issue is the strong parameters being permitted.
You have
params.require(:sale).permit(:internalId, :parentInternalId, :externalId, :internalIdForStore, :internalIdForCustomer, :sendReceiptType, :saleItems)
But salesItems is another class. You need something like
params.require(:sale).permit(:internalId, :parentInternalId, :externalId, :internalIdForStore, :internalIdForCustomer, :sendReceiptType, :saleItems_attributes => [:inventoryName, :anotherAttribute, :stillAnotherAttribute])
Kindly edit your answer the tell that what params you are getting in.
The things is params is data structure its a request object. And permit is a method which allow to permit the specific parameter .
So put the debugger and easily you will recognize what the problem is.

Resources