在带有事务的控制器操作中创建2个模型-Rails 4

时间:2019-07-01 21:01:09

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

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

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

如果您有订单的表单并可以在该表单中创建批次,我可以理解它是如何工作的,但是无法理解我的处境。

我有一个嵌套资源(批处理)的表单,其中父项(订单)可能存在或不存在。他们可以通过单选按钮选择是否存在。如果存在,那么他们只需选择它所属的顺序即可。如果不存在,我将显示订单字段并提交订单参数以及批处理参数。如果批次未保存,我想确保回滚订单创建。

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

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

这并不像我想要的那样,而且看起来很丑。我有种感觉,我正在变得比我所需要的更加困难。在这种情况下最好的方法是什么?非常感谢!

2 个答案:

答案 0 :(得分:2)

似乎这是使用服务对象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方法分解为两种可从控制器调用的方法可能很有意义。使用这样的服务类也使代码更容易进行单元测试。

答案 1 :(得分:1)

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

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

它仍然很复杂,但是绝对可读。下一步是将整个交易块提取到服务中。