Agile Web Development with Rails, Edition 4
12.2 Iteration G2: Atom Feeds
11.6 Iteration F6: Testing AJAX changes
12.1 Iteration G1: Capturing an Order
Create a model to contain orders
rails generate scaffold Order name address:text email pay_type
invoke active_record
create db/migrate/20160507044143_create_orders.rb
create app/models/order.rb
invoke test_unit
create test/models/order_test.rb
create test/fixtures/orders.yml
invoke resource_route
route resources :orders
invoke scaffold_controller
create app/controllers/orders_controller.rb
invoke erb
create app/views/orders
create app/views/orders/index.html.erb
create app/views/orders/edit.html.erb
create app/views/orders/show.html.erb
create app/views/orders/new.html.erb
create app/views/orders/_form.html.erb
invoke test_unit
create test/controllers/orders_controller_test.rb
invoke helper
create app/helpers/orders_helper.rb
invoke test_unit
invoke jbuilder
create app/views/orders/index.json.jbuilder
create app/views/orders/show.json.jbuilder
invoke assets
invoke coffee
create app/assets/javascripts/orders.coffee
invoke scss
create app/assets/stylesheets/orders.scss
invoke scss
identical app/assets/stylesheets/scaffolds.scss
Create a migration to add an order_id column to line_items
rails generate migration add_order_id_to_line_item order_id:integer
invoke active_record
create db/migrate/20160507044144_add_order_id_to_line_item.rb
Apply both migrations
rake db:migrate
mv 20160507044143_create_orders.rb 20160507000007_create_orders.rb
mv 20160507044144_add_order_id_to_line_item.rb 20160507000008_add_order_id_to_line_item.rb
== 20160507000007 CreateOrders: migrating =====================================
-- create_table(:orders)
-> 0.0008s
== 20160507000007 CreateOrders: migrated (0.0008s) ============================
== 20160507000008 AddOrderIdToLineItem: migrating =============================
-- add_column(:line_items, :order_id, :integer)
-> 0.0007s
== 20160507000008 AddOrderIdToLineItem: migrated (0.0007s) ====================
Add a Checkout button to the cart
edit app/views/carts/_cart.html.erb
<p id="notice"><%= notice %></p>
<h2>Your Cart</h2>
<table>
<%= render(cart.line_items) %>
<tr class="total_line">
<td colspan="2">Total</td>
<td class="total_cell"><%= number_to_currency(cart.total_price) %></td>
</tr>
</table>
<%= button_to "Checkout", new_order_path, method: :get %>
<%= button_to 'Empty cart', cart, method: :delete,
data: { confirm: 'Are you sure?' } %>
Return a notice when checking out an empty cart
edit app/controllers/orders_controller.rb
class OrdersController < ApplicationController
include CurrentCart
before_action :set_cart, only: [:new, :create]
before_action :set_order, only: [:show, :edit, :update, :destroy]
# GET /orders
#...
end
edit app/controllers/orders_controller.rb
def new
if @cart.line_items.empty?
redirect_to store_url, notice: "Your cart is empty"
return
end
@order = Order.new
end
Modify tests to ensure that there is an item in the cart
edit test/controllers/orders_controller_test.rb
test "requires item in cart" do
get :new
assert_redirected_to store_path
assert_equal flash[:notice], 'Your cart is empty'
end
test "should get new" do
item = LineItem.new
item.build_cart
item.product = products(:ruby)
item.save!
session[:cart_id] = item.cart.id
get :new
assert_response :success
end
Modify the template for new orders
edit app/views/orders/new.html.erb
<div class="depot_form">
<fieldset>
<legend>Please Enter Your Details</legend>
<%= render 'form', order: @order %>
</fieldset>
</div>
Add payment types to the order
edit app/models/order.rb
class Order < ActiveRecord::Base
PAYMENT_TYPES = [ "Check", "Credit card", "Purchase order" ]
end
Modify the partial used by the template
edit app/views/orders/_form.html.erb
<%= form_for(@order) do |f| %>
<% if @order.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@order.errors.count, "error") %>
prohibited this order from being saved:</h2>
<ul>
<% @order.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name, size: 40 %>
</div>
<div class="field">
<%= f.label :address %><br>
<%= f.text_area :address, rows: 3, cols: 40 %>
</div>
<div class="field">
<%= f.label :email %><br>
<%= f.email_field :email, size: 40 %>
</div>
<div class="field">
<%= f.label :pay_type %><br>
<%= f.select :pay_type, Order::PAYMENT_TYPES,
prompt: 'Select a payment method' %>
</div>
<div class="actions">
<%= f.submit 'Place Order' %>
</div>
<% end %>
Add some CSS
edit app/assets/stylesheets/application.css.scss
.depot_form {
fieldset {
background: #efe;
legend {
color: #dfd;
background: #141;
font-family: sans-serif;
padding: 0.2em 1em;
}
}
form {
label {
width: 5em;
float: left;
text-align: right;
padding-top: 0.2em;
margin-right: 0.1em;
display: block;
}
select, textarea, input {
margin-left: 0.5em;
}
.submit {
margin-left: 4em;
}
br {
display: none
}
}
}
Validate name and pay_type
edit app/models/order.rb
class Order < ActiveRecord::Base
# ...
validates :name, :address, :email, presence: true
validates :pay_type, inclusion: PAYMENT_TYPES
end
Update the test data in the orders fixture
edit test/fixtures/orders.yml
# Read about fixtures at
# http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
name: Dave Thomas
address: MyText
email: dave@example.org
pay_type: Check
two:
name: MyString
address: MyText
email: MyString
pay_type: MyString
Move a line item from a cart to an order
edit test/fixtures/line_items.yml
# Read about fixtures at
# http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
product: two
cart: one
two:
product: ruby
order: one
Define an optional relationship from the line item to the order
edit app/models/line_item.rb
class LineItem < ActiveRecord::Base
belongs_to :order, required: false
belongs_to :product, required: false
belongs_to :cart
def total_price
product.price * quantity
end
end
Define a relationship from the order to the line item
edit app/models/order.rb
class Order < ActiveRecord::Base
has_many :line_items, dependent: :destroy
# ...
end
Add line item to order, destroy cart, and redisplay catalog page
edit app/controllers/orders_controller.rb
def create
@order = Order.new(order_params)
@order.add_line_items_from_cart(@cart)
respond_to do |format|
if @order.save
Cart.destroy(session[:cart_id])
session[:cart_id] = nil
format.html { redirect_to store_url, notice:
'Thank you for your order.' }
format.json { render :show, status: :created,
location: @order }
else
format.html { render :new }
format.json { render json: @order.errors,
status: :unprocessable_entity }
end
end
end
Implement add_line_items_from_cart
edit app/models/order.rb
class Order < ActiveRecord::Base
# ...
def add_line_items_from_cart(cart)
cart.line_items.each do |item|
item.cart_id = nil
line_items << item
end
end
end
Modify the test to reflect the new redirect
edit test/controllers/orders_controller_test.rb
test "should create order" do
assert_difference('Order.count') do
post :create, order: { address: @order.address,
email: @order.email, name: @order.name,
pay_type: @order.pay_type }
end
assert_redirected_to store_path
end
take a look at the validation errors
get /orders/new
Pragmatic Bookshelf
Your Cart
1×
CoffeeScript
$36.00
Total
$36.00
post /orders
Pragmatic Bookshelf
process an order
get /orders/new
Pragmatic Bookshelf
post /orders
order[name] => Dave Thomas
order[address] => 123 Main St
order[email] => customer@example.com
order[pay_type] => Check
You are being
redirected .
get http://localhost:3000/
Pragmatic Bookshelf
Thank you for your order.
Your Pragmatic Catalog
look at the underlying database
sqlite3> select * from orders
id = 1
name = Dave Thomas
address = 123 Main St
email = customer@example.com
pay_type = Check
created_at = 2016-05-07 04:41:46.515742
updated_at = 2016-05-07 04:41:46.515742
sqlite3> select * from line_items
id = 10
product_id = 2
cart_id =
created_at = 2016-05-07 04:41:35.991766
updated_at = 2016-05-07 04:41:46.517708
quantity = 1
price = 36
order_id = 1
hide the notice when adding items to the cart
edit app/views/line_items/create.js.erb
$('#notice').hide();
if ($('#cart tr').length == 1) { $('#cart').show('blind', 1000); }
$('#cart').html("<%= escape_javascript render(@cart) %>");
$('#current_item').css({'background-color':'#88ff88'}).
animate({'background-color':'#114411'}, 1000);
12.2 Iteration G2: Atom Feeds
11.6 Iteration F6: Testing AJAX changes