Rails Curriculum

From DevchixWiki

Jump to: navigation, search

Contents

Build and deploy a web application

We will build a web application that allows people to create topics and vote on them. Ultimately, this application will have registration and authentication and a variety of other features. Today we will focus on building the core features of topics and votes.

Here is the UI ([| User Interface]) design for the application:

File:Ui.jpg

Let's build a web application

  • Create a folder on the desktop for our programming stuff
  • Open the terminal or command line

rails suggestotron -m http://gist.github.com/316450.txt

Everything you need for your application is in this new folder. If you decide you don't like it, just delete it and start over with the rails command.

Note that the -m option is just a shortcut for a few commands listed in the github file (click here to see). It is optional. Usually you will not use that option, or maybe you will create your own template with your frequently used options). We'll explain them in more detail later.

  • Go to your new folder. Notice that Rails has created the following subdirectories within it:
File/Folder Purpose
README This is a brief instruction manual for your application.
Rakefile This file contains batch jobs that can be run from the terminal.
app/ Contains the controllers, models, and views for your application. You will do most of your work here.
config/ Configure your application’s runtime rules, routes, database, and more.
db/ Shows your current database schema, as well as the database migrations.
doc/ You would add documentation for your application here
features/ Added by the template. This is from cucumber, which is not part of core Rails
lib/ Extended modules for your application (not covered today)
log/ Application log files
public/ The only folder seen to the world as-is. This is where your images, JavaScript, stylesheets (CSS), and other static files go
script/ Scripts provided by Rails to do recurring tasks. We'll use some today
test/ Unit tests, fixtures, and other test apparatus
tmp/ Temporary files
vendor/ A place for third-party code


  • Run the web app:

ruby script/server

Make it your own

  • Start Komodo Edit
  • File -> New -> New Project...
  • Open your new "suggestotron" directory
  • View -> Tabs & Sidebars -> Projects

Edit index page

  • Note that the page you are seeing at http://localhost:3000 is in public/index.html
  • Make a change, save, and reload

Awesome! Let's ship it!

From your new "suggestotron" directory, create a local git repository:

git init
git add .
git commit -m 'basic web application'

Then deploy to heroku:

heroku create
git push heroku master

Notice the URL that heroku reports after you typed "heroku create." Type this URL in your browser to see your application on the Web.

A Closer Look at the Features

The features have been defined in the /features directory. These were written for you in advance as a specification of the application.

One of the basic features that we might build first is defined in the topics.feature file in the features directory:

Feature: Topics
     In order to see a list of potential topics for meetings
     people need to be able to create and edit them
   
     Scenario: Getting to the new topic page
       When I go to topics
       And I follow "New topic"
       Then I should see a "Create" button

You can run all of the features in that file with:

cucumber features/topics.feature

      • If you're on Windows, you might get this error:

cucumber.yml was found, but was blank or malformed. Please refer to cucumber's documentation on correct profile usage. If this is the case, open up config/cucumber.yml. If you see "default: --format pretty", get rid of the quotes, and try running the cucumber features again.


Of course, the feature fails because we haven't written any code yet! These feature descriptions are tests as well as documentation. Typically, we run the test, watch it fail, then implement a feature, then run the test again to see if it passes. We'll be doing that today.

The "topics" feature relies on some basic elements of a web app, which is what we'll build first. As we get features to pass, we'll look further at the features definitions to see what needs to be built.

To run a single feature scenario, you can provide its name:

cucumber features/topics.feature -n 'Getting to the new topic page'

First Feature: Adding topics

We can look through the features and screen shots and see how a topic is defined and how people expect to interact with it. We will use rails "scaffolding" to generate some pages and code.

  • topic will have a title and a description
  • we will enable basic "CRUD" actions:
    • Create - enter a new topic
    • Read - see everyone's topics
    • Update - change the topics you entered
    • Delete - remove your topics from the system

Scaffolding

  • Run this command line script to generate a Topic model along with the application logic and views:

ruby script/generate scaffold topic title:string description:text

  • Holy generated files, Batman!
  • Open the migration file (yours will have a different number):

db/migrate/20091014021209_create_topics.rb

See how it contains Ruby code to set up the database table. Migrations can also be used for modifying tables (add/remove/rename columns) and even modifying data.

  • Now let's set up the database for the test by running the migration file on the test database:

rake db:migrate RAILS_ENV=test

  • Run the cucumber feature again (and the first one should pass)

cucumber features/topics.feature

  • Now let's look at the feature we created. We will typically do so in the development environment. Create the topics table:

rake db:migrate

  • Then start your server:

ruby script/server

Congratulations! You have built a web application that works with a relational database.

A Quick Look at Your Database

You can access the database directly on your local machine. For this class we’re using SQLite, the GUI tool you installed that lets you inspect the database. In Firefox, select "Tools -> SQLite Manager."

Click the open folder icon File:Folder.jpg  or choose Database -> Connect Database to open suggestotron/db/ development.sqlite3

File:Sqllit.jpg

In SQLite, you can choose “Browse & Search” to interactively explore the database or “Execute SQL” to type SQL commands.

You can also access the database through the command line:

ruby script/dbconsole


Common SQL Commands
sqlite MySql
list tables in current db .tables show tables;
show SQL for table create list columns .schema

.schema people

show create table topics;

describe topics;

exit command line tool .quit exit
show all rows in table select * from topics;
show number of rows select count(*) from topics;
show matching record select * from topic where title = "My Topic";

Deploy Your Application

Don’t forget, "commit early, deploy often." Here's how:

git add .
git commit -m 'topic crud'
git push heroku master
heroku rake db:migrate

Congratulations! You have built and deployed a web application that works with a relational database.

What did we just do?

Rails implements a very specific notion of the Model-View-Controller pattern which guides how you build a web application.

Model

  • represents what is in the database
  • ActiveRecord

View

  • the model rendered as HTML
  • ActionView, erb

Controller

  • receives HTTP actions (GET, POST, PUT, DELETE)
  • decides what to do, such as rendering a view
  • ActionController

File:Mvc.jpg

When you executed the script/generate command, Rails generated files that implement a model, views, and a controller for the topics feature.

The Model

  • create app/models/topic.rb
  • create db/migrate/20090611073227_create_topics.rb

Four (4) Views

  • create app/views/topics/index.html.erb
  • create app/views/topics/show.html.erb
  • create app/views/topics/new.html.erb
  • create app/views/topics/edit.html.erb

The Controller

  • create app/controllers/topics_controller.rb
  • route map.resources :topics

A closer look

Rails allows you to easily invoke irb with all of the Rails libraries and your application code loaded:

	ruby script/console

Let’s look at the model that is defined here: app/models/topic.rb

>> t = Topic.new
=> #<Topic id: nil, title: nil, description: nil, created_at: nil, updated_at: nil>
>> t.title = "My topic"
=> "My topic"
>> t.description = "this is really cool"
=> "this is really cool"
>> t.save

Notice that the Topic class has title and description attributes which you did not need to explicitly declare in the class. This is handled by ActiveRecord which implements ORM (Object Relational Mapping) in Rails.

Routes

Rails routes control how URLs map to code. We can use rake to list the relationships between routes and controllers.

  $ rake routes
       topics GET    /topics(.:format)                  {:action=>"index", :controller=>"topics"}
             POST   /topics(.:format)                  {:action=>"create", :controller=>"topics"}
   new_topic GET    /topics/new(.:format)              {:action=>"new", :controller=>"topics"}
   edit_topic GET    /topics/:id/edit(.:format)         {:action=>"edit", :controller=>"topics"}
       topic GET    /topics/:id(.:format)              {:action=>"show", :controller=>"topics"}
             PUT    /topics/:id(.:format)              {:action=>"update", :controller=>"topics"}
             DELETE /topics/:id(.:format)              {:action=>"destroy", :controller=>"topics"}
                     /:controller/:action/:id           
                     /:controller/:action/:id(.:format)

Each method in the controller will take an HTTP request, usually find some data in the database (via an ActiveRecord model) and render a view or re-direct to another action.

Next feature: creating a topic

Let's run the next feature and see what we need to build next.

   cucumber features/topics.feature
Scenario: Creating a topic
     Given I go to topics
     And I follow "New topic"
     When I fill in "Title" with "Rails Fixtures"
     And I fill in "Description" with "Introduce how to add test data with fixtures."
     And I press "Create"
     Then I should see "Rails Fixtures"
     And I should be on topics

Run the server (script/server), look at the app (http://localhost:3000/topics/), and see how the scaffold template differs from the desired application as we've described it above using cucumber. What we want is the application to show the list of topics after the user creates a topic by completing the form. However, the scaffold displays instead a page showing only the individual topic the user just created.

Controller: adjusting the flow of your application

We looked at the scaffold-generated code a little earlier. To change the behavior and make the feature act as desired, we will look more closely now at the controller, which controls the general flow of your application; for instance, which page is displayed when the user clicks a link or a button.

  • Open app/controllers/topics_controller.rb and look at the new and create actions.
  • Notice that in create there is a redirect to a single topic, format.html { redirect_to(@topic) }. We want to redirect instead to the list of topics:
   format.html { redirect_to(topics_path) }  <-- note no @
  • Now run your feature again with cucumber and it should pass
    cucumber features/topics.feature

Next feature: the Topics page

Note that for this next set of feature scenarios we have a "Background" set of steps which will be executed before each scenario in the file. Since they depend on behavior that already works, they're rendered in green (pass).

Scenario: Clicking on the topic title
     When I follow "Rails Fixtures"
     Then I should see "Introduce how to add test data with fixtures."
     And I should not see "add a topic"
  • Run the scenario and watch it fail:
	cucumber features/topics_list_and_details.feature
  • In the last scenario we made it so the app redirected to "topics_path" after create. So, we expect that we will need to fix the error in that page. We can use rake routes to find the controller action, then we can see that the default view is rendered. The error is in views/topics/index.html.erb:
    <td><%= link_to h(topic.title), topic %></td>
  • Run the scenario again and see that the "Clicking on the topic title" passes, but we still have one failure which can be addressed in the same file
  • Open views/topics/index.html.erb and change "Destroy" to "Delete".

Next feature: allow voting on a topic

Feature: Votes
  In order to determine which talk to give
  people need to be able to vote for the ones they like
  
  Background: Make sure that we have a topic
    Given I go to topics
    And I follow "New topic"
    When I fill in "Title" with "Rails Fixtures"
    And I fill in "Description" with "Introduce how to add test data with fixtures."
    And I press "Create"
    
  Scenario: viewing votes already cast
    When I go to topics
    Then I should see "0 votes"
    
  Scenario: voting on a topic
    When I follow "+1"
    Then I should see "1 vote"

cucumber features/votes.feature

How will we build this feature?

  • Each vote will be an object (row in database table)
  • When someone votes on a topic, we'll create a new vote object and save it
  • Each vote is associated with a specific topic

Rails associations

   * Topic has_many :votes
   * Vote belongs_to :topic

File:Rails-associations.jpg‎

Add votes

Use cucumber to run the "votes" feature

   cucumber features/votes.feature

We will use the resource generation script to create a model and controller (no views):

   script/generate resource vote topic_id:integer

And then we migrate

   rake db:migrate db:test:prepare

The script creates files with:

  • model (including migration, unit, fixture)
  • controller (and route) with no code

Add code to to your models to create the associations:

  • /app/models/topic.rb
 class Topic < ActiveRecord::Base
   has_many :votes
 end
  • /app/models/vote.rb
 class Vote < ActiveRecord::Base
   belongs_to :topic
 end

Check it out in irb (script/console):

>> t = Topic.new
=> #<Topic id: nil, title: nil, description: nil, created_at: nil, updated_at: nil>
>> t.votes
=> []
>> t.votes.build
=> #<Vote id: nil, topic_id: nil, created_at: nil, updated_at: nil>
>> t.votes
=> [#<Vote id: nil, topic_id: nil, created_at: nil, updated_at: nil>]
>> t.save
=> true
>> t.votes
=> [#<Vote id: 2, topic_id: 2, created_at: "2010-02-22 01:42:27", updated_at: "2010-02-22 01:42:27">]

TODO: Missing rake db:migrate here?

Now you can use it in your view (/app/views/topics/index.html.erb):

<td><%= pluralize(topic.votes.length, "vote") %></td>

Allow people to vote

It's good to do one bit at a time and let the test failures drive what you do next.

Check rake routes for figuring out the pathTODO: diagram or screenshot of where this info is on the rake output, and edit your view (/app/views/topics/index.html.erb):

<td><%= link_to '+1', votes_path(:topic_id => topic.id), :method => :post %></td>

Now we need to create the controller action. Open the Votes controller (/app/controllers/votes_controller.rb) to add the create action:

class VotesController < ApplicationController
   def create
     topic = Topic.find(params[:topic_id])
     vote = topic.votes.build

     if vote.save
       flash[:notice] = 'Vote was successfully created.'
     else
       flash[:notice] = 'Sorry we could not count your vote.'
     end
     redirect_to(topics_path)
   end
 end
Personal tools