Giter Club home page Giter Club logo

tacta's Introduction

Tacta

Rails example of a simple Contact Manager.

New

Generate new rails app.

> rails new tacta

Model

Create the Contact model.

> rails generate model contact name:string phone:string email:string

Adds a model file:

# app/models/contact.rb

class Contact < ActiveRecord::Base
end

And a migration:

# db/migrate/20160214094710_create_contacts.rb

class CreateContacts < ActiveRecord::Migration
  def change
    create_table :contacts do |t|
      t.string :name
      t.string :phone
      t.string :email

      t.timestamps null: false
    end
  end
end

Migrate

Migrate the Database.

> rake db:migrate

Adds the Model's table to the database, with columns for name, phone, and email.

Seeds

Create some database seeds for Contacts.

# db/seeds.rb

Contact.create( { name: "Thomas Jefferson", phone: "+1 206 310 1369" , email: "[email protected]"       } )
Contact.create( { name: "Charles Darwin"  , phone: "+44 20 7123 4567", email: "[email protected]"  } )
Contact.create( { name: "Nikola Tesla"    , phone: "+385 43 987 3355", email: "[email protected]" } )
Contact.create( { name: "Genghis Khan"    , phone: "+976 2 194 2222" , email: "[email protected]" } )
Contact.create( { name: "Malcom X"        , phone: "+1 310 155 8822" , email: "[email protected]"     } )

Seed the database:

> rake db:seed

Or reset the database if you have already seeded it before. Clears out the old data first.

> rake db:reset

May give a drop table error if open in a DB viewer or elsewhere, but ok.

Console

Check to see the data in the database.

> rails console

irb(main):008:0>  Contact.all.each { |c| puts c.inspect }

Contact Load (1.0ms)  SELECT "contacts".* FROM "contacts"

#<Contact id: 1, name: "Thomas Jefferson", phone: "+1 206 310 1369", email: "[email protected]", created_at: "2016-02-14 10:51:15", updated_at: "2016-02-14 10:51:15">
#<Contact id: 2, name: "Charles Darwin", phone: "+44 20 7123 4567", email: "[email protected]", created_at: "2016-02-14 10:51:15", updated_at: "2016-02-14 10:51:15">
#<Contact id: 3, name: "Nikola Tesla", phone: "+385 43 987 3355", email: "[email protected]", created_at: "2016-02-14 10:51:15", updated_at: "2016-02-14 10:51:15">
#<Contact id: 4, name: "Genghis Khan", phone: "+976 2 194 2222", email: "[email protected]", created_at: "2016-02-14 10:51:15", updated_at: "2016-02-14 10:51:15">
#<Contact id: 5, name: "Malcom X", phone: "+1 310 155 8822", email: "[email protected]", created_at: "2016-02-14 10:51:15", updated_at: "2016-02-14 10:51:15">

Controller

Generate controller for Contacts.

> rails generate controller contacts

Produces

# app/controllers/contacts_controller.rb

class ContactsController < ApplicationController
end

Index View

Add Index action method to controller.

class ContactController < ApplicationController

   def index
      @contacts = Contact.all
   end

end

Create index view.

# app/views/contacts/index.html.erb

<h1>Contacts</h1>

<% @contacts.each do |contact| %>
    <p><%= contact.name %></p>
<% end %>

Add index to routes.

# config/routes.rb

Rails.application.routes.draw do

   get 'contacts' => 'contacts#index'

end

Root

Set home page to be index.

Rails.application.routes.draw do

   get 'contacts' => 'contacts#index'

   root 'contacts#index'

end

Show View

Add Show action method to controller.

class ContactsController < ApplicationController

   def index
      @contacts = Contact.all
   end

   def show
      @contact = Contact.find( params[:id] )
   end

end

Create a Show view.

# app/views/show.html.erb

<h1><%= @contact.name %></h1>

<p>Phone: <%= @contact.phone %></p>
<p>Email: <%= @contact.email %></p>

Add a show route.

Rails.application.routes.draw do

   get 'contacts' => 'contacts#index'
   get 'contacts/:id' => 'contacts#show'

   root 'contacts#index'

end

Links

Link contacts on index page to their show page.

<% @contacts.each do |contact| %>
    <p><%= link_to contact.name, contact_path( contact.id ) %></p>
<% end %>

Augment show route to generate the contact_path helper.

Rails.application.routes.draw do

   get 'contacts' => 'contacts#index'
   get 'contacts/:id' => 'contacts#show', as: :contact

end

Home Link

Add a link to home in the application layout, that will appear on all pages.

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

...

<body>

<%= yield %>

<br><br>
<hr>

<%= link_to "[All Contacts]", root_path %>

</body>

New Contact

Add a New action method to Contact controller.

class ContactsController < ApplicationController

   ...

   def new
      @contact = Contact.new
   end

end

Create a New Contact form.

<h1>New Contact</h1>

<%= form_for @contact do |f| %>

    <%= f.label :name %>
    <%= f.text_field :name %>

    <%= f.label :phone %>
    <%= f.text_field :phone, size: 15 %>

    <%= f.label :email %>
    <%= f.text_field :email, size: 20 %>

    <%= f.submit "Create" %>

<% end %>

Add a New Contact route. Must be before the Show route, or it will match first.

Rails.application.routes.draw do

   get 'contacts' => 'contacts#index'
   get 'contacts/new' => "contacts#new"
   get 'contacts/:id' => 'contacts#show', as: :contact

end

Able to now see the form at

localhost:3000/contacts/new

New Link

Add link to New Contact to Index page.

<h1>Contacts</h1>

<% @contacts.each do |contact| %>
    <p><%= link_to contact.name, contact_path( contact.id ) %></p>
<% end %>

<%= link_to "[New Contact]", new_contact_path %>

Augment route to generate new_contact_path helper used in the link.

get 'contacts/new' => "contacts#new", as: :new_contact

Create Contact

Add Create action method to Contacts controller, to handle form submit.

class ContactsController < ApplicationController

   ...

   def create
      contact_params = params.require( :contact ).permit( :name, :phone, :email )

      @contact = Contact.new( contact_params )

      if @contact.save
         redirect_to @contact
      else
         render 'new'
      end
   end

end

Add route for create post.

Rails.application.routes.draw do

   ...

   get 'contacts/new' => "contacts#new", as: :new_contact
   post 'contacts' => "contacts#create"

end

Edit Form

To share the form for edit and new, create a _form partial view.

# app/views/contacts/_form.html.erb

<%= form_for @contact do |f| %>

    <%= f.label :name %>
    <%= f.text_field :name %>

    <%= f.label :phone %>
    <%= f.text_field :phone, size: 15 %>

    <%= f.label :email %>
    <%= f.text_field :email, size: 20 %>

    <%= f.submit (@contact.new_record? ? "Create" : "Update") %>

<% end %>

Note the change to the submit button, to test for new or edit.

Alter the New view to use the partial.

<h1>New Contact</h1>

<%= render "form" %>

Create an Edit view with the same partial.

<h1>Edit Contact</h1>

<%= render "form" %>

Add an edit route

Rails.application.routes.draw do

   ...

   get 'contacts/new' => "contacts#new", as: :new_contact
   get 'contacts/:id/edit' => 'contacts#edit'

end

Now can view the edit form at

localhost:3000/contacts/5/edit

Edit Link

Add Edit link to Contact Show view.

<h1><%= @contact.name %></h1>

<p>Phone: <%= @contact.phone %></p>
<p>Email: <%= @contact.email %></p>

<%= link_to "[Edit]", edit_contact_path( @contact.id ) %>

Augment route to generate edit_contact_path helper used in the link.

get 'contacts/:id/edit' => 'contacts#edit', as: :edit_contact

Update

Add Update action method to Contacts controller, to handle edit form submit.

def update
  @contact = Contact.find( params[:id] )

  contact_params = params.require( :contact ).permit( :name, :phone, :email )

  if @contact.update_attributes( contact_params )
     redirect_to @contact
  else
     render 'edit'
  end
end

Add route for update patch.

Rails.application.routes.draw do

   ...

   post 'contacts' => "contacts#create"
   patch 'contacts/:id' => "contacts#update"

end

Now submiting the edit form will update the database.

Factor contact_params

Factor the common code for contact params into a private method.

class ContactsController < ApplicationController

   ...

private

   def contact_params
      params.require( :contact ).permit( :name, :phone, :email )
   end

end

Remove the old code for contact_params from the create and update methods.

Delete Contact

Add Destroy action method to Contacts controller.

class ContactsController < ApplicationController

   ...

   def destroy
      @contact = Contact.find( params[:id] )

      @contact.destroy

      redirect_to contacts_path
   end

end

Add a Delete link to the Contact Show page.

<h1><%= @contact.name %></h1>

<p>Phone: <%= @contact.phone %></p>
<p>Email: <%= @contact.email %></p>

<%= link_to "[Edit]", edit_contact_path( @contact.id ) %>
<%= link_to "[Delete]", contact_path( @contact.id ), method: :delete, data: { confirm: "Are you sure?" } %>

Add route for delete Contact to the destroy Contact action.

Rails.application.routes.draw do

   ...

   delete 'contacts/:id' => "contacts#destroy"

end

Resources Routes

The many routes for individual actions can be replaced with a resources statement.

Rails.application.routes.draw do

   resources :contacts
   # get 'contacts' => 'contacts#index'
   # get 'contacts/new' => "contacts#new", as: :new_contact
   # get 'contacts/:id/edit' => 'contacts#edit', as: :edit_contact
   # get 'contacts/:id' => 'contacts#show', as: :contact
   # post 'contacts' => "contacts#create"
   # patch 'contacts/:id' => "contacts#update"
   # delete 'contacts/:id' => "contacts#destroy"

   root 'contacts#index'

end

Could have used resources from the beginning, but instructive to see the code for each route.

Test Index

Test to see if index page renders without errors.

# app/test/controllers/contacts_controller_test.rb

class ContactsControllerTest < ActionController::TestCase

   test "should get index" do
      get :index
      assert_response :success
   end

end

Make some records for the Test Database

tjeff:
   name: Thomas Jefferson
   phone: +1 206 310 1369
   email: [email protected]

cdar:
   name: Charles Darwin
   phone: +44 20 7123 4567
   email: [email protected]

cdar:
   name: Nikola Tesla
   phone: +385 43 987 3355
   email: [email protected]

cdar:
   name: Genghis Khan
   phone: +976 2 194 2222
   email: [email protected]

cdar:
   name: Malcom X
   phone: +1 310 155 8822
   email: [email protected]

Run tests

> rake test

Finished in 0.405137s, 2.4683 runs/s, 2.4683 assertions/s.

1 runs, 1 assertions, 0 failures, 0 errors, 0 skips

Tests - Show, Edit & New

Test to get Show, Edit and New pages.

class ContactsControllerTest < ActionController::TestCase

   ...

   test "should get show" do
      get :show, id: contacts(:tjeff).id
      assert_response :success
   end

   test "should get new" do
      get :new
      assert_response :success
   end

   test "should get edit" do
      get :edit, id: contacts(:tjeff).id
      assert_response :success
   end

end

Test Create, Update and Delete

Test for modify actions Create, Update and Delete

class ContactsControllerTest < ActionController::TestCase

   ...

   test "should create contact" do
      assert_difference( 'Contact.count' ) do
         post :create, contact: { name: "Nelson Mandela", phone: "+27 21 654-4321", email: "[email protected]" }
      end

      assert_redirected_to contact_path( assigns( :contact ) )
   end

   test "should update contact" do
      patch :update, id: contacts(:cdar).id, contact: { name: "Albert Einstein", phone: "+49 40 2244 3355", email: "[email protected]" }

      assert_redirected_to contact_path( assigns(:contact) )
   end

   test "should destroy contact" do
      assert_difference('Contact.count', -1) do
         delete :destroy, id: contacts(:cdar).id
      end

      assert_redirected_to contacts_path
   end
end

Test Model

A simple model test to create a new Contact.

# test/models/contact_test.rb

class ContactTest < ActiveSupport::TestCase

   test "should save new contact" do
      contact = Contact.new( name: "Alan Turing", phone: "+44 20 7123 7654", email: "[email protected]" )
      assert contact.save
   end

end

Validation & Forms

Add validations to Contact that enforce rules about the data.

# app/models/contact.rb

class Contact
   validates :name, presence: true
   validates :name, uniqueness: true

   validates :phone, allow_blank: true, length: { in: 5..20 }
   validates :email, allow_blank: true, length: { in: 5..50 }

   validates_format_of :phone, allow_blank: true, :with => /\A[+ 0-9]+$\z/
   validates_format_of :email, allow_blank: true, :with => /@/, message: 'must contain @.'
end

A save operation to the database will fail if data is not valid. On failure, Rails attaches error to the object. When save fails in the controller, we rerender the form.

class ContactsController

   # ...

   def create
      @contact = Contact.new( contact_params )

      if @contact.save
         redirect_to @contact
      else
         render 'new'
      end
   end

end

And similar to fail to update on edit.

class ContactsController

   # ...

   def update
      @contact = Contact.find( params[:id] )

      if @contact.update_attributes( contact_params )
         redirect_to @contact
      else
         render 'edit'
      end
   end

end

Display the error messages in the Contact form.

<%= form_for @contact do |f| %>

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

   <%= f.label :name %>
   <%= f.text_field :name %>
   <%= f.label :phone %>
   <%= f.text_field :phone, size: 15 %>
   <%= f.label :email %>
   <%= f.text_field :email, size: 20 %>
   <%= f.submit (@contact.new_record? ? "Create" : "Update") %>
<% end %>

Rails also wraps the error fields in divs automatically. After errors, the form will render to something like

<form class="new_contact" id="new_contact" action="/contacts" method="post">

   <ul>
          <li>Name can&#39;t be blank</li>
   </ul>

   <div class="field_with_errors"><label for="contact_name">Name</label></div>
   <div class="field_with_errors"><input type="text" value="" name="contact[name]" id="contact_name" /></div>
   <label for="contact_phone">Phone</label>
   <input size="15" type="text" value="" name="contact[phone]" id="contact_phone" />
   <label for="contact_email">Email</label>
   <input size="20" type="text" value="" name="contact[email]" id="contact_email" />
   <input type="submit" name="commit" value="Create" />
</form>

Highlight the fields with errors using style rules to give a red border and background.

div.field_with_errors {
    display: inline;

    input {
        padding: 2px 2px;
        border: 1px solid #E66;
        background-color: #FFF7F7;
    }
}

Summary

The Rails version of Tacta builds on concepts from the Ruby and Sinatra versions, adding

  • Rails
  • Models
  • Databases
  • Migrations
  • Routes to Controllers & Actions
  • Resources
  • Link helpers
  • Form helpers
  • Safe params
  • Validation
  • Partials
  • Form errors
  • Testing (Controller Actions and Models)

And strengthens concepts

  • Views
  • Erb
  • Restful Structure

Does not cover

  • Multiple models, Joins, Many to X
  • Sessions
  • Users/Login & Devise

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.