CRUD & Bootstrap

MVC Review

Remember this handy diagram?

routes

What the CRUD?

In Rails we can easily generate a set of files that enable CRUD operations. What is CRUD you ask? 

$ rails g scaffold Destination city:string country:string description:text

Scaffold

Let's open up our travel projects and add to them.

 

To get our CRUD goodies we use a command called scaffold. Let's scaffold a resource called "Destination" with 3 attributes: city, country and description.

$ rake db:migrate

Then run the rake command to migrate the data columns.

Resource Routing

Where do we browse this page? /destinations of course!

Destinations

Notice that the scaffold command generated many more views this time. We have a set of files that allow us to create, read, update and delete (CRUD) records in our database.

It also created a model (which we'll get to later in the course) and some assets. Let's open up the scaffolds.css.scss file and delete it's contents so they don't interfere with the styles we'll be writing.

Update the Nav

Let's go back to layouts/application.html.erb and add a link to our destinations index to the navbar.

 

Remember how to do that using erb?

<ul class="nav navbar-nav">
  <li class="active">
    <%= link_to 'About', about_path %>
  </li>
  <li>
     <%= link_to 'Destinations', destinations_path %>
  </li>
...

Add Some Data

What are a few cities/ countries you'd like to visit? Make sure to add several cities in the same country and add a few countries with multiple words like "New Zealand."

Using Partials

Anytime we see a file with an underscore preceding its name this is referred to as a "partial." It's essentially just a snippet of code that can be used repeatedly in our site. The "_form" file, for example, is used in both the edit and new pages to generate a form.

<%= render 'form' %>
_form is referenced or 'rendered' in
new.html.erb & edit.html.erb 

Fancier Forms

Let's fancy up this Rails generated form we've been chatting about.

  <div class="field form-group">
    <%= f.label :city %><br>
    <%= f.text_field :city, class: "form-control" %>
  </div>
  <div class="field form-group">
    <%= f.label :country %><br>
    <%= f.text_field :country, class: "form-control" %>
  </div>
  <div class="field form-group">
    <%= f.label :description %><br>
    <%= f.text_area :description, class: "form-control", rows: "5" %>
  </div>
  <div class="actions form-group">
    <%= f.submit class: "btn btn-primary" %>
  </div>
app/destinations/_form.html.erb
<h1>My Destination Wish List</h1>

  <div class="row">
    <% @destinations.each do |destination| %>
      <div class="col-md-4">
       <div class="panel panel-default">
        <div class="panel-body">
         <h2><%= destination.city %></h2>
         <h3<%= destination.country %></h3>
         <p><%= destination.description %></p>
       
         <div class="btn-group">
          <%= link_to 'Show', destination, class: "btn btn-default" %>
          <%= link_to 'Edit', edit_destination_path(destination), class: "btn btn-default" %>
          <%= link_to 'Destroy', destination, method: :delete, data: { confirm: 'Are you sure?' }, class: "btn btn-default" %>
         </div>
        </div>
      </div>
    </div>
  <% end %>
</div>

<%= link_to 'New Destination', new_destination_path, class: "btn btn-primary" %>

Further Fancification

Let's give a facelift to our destinations index page. We'll update the h1, remove the table, add a 3 column grid, panels, and button groups. 

app/views/destinations/index.html.erb

And even more...

We can even make our show page a little nicer.

app/views/destinations/show.html.erb
<h1><%= @destination.city %>, <%= @destination.country %></h1>

<p> <%= @destination.description %></p>

<div class="btn-group">
  <%= link_to 'Edit', edit_destination_path(@destination), class: "btn btn-default" %> 
  <%= link_to 'Back', destinations_path, class: "btn btn-default" %>
</div>

Alert Challenge

http://getbootstrap.com/components/#alerts

Let's style up our "notice" text with a Bootstrap alert "success" style. Note that this text only appears when we do something awesome like create a new destination.

It will say:

 

 "Destination was successfully created."

 

Click back to the destinations index page and notice that the green box is still appearing. How would we get it to ONLY appear when there's text inside?

Alert Solution

<% if notice != nil %>
  <p id="notice" class="alert alert-success" role="alert">
    <%= notice %>
  </p>
<% end %>

This is really simple Ruby, right? Well, embedded Ruby. If notice isn't nil, or empty then display the paragraph tag. This logic can be added to the top of both the index and show pages. 

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

Before:

After:

Filter w/ Query String

Remember our good friend the query string? Instead of using it to change the page color we can use it to filter our destinations by country; a much more useful application.

  def index
    # @destinations = Destination.all
    @destinations = Destination.where(country: params[:country])
  end

If you have France in your database try navigating to:

/destinations?country=France

app/controllers/destinations_controller.rb

Filter Challenge

We've lost the ability to see ALL our countries ... and isn't that the point of the index page?

1. How can we write some logic to solve this?

 

Secondly we're not able to use lowercase variables in our query string. We want "France" and "france" to both work.

2. How can we make the filter work with words that begin with either an uppercase or lowercase letter? 

 

Finally, we need to make sure that "New_Zealand", "new_zealand", "New_zealand" and "new_Zealand" filter correctly.

3. How can we ensure the filter works when our countries have multiple words? 

  def index
    if params[:country] == nil
      @destinations = Destination.all 
    else
      @destinations = Destination.where(country: params[:country].titleize)
    end
  end
app/controllers/destinations_controller.rb

Filter Solution

We have to write some logic to check if the country parameter is nil (or empty). If so, display all records. If not, display the specific country passed through the query string, and titleize it (meaning capitalize each letter in the string if there are multiple words).

<%= link_to "All", destinations_path %>
# /destinations

<%= link_to "New Zealand", controller: "destinations", country: "new_zealand" %>
# /destinations?country=new_zealand

<%= link_to "France", controller: "destinations", country: "france" %>
# /destinations?country=france

Query String + link_to

Let's put some links at the top of our destinations index page that set the query string for us. This way we don't have to keep typing it into the address bar.

Notice that countries consisting of  2 words can be combined with an underscore. Spaces in URLs are generally bad practice so let's try to steer clear. 

Nav It Up

<ul class="nav nav-pills" role="tablist">
  <li><%= link_to "All", destinations_path %></li>
  <li><%= link_to "New Zealand", controller: "destinations", country: "new_zealand" %></li>
  <li><%= link_to "France", controller: "destinations", country: "france" %></li>
</ul>

Let's add some nav classes to our links so they sit nicely spaced out at the top of our page.

Dynamic Link
Challenge

What if there were a dozen or so countries in your database? Manually typing out each link sounds like a pain in the butt.

 

How can we dynamically pull country names so that whenever we create a new country the link will magically appear in our link list?

 

Hint: Push countries into an array in the controller...

 

Extra Credit: Alphabetize the list of countries.

Dynamic Solution

<ul class="nav nav-pills" role="tablist">
  <li><%= link_to "All", destinations_path %></li>
  <% @my_countries.each do |country| %>
    <li><%= link_to "#{country}", controller: "destinations", country: "#{country}" %></li>
  <% end %>
</ul>
 @destinations = Destination.all
    
 @my_countries = []
 
 @destinations.each do |destination|
   @my_countries.push(destination.country)
 end

 @my_countries = @my_countries.uniq

 # Alphabetical
 # @my_countries = @my_countries.uniq.sort_by{|country| country.downcase|}
app/controllers/destinations_controller.rb
app/views/destinations/index.html.erb

Filter w/ Routes

There's another way to filter our records. We could set up a new action in our destinations controller with a variable that selects only records that meet a certain criteria.

  def show_france 
    @france = Destination.where(country:"France")
  end
app/controllers/destinations_controller.rb
<div class="row">
    <% @france.each do |oohlala| %>
      <div class="col-md-4">
       <div class="panel panel-default">
        <div class="panel-body">
         <h2><%= oohlala.city %></h2>
         <p><%= oohlala.description %></p>
        </div>
      </div>
    </div>
  <% end %>
</div>

 Then create a corresponding view and route to match.

Filter w/ Routes

app/views/show_france.html.erb
config/routes.rb
  get "france" => "destinations#show_france"

A gem called Paperclip

Let's install a gem that helps us store images in our database. Instructions are here:

https://github.com/thoughtbot/paperclip 

Homework:

Get Paperclip working so you're able to add, update, remove and show images associated with each destination. Then add some logic so images only display if they exist in the database. (read: do not display broken images)

More Homework

Add Bootstrap and Paperclip to your Blog app 

 

Scaffold blog, blog_post and comments.

 

Rip out those CRUD tables and play with using 3 or 4 column grids to display content. Add styles like we did in class today and get creative!

 

Lab Tomorrow:

1-3pm in the Money Room

 

Travel App Part II

By tts-jaime

Travel App Part II

Part II of Views/Controllers week (PT)

  • 1,352