Showing posts with label Note. Show all posts
Showing posts with label Note. Show all posts

Tuesday, March 25, 2014

Testing with RSpec

Part 1: Installation

Add rspec-rails to both the :development and :test groups in the Gemfile:

group :development, :test do
  gem 'rspec-rails', '~> 3.0.0.beta'
end

Download and install by running:

$ bundle install

Initialize the spec/ directory (where specs will reside) with:

$ rails generate rspec:install

This adds spec/spec_helper.rb and .rspec files that are used for configuration. See those files for more information.

To run your specs, use the rspec command:

$ bundle exec rspec

# Run only model specs
$ bundle exec rspec spec/models

# Run only specs for AccountsController
$ bundle exec rspec spec/controllers/accounts_controller_spec.rb

Specs can also be run via rake spec, though this command may be slower to start than the rspec command.

Part 2: RSpec

Create a new file at /spec/user_spec.rb and copy these into the file.

require 'spec_helper'

describe User do
  # create
  before :each do
    @user = User.create(:username => "kirby510", :email => "kirby.kl05@yahoo.com", :password => "kirby510", :name => "Lee Kian Theng", :nickname => "Student Kirby")
    @user.add_role :admin
  end

  # correct condition
  it "#name" do
    @user.name.should == "Lee Kian Theng"
  end

  # false condition
  it "name is should not null" do
    @user.name.should == nil
    # @user.name.should_not == nil
  end

  # check role
  it "#role" do
    # puts @user.roles.first.name
    @user.roles.first.name.should == "admin"
  end

  # update
  it "can update" do
    @user.update_attributes(:username => "kirby510", :email => "kirby.kl05@yahoo.com", :password => "kirby510", :name => "Lee Kian Leong", :nickname => "Student Kirby 2012")
    @user.nickname.should == "Student Kirby 2012"
  end

  # destroy
  it "can delete" do
    @user.destroy
  end
end

$ rspec spec/user_spec.rb

rspec/rspec-rails · GitHub
Return to Internship Note (LoanStreet)
Previous Episode: Create User Profile Rails App
Next Episode: Capybara with RSpec & Rails 3

Sunday, March 23, 2014

Create User Profile Rails App

Part 1: Setting Up the Rails App

$ rails _3.2.13_ new user_profile -d mysql

$ cd user_profile

Part 2: Devise Users

In /Gemfile,

gem 'devise'

$ bundle install

$ rails generate devise:install

In /config/environment/development.rb,

config.action_mailer.default_url_options = { :host => 'localhost:3000' }

In /config/routes.rb,

root :to => "home#index"

In /app/views/layours/application.html.erb,

<body>
  <p class="notice"><%= notice %></p>
  <p class="alert"><%= alert %></p>

<%= yield %>

In /config/application.rb,

# Enable the asset pipeline
config.assets.enabled = true
config.assets.initialize_on_precompile = false

$ rm public/index.html

Part 3: User Model

$ rails generate devise user

$ rake db:migrate

Part 4: Home Index Page

$ rails g controller home

In /app/controllers/home_controller.rb,

class HomeController < ApplicationController
  def index
    
  end
end

Create your own index page at /app/views/home/index.html.erb.

Part 5: Sign In, Sign Out and Register Links

Create the new file on /app/views/common/_session.html.erb and include the code below into the file:

<%- if user_signed_in? %>
  <p>Hello <%= current_user.email %><br />
  <%= link_to 'Sign out', destroy_user_session_path, :method => :delete %></p>
<%- else %>
  <p>
    <%= link_to 'Register', new_user_registration_path %> |
    <%= link_to 'Sign in', new_user_session_path %>
  </p>
<%- end %>

In /app/views/home/index.html.erb,

  <div id="secondaryContent">
    <%= render 'common/session' %>
  </div>

$ rails g devise:views

Part 6: Authorization

In /app/controllers/application_controller.rb,

class ApplicationController < ActionController::Base
  protect_from_forgery

  protected
  def authorize_user!
    if user_signed_in? 
      return
    else
      flash[:notice] = 'You need to sign in first'
      redirect_to new_user_session_path
    end
  end
end

In /app/controllers/home_controller.rb,

class HomeController < ApplicationController
  before_filter :authorize_user!
  
  def index
    
  end
end

Part 7: Username

$ rails g migration add_username_to_users username:string

$ rake db:migrate

Add the code below into the 3 files as belows:
  • /app/views/devise/registrations/edit.html.erb
  • /app/views/devise/registrations/new.html.erb
  • /app/views/devise/sessions/new.html.erb

  <div><%= f.label :username %><br />
  <%= f.text_field :username %></div>

In /config/initializers/devise.rb,

  # ==> Configuration for any authentication mechanism
  # Configure which keys are used when authenticating a user. The default is
  # just :email. You can configure it to use [:username, :subdomain], so for
  # authenticating a user, both parameters are required. Remember that those
  # parameters are used only when authenticating and not when retrieving from
  # session. If you need permissions, you should implement that in a before filter.
  # You can also supply a hash where the value is a boolean determining whether
  # or not authentication should be aborted when the value is not present.
  config.authentication_keys = [ :username ]

In /app/models/user.rb,

class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

  # Setup accessible (or protected) attributes for your model

  attr_accessible :username, :email, :password, :password_confirmation, :remember_me
end

Part 8: Name & Nickname

$ rails g migration add_name_to_users name:string

$ rails g migration add_nickname_to_users nickname:string

$ rake db:migrate

Add the code below into the 2 files as belows:
  • /app/views/devise/registrations/edit.html.erb
  • /app/views/devise/registrations/new.html.erb

  <div><%= f.label :name %><br />
  <%= f.text_field :name %></div>
  
  <div><%= f.label :nickname %><br />
  <%= f.text_field :nickname %></div>

In /app/models/user.rb,

class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

  # Setup accessible (or protected) attributes for your model
  attr_accessible :username, :email, :password, :password_confirmation, :name, :nickname, :remember_me
end

In /app/common/_session.html.erb,

<%- if user_signed_in? %>
  <p>Welcome, <%= current_user.nickname %>!<br />
  <%= link_to 'Sign out', destroy_user_session_path, :method => :delete %></p>
<%- else %>
  <p>
    <%= link_to 'Register', new_user_registration_path %> |
    <%= link_to 'Sign in', new_user_session_path %>
  </p>
<%- end %>

Part 9: Carrierwave File Uploads

In /Gemfile,

gem 'carrierwave'

$ bundle install

$ rails g uploader photo

$ rails g migration add_photo_to_users photo:string

$ rake db:migrate

In /app/models/user.rb,

class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
 
  # Setup accessible (or protected) attributes for your model
  attr_accessible :username, :email, :password, :password_confirmation, :name, :nickname, :photo, :remote_photo_url, :remember_me

  mount_uploader :photo, PhotoUploader
end

In /app/uploaders/photo_uploader.rb,

class PhotoUploader < CarrierWave::Uploader::Base
  def extension_white_list
    %w(jpg jpeg gif png)
  end
end

Add the code below into the 2 files as belows:
  • /app/views/devise/registrations/edit.html.erb
  • /app/views/devise/registrations/new.html.erb

  <div><%= f.label :photo %><br />
  <%= f.file_field :photo %><br />
  <%= f.label :remote_photo_url, "or photo URL" %>
  <%= f.text_field :remote_photo_url %> </div>

Part 10: User Authorization with Rolify and Cancan

In /Gemfile,

gem 'rolify'
gem 'cancan'

$ bundle install

$ rails g rolify Role User

$ rake db:migrate

In /app/models/user.rb,

class User < ActiveRecord::Base
  rolify
  
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
  
  # Setup accessible (or protected) attributes for your model
  attr_accessible :username, :email, :password, :password_confirmation, :name, :nickname, :photo, :remote_photo_url, :remember_me
  
  attr_accessor :current_role

  mount_uploader :photo, PhotoUploader
end

In /app/models/role.rb,

class Role < ActiveRecord::Base
  has_and_belongs_to_many :users, :join_table => :users_roles
  belongs_to :resource, :polymorphic => true
  
  scopify
  
  attr_accessible :name
end

$ rails g cancan:ability

In /app/models/ability.rb,

class Ability
  include CanCan::Ability

  def initialize(user)
    
    user ||= User.new
    
    if user.role? :admin
      can :manage, :all
    else
      cannot :create, User
      can :read, User
      can :update, User
      cannot :destroy, User
      cannot :index, User
    end
  end
end

Part 11: User with Roles

Add the code below into the 2 files as belows:
  • /app/views/devise/registrations/edit.html.erb
  • /app/views/devise/registrations/new.html.erb

  <div><%= f.label :role %><br />
  <% for role in Role.find(:all) %>
    <%= check_box_tag "user[role_ids][]", role.id, @user.roles.include?(role) %>
    <%= role.name %><br/>
  <% end %></div>

In /app/models/user.rb,

class User < ActiveRecord::Base
  rolify

  has_and_belongs_to_many :roles, :join_table => :users_roles
  
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
  
  # Setup accessible (or protected) attributes for your model
  attr_accessible :username, :email, :password, :password_confirmation, :name, :nickname, :photo, :remote_photo_url, :role_ids, :remember_me
  
  attr_accessor :current_role

  mount_uploader :photo, PhotoUploader

  def role?(role_sym)
    roles.any? { |r| r.name.underscore.to_sym == role_sym }
  end
end

Part 12: User Management via CRUD

In order to manage our users through a CRUD interface we have to create a users controller and its associated views. First use rails to generate the users controller.

$ rails g controller users

In our users controller we are going to define the seven basic actions to maintain the RESTful protocol, so open up app/controllers/users_controller.rb and make it look like the following:

class UsersController < ApplicationController
  before_filter :authorize_user!

  load_and_authorize_resource

  def index
    @users = User.all
  end

  def show
    @user = User.find(params[:id])
  end

  def new
    @user = User.new
  end

  def edit
    @user = User.find(params[:id])
  end

  def create
    @user = User.new(params[:user])

    if @user.save
      redirect_to @user, :flash => { :success => 'User was successfully created.' }
    else
      render :action => 'new'
    end
  end

  def update
    @user = User.find(params[:id])

    if @user.update_attributes(params[:user])
      sign_in(@user, :bypass => true) if @user == current_user
      redirect_to @user, :flash => { :success => 'User was successfully updated.' }
    else
      render :action => 'edit'
    end
  end

  def destroy
    @user = User.find(params[:id])
    @user.destroy
    redirect_to users_path, :flash => { :success => 'User was successfully deleted.' }
  end
end

Now we need to create the associated views and a form partial.

$ cd app/views/users && touch _form.html.erb edit.html.erb index.html.erb new.html.erb show.html.erb

Make each of these files look as follows respectively:

In /app/views/users/_form.html.erb,

<%= form_for @user do |f| %>
  <% if @user.errors.any? %>
    <div class="error_explanation">
      <h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2>

      <ul>
      <% @user.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :username %>
    <div class="controls">
      <%= f.text_field :username %>
    </div>
  </div>

  <div class="field">
    <%= f.label :email %>
    <div class="controls">
      <%= f.email_field :email %>
    </div>
  </div>

  <div class="field">
    <%= f.label :password, "Password" %>
    <div class="controls">
      <%= f.password_field :password %>
    </div>
  </div>

  <div class="field">
    <%= f.label :password_confirmation %>
    <div class="controls">
      <%= f.password_field :password_confirmation %>
    </div>
  </div>

  <div class="field">
    <%= f.label :name %>
    <div class="controls">
      <%= f.text_field :name %>
    </div>
  </div>

  <div class="field">
    <%= f.label :nickname %>
    <div class="controls">
      <%= f.text_field :nickname %>
    </div>
  </div>

  <div class="field">
    <%= f.label :photo %>
    <div class="controls">
      <%= f.file_field :photo %>
    </div>
    <%= f.label :remote_photo_url, "or photo URL" %>
    <%= f.text_field :remote_photo_url %>
  </div>

<% if can? :create, @user %>
  <div class="field">
    <%= f.label :role %>
    <div class="controls">
      <% for role in Role.find(:all) %>
        <%= check_box_tag "user[role_ids][]", role.id, @user.roles.include?(role) %>
        <%= role.name %><br/>
      <% end %>
    </div>
  </div>
<% end %>

  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

In /app/views/users/edit.html.erb,

<h1>Edit User</h1>

<%= render 'form' %>

<%= link_to 'Show User', @user %>
<% if can? :index, @user %>
 | <%= link_to 'All Users', users_path %>
<% end %>

In /app/views/users/index.html.erb,

<h1>Users</h1>

<table>
  <thead>
    <tr>
      <th>Username</th>
      <th>Email</th>
      <th>Name</th>
      <th>Nickname</th>
      <th>Role</th>
      <th>Actions</th>
    </tr>
  </thead>
  <tbody>
    <% @users.each do |user| %>
      <tr>
        <td><%= user.username %></td>
        <td><%= user.email %></td>
        <td><%= user.name %></td>
        <td><%= user.nickname %></td>
        <td><%= user.roles.map(&:name).join(", ") %></td>
        <td>
          <%= link_to 'Show', user %>
          <%= link_to 'Edit', edit_user_path(user) %>
          <%= link_to 'Delete', user_path(user), :method => 'delete', :confirm => 'Are you sure?' %>
        </td>
      </tr>
    <% end %>
  </tbody>
</table>

<%= link_to 'New User', new_user_path %>

In /app/views/users/new.html.erb,

<h1>New User</h1>

<%= render 'form' %>

<%= link_to 'All Users', users_path %>

In /app/views/users/show.html.erb,

<h1>User</h1>

<p><strong>Username:</strong> <%= @user.username %></p>

<p><strong>Email:</strong> <%= @user.email %></p>

<p><strong>Name:</strong> <%= @user.name %></p>

<p><strong>Nickname:</strong> <%= @user.nickname %></p>

<p><strong>Photo:<strong><br />
  <%= image_tag @user.photo_url %>
</p>

<p><strong>Role:</strong> <%= @user.roles.map(&:name).join(", ") %></p>

<% if can? :index, @user %>
<%= link_to 'All Users', users_path %> |
<% end %>
<%= link_to 'Edit User', edit_user_path(@user) %>
<% if can? :destroy, @user %>
 | <%= link_to 'Delete User', user_path(@user), :method => 'delete', :confirm => 'Are you sure?' %>
<% end %>

Next we need to set up some routes so that the routes for devise and our users controller don't have any conflicts. So, open up config/routes.rb and make it look as follows:

Userapp::Application.routes.draw do
  resources :users

  devise_for :users, :skip => [:registrations, :sessions]

  as :user do
    get "/login" => "devise/sessions#new", :as => :new_user_session
    post "/login" => "devise/sessions#create", :as => :user_session
    delete "/logout" => "devise/sessions#destroy", :as => :destroy_user_session
  end

  root :to => 'users#index'
end

This routes.rb file first defines the RESTful routes for our users controller then tells devise to generate routes for users but to skip registrations and sessions. We skip the sessions routes because we will have to define them in a custom fashion so that they will not conflict with our RESTful users routes.

In /app/views/common/_session.html.erb,

<%- if user_signed_in? %>
  <p>Welcome, <%= current_user.nickname %>!<br />
  <%= link_to 'Sign out', destroy_user_session_path, :method => :delete %></p>
<%- else %>
  <p>
    <%= link_to 'Sign in', new_user_session_path %>
  </p>
<%- end %>

In /app/controllers/application_controller.rb,

class ApplicationController < ActionController::Base
  protect_from_forgery
  
  def after_sign_in_path_for(resource)
    user_path(current_user) #your path
  end

  protected
  def authorize_user!
    if user_signed_in?
      return
    else
      flash[:notice] = 'You need to sign in first'
      redirect_to new_user_session_path
    end
  end
end

In /app/views/devise/shared/_links.erb,

<%- if controller_name != 'sessions' %>
  <%#= link_to "Sign in", new_session_path(resource_name) %><!--<br />-->
<% end -%>

<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
  <%#= link_to "Sign up", new_registration_path(resource_name) %><!--<br />-->
<% end -%>

Now lets launch the application.

$ rails s

Navigate to http://localhost:3000/users/new in a web browser. Here we can create the first user of our application. Go ahead and create a user with an email address and password of your choice. Now go to http://localhost:3000 and click on the Log In link we made earlier. This brings us to the sign in page generated by devise but uses our custom /login route we specified in config/routes.rb. Authenticate with the account you just created. You should be greeted by the home page of the application with a success flash message and now a Log Out link. Congratulations! You can now create users and authenticate with devise. You can view a list of all the users by going to http://localhost:3000/users and from there you can edit or delete them.

Return to Internship Note (LoanStreet)
Previous Episode: User Authorization with Rolify and Cancan
Next Episode: Testing with RSpec

Wednesday, March 12, 2014

User Authorization with Rolify and Cancan

Step 1: Installation

In Rails 3, add this to your Gemfile and run the +bundle+ command.

In /Gemfile,

gem 'rolify'
gem 'cancan'

$ bundle install

Step 2: Generate Role Model

First, create your Role model and migration file using this generator:

$ rails g rolify Role User

Role and User classes are the default. You can specify any Role class name you want. This is completly a new file so any name can do the job. For the User class name, you would probably use the one provided by your authentication solution. rolify just adds some class methods in an existing User class.

Step 3: Run the migration (only required when using ActiveRecord)

Let's migrate!

$ rake db:migrate

Step 4: Configure your resource models

In the resource models you want to apply roles on, just add resourcify method. For example, on this ActiveRecord class:

In /app/models/post.rb,

class Post < ActiveRecord::Base
  resourcify

  //the other association...
end

Step 5: Adding Associations to Role and User Models

In /app/models/user.rb,

class User < ActiveRecord::Base
  rolify

  devise :database_authenticatable, :registerable,
         :rememberable, :trackable, :validatable

  attr_accessible :email, :password, :password_confirmation, :remember_me

  attr_accessor :current_role
end

Step 6: Define Abilities

User permissions are defined in an Ability class. CanCan 1.5 includes a Rails 3 generator for creating this class.

$ rails g cancan:ability

Step 7: Define Roles and Abilities using Cancan

In /app/models/ability.rb,

class Ability
  include CanCan::Ability

  def initialize(user)

    user ||= User.new # This is used for not logged user if you have a need for it

    if user.has_role? :blog_owner
      can :manage, :all
    else
      if user.has_role? :writer
        can :create, Post
        can :read, Post
        can :update, Post
        can :delete_tags, Post
        can :delete_labels, Post
        cannot :destroy, Post
      
        can :create, Comment
        can :destroy, Comment

        can :manage, Tag
      
        cannot :manage, Author
      end

      can :read, :all

      can :add_favourite, Post
      can :delete_favourite, Post
    end
  end
end

In /app/models/role.rb,

class Role < ActiveRecord::Base
  attr_accessible :name
end

Step 8: Populating the Database with seeds.rb

In /db/seeds.rb,

Author.create([{ name: 'Joseph' }, { name: 'Dhendy' }, { name: 'Kirby' }])

Role.create([{ name: 'blog_owner' }, { name: 'writer' }])

user = User.create(email: 'owner@kirby.com', password: '1234567890')
user.add_role :blog_owner

user = User.create(email: 'writer@kirby.com', password: '1234567890')
user.add_role :writer

$ rake db:seed

EppO/rolify · GitHub
ryanb/cancan · GitHub
Return to Internship Note (LoanStreet)
Previous Episode: CarrierWave File Uploads
Next Episode: Create User Profile Rails App

Adding Delete Comment Feature into Blog Post

In /app/comments/_comment.html.erb,

<%= div_for comment do %>
    <p>
        <strong>
            Posted <%= time_ago_in_words(comment.created_at) %> ago
        </strong>
        <br/>
        <%= comment.body %><br/>
        <%= link_to 'Delete Comment', comment, method: :delete %>
    </p>
<% end %>

In /app/controllers/comments_controller.rb,

class CommentsController < ApplicationController
  def create
    @post = Post.find(params[:post_id])
    @comment = @post.comments.create!(params[:comment])
    redirect_to @post
  end

  def destroy
    @post = Post.find(params[:id])
    @comment = Comment.find(params[:id])
    if @comment.destroy
      redirect_to @post
    end
  end
end

In config/routes.rb,

QuickBlog::Application.routes.draw do
  resources :comments
  
  resources :posts do
    resources :comments, :only => [:create, :destroy]
  end
end

Return to Internship Note (LoanStreet)
Previous Episode: Create Simple Rails App
Next Episode: Update Blog with Author

Tuesday, March 11, 2014

Carrierwave File Uploads

Step 1: Installing CarrierWave

The first step is to add the CarrierWave gem to our application. The gem is installed in the usual way by adding a reference to it in the application’s Gemfile.

In /Gemfile,

gem 'carrierwave'

We’ll need to run bundle to make sure that the gem is installed on our system. Once it has installed the first thing we’ll need to do is generate an uploader class. CarrierWave provides a generator called uploader to do this to which we pass the name we want to give our uploader, in this case image.

$ rails g uploader image
      create  app/uploaders/image_uploader.rb

The generator creates a new directory called uploaders under the app directory and in it a file called image_uploader.rb. In this file are a number of comments explaining how to customize the uploader. For example there is code to change the upload location, perform processing on the image after uploading and to restrict the type of files that can be uploaded. We’ll leave the defaults as they are for now and come back and make some customizations later.

Next we’ll need to add the uploader to the Post model. We’ll need a column in the posts table to store it in so we’ll generate a migration to do add it.

$ rails g migration add_image_to_posts image:string

Note that the new column is a string and has the same name as we gave the uploader when we generated it. We’ll need to run $ rake db:migrate to update the database.

Next we’ll add the uploader to the Post model which is done by calling the mount_uploader method, passing in the column name that we generated above and the class for the uploader. We also need to add the image column to the list in the attr_accessible call so that it can be accessed from the controllers.

In /app/models/post.rb,

class Post < ActiveRecord::Base
  attr_accessible :body, :title, :author_id, :image

  belongs_to :author

  has_many :comments

  has_many :favourites

  has_many :post_tags
  has_many :tags, through: :post_tags

  has_and_belongs_to_many :labels

  mount_uploader :image, ImageUploader

  validates_presence_of :body, :title
end

With this in place we can modify the form for adding or modifying a post so that it has a file upload field.

In /app/views/posts/_form.html.erb,

<%= form_for(@post) do |f| %>
  <% if @post.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2>

      <ul>
      <% @post.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :title %><br />
    <%= f.text_field :title %>
  </div>
  <div class="field">
    <%= f.label :body %><br />
    <%= f.text_area :body %>
  </div>
  <div class="field">
    <%= f.label :author %><br />
    <%= collection_select(:post, :author_id, Author.all, :id, :name) %>
  </div>
  <div class="field">
    <%= f.label :image %><br />
    <%= f.file_field :image %> 
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

Step 2: Securing Uploads

Certain file might be dangerous if uploaded to the wrong location, such as php files or other script files. CarrierWave allows you to specify a white-list of allowed extensions.

If you're mounting the uploader, uploading a file with the wrong extension will make the record invalid instead. Otherwise, an error is raised.

class MyUploader < CarrierWave::Uploader::Base
  def extension_white_list
    %w(jpg jpeg gif png)
  end
end

Step 3: Viewing The Uploaded Images

In /app/views/posts/show.html.erb,

  <h2>Images</h2>
  <div id="images">
    <%= image_tag @post.image_url %>
  </div>

Step 4: Uploading Images via a URL

Another feature that CarrierWave provides is the ability to add photographs via a URL instead of uploading them directly. We can do this by adding another field to the upload form called remote_image_url. The name is important, when CarrierWave sees a field with that name it knows that it has to fetch the image from a URL.

In /app/views/posts/_form.html.erb,

<%= form_for(@post) do |f| %>
  <% if @post.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2>

      <ul>
      <% @post.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :title %><br />
    <%= f.text_field :title %>
  </div>
  <div class="field">
    <%= f.label :body %><br />
    <%= f.text_area :body %>
  </div>
  <div class="field">
    <%= f.label :author %><br />
    <%= collection_select(:post, :author_id, Author.all, :id, :name) %>
  </div>
  <div class="field">
    <%= f.label :image %><br />
    <%= f.file_field :image %><br />
    <%= f.label :remote_image_url, "or image URL" %> 
    <%= f.text_field :remote_image_url %> 
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

As we’ve added a field to the post form we’ll need to add it to the list of attr_accessible fields in the Post model.

In /app/models/post.rb,

class Post < ActiveRecord::Base
  attr_accessible :body, :title, :author_id, :image, :remote_image_url

  belongs_to :author

  has_many :comments

  has_many :favourites

  has_many :post_tags
  has_many :tags, through: :post_tags

  has_and_belongs_to_many :labels

  mount_uploader :image, ImageUploader

  validates_presence_of :body, :title
end

Now in the form we’ll have a remote_image_url field and we can enter a URL in that field to add an image to the post without uploading it manually.

carrierwaveuploader/carrierwave · GitHub
Return to Internship Note (LoanStreet)
Previous Episode: Authentication with Devise
Next Episode: User Authorization with Rolify and Cancan

Sunday, March 9, 2014

Update Blog with Tags

Step 1: Set up a Model and Join Table

$ rails g model Tag name

$ rails g model Post_Tag post_id:integer tag_id:integer

$ rake db:migrate

Step 2: Add many-to-many relationship into Models

In app/models/post.rb,

class Post < ActiveRecord::Base
  attr_accessible :body, :title, :author_id

  belongs_to :author

  has_many :comments

  has_many :favourites

  has_many :post_tags
  has_many :tags, through: :post_tags

  validates_presence_of :body, :title
end

In app/models/post_tag.rb,

class PostTag < ActiveRecord::Base
  belongs_to :post

  belongs_to :tag
end

In app/models/tag.rb,

class Tag < ActiveRecord::Base
  attr_accessible :name

  has_many :post_tags
  has_many :posts, through: :post_tags
  
  validates_presence_of :name
end

Step 3: Adding new methods into Posts Controller

In app/controllers/posts_controller.rb,

class PostsController < ApplicationController
  before_filter :authenticate, :except => [ :index, :show ]

  # ... all the action go in here

  def delete_tags
    tag = Tag.find(params[:tag_id])
    post = Post.find(params[:post_id])
    if tag.destroy
      redirect_to post_path(post) #redirect_to action: :index
    end
  end

  # the authentication action
end

Step 4: Adding Create Tag methods into Tags Controller

In app/controllers/tags_controller.rb,

class TagsController < ApplicationController
  def create
   @post = Post.find(params[:post_id])
    @tag = @post.tags.create(params[:tag])
    redirect_to @post
  end
end

Step 5: Connecting URLs to Code by Editing Routes Config

In config/routes.rb,

QuickBlog::Application.routes.draw do
  resources :authors

  resources :comments

  get '/posts/add_favourite', to: 'posts#add_favourite'
  get '/posts/delete_favourite', to: 'posts#delete_favourite'
  get '/posts/delete_tags', to: 'posts#delete_tags'
  resources :posts do
    resources :comments, :only => [:create, :destroy]
  end
end

Step 6: Edit the Views in Posts

In app/views/posts/show.html.erb,

  <p id="notice"><%= notice %></p>

  <%= render :partial => @post %>

  by <%= @post.author.name %>
  <br/>

  <%= link_to 'Edit', edit_post_path(@post) %> |
  <%= link_to 'Back', posts_path %>

  <p><%= link_to 'Add Favourite', "/posts/add_favourite?post_id=#{@post.id}" %> (<%= @post.favourites.size %>) | <%= link_to 'Delete Favourite', "/posts/delete_favourite?post_id=#{@post.id}" %></p>

  <h2>Tags</h2>
  <div id="tags">
    <%#= @post.tags.map(&:name).join(", ") %>
    <% @post.tags.each do |tag| %>
      <%= tag.name %> - <%= tag.created_at.strftime("%B %e, %Y %l:%M %p") %> [<%= link_to "delete", "/posts/delete_tags?tag_id=#{tag.id}&post_id=#{@post.id}" %>]<br/> <!--"delete #{tag.name}" -->
    <% end %>

  </div>

<%= form_for [@post, Tag.new] do |f| %>
    <p>
        <%= f.label :name, "New tag" %><br/>
        <%= f.text_field :name %>
    </p>
    <p><%= f.submit "Add tag" %></p>
<% end %>

  <!-- Comments -->

Return to Internship Note (LoanStreet)
Previous Episode: Update Blog with Favourites (Part 2)
Next Episode: Authentication with Devise

Update Blog with Favourites (Part 2)

Step 6: Adding Delete Favourite methods into Posts Controller

In app/controllers/posts_controller.rb,

class PostsController < ApplicationController
  before_filter :authenticate, :except => [ :index, :show ]

  # ... all the action go in here

  def add_favourite
    post = Post.find(params[:post_id])
    post.favourites.build(:post_id => params[:post_id])
    if post.save
      redirect_to post_path(post) #redirect_to action: :index
    end
  end

  def delete_favourite
    post = Post.find(params[:post_id])
    if post.favourites.size > 0
      post.favourites.first.destroy
      redirect_to post_path(post) #redirect_to action: :index
    else
      redirect_to post_path(post)
    end
  end

  # the authentication action
end

Step 7: Connecting URLs to Code by Editing Routes Config

In config/routes.rb,

QuickBlog::Application.routes.draw do
  resources :authors

  resources :comments

  get '/posts/add_favourite', to: 'posts#add_favourite'
  get '/posts/delete_favourite', to: 'posts#delete_favourite'
  resources :posts do
    resources :comments, :only => [:create, :destroy]
  end
end

Step 8: Edit the Views in Posts

In app/views/posts/show.html.erb,

  <p id="notice"><%= notice %></p>

  <%= render :partial => @post %>

  by <%= @post.author.name %>
  <br/>

  <%= link_to 'Edit', edit_post_path(@post) %> |
  <%= link_to 'Back', posts_path %>

  <p><%= link_to 'Add Favourite', "/posts/add_favourite?post_id=#{@post.id}" %> (<%= @post.favourites.size %>) | <%= link_to 'Delete Favourite', "/posts/delete_favourite?post_id=#{@post.id}" %></p>

Return to Internship Note (LoanStreet)
Previous Episode: Update Blog with Favourites (Part 1)
Next Episode: Update Blog with Tags

Update Blog with Favourites (Part 1)

Step 1: Set Up a Model

$ rails g model Favourite post_id:integer

$ rake db:migrate

Step 2: Add one-to-many relationship into Models

In app/models/post.rb,

class Post < ActiveRecord::Base
  attr_accessible :body, :title, :author_id

  belongs_to :author

  has_many :comments

  has_many :favourites

  validates_presence_of :body, :title
end

In app/models/favourite.rb,

class Favourite < ActiveRecord::Base
  attr_accessible :post_id

  belongs_to :post
end

Step 3: Adding new methods into Posts Controller

In app/controllers/posts_controller.rb,

class PostsController < ApplicationController
  before_filter :authenticate, :except => [ :index, :show ]

  # ... all the action go in here

  def add_favourite
    post = Post.find(params[:post_id])
    post.favourites.build(:post_id => params[:post_id])
    if post.save
      redirect_to post_path(post) #redirect_to action: :index
    end
  end

  # the authentication action
end

Step 4: Connecting URLs to Code by Editing Routes Config

In config/routes.rb,

QuickBlog::Application.routes.draw do
  resources :authors

  resources :comments

  get '/posts/add_favourite', to: 'posts#add_favourite'
  resources :posts do
    resources :comments, :only => [:create, :destroy]
  end
end

Step 5: Edit the Views in Posts

In app/views/posts/show.html.erb,

  <p id="notice"><%= notice %></p>

  <%= render :partial => @post %>

  by <%= @post.author.name %>
  <br/>

  <%= link_to 'Edit', edit_post_path(@post) %> |
  <%= link_to 'Back', posts_path %>

  <p><%= link_to 'Add Favourite', "/posts/add_favourite?post_id=#{@post.id}" %> (<%= @post.favourites.size %>)</p>

Return to Internship Note (LoanStreet)
Previous Episode: Update Blog with Author
Next Episode: Update Blog with Favourites (Part 2)

Update Blog with Author

Step 1: Set Up a Scaffold

$ rails g scaffold authors name

$ rake db:migrate

Step 2: Add one-to-one relationship into Models

In app/models/post.rb,

class Post < ActiveRecord::Base
  attr_accessible :body, :title, :author_id
  
  belongs_to :author
  
  has_many :comments
  
  validates_presence_of :body, :title
end

In app/models/author.rb,

class Author < ActiveRecord::Base
  attr_accessible :name
  
  has_one :post
  
  validates_presence_of :name
end

Step 3: Adding author_id into Posts table

$ rails g migration add_author_id_to_posts

In db/migrate/..._add_author_id_to_posts.rb,

class AddAuthorIdToPosts < ActiveRecord::Migration
  def change
    add_column :posts, :author_id, :integer
  end
end

$ rake db:migrate

Step 4: Edit the Views in Posts

In app/views/posts/_form.html.erb,

<%= form_for(@post) do |f| %>
  <!-- error message command -->

    <div class="field">
    <%= f.label :title %><br />
    <%= f.text_field :title %>
  </div>
  <div class="field">
    <%= f.label :body %><br />
    <%= f.text_area :body %>
  </div>
  <div class="field">
    <%= f.label :author %><br />
    <%= collection_select(:post, :author_id, Author.all, :id, :name) %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

In app/views/posts/show.html.erb,

  <p id="notice"><%= notice %></p>

  <%= render :partial => @post %>

  by <%= @post.author.name %>
  <br/>

  <%= link_to 'Edit', edit_post_path(@post) %> |
  <%= link_to 'Back', posts_path %>

Return to Internship Note (LoanStreet)
Previous Episode: Adding Delete Comment Feature into Blog Post
Next Episode: Update Blog with Favourites (Part 1)

Friday, March 7, 2014

Authentication with Devise

Part 1: Devise Users

Devise is a gem that makes adding authentication to any web application very simple. While it does take a few steps to set up, they're quick and since you'll be doing this for virtually every web application (should you choose to continue using Devise), you'll remember them. First, add the Devise gem to your Gemfile and run bundle install.

# Bundle edge Rails instead:
# gem 'rails', :git => 'git://github.com/rails/rails.git'

gem 'sqlite3'
gem 'devise'

$ bundle install

To start using bundle, you have to run a generator called bundle:install. This will install the library into your Rails application and get ready to actually generate a devise user model and migration. The generator will tell you of a few more things to clean up after it's finished running, you should read through these. Never mind the first one, we won't be using mail and the last one, we won't be using the Devise views yet. But you do need to set up the root route, display alerts and tell it not to load the DB when pre-compiling assets. The directions are rather clear, but I show the relevant portions of the files in this project and if you've been following along, some of these are already done in the project.

$ rails generate devise:install

From config/routes.rb.

Quick_Blog::Application.routes.draw do
  resources :posts
  
  root :to => 'posts#index'

From app/views/layouts/application.html.erb.

<body>
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>

<%= yield %>

From config/application.rb.

# Enable the asset pipeline
config.assets.enabled = true
config.assets.initialize_on_precompile = false

And finally, delete public/index.html, our root route won't work with it there.

$ rm public/index.html

And now, if your Rails server is already running, restart your Rails server. This is something I forget to do when installing Devise, and then wonder why none of the Devise methods work. This is necessary when you add any gem to your Gemfile, as these are only loaded when the server starts.

Part 2: User Model

Good, we're ready to go. Let's generate our User model and migration. This is handled by Devise for the most part, there's very little we want to add on our own. The only field we'll need to add is an is_blogger field to differentiate between registered comment posters (comments are implemented in a later article) and bloggers able to post blog posts on the site. These are simply fields added to the end of the devise generator. Running this generator looks a lot like running the scaffold generator. The first parameter after the generator name is the name of the model we want to either generate, or modify. In our case, we want to generate a User model. Devise will automatically generate all the fields it needs (email, password, etc), and any fields you list will be generated in addition to the Devise fields.

$ rails generate devise user is_blogger:boolean

      invoke  active_record
      create    db/migrate/20130211163618_add_devise_to_users.rb
      insert    app/models/user.rb
       route  devise_for :users

Devise's functionality lives in modules. These modules can be turned off for features you don't need or want and are selected (in our project) in the newly generated app/models/user.rb and the data they need are laid out in the newly generated migration in db/migrate, with comments telling which fields are needed for which features. There is one feature we don't want for our users, recoverable (if only so I can show you how to remove one of the modules). So let's remove it from app/models/user.rb, it will now look like this.

class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :token_authenticatable, :confirmable,
  # :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :rememberable, :trackable, :validatable

And its associated fields from the migration. Also remember to remove the index from the bottom of the file, since you're telling it to add an index for a field that no longer exists.

class DeviseCreateUsers < ActiveRecord::Migration
  def change
    create_table(:users) do |t|
      ## Database authenticatable
      t.string :email,              :null => false, :default => ""
      t.string :encrypted_password, :null => false, :default => ""

      ## Recoverable
      # t.string   :reset_password_token
      # t.datetime :reset_password_sent_at

      # Some fields not listed here for brevity

      t.boolean :is_blogger

      t.timestamps
    end

    add_index :users, :email,                :unique => true
    # add_index :users, :reset_password_token, :unique => true
    # add_index :users, :confirmation_token,   :unique => true
    # add_index :users, :unlock_token,         :unique => true
    # add_index :users, :authentication_token, :unique => true
  end
end

Everything else looks fine, so let's run the migration and start the server if it's not running.

$ rake db:migrate
...
$ rails server
=> Booting WEBrick
=> Rails 3.2.11 application starting in development on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server

Also, it's a good time to see what kind of routes were generated by Devise. You're going to need to know the names of the routes in order to generate the links to these features, so run rake routes and peruse its output. If you're curious as to where these routes come from, take a look at config/routes.rb, the Devise generator added a line that reads devise_for :users.

Note that there can be more than one Devise model in a web application, and the route names will differ. We called our Devise model "User," but we could have a second one called "Admin." This would generate two sets of named routes with names like new_user_session, but also new_admin_session. This is not an issue for our web application though, as we differentiate between our users with a column in the database, not a separate table. Of particular interest are new_user_registration, which displays the form to register, new_user_session which lets you log in and destroy_user_session which lets you log out.

Part 3: Sign In, Sign Out and Register Links

Now it's time to do a bit of work on the views a little bit. We're going to add either "Sign In" and "Register" links to the top of the page or, if you're logged in, it'll tell you who you're logged in as and give you a "Sign Out" link. Later on we'll put these in a more intelligent location, normal users don't want to be pestered to log in unless they want to post a comment.

So open up a new file at app/views/common/_session.html.erb, you'll have to create that directory first. Any view that begins with an underscore (in this case, _session) is known as a partial, or a small piece of a view that you're going to be rendering from other views. This is going to be the partial that displays the sign in, sign out and register links. The contents are below, and they're pretty straightforward. The user_signed_in? helper is from Devise (and again, if we had a second Devise class called Admin, there would also be a admin_signed_in?), it'll return true if the user is signed in (what a surprise) and current_user is another helper that returns the current user.

<%- if user_signed_in? %>
  <p>Hello <%= current_user.email %><br />
  <%= link_to 'Sign out', destroy_user_session_path, :method => :delete %></p>
<%- else %>
  <p>
    <%= link_to 'Register', new_user_registration_path %> |
    <%= link_to 'Sign in', new_user_session_path %>
  </p>
<%- end %>

And since we want to render this on every page, put it in the application template. We'll put it in a temporary location of the secondaryContent div in app/views/layouts/application.html.erb.

  <div id="secondaryContent">
    <%= render 'common/session' %>
  </div>

Users can now register, sign in and sign out. Great, this is what we wanted, but what about the is_blogger field? It wasn't in the registration form, there's no way to access it. It's also protected from mass assignment by the attr_accessible line in the User model, so even if a nefarious user were to add it to the client-side form, it still wouldn't be accessible. Whether you want to create an interface for this is up to you, but I prefer not to. Since this is intended to be a personal blog, there's no real reason to. You can set that field manually in the Rails console once and be done with it, it's not likely you'll need more than that. And yes, even when the application is deployed this is not a hard thing to do. So go ahead and create yourself a user and fire up the Rails console, find the user you created (preferably by email, don't accidentally make the wrong user a blogger, not an issue here but probably a good practice), set the is_blogger field and save the user.

>> u = User.find_by_email('uzimonkey@gmail.com')
 User Load (0.2ms)  SELECT "users".* FROM "users" WHERE "users"."email" = 'uzimonkey@gmail.com' LIMIT 1
...
>> u.is_blogger = true
true
>> u.save
  (0.0ms) begin transaction
  (0.4ms) UPDATE "users" SET "is_blogger" = 't', "updated_at" = '2013-02-11 17:26:03.807505' WHERE "users"."id" = 1
  (7.3ms) commit transaction
true

Part 4: Authorization

Authentication only goes so far. The site now knows who you are, but it still needs to restrict what you can do. We only want bloggers to be able to create or modify posts in any way, users can only list and show posts. While Devise does give us a helper that restricts methods to those who are logged in, this is not exactly what we want. We want a before filter to check if we're logged in and the current user has the is_blogger field set. This is easy though, we'll add a method in app/controllers/application_controller.rb to do this.

class ApplicationController < ActionController::Base
  protect_from_forgery

  protected
  def authorize_blogger!
    if user_signed_in? && current_user.is_blogger
      return
    elsif user_signed_in?
      flash[:notice] = 'You must be an authorized blogger to do that'
      redirect_to :root
    else
      flash[:notice] = 'You need to sign in first'
      redirect_to new_user_session_path
    end
  end
end

And then add this as a before_filter in app/controllers/posts_controller.rb.

class PostsController < ApplicationController
  before_filter :authorize_blogger!, :except => [:index, :show]

This is pretty straightforward, if the user is logged in and is a blogger, keep on going with the request. However, if either of the other conditions are met (user is logged in but is not a blogger, user is not logged in) the user gets redirected. If a before_filter either renders a page or redirects, the action the before filter protects will not run. Also, make sure you use :except with the before filter. It's tempting to go and list all the actions you want protected, but it's easy to miss one (or forget to add one) and leave it unprotected.

Test this by creating a second user without the is_blogger field set. You should see error message if you try to create or edit blog posts.

plataformatec/devise · GitHub
Return to Internship Note (LoanStreet)
Previous Episode: Update Blog with Tags
Next Episode: CarrierWave File Uploads

Monday, March 3, 2014

Create Simple Rails App

Step 1: Setting up our Rails app

$ sudo apt-get install mysql-server

$ rails _3.2.13_ new quick_blog -d mysql

Entering this command into your command prompt will cause Rails to generate a new application and begin to install depedencies for your application. This process may take a few minutes, so you should let it continue.

Error:

Gem::Installer::ExtensionBuildError: ERROR: Failed to build gem native extension.

/home/kirby510/.rbenv/versions/1.9.3-p385/bin/ruby extconf.rb
checking for ruby/thread.h... no
checking for rb_thread_blocking_region()... yes
checking for rb_wait_for_single_fd()... yes
checking for rb_hash_dup()... yes
checking for rb_intern3()... yes
checking for mysql_query() in -lmysqlclient... no
checking for main() in -lm... yes
checking for mysql_query() in -lmysqlclient... no
checking for main() in -lz... yes
checking for mysql_query() in -lmysqlclient... no
checking for main() in -lsocket... no
checking for mysql_query() in -lmysqlclient... no
checking for main() in -lnsl... yes
checking for mysql_query() in -lmysqlclient... no
checking for main() in -lmygcc... no
checking for mysql_query() in -lmysqlclient... no
*** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of
necessary libraries and/or headers.  Check the mkmf.log file for more
details.  You may need configuration options.

Solution:

$ sudo apt-get install libmysql-ruby

$ sudo apt-get install libmysqlclient-dev

After Rails generate a new application, type:

$ cd quick_blog

To change into the folder where your application is stored. If you look at the contents of this folder you'll see:


This is the standard structure of a new Rails application. Once you learn this structure it makes working with Rails easier since everything is in a standard place. Next we'll run this fresh application to check that our Rails install is working properly. Type:

$ rails server

Error: /home/kirby510/.rbenv/versions/1.9.3-p385/lib/ruby/gems/1.9.1/gems/execjs-2.0.2/lib/execjs/runtimes.rb:51:in `autodetect': Could not find a JavaScript runtime. See https://github.com/sstephenson/execjs for a list of available runtimes. (ExecJS::RuntimeUnavailable)

Solution:

$ sudo apt-get install libpq-dev

$ sudo apt-get install nodejs

Error: /home/kirby510/.rbenv/versions/1.9.3-p385/lib/ruby/gems/1.9.1/gems/mysql2-0.3.15/lib/mysql2/client.rb:67:in `connect': Unknown database 'quick_blog_development' (Mysql2::Error)

Solution:

$ rake db:create

Error: /home/kirby510/.rbenv/versions/1.9.3-p385/lib/ruby/gems/1.9.1/gems/mysql2-0.3.15/lib/mysql2/client.rb:67:in `connect': Access denied for user 'root'@'localhost' (using password: NO) (Mysql2::Error)

Solution:

$ nano config/database.yml

=> Booting WEBrick
=> Rails 3.2.13 application starting in development on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
[2014-03-03 17:27:19] INFO  WEBrick 1.3.1
[2014-03-03 17:27:19] INFO  ruby 1.9.3 (2013-02-06) [x86_64-linux]
[2014-03-03 17:27:19] INFO  WEBrick::HTTPServer#start: pid=31052 port=3000

Open your web-browser and head to: http://localhost:3000 you should see something that looks like:


Step 2: Creating basic functionality

Now we're ready to get started building an actual blog. In your command prompt press Ctrl+C to stop the Rails server, or open a new command prompt and navigate to your Rails application folder. Then you can use a Rails generator to build some code for you:

$ rails g scaffold Post title body:text

You'll be presented with something that looks like:


An important file that was generated was the migration file: db/migrate/20130422001725_create_posts.rb Note that you will have a different set of numbers for in yours.

class CreatePosts < ActiveRecord::Migration
  def change
    create_table :posts do |t|
      t.string :title
      t.text :body

      t.timestamps
    end
  end
end

This file is some Ruby code that is a database agnostic way to manage your schema. You can see that this code is to create a table called Posts and to create two columns in this table, a title column and a body column. Finally we need to instruct Rails to apply this to our database. Type:

$ rake db:migrate

Once this command has run you can start up your Rails server again with $ rails server and then navigate to http://localhost:3000/posts to see the changes you've made to your application.


From here you can play around with your application. Go ahead and create a new blog post.


You'll notice you can create new posts, edit or delete them. We're going to add in some functionality to our new Rails app which enforces a rule that every post must have a title. Open app/models/post.rb and add the line:

validates_presence_of :body, :title

To the code. Your post.rb file should look like:

class Post < ActiveRecord::Base
  attr_accessible :body, :title

  validates_presence_of :body, :title
end

We can check that this works by editing our blog post, deleting the title and clicking Update Post. You'll get an error informing you that you've just attempted to break the rule you just inserted:


Step 3: Making things prettier

Right now our show post page isn't looking very good. We'll open app/views/posts/show.html.erb and make it look like the following:

  <p id="notice"><%= notice %></p>

  <h2><%= link_to_unless_current @post.title, @post %></h2>
  <%= simple_format @post.body %>

  <%= link_to 'Edit', edit_post_path(@post) %> |
  <%= link_to 'Back', posts_path %>

At this point you can refresh the show post page in your browser to see the changes you've made.

We'll also want to make our blog listing prettier too, we'll use a Rails partial (a partial is simply a reusuable block of HTML code. It's part of a web page) to achieve this. We want our listing and the individual blog pages to look the same so first we'll create a file: app/views/posts/_post.html.erb The underscore in front of the filename here tells Rails that this is a partial. We'll take

  <h2><%= link_to_unless_current @post.title, @post %></h2>
  <%= simple_format @post.body %>

Out of app/views/posts/show.html.erb and put it in our _post.html.erb file. After that change all the @post to be post instead. This means your _post.html.erb file will be:

  <h2><%= link_to_unless_current post.title, post %></h2>
  <%= simple_format post.body %>

In our show.html.erb file we want insert the code to put our partial into our show view. Insert the code: <%= render :partial => @post %> to make it look like:

  <p id="notice"><%= notice %></p>

  <%= render :partial => @post %>

  <%= link_to 'Edit', edit_post_path(@post) %> |
  <%= link_to 'Back', posts_path %>

Save all these files and refresh the show posts page. This is to check that you haven't broken anything with those changes.

Our index page still hasn't changed though so we're going to open the index.html.erb file up and remove the table in there and replace it with the partial again so we're re-using that code:

  <h1>Listing posts</h1>

  <%= render :partial => @posts %>

  <%= link_to 'New Post', new_post_path %>

Step 4: Access control

One huge problem with our blog is that anyone can create, edit and delete blog posts. Lets fix that. We'll use HTTP Basic authenticate to put a password on actions we don't want everyone accessing. Open app/controllers/posts_controller.rb and add before_filter :authenticate, :except => [ :index, :show ] on line 2 just below the class declaration. At the bottom of your file put the following code:

  private
  def authenticate
    authenticate_or_request_with_http_basic do |name, password|
      name == "admin" && password == "secret"
    end
  end

Overall your posts_controller.rb should have the following code at the top and the bottom of the file. Note that all the methods are excluded here for brevity.

class PostsController < ApplicationController
  before_filter :authenticate, :except => [ :index, :show ]

  // … all your actions go in here

  private
  def authenticate
    authenticate_or_request_with_http_basic do |name, password|
      name == "admin" && password == "secret"
    end
  end
end

With that code in place you can try to add a new post and you'll be prompted to enter a username and password.


Step 5: Adding comments

Step 5.1: Creating a database model and routing

No blog is complete without comments. Lets add them in. On our command prompt shut down your rails server by hitting Ctrl+C in your command prompt and then type in:

$ rails generate resource Comment post:references body:text

Then you'll want to update your database here to reflect the schema change you've just made:

$ rake db:migrate

After this you'll need to inform Rails that your Posts will potentially have many Comments. Open app/models/post.rb and add the line: has_many :comments somewhere inside the class. This should look like:

class Post < ActiveRecord::Base
  attr_accessible :body, :title

  has_many :comments

  validates_presence_of :body, :title
end

The back-end for your comments is almost complete, we only need to configure the url that is used to create your comments. Since comments belong to a post we'll make the URL reflect this. Right now you can see all the configured URLs by typing $ rake routes in your command prompt. If you do this now you'll get something like the following:


Your URLs (or routes) are configured in all Rails applications in a file config/routes.rb, open it now and remove the line resources :comments. Re-run $ rake routes and you'll notice that all the URLs for comments have disappeared. Update your routes.rb file to look like the following:

QuickBlog::Application.routes.draw do
  resources :posts do
    resources :comments, :only => [:create]
  end

  # root :to => 'welcome#index'
end

Because comments will be visible from the show Post page along with the form for creating them, we don't need to have URLs for displaying comment listings, or individual comments. When you rerun $ rake routes now you'll see the following line:


Before we're finished with the backend for our commenting system we need to write the action that will create our comments. For more information on actions please read the Rails Guide on ActionController.

Open app/controllers/comments_controller.rb and and make your code look like the following:

class CommentsController < ApplicationController
    def create
        @post = Post.find(params[:post_id])
        @comment = @post.comments.create!(params[:comment])
        redirect_to @post
    end
end

Step 5.2: Putting comments into your HTML view

You've creating the database model for your comments, migrated your database, informed Rails of the relationship between comments and posts, configured a URL that lets you create your comments and created the controller action that will create the comments. Now you need to display any comments that have been submitted for a post, and allow users to submit comments. Open app/views/posts/show.html.erb and make it look like:

  <p id="notice"><%= notice %></p>

  <%= render :partial => @post %>

  <%= link_to 'Edit', edit_post_path(@post) %> |
  <%= link_to 'Back', posts_path %>

  <h2>Comments</h2>
  <div id="comments">
    <%= render :partial => @post.comments %>
  </div>

You'll now need to create a file called app/views/comments/_comment.html.erb with the following contents:

<%= div_for comment do %>
    <p>
        <strong>
            Posted <%= time_ago_in_words(comment.created_at) %> ago
        </strong>
        <br/>
        <%= comment.body %>
    </p>
<% end %>

Back in app/views/posts/show.html.erb you need to add in the form for submitting a comment so add the following code to the bottom of that file.

<%= form_for [@post, Comment.new] do |f| %>
    <p>
        <%= f.label :body, "New comment" %><br/>
        <%= f.text_area :body %>
    </p>
    <p><%= f.submit "Add comment" %></p>
<% end %>

Comments are now working (if they aren't make sure you restart your rails server) so go ahead and browse to your post and add a new comment.

Step 6: Publishing your Blog on the internet

Heroku is a fantastically simple service that can be used to host Ruby on Rails applications. You'll be able to host your blog on Heroku on their free-tier but first you'll need a Heroku account. Head to https://www.heroku.com/, click Sign Up and create an account. The starter documentation for Heroku is available at: https://devcenter.heroku.com/articles/quickstart. You've already got an account so you'll need to download the Toolbelt from https://toolbelt.heroku.com/ and set it up on your computer.

Step 6.1: About databases

Up until this point we've been using SQLite as our database, but unfortunately Heroku doesn't support the use of SQLite. So we're going to be running Postgres instead. Setting this up is easy you'll need to open the Gemfile and make your Gemfile look like:

source 'https://rubygems.org'

gem 'rails', '3.2.13'

group :development, :test do
    gem 'sqlite3'
end

group :production do
    gem 'pg'
end

group :assets do
  gem 'sass-rails', '~> 3.2.3'
  gem 'coffee-rails', '~> 3.2.1'

  gem 'uglifier', '>= 1.0.3'
end

gem 'jquery-rails'

After this run the command $ bundle install --without=production on your command line.

Step 6.2: Regarding version control

Heroku also requires that every application is placed under version control before it is deployed. Simply run the following commands on the command prompt to make sure your application is properly controlled:

git init
git add .
git commit -m "initial blog commit"

Step 6.3: Deploying your application

In the same command prompt you should be ready to deploy your application. First we create our Heroku application:

$ heroku create

Now we push our application to Heroku:

$ git push heroku master

Finally we setup our database:

$ heroku run rake db:setup

This setup should only need to take place the first time you deploy to heroku. Afterwards you may need to run $ heroku run rake db:migrate instead.

Finally you should be able to browse to the URL that Heroku has given you and check to see that your blog has been deployed properly!

Return to Internship Note (LoanStreet)
Previous Episode: Setup Ruby On Rails Environment
Next Episode: Adding Delete Comment Feature into Blog Post

Setup Ruby On Rails environment

Step 1: Installing rbenv

Step 1.1: Setup

$ sudo apt-get update

$ sudo apt-get upgrade -y

Step 1.2: Install Required Components For Ruby and other dependencies 

$ sudo apt-get install git-core -y

$ sudo apt-get install curl -y

$ sudo apt-get install build-essential openssl libreadline6 libreadline6-dev curl git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev libgdbm-dev ncurses-dev automake libtool bison subversion pkg-config libffi-dev

Step 1.3: Clone rbenv

$ git clone git://github.com/sstephenson/rbenv.git ~/.rbenv

$ echo ‘export PATH="$HOME/.rbenv/bin:$PATH"’ >> ~/.bashrc

$ echo ‘eval "$(rbenv init -)"’ >> ~/.bashrc

Error:
The program 'rbenv' is currently not installed. You can install it by typing:
sudo apt-get install rbenv

Solution:

$ sudo apt-get install rbenv

Step 1.4: Install some required rbenv plugins

$ cd ~/.rbenv/

$ mkdir plugins

$ cd ~/.rbenv/plugins

$ git clone git://github.com/sstephenson/ruby-build.git

Step 1.5: Restart Terminal

Error:
‘export: command not found
‘eval: command not found
‘eval: command not found
bash: /home/kirby510/.bashrc: line 134: syntax error: unexpected end of file

Solution:

$ nano /home/kirby510/.bashrc

Search for these text:
‘export PATH=/home/kirby510/.rbenv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin$
‘eval ’
‘eval export PATH="/home/kirby510/.rbenv/shims:${PATH}"
and change to these text:
export PATH=/home/kirby510/.rbenv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin$
eval
eval export PATH="/home/kirby510/.rbenv/shims:${PATH}"

Error:
’: command not found
bash: /home/kirby510/.bashrc: line 134: syntax error: unexpected end of file

Solution:

$ nano /home/kirby510/.bashrc

Search for these text:
}’
and change to these text:
}

Step 2: Installing Ruby

Step 2.1: Installing Ruby 1.9.3

$ rbenv install 1.9.3-p385

Error: rbenv: no such command 'install'.

Solution:

$ cd ~/.rbenv

$ cd plugins

$ cd ruby-build

$ ./install.sh

Step 2.2: Set Your Global Ruby version

$ rbenv global 1.9.3-p385

$ rbenv rehash

$ ruby -v

Step 3: Install Ruby-On-Rails Framework

$ gem install bundler

$ gem install rails -v=3.2.13

(Don't panic, installing rails will take some time.)

Return to Internship Note (LoanStreet)
Previous Episode: Install Ubuntu Desktop
Next Episode: Create Simple Rails App

Install Ubuntu Desktop

Install Ubuntu with the Windows installer

Once the Windows installer has been downloaded, you need to open it to install Ubuntu. You'll find detailed instructions below. If you need further help, support options are listed at the bottom of this page.

Step 1:
If you are using Internet Explorer, you'll be asked whether you want to run or save the file. Choose 'Run' to launch the installer.


Most other web browsers (like Firefox, for example) will only ask you to save the file. To start the installation, click 'Save' and then double-click the downloaded file.


Step 2:
If a security message like this appears, click 'Continue' to proceed.


Step 3:
To install Ubuntu, all you need to do is choose your user name and password. Do note that you need to enter your password twice to make sure you typed it correctly.


Step 4:
After choosing your password, click 'Install'. The files will be downloaded and installed automatically.

Step 5:
Wait until Ubuntu is downloaded and installed. This can take quite a while — the downloaded file size is around 500MB - but you can keep using your computer throughout.


Step 6:
When the installation is complete, you’ll be prompted to restart your computer. Click 'Finish' to restart.


Step 7:
After your computer restarts, choose 'Ubuntu' from the boot menu.


Return to Internship Note (LoanStreet)
Previous Episode: PHP CRUD Tutorial
Next Episode: Setup Ruby On Rails environment

Monday, February 17, 2014

PHP CRUD Tutorial

GmailKirby Lee Kian Theng <kirby.kl05@gmail.com>

[Loanstreet] Task Assignment - Kian Theng (Feb 6, 2014 - Feb 12, 2014)

Dhendy Ferdian <dhendy.ferdian@loanstreet.com.my>Thu, Feb 6, 2014 at 4:46 PM
To: kirby.kl05@hotmail.com, kirby.kl05@gmail.com, Jocelyn Chua <jocelyn.chua@loanstreet.com.my>

hi Kian Theng,

Please build very simple CRUD (Create Read Update Delete) processing using PHP and mysql. This is not an college exam, so you can find to any resources and ask to anyone, but the important key is you understand with what you write in the code

see this as example http://crud-demo.herokuapp.com/, do the same things as the example

send me your code and your sql from mysql dump for the database in 1 week from today, put in a zip, gz, bzip2 or any compression tools

do your best, I will see how fast you learn new things, how fast you adapt new language for you, and how you implement what you get so far on your college on the real code

any questions feel free to reply this email

best regards,
Dhendy Ferdian

List of Contents

Introduction
Step 1: Creating a sample Database table
Step 2: Connecting to Database
Step 3: Create an "Index" page
Step 4: Adding "Create" button and "Read" button
Step 5: Creating a "Create" page
Step 6: Creating a "Read" page
Step 7: Adding "Update" button and "Delete" button
Step 8: Creating a "Update" page
Step 9: Creating a "Delete" page
Final and Source Code

Introduction

Creating CRUD grid is a very common task in web development (CRUD stands for Create/Read/Update/Delete). If you are a senior web developer, you must have created plenty of CRUD grids already. They maybe exist in a content management system, an inventory management system, or accounting software. If you just started web development, you are certainly going to experience lots of CRUD grids' creation work in your later career.

The main purpose of a CRUD grid is that enables users create/read/update/delete data. Normally data is stored in MySQL Database.PHP will be the server-side language that manipulates MySQL Database tables to give front-end users power to perform CRUD actions.

In this tutorial series, we will go through steps of a creating PHP CRUD grid. We want to demonstrate how PHP as a server side language, communicates with backend MySQL, and meanwhile renders front-end HTML. We hope you can learn something from this tutorial.

Step 1: Creating a sample Database table

In this tutorial, we will work on a simple Database table as below. After this tutorial, you should use the idea here to create CRUD grid on your own Database tables.

Import the Database table below to your own MySQL Database.

CREATE TABLE Books(
  id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, 
  title VARCHAR(100), 
  author VARCHAR(100), 
  date_pub TIMESTAMP, 
  notes VARCHAR(200)
);

As you can see, this is a very simple table for tracking books' information (title, author, date published and notes). And we prefer an auto incremental primary key (id).

Step 2: Connecting to Database

Create a PHP file "database.php"; this file contains a PHP class named "Database". Throughout this application, Database handles all the stuff related to database connections, such as connecting and disconnecting.

<?php 
  class Database{ 
    private static $dbName = "CRUD_Books"; 
    private static $dbHost = "localhost"; 
    private static $dbUsername = "root"; 
    private static $dbUserPassword = ""; 
    private static $cont = null; 
    
    public function __construct(){ 
      die("Init function is not allowed"); 
    } 
    
    public static function connect(){ 
      // One connection through whole application 
      if(null == self::$cont){ 
        try{ 
          self::$cont = new PDO("mysql:host=".self::$dbHost.";"."dbname=".self::$dbName,self::$dbUsername,self::$dbUserPassword); 
        } 
        catch(PDOException $e){ 
          die($e->getMessage()); 
        } 
      } 
      return self::$cont; 
    } 
    
    public static function disconnect(){ 
      self::$cont = null; 
    } 
  }; 
?> 

As you can see, we are using PDO for database access. There are a lot of benefits of using PDO. One of the most significant benefits is that it provides a uniform method of access to multiple databases.

To use this class, you will need to supply correct values for $dbName, $dbHost, $dbUsername, $dbUserPassword.

  • $dbName: Database name which you use to store 'customers' table.
  • $dbHost: Database host, this is normally "localhost".
  • $dbUsername: Database username.
  • $dbUserPassword: Database user's password.

Let us take a look at three functions of this class:
  • __construct(): This is the constructor of class Database. Since it is a static class, initialization of this class is not allowed. To prevent misuse of the class, we use a "die" function to remind users.
  • connect: This is the main function of this class. It uses singleton pattern to make sure only one PDO connection exist across the whole application. Since it is a static method. We use Database::connect() to create a connection.
  • disconnect: Disconnect from database. It simply sets connection to NULL. We need to call this function to close connection.

Step 3: Create an "Index" page

Here comes the grid without CRUD capabilities. Because CRUD operation can only be performed when there is a grid. We firstly need to build a grid. From there we can subsequently add "Create" page, "Read" page, "Update" page and finally "Delete" page.

After that, create a PHP file "index.php" and copy the content below:

<!DOCTYPE html> 
<html> 

  <head> 
    <h1>Listing books</h1> 
  </head> 
  
  <body> 
    <table> 
      <thead> 
        <tr> 
          <th>Title</th> 
          <th>Author</th> 
          <th>Published at</th> 
          <th>Notes</th> 
        </tr> 
      </thead> 
      <tbody> 
        <?php 
          include "database.php"; 
          
          $pdo = Database::connect(); 
          $sql = "SELECT * FROM Books ORDER BY id ASC"; 
          
          foreach($pdo->query($sql) as $row){ 
            echo "<tr>"; 
            echo "<td>".$row['title']."</td>"; 
            echo "<td>".$row['author']."</td>"; 
            echo "<td>".$row['date_pub']." UTC</td>"; 
            echo "<td>".$row['notes']."</td>"; 
            echo "</tr>"; 
          }
          
          Database::disconnect(); 
        ?> 
      </tbody> 
    </table> 
  </body> 

</html> 

This is the main part of the file. It is where we retrieve data from database and show it on the grid. Let dig deep into each lines carefully.

<table> 
  <thead> 
    <tr> 
      <th>Title</th> 
      <th>Author</th> 
      <th>Published at</th> 
      <th>Notes</th> 
    </tr> 
  </thead> 
  <tbody> 
    <?php 
      include "database.php"; 
      
      $pdo = Database::connect(); 
      $sql = "SELECT * FROM Books ORDER BY id ASC"; 
      
      foreach($pdo->query($sql) as $row){ 
        echo "<tr>"; 
        echo "<td>".$row['title']."</td>"; 
        echo "<td>".$row['author']."</td>"; 
        echo "<td>".$row['date_pub']." UTC</td>"; 
        echo "<td>".$row['notes']."</td>"; 
        echo "</tr>"; 
      } 
      
      Database::disconnect(); 
    ?> 
  </tbody> 
</table> 

We firstly create a table with headers corresponding to database table "Books"'s fields. Which includes "Title", "Author", "Date Published", "Notes":

<thead> 
  <tr> 
    <th>Title</th> 
    <th>Author</th> 
    <th>Published at</th> 
    <th>Notes</th> 
  </tr> 
</thead> 

Then we include "database.php", create a PDO connection to database, and use a general "SELECT" statement to retrieve data. Lastly, we loop through each row to print content. Do not forget to close the connection as we mentioned at the beginning.

<tbody> 
 <?php 
  include "database.php"; 
  $pdo = Database::connect(); 
  $sql = "SELECT * FROM Books ORDER BY id ASC"; 
  foreach($pdo->query($sql) as $row){ 
  echo "<tr>"; 
  echo "<td>".$row['title']."</td>"; 
  echo "<td>".$row['author']."</td>"; 
  echo "<td>".$row['date_pub']." UTC</td>"; 
  echo "<td>".$row['notes']."</td>"; 
  echo "</tr>"; 
  } 
  Database::disconnect(); 
 ?> 
</tbody> 

If you have followed correctly, you should have an empty grid as below if you navigate to "index.php" from browser:


The grid is empty because there is no actual data inside "Books" table, to test if it is actually working, you can manually insert data into "Books" table. It should show them on the grid.

Step 4: Adding "Create" button and "Read" button

To start, we firstly need a navigation button which leads to "Create" page and also a button to read a record. Open "index.php" file we created in step 3 of this tutorial. And add a "Create" button at the bottom of the table, and also "Read" buttons for each row of the table.

Now the "index.php" file's code should look like below, the highlighted codes are what we have added:

<!DOCTYPE html> 
<html> 

  <head> 
   <h1>Listing books</h1> 
  </head> 

  <body> 
    <table> 
      <thead> 
        <tr> 
          <th>Title</th> 
          <th>Author</th> 
          <th>Published at</th> 
          <th>Notes</th> 
          <th></th> 
        </tr> 
      </thead> 
      <tbody> 
        <?php 
          include "database.php"; 
$pdo = Database::connect(); 
          $sql = "SELECT * FROM Books ORDER BY id ASC"; 
          
          foreach($pdo->query($sql) as $row){ 
            echo "<tr>"; 
            echo "<td>".$row['title']."</td>"; 
            echo "<td>".$row['author']."</td>"; 
            echo "<td>".$row['date_pub']." UTC</td>"; 
            echo "<td>".$row['notes']."</td>"; 
            echo "<td><a href='read.php?id=".$row['id']."'>Show</a></td>"; 
            echo "</tr>"; 
          } 
          
          Database::disconnect(); 
        ?> 
      </tbody> 
    </table> 
    <p> 
      <a href="create.php">New Book</a> 
    </p> 
  </body> 

</html> 

Now if you navigate to "index.php page". You should notice a "Create" button. However since its linked page is not created, it will redirect to an error page if you click the button. We will fix that next step:


Step 5: Creating a "Create" page

Create a PHP file "create.php"; This file contains an html form and it is the "Create" part of CRUD grid.

We will go through this file as two parts.

First part of the codes is an html form. It is pretty straightforward; all it does is to create an html form. As we need to provide validation for form entries. Hence there is a PHP variable for holding validation error for each field. Copy the code below to "create.php" file:

<!DOCTYPE html> 
<html> 
 
  <head> 
    <h1>New book</h1> 
  </head>
  
  <body> 
    <form action="create.php" method="post"> 
      Title<br> 
      <input type="text" name="title" id="title"><br> 
      Author<br> 
      <input type="text" name="author" id="author"><br> 
      Published at<br> 
      <select name="year"> 
        <option value="2009" <?php if($year == 2009){echo("selected");}?>>2009</option> 
        <option value="2010" <?php if($year == 2010){echo("selected");}?>>2010</option> 
        <option value="2011" <?php if($year == 2011){echo("selected");}?>>2011</option> 
        <option value="2012" <?php if($year == 2012){echo("selected");}?>>2012</option> 
        <option value="2013" <?php if($year == 2013){echo("selected");}?>>2013</option> 
        <option value="2014" <?php if($year == 2014){echo("selected");}?>>2014</option> 
        <option value="2015" <?php if($year == 2015){echo("selected");}?>>2015</option> 
        <option value="2016" <?php if($year == 2016){echo("selected");}?>>2016</option> 
        <option value="2017" <?php if($year == 2017){echo("selected");}?>>2017</option> 
        <option value="2018" <?php if($year == 2018){echo("selected");}?>>2018</option> 
        <option value="2019" <?php if($year == 2019){echo("selected");}?>>2019</option> 
      </select> 
      <select name="month"> 
        <option value="01" <?php if($month == 1){echo("selected");}?>>January</option> 
        <option value="02" <?php if($month == 2){echo("selected");}?>>February</option> 
        <option value="03" <?php if($month == 3){echo("selected");}?>>March</option> 
        <option value="04" <?php if($month == 4){echo("selected");}?>>April</option> 
        <option value="05" <?php if($month == 5){echo("selected");}?>>May</option> 
        <option value="06" <?php if($month == 6){echo("selected");}?>>June</option> 
        <option value="07" <?php if($month == 7){echo("selected");}?>>July</option> 
        <option value="08" <?php if($month == 8){echo("selected");}?>>August</option> 
        <option value="09" <?php if($month == 9){echo("selected");}?>>September</option> 
        <option value="10" <?php if($month == 10){echo("selected");}?>>October</option> 
        <option value="11" <?php if($month == 11){echo("selected");}?>>November</option> 
        <option value="12" <?php if($month == 12){echo("selected");}?>>December</option> 
      </select> 
      <select name="day"> 
        <option value="01" <?php if($day == 1){echo("selected");}?>>1</option> 
        <option value="02" <?php if($day == 2){echo("selected");}?>>2</option> 
        <option value="03" <?php if($day == 3){echo("selected");}?>>3</option> 
        <option value="04" <?php if($day == 4){echo("selected");}?>>4</option> 
        <option value="05" <?php if($day == 5){echo("selected");}?>>5</option> 
        <option value="06" <?php if($day == 6){echo("selected");}?>>6</option> 
        <option value="07" <?php if($day == 7){echo("selected");}?>>7</option> 
        <option value="08" <?php if($day == 8){echo("selected");}?>>8</option> 
        <option value="09" <?php if($day == 9){echo("selected");}?>>9</option> 
        <option value="10" <?php if($day == 10){echo("selected");}?>>10</option> 
        <option value="11" <?php if($day == 11){echo("selected");}?>>11</option> 
        <option value="12" <?php if($day == 12){echo("selected");}?>>12</option> 
        <option value="13" <?php if($day == 13){echo("selected");}?>>13</option> 
        <option value="14" <?php if($day == 14){echo("selected");}?>>14</option> 
        <option value="15" <?php if($day == 15){echo("selected");}?>>15</option> 
        <option value="16" <?php if($day == 16){echo("selected");}?>>16</option> 
        <option value="17" <?php if($day == 17){echo("selected");}?>>17</option> 
        <option value="18" <?php if($day == 18){echo("selected");}?>>18</option> 
        <option value="19" <?php if($day == 19){echo("selected");}?>>19</option> 
        <option value="20" <?php if($day == 20){echo("selected");}?>>20</option> 
        <option value="21" <?php if($day == 21){echo("selected");}?>>21</option> 
        <option value="22" <?php if($day == 22){echo("selected");}?>>22</option> 
        <option value="23" <?php if($day == 23){echo("selected");}?>>23</option> 
        <option value="24" <?php if($day == 24){echo("selected");}?>>24</option> 
        <option value="25" <?php if($day == 25){echo("selected");}?>>25</option> 
        <option value="26" <?php if($day == 26){echo("selected");}?>>26</option> 
        <option value="27" <?php if($day == 27){echo("selected");}?>>27</option> 
        <option value="28" <?php if($day == 28){echo("selected");}?>>28</option> 
        <option value="29" <?php if($day == 29){echo("selected");}?>>29</option> 
        <option value="30" <?php if($day == 30){echo("selected");}?>>30</option> 
        <option value="31" <?php if($day == 31){echo("selected");}?>>31</option> 
      </select> 
      &#8212; 
      <select name="hour"> 
        <option value="00" <?php if($hour == 0){echo("selected");}?>>00</option> 
        <option value="01" <?php if($hour == 1){echo("selected");}?>>01</option> 
        <option value="02" <?php if($hour == 2){echo("selected");}?>>02</option> 
        <option value="03" <?php if($hour == 3){echo("selected");}?>>03</option> 
        <option value="04" <?php if($hour == 4){echo("selected");}?>>04</option> 
        <option value="05" <?php if($hour == 5){echo("selected");}?>>05</option> 
        <option value="06" <?php if($hour == 6){echo("selected");}?>>06</option> 
        <option value="07" <?php if($hour == 7){echo("selected");}?>>07</option> 
        <option value="08" <?php if($hour == 8){echo("selected");}?>>08</option> 
        <option value="09" <?php if($hour == 9){echo("selected");}?>>09</option> 
        <option value="10" <?php if($hour == 10){echo("selected");}?>>10</option> 
        <option value="11" <?php if($hour == 11){echo("selected");}?>>11</option> 
        <option value="12" <?php if($hour == 12){echo("selected");}?>>12</option> 
        <option value="13" <?php if($hour == 13){echo("selected");}?>>13</option> 
        <option value="14" <?php if($hour == 14){echo("selected");}?>>14</option> 
        <option value="15" <?php if($hour == 15){echo("selected");}?>>15</option> 
        <option value="16" <?php if($hour == 16){echo("selected");}?>>16</option> 
        <option value="17" <?php if($hour == 17){echo("selected");}?>>17</option> 
        <option value="18" <?php if($hour == 18){echo("selected");}?>>18</option> 
        <option value="19" <?php if($hour == 19){echo("selected");}?>>19</option> 
        <option value="20" <?php if($hour == 20){echo("selected");}?>>20</option> 
        <option value="21" <?php if($hour == 21){echo("selected");}?>>21</option> 
        <option value="22" <?php if($hour == 22){echo("selected");}?>>22</option> 
        <option value="23" <?php if($hour == 23){echo("selected");}?>>23</option> 
      </select> 
      : 
      <select name="minute"> 
        <option value="00" <?php if($minute == 0){echo("selected");}?>>00</option> 
        <option value="01" <?php if($minute == 1){echo("selected");}?>>01</option> 
        <option value="02" <?php if($minute == 2){echo("selected");}?>>02</option> 
        <option value="03" <?php if($minute == 3){echo("selected");}?>>03</option> 
        <option value="04" <?php if($minute == 4){echo("selected");}?>>04</option> 
        <option value="05" <?php if($minute == 5){echo("selected");}?>>05</option> 
        <option value="06" <?php if($minute == 6){echo("selected");}?>>06</option> 
        <option value="07" <?php if($minute == 7){echo("selected");}?>>07</option> 
        <option value="08" <?php if($minute == 8){echo("selected");}?>>08</option> 
        <option value="09" <?php if($minute == 9){echo("selected");}?>>09</option> 
        <option value="10" <?php if($minute == 10){echo("selected");}?>>10</option> 
        <option value="11" <?php if($minute == 11){echo("selected");}?>>11</option> 
        <option value="12" <?php if($minute == 12){echo("selected");}?>>12</option> 
        <option value="13" <?php if($minute == 13){echo("selected");}?>>13</option> 
        <option value="14" <?php if($minute == 14){echo("selected");}?>>14</option> 
        <option value="15" <?php if($minute == 15){echo("selected");}?>>15</option> 
        <option value="16" <?php if($minute == 16){echo("selected");}?>>16</option> 
        <option value="17" <?php if($minute == 17){echo("selected");}?>>17</option> 
        <option value="18" <?php if($minute == 18){echo("selected");}?>>18</option> 
        <option value="19" <?php if($minute == 19){echo("selected");}?>>19</option> 
        <option value="20" <?php if($minute == 20){echo("selected");}?>>20</option> 
        <option value="21" <?php if($minute == 21){echo("selected");}?>>21</option> 
        <option value="22" <?php if($minute == 22){echo("selected");}?>>22</option> 
        <option value="23" <?php if($minute == 23){echo("selected");}?>>23</option> 
        <option value="24" <?php if($minute == 24){echo("selected");}?>>24</option> 
        <option value="25" <?php if($minute == 25){echo("selected");}?>>25</option> 
        <option value="26" <?php if($minute == 26){echo("selected");}?>>26</option> 
        <option value="27" <?php if($minute == 27){echo("selected");}?>>27</option> 
        <option value="28" <?php if($minute == 28){echo("selected");}?>>28</option> 
        <option value="29" <?php if($minute == 29){echo("selected");}?>>29</option> 
        <option value="30" <?php if($minute == 30){echo("selected");}?>>30</option> 
        <option value="31" <?php if($minute == 31){echo("selected");}?>>31</option> 
        <option value="32" <?php if($minute == 32){echo("selected");}?>>32</option> 
        <option value="33" <?php if($minute == 33){echo("selected");}?>>33</option> 
        <option value="34" <?php if($minute == 34){echo("selected");}?>>34</option> 
        <option value="35" <?php if($minute == 35){echo("selected");}?>>35</option> 
        <option value="36" <?php if($minute == 36){echo("selected");}?>>36</option> 
        <option value="37" <?php if($minute == 37){echo("selected");}?>>37</option> 
        <option value="38" <?php if($minute == 38){echo("selected");}?>>38</option> 
        <option value="39" <?php if($minute == 39){echo("selected");}?>>39</option> 
        <option value="40" <?php if($minute == 40){echo("selected");}?>>40</option> 
        <option value="41" <?php if($minute == 41){echo("selected");}?>>41</option> 
        <option value="42" <?php if($minute == 42){echo("selected");}?>>42</option> 
        <option value="43" <?php if($minute == 43){echo("selected");}?>>43</option> 
        <option value="44" <?php if($minute == 44){echo("selected");}?>>44</option> 
        <option value="45" <?php if($minute == 45){echo("selected");}?>>45</option> 
        <option value="46" <?php if($minute == 46){echo("selected");}?>>46</option> 
        <option value="47" <?php if($minute == 47){echo("selected");}?>>47</option> 
        <option value="48" <?php if($minute == 48){echo("selected");}?>>48</option> 
        <option value="49" <?php if($minute == 49){echo("selected");}?>>49</option> 
        <option value="50" <?php if($minute == 50){echo("selected");}?>>50</option> 
        <option value="51" <?php if($minute == 51){echo("selected");}?>>51</option> 
        <option value="52" <?php if($minute == 52){echo("selected");}?>>52</option> 
        <option value="53" <?php if($minute == 53){echo("selected");}?>>53</option> 
        <option value="54" <?php if($minute == 54){echo("selected");}?>>54</option> 
        <option value="55" <?php if($minute == 55){echo("selected");}?>>55</option> 
        <option value="56" <?php if($minute == 56){echo("selected");}?>>56</option> 
        <option value="57" <?php if($minute == 57){echo("selected");}?>>57</option> 
        <option value="58" <?php if($minute == 58){echo("selected");}?>>58</option> 
        <option value="59" <?php if($minute == 59){echo("selected");}?>>59</option> 
      </select><br> 
      Notes<br> 
      <textarea name="notes"></textarea><br> 
      <input name="submit" type="submit" value="Create Book"><br> 
      <a href="index.php">Back</a> 
    </form> 
  </body> 
</html> 

Second part of the codes is where the magic happens. It handles record creation process. Copy the codes below to the beginning of "create.php" file, we will go through them afterwards:

<?php 
  require "database.php"; 
  
  if (!empty($_POST)) { 
    //keep track post values 
    $title = isset($_POST['title'])?$_POST['title']:''; 
    $author = isset($_POST['author'])?$_POST['author']:''; 
    $date_pub = (isset($_POST['year'])?$_POST['year']:'')."-".(isset($_POST['month'])?$_POST['month']:'')."-".(isset($_POST['day'])?$_POST['day']:'')." ".(isset($_POST['hour'])?$_POST['hour']:'').":".(isset($_POST['minute'])?$_POST['minute']:'').":00"; 
    $notes = isset($_POST['notes'])?$_POST['notes']:''; 
    
    //insert data 
    $pdo = Database::connect(); 
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 
    $sql = "INSERT INTO Books (title,author,date_pub,notes) VALUES (?,?,?,?)"; 
    $q = $pdo->prepare($sql); 
    $q->execute(array($title,$author,$date_pub,$notes)); 
    $sql = "SELECT * FROM Books"; 
    foreach($pdo->query($sql) as $row){ 
      if(($title == $row['title']) && ($date_pub == $row['date_pub'])){ 
        $id = $row['id']; 
      } 
    }

    Database::disconnect(); 
    header("Location: index.php"); 
  } 
  else{ 
    date_default_timezone_set('Europe/London'); 
    $year = gmdate("Y"); 
    $month = gmdate("m"); 
    $day = gmdate("d"); 
    $hour = gmdate("G"); 
    $minute = gmdate("i"); 
  } 
?> 

As you see, firstly we check if there is form submit by checking $_POST variable. If so, we check each entries to ensure they are not empty. Additionally for email address entry, we use PHP filter to verify if it is a valid email address. Then if it passes all validation rules, it inserts data to database using Database class. At last it will redirect to "index.php" using PHP header() function. However if there is any validation error, the validation variables will be showed in the form.

If you have followed correctly. Navigate to "create.php" page, and click on the "Create" button, you should be able to see a PHP form with validation errors as below:


Create some records by entering correct customer information. You should be able to see a CRUD grid as below.


You must have noticed a "Read" button for each row. That is what we have added in step 4 of this tutorial. If you click it now, it will lead you to an error page. And that is what we are going to create next step.

Step 6: Creating a "Read" page

Open "create.php" file we created in step 5 of this tutorial. And edit the code that are highlighted as below:

<?php 
  require "database.php"; 
  
  if (!empty($_POST)) { 
    //keep track post values 
    $title = isset($_POST['title'])?$_POST['title']:''; 
    $author = isset($_POST['author'])?$_POST['author']:''; 
    $date_pub = (isset($_POST['year'])?$_POST['year']:'')."-".(isset($_POST['month'])?$_POST['month']:'')."-".(isset($_POST['day'])?$_POST['day']:'')." ".(isset($_POST['hour'])?$_POST['hour']:'').":".(isset($_POST['minute'])?$_POST['minute']:'').":00"; 
    $notes = isset($_POST['notes'])?$_POST['notes']:''; 
    
    //insert data 
    $pdo = Database::connect(); 
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 
    $sql = "INSERT INTO Books (title,author,date_pub,notes) VALUES (?,?,?,?)"; 
    $q = $pdo->prepare($sql); 
    $q->execute(array($title,$author,$date_pub,$notes)); 
    $sql = "SELECT * FROM Books"; 
    foreach($pdo->query($sql) as $row){ 
      if(($title == $row['title']) && ($date_pub == $row['date_pub'])){ 
        $id = $row['id']; 
      } 
    }
    
    Database::disconnect(); 
    header("Location: read.php?id=".$id); 
  } 
  else{ 
    date_default_timezone_set('Europe/London'); 
    $year = gmdate("Y"); 
    $month = gmdate("m"); 
    $day = gmdate("d"); 
    $hour = gmdate("G"); 
    $minute = gmdate("i"); 
  } 
?> 

Create a PHP file "read.php"; this file is the "Read" part of CRUD grid. Comparing to "Create" part, this step is pretty straightforward. Copy codes below to the file and we will explain afterwards.

<?php 
  require "database.php"; 
  
  $id = null; 
  if(!empty($_GET['id'])){ 
    $id = $_REQUEST['id']; 
  } 
  
  if (null == $id){ 
    header("Location: index.php"); 
  } 
  else{ 
    $pdo = Database::connect(); 
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 
    $sql = "SELECT * FROM Books WHERE id = ?"; 
    $q = $pdo->prepare($sql); 
    $q->execute(array($id)); 
    $data = $q->fetch(PDO::FETCH_ASSOC); 
    Database::disconnect(); 
  } 
?> 

<!DOCTYPE html> 
<html>

  <head> 
  </head> 
  
  <body> 
    <?php 
      if(isset($_SERVER['HTTP_REFERER'])){ 
        $prev_url = $_SERVER['HTTP_REFERER']; 
        
        if($prev_url == "http://localhost/CRUD_Books/create.php"){ 
          echo "<p>Book was successfully created. </p>"; 
        } 
        else if($prev_url == "http://localhost/CRUD_Books/update.php?id=".$id){ 
          echo "<p>Book was successfully updated. </p>"; 
        } 
      } 
    ?> 
    <p><b>Title: </b><?php echo $data['title'];?></p> 
    <p><b>Author: </b><?php echo $data['author'];?></p> 
    <p><b>Published at: </b><?php echo $data['date_pub'];?> UTC</p> 
    <p><b>Notes: </b><?php echo $data['notes'];?></p> 
    <p> 
    <?php 
      echo "<a href='update.php?id=".$id."'>Edit</a>"; 
    ?> 
     | <a href="index.php">Back</a></p> 
  </body> 

</html> 

Firstly let us look at the beginning part of the PHP codes. What it does is that, it tries to allocate a $_GET['id'] variable. If it is not found, it redirects to "index.php" page. Otherwise, it will read data from database using the "id" field and store data into a PHP variable $data.

Next part, which is the static html part, is pretty simple. It prints out the $data variable.

If you get it working. "read.php" page should look like below:


Step 7: Adding "Update" button and "Delete" button

Similar to creating "Create" and "Read" part of PHP CRUD grid. We will firstly need "Update" and "Delete" buttons for each row of the table. Open "index.php" file and add following highlighted codes:

Now the "index.php" file's code should look like below, the highlighted codes are what we have added (or you can just copy all the codes below):

<!DOCTYPE html> 
<html> 

  <head> 
    <h1>Listing books</h1> 
  </head> 
  
  <body> 
    <table> 
      <thead> 
        <tr> 
          <th>Title</th> 
          <th>Author</th> 
          <th>Published at</th> 
          <th>Notes</th> 
          <th></th> 
        </tr> 
      </thead> 
      <tbody> 
        <?php 
          include "database.php"; 
          
          $pdo = Database::connect(); 
          $sql = "SELECT * FROM Books ORDER BY id ASC"; 
          
          foreach($pdo->query($sql) as $row){ 
            echo "<tr>"; 
            echo "<td>".$row['title']."</td>"; 
            echo "<td>".$row['author']."</td>"; 
            echo "<td>".$row['date_pub']." UTC</td>"; 
            echo "<td>".$row['notes']."</td>"; 
            echo "<td>"; 
            echo "<a href='read.php?id=".$row['id']."'>Show</a>"; 
            echo " "; 
            echo "<a href='update.php?id=".$row['id']."'>Edit</a>"; 
            echo " "; 
            echo "<a href='delete.php?id=".$row['id']."'>Destroy</a>"; 
            echo "</td>"; 
            echo "</tr>"; 
          } 
          
          Database::disconnect(); 
        ?> 
      </tbody> 
    </table> 
    <p> 
      <a href="create.php">New Book</a> 
    </p> 
  </body>

</html> 

Now if you navigate to "index.php page". You should notice "Update" and "Delete" buttons for each row. They are not functional yet. We are going to complete the "Update" page next.


Step 8: Creating a "Update" page

Create a PHP file "update.php"; this is the "Update" part of CRUD grid. It is almost identically to "Create" part, besides the fact that we will not only update the record, but also show the data.

We will go through this file as two parts like we did for "Create" page.

First part of the codes is an html form. This form part is exactly the same as "Create" page. Copy the code below to "update.php" file:

<!DOCTYPE html> 
<html> 

  <head> 
    <h1>Editing book</h1> 
   </head> 
   
  <body> 
    <form action="update.php?id=<?php echo $id?>" method="post"> 
      Title<br> 
      <input type="text" name="title" id="title" value="<?php echo !empty($title)?$title:'';?>"><br> 
      Author<br> 
      <input type="text" name="author" id="author" value="<?php echo !empty($author)?$author:'';?>"><br> 
      Published at<br> 
      <select name="year"> 
        <option value="2009" <?php if($year == 2009){echo("selected");}?>>2009</option> 
        <option value="2010" <?php if($year == 2010){echo("selected");}?>>2010</option> 
        <option value="2011" <?php if($year == 2011){echo("selected");}?>>2011</option> 
        <option value="2012" <?php if($year == 2012){echo("selected");}?>>2012</option> 
        <option value="2013" <?php if($year == 2013){echo("selected");}?>>2013</option> 
        <option value="2014" <?php if($year == 2014){echo("selected");}?>>2014</option> 
        <option value="2015" <?php if($year == 2015){echo("selected");}?>>2015</option> 
        <option value="2016" <?php if($year == 2016){echo("selected");}?>>2016</option> 
        <option value="2017" <?php if($year == 2017){echo("selected");}?>>2017</option> 
        <option value="2018" <?php if($year == 2018){echo("selected");}?>>2018</option> 
        <option value="2019" <?php if($year == 2019){echo("selected");}?>>2019</option> 
      </select> 
      <select name="month"> 
        <option value="01" <?php if($month == 1){echo("selected");}?>>January</option> 
        <option value="02" <?php if($month == 2){echo("selected");}?>>February</option> 
        <option value="03" <?php if($month == 3){echo("selected");}?>>March</option> 
        <option value="04" <?php if($month == 4){echo("selected");}?>>April</option> 
        <option value="05" <?php if($month == 5){echo("selected");}?>>May</option> 
        <option value="06" <?php if($month == 6){echo("selected");}?>>June</option> 
        <option value="07" <?php if($month == 7){echo("selected");}?>>July</option> 
        <option value="08" <?php if($month == 8){echo("selected");}?>>August</option> 
        <option value="09" <?php if($month == 9){echo("selected");}?>>September</option> 
        <option value="10" <?php if($month == 10){echo("selected");}?>>October</option> 
        <option value="11" <?php if($month == 11){echo("selected");}?>>November</option> 
        <option value="12" <?php if($month == 12){echo("selected");}?>>December</option> 
      </select> 
      <select name="day"> 
        <option value="01" <?php if($day == 1){echo("selected");}?>>1</option> 
        <option value="02" <?php if($day == 2){echo("selected");}?>>2</option> 
        <option value="03" <?php if($day == 3){echo("selected");}?>>3</option> 
        <option value="04" <?php if($day == 4){echo("selected");}?>>4</option> 
        <option value="05" <?php if($day == 5){echo("selected");}?>>5</option> 
        <option value="06" <?php if($day == 6){echo("selected");}?>>6</option> 
        <option value="07" <?php if($day == 7){echo("selected");}?>>7</option> 
        <option value="08" <?php if($day == 8){echo("selected");}?>>8</option> 
        <option value="09" <?php if($day == 9){echo("selected");}?>>9</option> 
        <option value="10" <?php if($day == 10){echo("selected");}?>>10</option> 
        <option value="11" <?php if($day == 11){echo("selected");}?>>11</option> 
        <option value="12" <?php if($day == 12){echo("selected");}?>>12</option> 
        <option value="13" <?php if($day == 13){echo("selected");}?>>13</option> 
        <option value="14" <?php if($day == 14){echo("selected");}?>>14</option> 
        <option value="15" <?php if($day == 15){echo("selected");}?>>15</option> 
        <option value="16" <?php if($day == 16){echo("selected");}?>>16</option> 
        <option value="17" <?php if($day == 17){echo("selected");}?>>17</option> 
        <option value="18" <?php if($day == 18){echo("selected");}?>>18</option> 
        <option value="19" <?php if($day == 19){echo("selected");}?>>19</option> 
        <option value="20" <?php if($day == 20){echo("selected");}?>>20</option> 
        <option value="21" <?php if($day == 21){echo("selected");}?>>21</option> 
        <option value="22" <?php if($day == 22){echo("selected");}?>>22</option> 
        <option value="23" <?php if($day == 23){echo("selected");}?>>23</option> 
        <option value="24" <?php if($day == 24){echo("selected");}?>>24</option> 
        <option value="25" <?php if($day == 25){echo("selected");}?>>25</option> 
        <option value="26" <?php if($day == 26){echo("selected");}?>>26</option> 
        <option value="27" <?php if($day == 27){echo("selected");}?>>27</option> 
        <option value="28" <?php if($day == 28){echo("selected");}?>>28</option> 
        <option value="29" <?php if($day == 29){echo("selected");}?>>29</option> 
        <option value="30" <?php if($day == 30){echo("selected");}?>>30</option> 
        <option value="31" <?php if($day == 31){echo("selected");}?>>31</option> 
      </select> 
      &#8212; 
      <select name="hour"> 
        <option value="00" <?php if($hour == 0){echo("selected");}?>>00</option> 
        <option value="01" <?php if($hour == 1){echo("selected");}?>>01</option> 
        <option value="02" <?php if($hour == 2){echo("selected");}?>>02</option> 
        <option value="03" <?php if($hour == 3){echo("selected");}?>>03</option> 
        <option value="04" <?php if($hour == 4){echo("selected");}?>>04</option> 
        <option value="05" <?php if($hour == 5){echo("selected");}?>>05</option> 
        <option value="06" <?php if($hour == 6){echo("selected");}?>>06</option> 
        <option value="07" <?php if($hour == 7){echo("selected");}?>>07</option> 
        <option value="08" <?php if($hour == 8){echo("selected");}?>>08</option> 
        <option value="09" <?php if($hour == 9){echo("selected");}?>>09</option> 
        <option value="10" <?php if($hour == 10){echo("selected");}?>>10</option> 
        <option value="11" <?php if($hour == 11){echo("selected");}?>>11</option> 
        <option value="12" <?php if($hour == 12){echo("selected");}?>>12</option> 
        <option value="13" <?php if($hour == 13){echo("selected");}?>>13</option> 
        <option value="14" <?php if($hour == 14){echo("selected");}?>>14</option> 
        <option value="15" <?php if($hour == 15){echo("selected");}?>>15</option> 
        <option value="16" <?php if($hour == 16){echo("selected");}?>>16</option> 
        <option value="17" <?php if($hour == 17){echo("selected");}?>>17</option> 
        <option value="18" <?php if($hour == 18){echo("selected");}?>>18</option> 
        <option value="19" <?php if($hour == 19){echo("selected");}?>>19</option> 
        <option value="20" <?php if($hour == 20){echo("selected");}?>>20</option> 
        <option value="21" <?php if($hour == 21){echo("selected");}?>>21</option> 
        <option value="22" <?php if($hour == 22){echo("selected");}?>>22</option> 
        <option value="23" <?php if($hour == 23){echo("selected");}?>>23</option> 
      </select> 
      : 
      <select name="minute"> 
        <option value="00" <?php if($minute == 0){echo("selected");}?>>00</option> 
        <option value="01" <?php if($minute == 1){echo("selected");}?>>01</option> 
        <option value="02" <?php if($minute == 2){echo("selected");}?>>02</option> 
        <option value="03" <?php if($minute == 3){echo("selected");}?>>03</option> 
        <option value="04" <?php if($minute == 4){echo("selected");}?>>04</option> 
        <option value="05" <?php if($minute == 5){echo("selected");}?>>05</option> 
        <option value="06" <?php if($minute == 6){echo("selected");}?>>06</option> 
        <option value="07" <?php if($minute == 7){echo("selected");}?>>07</option> 
        <option value="08" <?php if($minute == 8){echo("selected");}?>>08</option> 
        <option value="09" <?php if($minute == 9){echo("selected");}?>>09</option> 
        <option value="10" <?php if($minute == 10){echo("selected");}?>>10</option> 
        <option value="11" <?php if($minute == 11){echo("selected");}?>>11</option> 
        <option value="12" <?php if($minute == 12){echo("selected");}?>>12</option> 
        <option value="13" <?php if($minute == 13){echo("selected");}?>>13</option> 
        <option value="14" <?php if($minute == 14){echo("selected");}?>>14</option> 
        <option value="15" <?php if($minute == 15){echo("selected");}?>>15</option> 
        <option value="16" <?php if($minute == 16){echo("selected");}?>>16</option> 
        <option value="17" <?php if($minute == 17){echo("selected");}?>>17</option> 
        <option value="18" <?php if($minute == 18){echo("selected");}?>>18</option> 
        <option value="19" <?php if($minute == 19){echo("selected");}?>>19</option> 
        <option value="20" <?php if($minute == 20){echo("selected");}?>>20</option> 
        <option value="21" <?php if($minute == 21){echo("selected");}?>>21</option> 
        <option value="22" <?php if($minute == 22){echo("selected");}?>>22</option> 
        <option value="23" <?php if($minute == 23){echo("selected");}?>>23</option> 
        <option value="24" <?php if($minute == 24){echo("selected");}?>>24</option> 
        <option value="25" <?php if($minute == 25){echo("selected");}?>>25</option> 
        <option value="26" <?php if($minute == 26){echo("selected");}?>>26</option> 
        <option value="27" <?php if($minute == 27){echo("selected");}?>>27</option> 
        <option value="28" <?php if($minute == 28){echo("selected");}?>>28</option> 
        <option value="29" <?php if($minute == 29){echo("selected");}?>>29</option> 
        <option value="30" <?php if($minute == 30){echo("selected");}?>>30</option> 
        <option value="31" <?php if($minute == 31){echo("selected");}?>>31</option> 
        <option value="32" <?php if($minute == 32){echo("selected");}?>>32</option> 
        <option value="33" <?php if($minute == 33){echo("selected");}?>>33</option> 
        <option value="34" <?php if($minute == 34){echo("selected");}?>>34</option> 
        <option value="35" <?php if($minute == 35){echo("selected");}?>>35</option> 
        <option value="36" <?php if($minute == 36){echo("selected");}?>>36</option> 
        <option value="37" <?php if($minute == 37){echo("selected");}?>>37</option> 
        <option value="38" <?php if($minute == 38){echo("selected");}?>>38</option> 
        <option value="39" <?php if($minute == 39){echo("selected");}?>>39</option> 
        <option value="40" <?php if($minute == 40){echo("selected");}?>>40</option> 
        <option value="41" <?php if($minute == 41){echo("selected");}?>>41</option> 
        <option value="42" <?php if($minute == 42){echo("selected");}?>>42</option> 
        <option value="43" <?php if($minute == 43){echo("selected");}?>>43</option> 
        <option value="44" <?php if($minute == 44){echo("selected");}?>>44</option> 
        <option value="45" <?php if($minute == 45){echo("selected");}?>>45</option> 
        <option value="46" <?php if($minute == 46){echo("selected");}?>>46</option> 
        <option value="47" <?php if($minute == 47){echo("selected");}?>>47</option> 
        <option value="48" <?php if($minute == 48){echo("selected");}?>>48</option> 
        <option value="49" <?php if($minute == 49){echo("selected");}?>>49</option> 
        <option value="50" <?php if($minute == 50){echo("selected");}?>>50</option> 
        <option value="51" <?php if($minute == 51){echo("selected");}?>>51</option> 
        <option value="52" <?php if($minute == 52){echo("selected");}?>>52</option> 
        <option value="53" <?php if($minute == 53){echo("selected");}?>>53</option> 
        <option value="54" <?php if($minute == 54){echo("selected");}?>>54</option> 
        <option value="55" <?php if($minute == 55){echo("selected");}?>>55</option> 
        <option value="56" <?php if($minute == 56){echo("selected");}?>>56</option> 
        <option value="57" <?php if($minute == 57){echo("selected");}?>>57</option> 
        <option value="58" <?php if($minute == 58){echo("selected");}?>>58</option> 
        <option value="59" <?php if($minute == 59){echo("selected");}?>>59</option> 
      </select><br> 
      Notes<br> 
      <textarea name="notes"><?php echo !empty($notes)?$notes:'';?></textarea><br> 
      <input name="submit" type="submit" value="Update Book"><br> 
      <a href="index.php">Back</a> 
    </form> 
  </body> 

</html> 

Second part of the codes is where record update process happens. Copy the codes below to the beginning of "update.php" file; we will go through them afterwards:

<?php 
  require "database.php"; 
  
  $id = null; 
  if(!empty($_GET['id'])){ 
    $id = $_REQUEST['id']; 
  } 
  
  if (null == $id){ 
    header("Location: index.php"); 
  } 
  if (!empty($_POST)) {
    //keep track post values 
    $title = isset($_POST['title'])?$_POST['title']:''; 
    $author = isset($_POST['author'])?$_POST['author']:''; 
    $date_pub = (isset($_POST['year'])?$_POST['year']:'')."-".(isset($_POST['month'])?$_POST['month']:'')."-".(isset($_POST['day'])?$_POST['day']:'')." ".(isset($_POST['hour'])?$_POST['hour']:'').":".(isset($_POST['minute'])?$_POST['minute']:'').":00"; 
    $notes = isset($_POST['notes'])?$_POST['notes']:''; 

    //update data 
    $pdo = Database::connect(); 
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 
    $sql = "UPDATE Books SET title = ?, author = ?, date_pub = ?, notes = ? WHERE id = ?"; 
    $q = $pdo->prepare($sql); 
    $q->execute(array($title,$author,$date_pub,$notes,$id)); 
    
    $sql = "SELECT * FROM Books"; 
    foreach($pdo->query($sql) as $row){ 
      if(($title == $row['title']) && ($date_pub == $row['date_pub'])){ 
        $id = $row['id']; 
      } 
    } 
    
    Database::disconnect(); 
    header("Location: read.php?id=".$id); 
  } 
  else{ 
    $pdo = Database::connect(); 
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 
    $sql = "SELECT * FROM Books WHERE id = ?"; 
    $q = $pdo->prepare($sql); 
    $q->execute(array($id)); 
    $data = $q->fetch(PDO::FETCH_ASSOC); 
    $title = $data['title']; 
    $author = $data['author']; 
    $date_pub = $data['date_pub']; 
    date_default_timezone_set('Europe/London'); 
    $timestamp = strtotime($date_pub); 
    $year = idate('Y', $timestamp); 
    $month = idate('m', $timestamp); 
    $day = idate('d', $timestamp); 
    $hour = idate('H', $timestamp); 
    $minute = idate('i', $timestamp); 
    $notes = $data['notes']; 
    Database::disconnect(); 
  } 
?> 

As you see, firstly we check if there is form submit by checking $_POST variable. If so, we check each entries to ensure they pass validation rules, after that it updates database using $_POST data. At last it will redirect to "index.php" using PHP header() function. On the flip side, it is a $_GET request, it will retrieve data record from database.

If you have followed correctly. Clicking "Update" button from "index.php" page should lead you to a page similar to below:


Step 9: Creating a "Delete" page

Here comes the final piece. Create a PHP file "delete.php"; Copy codes below to the file and we will explain afterwards.

<?php 
  require "database.php"; 
  
  $id = 0; 
  if (!empty($_GET['id'])){ 
    $id = $_REQUEST['id']; 
  } 

  if (!empty($_POST)){ 
    //keep track post values 
    $id = isset($_POST['id'])?$_POST['id']:''; 
    
    //delete data 
    $pdo = Database::connect(); 
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 
    $sql = "DELETE FROM Books WHERE id = ? LIMIT 1"; 
    $q = $pdo->prepare($sql); 
    $q->execute(array($id)); 
    Database::disconnect(); 
    header("Location: index.php"); 
  } 
  else{ 
    $pdo = Database::connect();  
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 
    $sql = "SELECT * FROM Books WHERE id = ?";  
    $q = $pdo->prepare($sql);  
    $q->execute(array($id));  
    $data = $q->fetch(PDO::FETCH_ASSOC);  
    Database::disconnect();  
  } 
?>
 
<!DOCTYPE html> 
<html>
 
  <head> 
    <h1>Destroying book</h1> 
  </head> 
  
  <body> 
    <p><b>Title: </b><?php echo $data['title'];?></p> 
    <p><b>Author: </b><?php echo $data['author'];?></p> 
    <p><b>Published at: </b><?php echo $data['date_pub'];?> UTC</p> 
    <p><b>Notes: </b><?php echo $data['notes'];?></p> 
    <form action="delete.php" method="post"> 
      <input type="hidden" name="id" value="<?php echo $id;?>"/> 
      <input name="delete" type="submit" value="Delete Book"><br> 
    </form> 
    <a href="index.php">Back</a> 
  </body> 

</html> 

Let us look at the beginning part of the PHP codes. It firstly capture the $id from $_GET request. Once a $_GET request is determined. It shows a confirmation page. If a $_POST request is detected, it indicates that user has click confirmation button "Yes". Then it will proceed to delete the data record and redirects to "index.php" page.

Next part, which is the static html part is pretty simple. It simple store the $_GET['id'] to a hidden field.

If you get it working. "delete.php" page should look like below. If you click "Yes" button, it should direct to index page and the selected record should be deleted.


Final and Source Code

You can download the full source code here.

Return to Internship Note (LoanStreet)
Previous Episode:
Next Episode: Install Ubuntu Desktop