Дублирование записи и ее дочерние элементы, но дочерние элементы удаляются из старой записи

Моя проблема похожа на: Мой метод клонирования заключается в краже дочерних элементов исходной модели

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

Вот код:

def new
 @old_order = Order.includes(:line_items).find(params[:id])
 @order = Order.new @old_order.attributes 
 @order.line_items = []
 @old_order.line_items.each do |old|
   new = old.dup    # the line_item id is set before creation. 
   new.order_id = @order.id
   new.save!

   @order.line_items << new
   @old_order.line_items << old   # this was to see if the old line_items would reappend to the old order. Didn't help...
 end
end

def create
 @order = Order.new(exchange_order_params)
 if @order.save
   @order.update_attributes!(stage: 2, ordered_at: Date.today)
   redirect_to admin_returns_url, notice: "Order moved to 'accepted' for processing"
 else
   flash.now[:alert] = "Please try again"
   render :action => "new"
 end
end

private
  def exchange_order_params
  params.require(:order).permit(:id, :user_id,
                 line_items_attributes: [:id, :order_id, :cart_id, :quantity, :_destroy, 
                 product_attributes: [:id, :sku, :euro_price, :sterling_price, :product_group_id, :product_size_id, :product_waistband_id]])
end

Схема.rb

create_table "orders", force: :cascade do |t|
    t.datetime "created_at",                         null: false
    t.datetime "updated_at",                         null: false
    t.boolean  "returned",           default: false
    t.date     "date_sent"
    t.date     "ordered_at"
    t.integer  "user_id"
    t.boolean  "return_requested",   default: false
    t.integer  "stage",              default: 0
    t.decimal  "order_total",        default: 0.0
    t.string   "transaction_secret"
    t.string   "token"
    t.string   "uuid"
    t.string   "currency"
    t.float    "discounted_by",      default: 0.0
  end

  add_index "line_items", ["cart_id"], name: "index_line_items_on_cart_id", using: :btree
  add_index "line_items", ["order_id"], name: "index_line_items_on_order_id", using: :btree
  add_index "line_items", ["product_id"], name: "index_line_items_on_product_id", using: :btree

  create_table "line_items", force: :cascade do |t|
    t.integer  "quantity"
    t.integer  "order_id"
    t.integer  "cart_id"
    t.integer  "product_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.float    "unit_price"
    t.string   "currency"
  end



  create_table "product_groups", force: :cascade do |t|
    t.string   "name"
    t.text     "description"
    t.datetime "created_at",  null: false
    t.datetime "updated_at",  null: false
  end

  create_table "product_sizes", force: :cascade do |t|
    t.string   "specification"
    t.datetime "created_at",    null: false
    t.datetime "updated_at",    null: false
  end

  create_table "product_waistbands", force: :cascade do |t|
    t.string   "specification"
    t.datetime "created_at",    null: false
    t.datetime "updated_at",    null: false
  end

  create_table "products", force: :cascade do |t|
    t.integer  "sku"
    t.integer  "product_group_id"
    t.integer  "product_size_id"
    t.integer  "product_waistband_id"
    t.decimal  "euro_price"
    t.decimal  "sterling_price"
    t.datetime "created_at",                       null: false
    t.datetime "updated_at",                       null: false
    t.integer  "stock_level",          default: 0
  end

  add_index "products", ["product_group_id"], name: "index_products_on_product_group_id", using: :btree
  add_index "products", ["product_size_id"], name: "index_products_on_product_size_id", using: :btree
  add_index "products", ["product_waistband_id"], name: "index_products_on_product_waistband_id", using: :btree

Также в модели Order я рандомизирую идентификатор before_create, чтобы, когда пользователь отправляет форму, он создавал дублирующую копию с другим идентификатором Order. Это то же самое для LineItems.

Order.rb (то же самое в LineItem.rb)

before_create :randomize_id

private
  def randomize_id
    begin
      self.id = SecureRandom.random_number(1_000_000)
    end while Order.where(id: self.id).exists?
  end

person jellybean_232    schedule 01.10.2015    source источник
comment
Не могли бы вы добавить соответствующие части вашей схемы к вашему вопросу, пожалуйста.   -  person Max Williams    schedule 01.10.2015
comment
Я скопировал то, что считаю нужным. Извините, если немного длинно   -  person jellybean_232    schedule 01.10.2015


Ответы (1)


Мой подход состоял бы в том, чтобы переопределить метод ActiveRecord::Base#dup в модели Order, чтобы он был рекурсивным, что означает, что он также дублирует коллекцию LineItem:

class Order < ActiveRecord::Base
  def dup
    duped_order = super
    duped_order.line_items = line_items.map(&:dup)
    duped_order
  end
end

делая это таким образом, его легко проверить. Теперь контроллер становится:

class OrderController < ApplicationController
  def new
    @order = Order.find(params[:id]).dup
  end

  def create
    # not sure how your form populates the params hash
    # here you need to new-up and then save the order and the line items
    # with the attributes from the form
  end
end

Напишите тесты, чтобы подтвердить, что это делает то, что вы намеревались. Это прекрасный пример того, где следует применять старую парадигму «толстая модель тощего контроллера».

person Les Nightingill    schedule 02.10.2015
comment
Я пытался это сделать, но проблема возникает, когда я создаю. Из кода я передаю exchange_order_params (который я добавил в свой фрагмент), но идентификатор продукта ищет идентификатор LineItem (ошибка: не удалось найти продукт с ID = 2 для LineItem с ID =). Я заметил, что идентификатор LineItem и идентификатор заказа теперь равны нулю по какой-то странной причине (вероятно, из-за дублирования). Мне нужен старый идентификатор LineItem и Order, чтобы передать старую запись в новую (конечно, с другими идентификаторами). Извините, если это немного сложно понять, но я изо всех сил стараюсь объяснить свою проблему. - person jellybean_232; 05.10.2015
comment
Также просто упомянем, что мне нужно сохранить идентификаторы. Я не могу снять их по причинам. Я понимаю, что если я удалю идентификаторы, это сработает, но тогда возникнет еще одна проблема, которая была решена путем сохранения идентификаторов. Так есть ли способ передать старый идентификатор LineItem, обманывая его? - person jellybean_232; 05.10.2015
comment
Если вам нужно сохранить идентификаторы, вы не «клонируете» объекты, а вместо этого используете настоящие исходные объекты, и поэтому я не понимаю, что вы пытаетесь сделать. Да, 'dup' удаляет идентификатор, и обычно это желаемое поведение. Другая проблема, о которой вы упоминаете, вероятно, может быть решена без сохранения идентификаторов. Насколько я понимаю ваш вопрос, вам не следует копировать идентификаторы, но, возможно, я не совсем понимаю ваш вопрос. Если вы хотите уточнить другую проблему, возможно, мы сможем решить эту. - person Les Nightingill; 05.10.2015
comment
Да, извините, довольно сложно объяснить, но в основном у меня есть таблица продуктов, в которой перечислены все продукты и уровни запасов. Идентификаторы появляются там, где я создаю новую запись «Заказ», но для этого необходимо обновить уровни запасов в таблице продуктов. Я знаю, что клонирование записи, но сохранение идентификатора звучит очень странно, но я в основном делаю форму заказа на обмен, где она берет атрибуты старого заказа и заполняет новую форму заказа (потому что я хочу создать новую запись из старой). те). - person jellybean_232; 05.10.2015
comment
Таким образом, сохранение идентификаторов в сильных параметрах означает, что я обновляю таблицу продуктов, а не создаю новые записи. Я нашел это решение здесь: stackoverflow .com/questions/18946479/ - person jellybean_232; 05.10.2015
comment
Вы не дублируете объекты продукта. Когда вы дублируете позиции, они сохраняют тот же product_id, но устанавливают свой собственный идентификатор равным нулю. Обновление уровней запасов продукта, вероятно, следует выполнять с помощью обратного вызова after_create в модели LineItem. Я думаю, что опрометчивое решение управления запасами вызвало проблему, которую вы здесь разместили. Когда вы говорите, что что-то вроде xxx звучит очень странно, это намек на то, что вы, возможно, делаете это не лучшим образом. Предлагаю вам решить эту проблему, как я предложил, и вернуться к проблеме управления запасами. Ваш набор тестов подтвердит, что обе проблемы решены. - person Les Nightingill; 05.10.2015
comment
Да, я хорошо подумал об этом, и то, что вы сказали, верно. Я определенно должен найти новый способ. Очевидно, что не так много людей задавали вопросы моего типа по какой-то причине =) Большое спасибо за то, что вы предложили, это действительно работает очень хорошо. отмечу как принятое =) - person jellybean_232; 05.10.2015