In the previous part I showed you how to create a cart, add products to it and how to keep products in user’s cart so they can leave the session and come back at a later time and their products will still be in their cart. In this part I will show you how to create an order and the admin panel. Let’s get started.
We finished off by creating the view carts/index.html.erb where we create an order.
<div class="total-price"><%= link_to 'Next', new_order_path, class: "btn btn-success" %></div>
Obviously, we need a view that will render after clicking “Next” but before we create the view we need to create the model Order first. You can use rails generate model Order, which will also generate the migration and tests for you, but I like to do it manually.
class Order < ApplicationRecord belongs_to :cart validates :delivery_address, :delivery_type, :payment_type, presence: true DELIVERY_TYPES = ["Courier(DPD)", "Personal collection", "InPost"] PAYMENT_TYPES = ["Cash On Delivery", "Bank Transfer", "Dotpay"] end
delivery_types and payment_types are for the select options in the view.
rails generate migration CreateOrders delivery_address delivery_type payment_type
(Note the “underscore” is missing in delivery_type and payment_type as WordPress does not understand this syntax (sic!)
Note, that rails does not automatically add timestamps for you when using “manual approach” so I added it manually in the migration file.
class CreateOrders < ActiveRecord::Migration[5.0] def change create_table :orders do |t| t.string :delivery_address t.string :delivery_type t.string :payment_type t.timestamps end end end
I want to be able to see what products were added to a particular order by a usr so I need to create associations between order, cart and user.
rails generate migration AddUsertoOrders
class AddColumnUsersToOrders < ActiveRecord::Migration[5.0] def change add_reference :orders, :user, foreign_key: true end end
rails generate migration AddCartToOrders
class AddCartToOrders < ActiveRecord::Migration[5.0] def change add_reference :orders, :cart, foreign_key: true end end
Now we can create the view.
<% provide(:title, 'Finalize') %> <% provide(:button_text, 'Create order') %> <h1>Please enter your details</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= render 'form' %></div> </div>
Here I provide the title of the page, a class to the button and render the form, which looks like this:
<%= form_for(@order) do |f| %> <% if @order.errors.any? %> <div id="error_explanation"> <div class="alert alert-danger"> The form contains <%= pluralize(@order.errors.count, "error") %>.</div> <ul> <% @order.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %></ul> </div> <% end %> <%= f.label :delivery_address %> <%= f.text_area :delivery_address, rows: 3, cols: 25, class: 'form-control' %> <%= f.label :delivery_type %> <%= f.select :delivery_type, Order::DELIVERY_TYPES, prompt: 'Select a delivery method', class: 'form-control' %> <%= f.label :payment_type %> <%= f.select :payment_type, Order::PAYMENT_TYPES, prompt: 'Select a payment method', class: 'form-control' %> <%= f.hidden_field :user_id, value: current_user.id %> <%= f.hidden_field :cart_id, value: current_cart.id %> <%= f.submit yield(:button_text), class: "btn btn-primary" %> <% end %>
We want to give admin users the ability to add products, view and manage orders so we add the following to our routes.rb
namespace :admin do resources :products resources :orders, only: [:index, :show] root 'products#index' end
This gives us routes under the path /admin/products and /admin/orders which are directed to Admin::ProductsController and Admin::OrdersController.
There are a few things we need to keep in mind for our namespaced controller.
First, Rails expects this controller file to exist in an admin/ folder within app/controllers/ (i.e., at app/controllers/admin/orders_controller.rb).
Second, because the controller is named within the admin namespace, we use the scope resolution operator to define the controller class:
class Admin::OrdersController < ApplicationController # Your Methods Here end
Views that correspond to our Admin::CategoriesController actions are namespaced just like the controller is. When Rails needs these view files, it will look in the app/views/admin/ subfolder.
Additionally, we’ll need to point our path helpers to the right place for namespaced routes. For example, we’ll use paths like new_admin_category_path and form_for [:admin, @category] in our views.
Restricting Access To Admins
There are a couple of approaches here. We’ll start by adding a simple before_action to the Admin::OrdersController:
class Admin::OrdersController < ApplicationController before_action :require_admin # Your Methods Here def require_admin unless current_user.admin? redirect_to root_path end end end
This approach works, but it’s not very flexible. If we add other admin controller and actions (say, for filtering products), we have to redefine this require_admin method in each namespaced controller.
One solution might be to define the require_admin method in our ApplicationController. That’s a step in the right direction, but we would have to remember to add the before_action to each admin controller. These controllers wouldn’t be secure by default.
The solution I prefer is to define a separate class (in app/controllers/) called AdminController that inherits from ApplicationController:
class AdminController < ApplicationController before_action :require_admin def require_admin unless current_user.admin? redirect_to root_path end end end
Now, each namespaced admin controller can inherit directly (and quite appropriately) from AdminController. Since we’ve defined the before_action in AdminController, actions in any sub-classed controller will be restricted to admin users by default.
We no longer need to list the before_action or define a require_admin method in each of our namespaced controllers; they just inherit from AdminController:
class Admin::OrdersController < AdminController # Methods omitted end