【问题标题】:Creating 2 Models in Controller Action With Transaction - Rails 4使用事务在控制器动作中创建 2 个模型 - Rails 4
【发布时间】:2019-07-01 21:01:09
【问题描述】:

有多个答案可以解释如何拥有嵌套资源,但是,我的用例有点不同。

批次属于订单,一个订单有多个批次。

如果您有订单表格并且可以在该表格中创建批次,我可以理解它是如何工作的,但无法理解适合我的情况的好方法。

我有一个嵌套资源(批次)的表单,其中父级(订单)可能存在也可能不存在。他们可以通过单选按钮选择它是否存在。如果它存在,那么他们只需简单地选择它属于哪个订单......简单。如果它不存在,我会显示订单字段并在批处理参数旁边提交订单参数。如果批次没有保存,我想确保回滚订单创建。

这是我到目前为止的代码。

def create
  @batch = Batch.new(batch_params)

  Batch.transaction do
    if params[:new_order] == "newOrder"
      @order = Order.new(order_params)
      @order.project_id = params[:batch][:project_id]
      begin
        @order.save!
      rescue
        respond_to do |format|
          format.html { render action: 'new' }
          format.json { render json: {order: @order.errors}, status: :unprocessable_entity }
          format.js { render json: {order: @order.errors}, status: :unprocessable_entity }
        end
        raise ActiveRecord::Rollback
        return
      end
      #@batch.order_id = @order.id
    end

    respond_to do |format|
      begin
        @batch.save!
        format.html { redirect_to @batch, notice: 'Batch was successfully created.' }
        format.json { render json: @batch }
        format.js { render json: @batch }
      rescue
        binding.pry
        raise ActiveRecord.Rollback
        format.html { render action: 'new' }
        format.json { render json: {batch: @batch.errors}, status: :unprocessable_entity }
        format.js { render json: {batch: @batch.errors}, status: :unprocessable_entity }
      end
    end
  end
end

这与我想要的不太一样,而且看起来很丑陋。我有一种感觉,我让它变得比我需要的更困难。在这种情况下最好的方法是什么?非常感谢!

【问题讨论】:

    标签: ruby-on-rails ruby ruby-on-rails-4 activerecord


    【解决方案1】:

    这似乎是一个使用服务对象的好机会:https://www.engineyard.com/blog/keeping-your-rails-controllers-dry-with-services

    这种模式对于保持模型和控制器的清洁以及确保应用程序的这些部分遵守单一职责原则非常有用。

    在这种情况下,我要做的是创建一个名为CreateBatch 的服务类,它接受参数并为每种情况执行正确的逻辑。然后,您可以在控制器中呈现正确的输出。这也将有助于清理您拥有的条件和早期回报。

    例如:

    # app/controllers/batches_controller.rb
    def create
      project_id = params[:batch][:project_id]
      new_order = params[:new_order]
    
      result = CreateBatch.new(new_order, batch_params, order_params, project_id).call
    
      if result.errors
        # handle errors with correct format
      else
        # handle successful response with correct format
      end
    end
    
    # app/services/create_batch.rb
    class CreateBatch
    
      def initialize(new_order, batch_params, order_params, project_id)
        @new_order = new_order
        @batch_params = batch_params
        @order_params = order_params
        @project_id = project_id
      end
    
      def call
        if new_order?
          create_new_order
        else
          add_batch_to_existing_order
        end
      end
    
      private
    
      def new_order?
        @new_order
      end
    
      def create_new_order
        order_params = @order_params.merge(project_id: @project_id)
        Order.save(order_params)
      end
    
      def add_batch_to_existing_order
        Batch.create(@batch_params)
      end
    end
    

    我没有运行此程序,因此可能需要进行一些调整才能工作,但是,我希望这是一个很好的起点。这个重构的一个很棒的事情是你现在有 1 个逻辑条件和 1 个响应条件,不需要添加 Transaction 块,也没有提前返回。将call 方法分解为可以从控制器调用的两种不同方法可能是有意义的。使用这样的服务类也使代码更容易进行单元测试。

    【讨论】:

    • 谢谢!这很有趣,我以前从未在 Rails 文章中看到过约定。对我来说,这似乎只是将代码移到别处而没有太多好处;请您概述一下这样做的一些好处吗?
    • 当然! 1)有关数据层的详细信息不应成为控制器中的依赖项。 2)您的模型应该只是数据库上的一个薄层,而无需引入额外的业务逻辑。假设您想创建一个批次时,您需要遵循相同的流程。在这种情况下,将它放在一个专门的类中,该类的工作是提出这些问题并正确处理它,这样可以更轻松地对这种行为进行单元测试、重用它,并使应用程序的其他部分更简单且单一职责。
    • 这是 Gary Bernhardt 的一次有趣的演讲,强调了其中一些要点 - youtube.com/watch?v=iUe6tacW3JE
    【解决方案2】:

    为什么不将错误处理和响应呈现移到事务之外?

    def create
      @batch = Batch.new(batch_params)
      Batch.transaction do
        if params[:new_order] == "newOrder"
          @order = Order.new(order_params)
          @order.project_id = params[:batch][:project_id]
          @order.save!
          @batch.order_id = @order.id
          @batch.save!
        end
      end    
      respond_to do |format|
        format.html { redirect_to @batch, notice: 'Batch was successfully created.' }
        format.json { render json: @batch }
        format.js { render json: @batch }
      end
    rescue StandardError => error
       @error = error
       format.html { render action: 'new' }
       format.json { render json: {error: @error, batch: @batch.errors}, status: :unprocessable_entity }
       format.js { render json: {error: @error, batch: @batch.errors}, status: :unprocessable_entity  }
    end
    

    它仍然相当复杂,但它肯定更具可读性。下一步是将整个事务块提取到服务中。

    【讨论】:

    • 这看起来好多了。但是,您在哪里发现订单创建的任何错误?
    • 另一方面,如果他们决定需要新订单,然后让表单遵循 Rails 约定,让表单用于订单,然后嵌套,您认为完全换掉表单是否值得?批次的属性?
    • 是的,这可能是一个很好的解决方案——你不必在控制器中重新实现这个逻辑。在这种情况下,我可能会这样做。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-04-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多