如何迭代一个对象数组以创建一个类的新实例?Ruby on Rails

huangapple go评论72阅读模式
英文:

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转义字符(例如&quot;)以使文本更清晰。如果您在代码中使用这些字符,请确保保留它们。

英文:

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

{
    &quot;id&quot;: 5,
    &quot;customer_id&quot;: 1,
    &quot;order_date&quot;: &quot;2023-01-03&quot;,
    &quot;total_cost&quot;: 0,
    &quot;item_details&quot;: [
        {
            &quot;product_id&quot;: 3,
            &quot;quantity&quot;: 3
        },
        {
            &quot;product_id&quot;: 9,
            &quot;quantity&quot;: 4
        }
    ],

Schema

  create_table &quot;order_details&quot;, force: :cascade do |t|
    t.integer &quot;product_id&quot;
    t.integer &quot;order_id&quot;
    t.integer &quot;quantity&quot;
    t.datetime &quot;created_at&quot;, precision: 6, null: false
    t.datetime &quot;updated_at&quot;, precision: 6, null: false
  end

  create_table &quot;orders&quot;, force: :cascade do |t|
    t.integer &quot;customer_id&quot;
    t.string &quot;order_date&quot;
    t.datetime &quot;created_at&quot;, precision: 6, null: false
    t.datetime &quot;updated_at&quot;, precision: 6, null: false
    t.json &quot;item_details&quot;
  end

Models

class Order &lt; ApplicationRecord
    belongs_to :customer
    has_many :order_details
    has_many :products, through: :order_details

end

class OrderDetail &lt; ApplicationRecord

    validates :quantity, numericality: { only_integer: true }

    belongs_to :order
    belongs_to :product  

end

Serializers

class OrderSerializer &lt; 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 &lt;&lt; product.price * details.quantity
    end
    return cost.sum
  end

 class OrderDetailSerializer &lt; ActiveModel::Serializer
  attributes :id, :product_id, :order_id, :quantity, :product

  belongs_to :order
  belongs_to :product
end

Order Controller

class OrdersController &lt; ApplicationController
    wrap_parameters format: []
    skip_before_action :authorized, only: :create

    def index
        orders = Order.all
        if orders
        render json: orders
        else
            render json: {error: &quot;Order Not Found&quot; }, status: :not_found
        end
    end

    def show
        order = Order.find_by(id: params[:id])
        if order
            render json: order
        else
            render json: { error: &quot;Order Not Found&quot; }, 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: &quot;Order Not Found&quot; }, 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: &quot;Order Not Found&quot;}, 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 &lt; ApplicationController

    skip_before_action :authorized, only: :create

    def index
        order_details = OrderDetail.all
        if order_details
        render json: order_details
        else
            render json: {error: &quot;Not Found&quot;}, 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: &quot;Not Found&quot; }, 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: &quot;Not Found&quot; },这只是一个愚蠢的反模式。

  • 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 not find_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 return json: { error: &quot;Not Found&quot; }. 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.

huangapple
  • 本文由 发表于 2023年1月9日 08:26:13
  • 转载请务必保留本文链接:https://go.coder-hub.com/75052251.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定