英文:
How do I Iterate over an array of objects to create new instances of a Class? Ruby on Rails
问题
我有一个订单模型,其中包含一个名为item_details的对象数组。
当我创建一个新订单时,我想要遍历item_details并创建包含订单id的新OrderDetail实例。
OrderDetail是一个关联表,所以我希望在创建订单后创建实例,以便在OrderDetails中包含order_id。
我应该如何操作?我将item_details的数据类型设置为json,这样我就成功地将其保存到数据库中。
在之前,我将其设置为文本/字符串,它会将符号保存为字符串。
订单示例:
{
"id": 5,
"customer_id": 1,
"order_date": "2023-01-03",
"total_cost": 0,
"item_details": [
{
"product_id": 3,
"quantity": 3
},
{
"product_id": 9,
"quantity": 4
}
]
}
模式:
create_table "order_details", force: :cascade do |t|
t.integer "product_id"
t.integer "order_id"
t.integer "quantity"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "orders", force: :cascade do |t|
t.integer "customer_id"
t.string "order_date"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.json "item_details"
end
模型:
class Order < ApplicationRecord
belongs_to :customer
has_many :order_details
has_many :products, through: :order_details
end
class OrderDetail < ApplicationRecord
validates :quantity, numericality: { only_integer: true }
belongs_to :order
belongs_to :product
end
序列化器:
class OrderSerializer < ActiveModel::Serializer
attributes :id, :customer_id, :order_date, :total_cost, :item_details
belongs_to :customer
has_many :order_details
has_many :products
def total_cost
cost = []
self.object.order_details.each do |details|
product = self.object.products.find { |product| product.id == details.product_id }
cost << product.price * details.quantity
end
return cost.sum
end
end
class OrderDetailSerializer < ActiveModel::Serializer
attributes :id, :product_id, :order_id, :quantity, :product
belongs_to :order
belongs_to :product
end
订单控制器:
class OrdersController < ApplicationController
wrap_parameters format: []
skip_before_action :authorized, only: :create
# 其他控制器方法...
end
OrderDetail控制器:
class OrderDetailsController < ApplicationController
skip_before_action :authorized, only: :create
# 其他控制器方法...
end
请注意,我已经删除了一些HTML转义字符(例如"
)以使文本更清晰。如果您在代码中使用这些字符,请确保保留它们。
英文:
I have an Order Model which contains an array of objects called item_details.
When I create a new order, I want to iterate over item_details and create new instances of OrderDetail which include the Order id.
OrderDetail is a join table so I want to create instances after creating an Order so that I can include the order_id in OrderDetails.
How do I go about doing this? I have the data type for item_details as json, this way I managed to save it to my database.
Before I had it as text/string and it was saving a symbol as a string.
Order Sample
{
"id": 5,
"customer_id": 1,
"order_date": "2023-01-03",
"total_cost": 0,
"item_details": [
{
"product_id": 3,
"quantity": 3
},
{
"product_id": 9,
"quantity": 4
}
],
Schema
create_table "order_details", force: :cascade do |t|
t.integer "product_id"
t.integer "order_id"
t.integer "quantity"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "orders", force: :cascade do |t|
t.integer "customer_id"
t.string "order_date"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.json "item_details"
end
Models
class Order < ApplicationRecord
belongs_to :customer
has_many :order_details
has_many :products, through: :order_details
end
class OrderDetail < ApplicationRecord
validates :quantity, numericality: { only_integer: true }
belongs_to :order
belongs_to :product
end
Serializers
class OrderSerializer < ActiveModel::Serializer
attributes :id, :customer_id, :order_date, :total_cost, :item_details
belongs_to :customer
has_many :order_details
has_many :products
def total_cost
cost = []
self.object.order_details.each do |details|
product = self.object.products.find {|product| product.id == details.product_id}
cost << product.price * details.quantity
end
return cost.sum
end
class OrderDetailSerializer < ActiveModel::Serializer
attributes :id, :product_id, :order_id, :quantity, :product
belongs_to :order
belongs_to :product
end
Order Controller
class OrdersController < ApplicationController
wrap_parameters format: []
skip_before_action :authorized, only: :create
def index
orders = Order.all
if orders
render json: orders
else
render json: {error: "Order Not Found" }, status: :not_found
end
end
def show
order = Order.find_by(id: params[:id])
if order
render json: order
else
render json: { error: "Order Not Found" }, status: :not_found
end
end
def create
order = Order.create(order_params)
if order.valid?
order.item_details.each do |i|
OrderDetail.create(order_id: params[:id], product_id: i[:product_id], quantity: i[:quantity])
end
render json: order
else
render json: { errors: order.errors.full_messages }, status: :unprocessable_entity
end
end
def update
order = Order.find_by(id: params[:id])
if order
order.update(order_params)
render json: order
else
render json: { error: "Order Not Found" }, status: :not_found
end
end
def destroy
order = Order.find_by(id: params[:id])
if order
order.destroy
head :no_content
else
render json: {error: "Order Not Found"}, status: :not_found
end
end
private
def order_params
params.permit(:customer_id, :order_date, item_details: [:product_id, :quantity] )
end
end
OrderDetail Controller
class OrderDetailsController < ApplicationController
skip_before_action :authorized, only: :create
def index
order_details = OrderDetail.all
if order_details
render json: order_details
else
render json: {error: "Not Found"}, status: :not_found
end
end
def create
order_detail = OrderDetail.create(order_details_params)
if order_detail.valid?
render json: order_detail
else
render json: { errors: order_detail.errors.full_messages }, status: :unprocessable_entity
end
end
def update
order_detail = OrderDetail.find_by(id: params[:id])
if order_detail
order_detail.update(order_details_params)
render json: order_detail
else
render json: { error: "Not Found" }, status: :not_found
end
end
private
def order_details_params
params.permit(:order_id, :product_id, :quantity)
end
end
答案1
得分: 0
例如
order = Order.create(order_attribs)
order_items.each do |item_attribs|
order.order_items.create(item_attribs)
end
英文:
For example
order = Order.create(order_attribs)
order_items.each do |item_attribs|
order.order_items.create(item_attribs)
end
答案2
得分: 0
以下是翻译好的部分:
通常在Rails中一次请求中创建多条记录的方式是使父记录接受嵌套属性以包含其子记录:
class Order
has_many :order_details
accepts_nested_attributes_for :order_details
validates_associated :order_details
end
这将创建一个order_details_attributes=
的setter方法,它接受一个哈希数组作为输入,并初始化/创建嵌套记录。
class OrdersController
# POST /orders
def create
@order = Order.new(order_params)
if @order.save
# 可以只响应一个位置
head :created, location: @order
# 或者作为 JSON 返回实体
render json: @order
else
render json: { errors: order_detail.errors.full_messages },
status: :unprocessable_entity
end
end
private
def order_params
params.require(:order)
.permit(:foo, :bar, order_details_attributes: [:product_id, :quantity])
end
end
然而,这只是一种在单个同步请求中管理多个资源的一种不太理想的方法,不一定会产生最佳的用户体验或良好的代码。
如果用户要将物品添加到购物车,最好在他们添加第一件物品时立即保存订单,然后让客户端发送原子性的POST /orders/:order_id/order_details
请求来添加物品到购物车 - 更新单个物品的数量可以使用PATCH /orders/:order_id/order_details/:id
来完成。请参阅嵌套路由。
此外,问题中的控制器存在许多问题,最好从头开始构建。
-
使用
find
而不是find_by(id: ...)
。如果找不到记录,它将引发NotFoundException并响应404状态响应。它会在不添加大量循环复杂性和重复的情况下退出方法。不需要返回json: { error: "Not Found" }
,这只是一个愚蠢的反模式。 -
if order_detail.valid?
实际上并不保证记录已保存到数据库中。它只表示验证通过。应该检查.save
或.persisted?
的返回值。 -
您实际上没有检查
order_detail.update(order_details_params)
是否成功。始终针对无效的用户输入编写代码。
英文:
The way that you typically creating multiple records in a single request in Rails is to have the parent record accept nested attributes for its children:
class Order
has_many :order_details
accepts_nested_attributes_for :order_details
validates_associated :order_details
end
This will create a order_details_attributes=
setter which takes an array of hashes as input. And which will initialize/create the nested records.
class OrdersController
# POST /orders
def create
@order = Order.new(order_params)
if @order.save
# either just respond with a location
head :created, location: @order
# or the entity as json
render json: @order
else
render json: { errors: order_detail.errors.full_messages },
status: :unprocessable_entity
end
end
private
def order_params
params.require(:order)
.permit(:foo, :bar, order_details_attributes: [:product_id, :quantity])
end
end
However this is really just somewhat of a kludge to manage multiple resources in one single syncronous request and doesn't always result in the best user experience or good code.
If your users are adding an item to a shopping cart it would be better to save the order right when they add the first item and then have the client send atomical POST /orders/:order_id/order_details
requests to add items to the cart - updating the quantity of a single item would be done with PATCH /orders/:order_id/order_details/:id
. See nested routes.
There are also a lot of issues with the controller in the question and you would be better off if you just started over from a scaffold.
-
Use
find
and notfind_by(id: ...)
. It will raise a NotFoundException if the record is not found and respond with a 404 status response. It will break out of the method without adding a bunch of cyclic complexity and duplication. You don't need to returnjson: { error: "Not Found" }
. That is just a silly anti-pattern. -
if order_detail.valid?
doesn't actually guarentee that the record is persisted to the database. It just says that the validations passed. Check the return value of.save
or.persisted?
instead. -
You're not actually checking if
order_detail.update(order_details_params)
is successful. Always code for invalid user input.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论