Duplicating record and it's children - but children get deleted from old record

74 views Asked by At

My problem is similar to: My cloning method is stealing the children from the original model

But I can't seem to get the solution for this to work with mine. I'm trying to create an order exchange form which involves populating the form with the old record details. So when I save the form it creates a new Order record but the children seem to get deleted from the old Order record and siphoned into the new one.

Here's the code:

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

Schema.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

Also in the Order model I am randomising the id before_create so that when the user submits the form, it creates a dupe copy with a different Order id. This is the same for LineItems.

Order.rb (same in 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
1

There are 1 answers

7
Les Nightingill On BEST ANSWER

My approach would be to override the ActiveRecord::Base#dup method in the Order model so that it's recursive, meaning that it also duplicates the LineItem collection:

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

doing it this way makes it easily testable. Now the controller becomes:

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

Do write tests to confirm that this is doing what you intend. This is the perfect example of where the old "fat model skinny controller" paradigm should be applied.