Hands On Forms Demo

Set up and modify gem file

rails new formsdemo

gem 'binding_of_caller'
gem 'awesome_print'
gem 'better_errors'
gem 'hirb'

bundle

Make models and define associations

rails generate scaffold items title:string  description:string owner:text category_id:integer
rails generate scaffold categories title:string description:string
rails generate scaffold comments from:string message:string item_id:integer

Model changes

# category.rb
has_many :items

# item.rb
belongs_to :category
has_many :comments

Create seed data (seeds.rb)

  • Notice an interesting way to create test data
puts "creating categories"
Category.destroy_all

books = Category.create(title: "book", description: "Books, Magazines etc.")
electronics = Category.create(title: "electronics", description: "Computer, Cellphone, etc")
clothing = Category.create(title: "clothing", description: "Shoes, Jackets, Belts, etc")
jewelry = Category.create(title: "jewelry", description: "Rings, watches, necklaces, etc")

owners = ["Jane Smith", "Ira Levin", "George Benson"]
colors = ["red", "brown", "green", "white", "tiny"]
clothingnames = ["shoes", "wallet", "belt"]
adjective = ["My favorite", "The perfect", "A great"]
categories = [books, electronics, clothing, jewelry]

puts "creating sample items"
50.times do
  title = colors.sample + " " + clothingnames.sample
  description = adjective.sample + " " + title
  Item.create(title: title, description: description, owner: owners.sample, category: categories.sample)
end

Create dbs and add seed data

rails db:migrate
rails db:seed

Add Bootstrap

  • Notice not using a gem, to keep it simple
  • Reference: https://getbootstrap.com/docs/4.0/getting-started/introduction/
  • Update views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>Formsdemo</title>
    <%= csrf_meta_tags %>

    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous">
    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
    <div class="container">
      <div class="row">
        <%= yield %>
      </div>
    </div>

    <script src="https://code.jquery.com/jquery-3.1.1.slim.min.js" integrity="sha384-A7FZj7v+d/sdmMqp/nOQwliLvUsJfDHW+k9Omg/a/EheAdgtzNs3hpfag6Ed950n" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js" integrity="sha384-DztdAPBWPRXSA/3eYEEUWrWCy7G5KFbe8fFjk5JAIxUYHKkDx6Qin1DkWx51bBrb" crossorigin="anonymous"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js" integrity="sha384-vBWWzlZJ8ea9aCX4pEW3rVHjgjt7zpkNpZk+02D9phzyeVkE+jo0ieGizqPLForn" crossorigin="anonymous"></script>
  </body>
</html>

Update the routes

  • Notice: how the scategories resource stays flat
  • Notice how items > comments is a nested resource
  • Notice how the collection is set up
Rails.application.routes.draw do
  resources :categories
  resources :items do
    collection do
      get 'search'
      get 'do_search'
    end
    resources :comments, only: [:index, :create]
  end
  root 'items#index'

end

Define the search form search.html.erb

<div>
  <h1>Search</h1>
    <%= form_tag(do_search_items_path, method: "get") do %>
      <div>
        <label>Title</label>
        <%= text_field_tag(:title, "") %>
      </div>

      <%= submit_tag("Search") %>
    <% end %>
</div>

And it’s corresponding actions

  • Why does search have two actions?
  • What controller do these actions go in?
  • Notice how to do “like” in order to allow close matches
  • Notice how we factor out the index page so we get a parital
#[items_controller.rb]

  def search
  end

  def do_search
    @items = Item.where("title LIKE ?", "%#{params\[:title\]}%")
  end

#[views/items/_index.html.erb]
<table>
  <thead>
    <tr>
      <th>Title</th>
      <th>Description</th>
      <th>Owner</th>
      <th>Category</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @items.each do |item| %>
      <tr>
        <td><%= item.title %></td>
        <td><%= item.description %></td>
        <td><%= item.owner %></td>
        <td><%= item.category_id %></td>
        <td><%= link_to 'Show', item %></td>
        <td><%= link_to 'Edit', edit_item_path(item) %></td>
        <td><%= link_to 'Destroy', item, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

#[views/items/index.html.erb\]
<h1>Listing items</h1>

<%= render "index" %>
<br/>

<%= link_to 'New Item', new_item_path %>

Let’s add some Bootstrap

  • Bootstrap is already “included” in layouts/application.html.erb
  • We bring it to life by adding classes to strategic spots
# [views/items/index.html.erb]
<h1>Listing items</h1>

<%= render "index" %>
<br>
<div class=btn-group %>
  <%= link_to 'New Item', new_item_path, class: "btn btn-default" %>
  <%= link_to 'Search', search_items_path, class: "btn btn-default" %>
</div>

#[search.html.erb]
<div class="col-md-offset-1 col-md-4">
  <h1>Search</h1>
form_tag(do_search_items_path, method: "get", class: "form") do %>

      <div class="form-group">
        <label>Title</label>
        <%= text_field_tag(:title, "", class: "form-control") %>
      </div>

      <%= submit_tag("Search", class: "btn btn-default") %>
    <% end %>
</div>

Now lets add some additional controls

  • A drop down control which will allow user to pick search options

# [views/items/search.html.erb]
    <div class="form-group">
      <label>Return matching or non-matching items</label>
      <%= select_tag(:match_no_match,
            options_for_select(
            \[['only matching', "match"\],
             \['only non matching', "nomatch"\]
            ]), class: "form-control") %>
    </div>

#[controllers/items_controller.rb, in do_search\]
def do_search
  match_no_match = params.fetch(:match_no_match)
  negation = match_no_match == "match" ? "" : " NOT "
  query_string = negation + "title LIKE ?"
  @items = Item.where(query_string, "%#{params\[:title\]}%")
end