Создание 2 моделей в действии контроллера с транзакцией - Rails 4 - PullRequest
1 голос
/ 02 июля 2019

Существует несколько ответов, объясняющих, как вы можете использовать вложенные ресурсы, однако мой вариант использования немного отличается.

Партии принадлежат заказам, а заказ состоит из множества партий.

IЯ могу понять, как это работает, если у вас есть форма для заказа и вы можете создавать партии в этой форме, но не можете найти хороший способ для моей ситуации.

У меня есть форма для вложенного ресурса (партии), гдеродитель (заказ) может или не может существовать.Они могут выбрать, существует ли он с помощью переключателей.Если он существует, они просто выбирают, к какому порядку он относится .. просто.Если его не существует, я показываю поля для заказа и отправляю параметры заказа вместе с параметрами партии.Я хочу убедиться, что откат создания заказа выполнен, если партия не сохраняется.

Вот код, который у меня есть до сих пор.

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 ]

2 голосов
/ 02 июля 2019

Кажется, что это отличная возможность использовать объект службы: 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 на 2 различных метода, которые вы можете вызывать из контроллера. Использование таких классов обслуживания также значительно упрощает код для модульного тестирования.

1 голос
/ 02 июля 2019

Почему бы не переместить обработку ошибок и отображение ответов за пределы транзакции?

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

Это все еще довольно сложно, но определенно более читабельно. Следующим шагом будет извлечение всего блока транзакции в службу.

...