Cookbook installation

From Expertiza_Wiki
Jump to navigation Jump to search

CSC/ECE 517 Ruby on Rails Practice Project: Online Shopping System Description

We will be implementing a simple online shopping portal where registered users can view and purchase products. This project will not focus on basic data validations or edge cases. Instead, it will focus on implementing different functionalities and introducing different concepts which will help in your projects. The general strategy is to create classes with the scaffold, and then link them together by implementing associations. The functionalities are limited but the scope of learning will be broader. Submission You may work individually or in teams. After you are finished, please submit your team’s work to Expertiza (AmazeZone assignment). Only one team member needs to submit for a team. You may submit a link to your repository. This will earn you attendance credit for today’s class. The deadline is midnight tonight. Functionalities The simple online shopping system that we will build will have the following features. The user will be able to: Register for the shopping website Create sessions by login and logout Purchase multiple items View their purchase history till date Will not be able to view pages that they aren’t authorized to view


Classes Users Products Credit Cards Transactions Prerequisite Software Compatible versions of Ruby and Rails. Please check this document to ensure compatibility between your Ruby version and Rails version. RubyMine (recommended) or your preferred editor To install RubyMine on Linux, e.g., if you are using Virtual Box, use sudo snap install rubymine --classic rubymine or see this installation guide. Git Goals We will be giving you a basic project with the User class and a simple login/logout system already set up. The goals will be as follows: Clone the basic project structure on your systems Implement associations among the classes, for example: A Credit Card belongs_to a User Transaction belongs_to and User, etc Allow user to purchase multiple items while updating item quantities Show user purchase history through transaction table Learn to connect to and view databases Set up access to pages by roles (authorization) Clone the project Fork the Git repository from here: https://github.ncsu.edu/efg/AmazeZone.com

Clone the forked repository on your system: git clone https://github.ncsu.edu/<your_username>/AmazeZone.com.git

Open the project (AmazeZone.com) in RubyMine. Also, run the following in the terminal: cd AmazeZone.com If your ruby version is different from 2.6.3, kindly follow the steps below: Open Amazezone.com/Gemfile and edit the ruby version number to match your ruby version. By default it is ‘2.6.3’ but if your ruby version is ‘2.6.5’ then edit it. Also, if there is a hidden file in the AmazeZone.com folder named .ruby-version, make the same edit in that file too. If your ruby version is (>= 3.x), along with making the above changes, you will also have to change the rails version (originally 5.2.4.4) in your Gemfile to ‘6.0.3’. Replace it at both the occurrences of rails version on Line 7. After ensuring that the Ruby and Rails version are correct and compatible, run the following commands:

bundle install (^ Having trouble? bundle update will *sometimes* resolve your version conflicts) rails db:migrate

Make sure the code runs on your system by switching to the folder (AmazeZone.com) and running: rails server

Visit www.localhost:3000 to ensure the code is running. Check out the signup and login functionality. Then proceed to implementing the below modules. Implement associations Active record associations are useful to link the models using a real-life like connection between them. For example, a user has_many credit cards or a transaction belongs_to a user. These relationships can be easily specified using associations, and doing so makes it easier to access or create linked models.

So let’s start by creating the structure for our project. We will create the Product, CreditCard and Transaction classes using the scaffold command and then create associations between them.

(Here, we are using scaffold commands to quickly generate all the required files for the associated model. For example, all the different actions and their associated .html.erb files.

Note: If you scaffolded incorrectly, then you can use this command to undo the scaffold)

rails generate scaffold Product product_id:uniq name:string category:string quantity:integer price:numeric

rails g scaffold CreditCard name:string card_number:string expiration_date:date cvv:string

rails g scaffold Transaction transaction_number:string quantity:integer total_cost:numeric


Note: If it asks to overwrite a /stylesheets/scaffolds.scss file, reply ‘Y’ (Yes)

We will run the following migration command to implement the database/schema related changes that we made through the scaffold commands. ( To learn more about migrations: Click here and here ) rails db:migrate

We have created the classes, and now we need to add the foreign keys that we need to make the associations work. For example, if a transaction belongs_to a user, a product and a credit card, it should have the foreign keys to user_id, product_id and credit_card_id in its table. So, we will create a new migration to add columns to our existing tables.

rails g migration add_keys_to_transactions user:references product:references credit_card:references

rails g migration add_key_to_credit_cards user:references

( To undo a migration, see this answer )


Again, we will run the migration for these schema changes to take effect. rails db:migrate


With all the foreign keys structure set, we will now create the associations. Associations are always specified in models files for all classes. The associations we will be implementing are as follows:

user.rb class User < ApplicationRecord has_secure_password has_many :credit_cards has_many :transactions validates :email_address, presence: true, uniqueness: true end

credit_card.rb class CreditCard < ApplicationRecord belongs_to :user end

transaction.rb class Transaction < ApplicationRecord belongs_to :user belongs_to :credit_card belongs_to :product end

Allow users to purchase items For simplicity, we are assuming that a user can purchase only one product at a time. The product quantity can be selected by the user (e.g., whether they want 5 units of that product or 10), and the total price will be determined accordingly. Let’s see what code needs to be added to enable a logged-in user to purchase products: ( In the following code snippets, the bold text refers to the code that you are supposed to add to the file. ) At the bottom of the home page, the user needs to have a link to “View all products” views/home/index.html.erb <% if current_user %>

Logged in as <%= current_user.name %>. 

<%= link_to 'View all products', products_path %>

… rest of the file

On clicking, it will direct us to a list of products that have already been created. Beside each product, there should be a link to “Purchase this product” views/products/index.html.erb … beginning of the file

<thead> </thead> <tbody> <% @products.each do |product| %> <% end %> </tbody>
Product Name Category Quantity Price
<%= product.product_id %> <%= product.name %> <%= product.category %> <%= product.quantity %> <%= product.price %> <%= link_to 'Show', product %> <%= link_to 'Edit', edit_product_path(product) %> <%= link_to 'Destroy', product, method: :delete, data: { confirm: 'Are you sure?' } %> <%= link_to_if product.quantity > 0, 'Purchase this product', new_transaction_path(:product_id => product.id) %>

… rest of the file


Now, since this will direct the link to the new method in transactions_controller.rb, we need to modify that method slightly as follows:

  1. GET /transactions/new

def new @transaction = Transaction.new

  1. We add these lines so that the selected product’s data fields and user’s credit cards are available to the views at the front end

if @product.nil?

 @product = Product.find(params[:product_id])

end @credit_cards = current_user.credit_cards end

transactions/new.html.erb in turn renders transactions/_form.html.erb where we can make the purchase. On this page we will specify the quantity of the product needed. The total price will be determined automatically and will be uneditable. Then we can click on “Complete purchase” to successfully make the purchase. views/transactions/_form.html.erb <%= form_with(model: transaction, local: true) do |form| %>

 <% if transaction.errors.any? %>

<%= pluralize(transaction.errors.count, "error") %> prohibited this transaction from being saved:

    <% transaction.errors.full_messages.each do |message| %>
  • <%= message %>
  • <% end %>
 <% end %>

You are purchasing the product: <%= @product.name %>

   <% qty = @product.quantity %>
   <%= form.label :quantity %>
   <%= form.number_field :quantity, :id => 'quantity', min: 1, max: qty, onchange: "calculateTotalPrice();" %>
   <%= form.label :total_cost %>
   <%= form.text_field :total_cost, :id => 'total_cost', :readonly => true %>
   <%= form.hidden_field :product_id, value: @product.id %>
   <%= form.label :credit_card_id, "Credit card number" %>
   <%= form.collection_select :credit_card_id, CreditCard.where(user_id: current_user.id),:id,:card_number, include_blank: false %>
   <%= form.submit "Complete purchase" %>

<% end %>

<script type="text/javascript">

  function calculateTotalPrice() {
      var price = "<%= @product.price %>";
      document.getElementById('total_cost').value = document.getElementById('quantity').value * price;
  }

</script>


Now on submit, this will trigger the create method in the transactions_controller.rb. The transaction_number (an attribute of transaction) will be generated automatically here. We only need to modify the methods create and transaction_params. transactions_controller.rb def create

 @transaction = Transaction.new(transaction_params)
 # We create the transaction number using a random string of length 10
 @transaction.transaction_number = Array.new(10){[*"A".."Z", *"0".."9"].sample}.join
 @product = Product.find(params[:transaction][:product_id])
 # We link the transaction to the user and their credit card
 @transaction.user = current_user
 # We update the original product quantity
 @product.quantity = @product.quantity - @transaction.quantity
 @product.save
respond_to do |format|
  if @transaction.save
    format.html { redirect_to @transaction, notice: "Transaction was successfully created." }
    format.json { render :show, status: :created, location: @transaction }
  else
    format.html { render :new, status: :unprocessable_entity }
    format.json { render json: @transaction.errors, status: :unprocessable_entity }
  end
end

end

  1. Only allow a list of trusted parameters through.

def transaction_params

 params.require(:transaction).permit(:quantity, :total_cost, :product_id, :credit_card_id)

end

Finally, we are done with the flow and now the user can purchase multiple quantities of one product. Now, this is a very bare-bones implementation, and no products are available for purchase unless someone adds them. Further, there is no admin account (that would be a nice extension to this app :-); any user can add products to the store. So, you will need to add some products and credit cards before making a purchase. You can create a quick link to create a new credit card on the home page itself. Just add the following line before “Logout”: <%= link_to 'Add credit card', new_credit_card_path %>

Also, the credit card belongs_to a user. So while creating a credit card, we need to associate the current logged-in user with it. credit_cards_controller.rb

  1. POST /credit_cards or /credit_cards.json

def create

@credit_card = CreditCard.new(credit_card_params)
@credit_card.user_id = current_user.id
respond_to do |format|

... rest of the file

View purchase history For a given registered user, we want them to be able to view a list of purchases they made. This list is similar to the list that appears on the index.html.erb file under the views/transactions folder. The only difference will be that instead of displaying ALL transactions, only the ones with the same user_id as the logged in user will be displayed. This page is rendered by the index function in transactions_controller.rb. So these are the files we will be modifying. Create a link at the bottom of the home page (views/home/index.html.erb) for a logged in user to view their purchase history. <%= link_to 'View purchase history', transactions_path %>

The transactions path will route to the index method in transactions_controller.rb. So let’s modify that now.

  1. GET /transactions or /transactions.json

def index if current_user.id != nil @transactions = Transaction.where(user_id: current_user.id) else @transactions = Transaction.all end end

That is all we need to view the purchase history. Try accessing the home page to confirm this.

Now, just to refine this further, when viewing purchase history, we don’t want users to be able to create or edit transactions manually through the default “New transaction” or “Edit” links. So let’s delete those links: In views/transactions/index.html.erb, remove: <%= link_to 'New Transaction', new_transaction_path %>

<%= link_to 'Edit', edit_transaction_path(transaction) %>

View databases If RubyMine is installed in your environment, you can view the tables and data in the DB interface. Follow these steps: Database => + => Data Source => SQLite


In File field: Open path to your_project_name/db/development.sqlite3 => Test Connection => Will see a green tick => Apply => OK


Now you will find all the tables and data under schemas on the right-hand side

Implement authorization The content of this section has been taken from the Authorization module in this blog. It has been modified in a minor way for our implementation.

We want most of the routes (like purchase history, view products, etc.) to be accessible only by registered users. But we have not defined this action as of yet. Let’s navigate to our Applications Controller, add methods called authorized and logged_in?, and a filter before_action, setting it to authorized.

class ApplicationController < ActionController::Base

helper_method :current_user
before_action :authorized
helper_method :logged_in?
def current_user
  if session[:user_id]
    @current_user ||= User.find(session[:user_id])
  else
    @current_user = nil
  end
end
def logged_in?
  !current_user.nil?
end
def authorized
  redirect_to root_path unless logged_in?
end

end

The macro before_action requires the authorized method to run (by default) before any other action is taken. In this case, unless a user is logged in, the user will always be redirected to the home page. However, we want the methods that allow a user to log in to be available; i.e., we want to skip authorization for these methods. First, let’s navigate back to the Users Controller and add the following macro skip_before_action:

class UsersController < ApplicationController

skip_before_action :authorized, only: [:new, :create]
before_action :set_user, only: %i[ show edit update destroy ]

… rest of the file

This macro allows the two methods to skip the authorized method requirement. We will also call the macro in the Sessions Controller so that new sessions can be created by skipping authorization.

class SessionsController < ApplicationController skip_before_action :authorized, only: [:new, :create] ...rest of the file

Lastly, we also want to be able to see the home page i.e. the index.html.erb file under the home folder (which contains the signup/login options) if the user is not logged in. class HomeController < ApplicationController skip_before_action :authorized, only: [:index] def index

	end

end

Now go to www.localhost:3000 and try to view certain pages before and after logging in to make sure the implementation is successful.