Rails Curriculum
From DevchixWiki
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:
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
- Point your web browser to http://localhost:3000, and see your web app actually there!
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
- And point your browser to http://localhost:3000/topics
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
 or choose Database -> Connect Database to open suggestotron/db/ development.sqlite3
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
| 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
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.rband look at thenewandcreateactions.
- Notice that in
createthere 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.erband 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
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





