Reputation: 115
I'm working on a sample project that allows Orders to be created, comprised of many Products, associated via LineItems (see my recent question at Should I include other fields in a HABTM table?).
Once my view for a new object gets rendered, I have access to the @orders object, and it seems like I should be collecting @line_items as I go. The problem is, the @orders object hasn't been saved yet.
The best example I could find on the web so far was at a Railscasts writeup that seemed to encourage storing object parameters in session:
def new
session[:order_params] ||= {}
@order = Order.new(session[:order_params])
@order.current_step = session[:order_step]
end
I just wasn't sure how to best pay this out when I'm dealing with storing multiple Products per Order (via LineItem). One thing that came to mind was to create and save the object in the database but to only mark it as "real" once the user actually saves it (vs just adding items to the order) - but given abandonment rates and such, it seemed like I might end up with too much garbage.
Is there a widely accepted convention to taking a new @order that's unsaved and "building" a has_many list of products "correctly?" For the record, I'm trying to replicate a project I built in PHP using RoR, if that helps provide context of my end game.
My schema (intended to support ordering gift cards for multiple Properties) looks a little something like:
# encoding: UTF-8
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# Note that this schema.rb definition is the authoritative source for your
# database schema. If you need to create the application database on another
# system, you should be using db:schema:load, not running all the migrations
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 3) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "line_items", id: false, force: true do |t|
t.integer "order_id"
t.integer "product_id"
t.decimal "pay_cost", precision: 8, scale: 2, null: false
t.decimal "pay_discount", precision: 8, scale: 2, default: 0.0
t.decimal "pay_linetotal", precision: 8, scale: 2, null: false
t.text "note"
end
add_index "line_items", ["order_id", "product_id"], name: "index_line_items_on_order_id_and_product_id", unique: true, using: :btree
create_table "orders", force: true do |t|
t.string "order_id", null: false
t.integer "property_id"
t.string "order_status", default: "new"
t.string "email_address"
t.string "bill_name"
t.string "bill_address1"
t.string "bill_address2"
t.string "bill_city"
t.string "bill_state"
t.string "bill_zip"
t.string "ship_name"
t.string "ship_address1"
t.string "ship_address2"
t.string "ship_city"
t.string "ship_state"
t.string "ship_zip"
t.string "pay_cardtype"
t.string "pay_pastfour"
t.text "order_summary"
t.boolean "is_gift", default: false
t.text "order_message"
t.boolean "pay_live", default: false
t.boolean "pay_paid", default: false
t.boolean "pay_refunded", default: false
t.decimal "pay_total", precision: 8, scale: 2, null: false
t.decimal "pay_discount", precision: 8, scale: 2, default: 0.0
t.integer "stripe_fee"
t.string "stripe_token"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "orders", ["order_id"], name: "index_orders_on_order_id", unique: true, using: :btree
add_index "orders", ["order_status"], name: "index_orders_on_order_status", using: :btree
create_table "products", force: true do |t|
t.string "name", null: false
t.decimal "price", precision: 8, scale: 2, null: false
t.boolean "active", default: true
end
create_table "products_properties", id: false, force: true do |t|
t.integer "product_id"
t.integer "property_id"
end
add_index "products_properties", ["product_id", "property_id"], name: "index_products_properties_on_product_id_and_property_id", unique: true, using: :btree
create_table "properties", force: true do |t|
t.string "name", null: false
t.string "slug", null: false
t.string "prefix", null: false
t.string "phone"
t.text "address"
t.string "email"
t.boolean "visible", default: true
end
add_index "properties", ["prefix"], name: "index_properties_on_prefix", unique: true, using: :btree
add_index "properties", ["slug"], name: "index_properties_on_slug", unique: true, using: :btree
create_table "properties_users", id: false, force: true do |t|
t.integer "user_id"
t.integer "property_id"
end
add_index "properties_users", ["user_id", "property_id"], name: "index_properties_users_on_user_id_and_property_id", unique: true, using: :btree
create_table "users", force: true do |t|
t.string "first_name"
t.string "last_name"
t.string "email", null: false
t.string "encrypted_password", default: "", null: false
t.datetime "locked_at"
t.boolean "active", default: true, null: false
t.boolean "superuser", default: false, null: false
end
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
end
Upvotes: 0
Views: 100
Reputation: 5847
To persist the shopping cart between requests you can either store the necessary details in session or use actual database records and mark the order as temporary/unconfirmed. In simple scenarios using the database might be overkill but you definitely want to avoid having complex data in session. If you change things in the future it might break old sessions.
Im working on a simple web shop right now, so I'll show how Im using the session to store and retrieve the working order. I store a hash in session[:cart]
with the keys being product ids and the values being the quantity
Add to cart action
def add_to_cart
product = Product.find(params[:product_id])
session[:cart][product.id] ||= 0
session[:cart][product.id] += 1
redirect_to :back
end
A before_action
method restores @order
between requests
class ApplicationController < ActionController::Base
before_action :restore_order
private
def restore_order
session[:cart] ||= {}
@order = Order.build_from_session(session[:cart])
end
end
The Order model
class Order < ActiveRecord::Base
has_many :line_items
def self.build_from_session(session_data)
order = self.new
session_data.each do |product_id, quantity|
product = Product.find_by_id(product_id)
next unless product && quantity > 0
order.line_items << LineItem.new(product: product, quantity: quantity)
end
return order
end
end
Upvotes: 1
Reputation: 4306
Storing it in the session should be fine. If you're configured to use CookieStore for sessions, try to keep the data as small as possible (just product IDs, ideally). You might also want to look at something like wicked if you've got a multi-step checkout process.
I would encourage you to think about storing orders directly in your database, though. That lets you do some cool stuff like show a user their abandoned cart when they come back to your site, or send out reminder emails/special offers to people who left in the middle of the process.
You would also want a scheduled cleanup process that purges incomplete orders older than a few weeks/months.
Upvotes: 1