Rails 2.0 Step by Step (part 2)
This is the second part of my series.
Part 1 is here.
Thanks for all of the great comments and help.
I think one of the most important comments came from enklare who pointed out that with Rails 2.0 you should explicitly set the database when you create a new Rails app by using the -d flag.
Instead of using the command
work$ rails exchange
use the command
work$ rails -d mysql exchange
To create the exchange app.
( typing 'rails --help' at the command line will give a short list of available options)
This will help Rails configure your application if you have other databases like SQLLite installed.
SQLLite is the default database for Rails as of Rails version 2.0.
-------------------------------------------------------------------------------------------------------------
Rails 2.0 Step by Step
(part 2)
Model View Controller
The Model View Controller(MVC) design pattern was first described in 1979 by Trygve Reenskaug while working at Xerox on Smalltalk. MVC is not a new syntax construct like an if statement or a data type like an array or int but more a way of looking at how to structure programs and divide the parts up in a logical and useful way.
Following MVC guidelines has been shown to organize applications in a way that makes them easy to manage and maintain. After working with the MVC pattern for a while you will grow to see the benefits that the division of labor produce. Rails is a strict MVC frame work.
If you stick to the MVC pattern you will find Rails easier to understand and use.
Model
The Model is all about the data. This includes getting the data in and out of the data store. The scaffolding we set up in part one gives us the four basic operation of using a data store Create, Read, Update and Destroy. Other data centric functionality also goes in the Model. Searching for data, manipulating data, validating input, and possibly editing the data for display (although sometimes this might logically be part of the view as well).
View
The View renders the Model in an interactive displayable format. It takes the data in the Model and paints it up on the screen for you to see and interact with.
Controller
The Controller responds to events communicating with the Model and the View. This is like the Main loop in a state machine waiting for events like user actions or Model data to show up and reacting as the program dictates to those events. Let's look at the Model View and Controller created by the Rails scaffolding, and start updating each of them with our own code.
We'll make changes to the Model and to the view and then get into the controller and migrations in the next installment.
The Model in Rails
The scaffold command created a model in the app/model/ folder called movies.rb.
Look in ~/work/exchange/app/models/movies.rb and you'll see a pretty sparse Model
The file is a Ruby file and the lines
class Movie <>
end
declare a class named Movie that inherits from class ActiveRecord::Base. Active Record is one of the gems installed along with Rails.
Looking in the Ruby on Rails api documentation for ActiveRecord::Base in the Classes section shows all of the methods, attributes, exceptions and other parts available in class ActiveRecord::Base. We'll see that an instance of the class Movie could be called anything but the Rails convention is to use a variable named movie or @movie. You will see this in code covered below.
The :: symbol is the Ruby scope operator. In ActiveRecord::Base it means that you are referring to the Base of ActiveRecord and not some other base.
We can customize our Model by adding code here just as in earlier versions of Rails. By adding our own code Movie will inherit from and extend the ActiveRecord::Base class.
The line
validates_presence_of :title, :description, :one_sheet_url
will check to make sure that there is, at least, some value entered in the fields on the form.
Let's create a new movie and see how the model behaves when we leave out the description and one sheet image location.
The Model won't allow a new row to be created if these fields are blank. Rails provides many validation features out of the box. The errors are listed at the top of the screen along with a description of the problem encountered and the fields with errors are highlighted in the form.
Taking a cue out of the AWDWR depot app we can check the format of data entered too.
Let's add some data but give the wrong file extension to our image to see how the model handles the input:
The model correctly flags the badly formatted entry as an error and prints a message. Note that the message displayed is the customized message we put in our model's validation "must be a URL for a GIF, JPG, or PNG image".
Fixing the file extension allows the model to accept the presence and format of our input. By fixing this error the Model saves the movie instance as a row in our table.
There are plenty of places to go to learn more about validation in Ruby on Rails. The Rails documentation is also a good resource as well as the models available to do validation.
Placing logic like this in the model, instead of embedding it in the logic for the screen, helps reduce duplication and ensures that all data being entered undergoes the same validation regardless of the interface used to enter the data.
The View in Rails
The view created by our scaffolding is pretty plain. Looking in the /app/views/ directory you will see two directories, layouts and movies. from the exchange directory change into the views directory and you can see them.
exchange$ cd app/views/
exchange/app/views$ ls -p
layouts/ movies/
movies/ has files that correspond to the various pages we have used in this demo so far
exchange/app/views$ cd movies/
exchange/app/views/movies$ ls -p
edit.html.erb index.html.erb new.html.erb show.html.erb
The .html.erb extension tells us that these files are chunks of html with embedded ruby. Looking at the file show.html.erb shows the html and the embedded ruby code.
Comparing this to the successfully created entry above shows where the Title:, Description:, and One sheet url: come from in our html. The parts between the '<%=' and the '%>' are the embedded ruby code. The code here displays the title, description, and one_sheet_url of the movie instance in @movie.
The two pieces of embedded Ruby with 'link_to' at the bottom show the buttons for Edit and Back. This code is where the text 'Edit' and 'Back' come from as well a the path or action for the controller to take when the button is pressed.
For the 'Edit' action the instance @movie is also passed as a parameter.
The 'h' in the '< % = h...% >' is there to strip any unwanted html and is a little beyond the discussion at this point.
Comparing this to the screen you cannot see where is no place for the green text "Movie was successfully created" originates.
The show.html.erb file is also missing the html, head, and body tags needed for a web page. We'll find these in the layouts directory.
exchange/app/views/movies$ cd ..
exchange/app/views$ cd layouts/
exchange/app/views/layouts$ ls -p
movies.html.erb
The scaffolding has created a default layout for the movie view called movies.html.erb. This file contains the parts needed to build a valid web page including our DOCTYPE, title, html, head and body tags, as well as where the color green came from for the friendly notice "Movie was successfully created".
The <%= yield %> is where the chunks of html from the files under the /views directory go when a page is built for display. The controller matches the chunk of html from the views/ directory to the action. Listing all of the movies uses the index.html.erb file, creating a new movie uses the new.html.erb file, editing an existing movie uses the edit.html.erb file, and showing one existing movie uses the show.html.erb file.
Also note that our original web page uses a css stylesheet called 'scaffold'.
Customized View
Side Note: I spend most of my professional time optimizing queries. Sounds exciting, I know. I don't have the gene for good design, so I'll keep it simple. There are plenty of people in the Rails community with great design ideas so there are plenty of better places for you to go to get layout tips.
Add some div id tags to our page and create a simple stylesheet. First change the movies.html.erb
The scaffold command created a stylesheet in the directory /exchange/public/stylesheets called scaffold.css. Create one called exchange.css that looks like
/* Global styles */
/* START:notice */
#notice {
border: 2px solid red;
padding: 1em;
margin-bottom: 2em;
background-color: #f0f0f0;
font: bold smaller sans-serif;
}
/* END:notice */
/* Styles for main page */
#banner {
background: #0099cc;
padding-top: 10px;
padding-bottom: 20px;
border-bottom: 4px solid;
/* font: small-caps 50px/50px "Times New Roman", serif; */
font: small-caps 50px/50px serif;
color: #000080;
text-align: center;
}
#banner img {
float: left;
}
#columns {
background: #9999ff;
}
#main {
margin-left: 12em;
padding-top: 4ex;
padding-left: 2em;
background: white;
}
#side {
float: left;
padding-top: 1em;
padding-left: 1em;
padding-bottom: 1em;
width: 10em;
background: #9999ff;
}
#side a {
color: #00ffff;
font-size: small;
}
h1 {
font: 150% sans-serif;
color: #226;
border-bottom: 3px dotted #77d;
}
Starting script/server and pointing your browser to http://localhost:3000/movies should give you something like this
It looks a little nicer, but the listing can be made nicer and we can save some duplication in the edit and new pages with a partial form. (The rails.png logo was put in the /exchange/public/images directory by the rails command when our app was created).
Partials
If you compare the new.html.erb and the edit.html.erb files you will see that they are almost exactly the same.
Look at the do loop between the <% form_for(@movie) do |f| %> and <% end %> and the only difference is the text passed in the line with 'f.submit'. The new page has the text "Create" and the edit page has the text "Update".
Make a copy of this section from either file and paste it in a file called _form.html.erb in the same views directory.
Edit the text passed in the 'f.submit' line and use a variable named label_text instead so the /exchange/app/views/movies/_form.html.erb file looks like this
The variable label_text will be passed in from the edit or new pages with the correct value inside. Make the edit.html.erb page look like this
Everything between the form_for do and the end is replaced with the one line
<%= render :partial => "form", :locals => { :f => f, :label_text => "Update"} %>
Similarly , edit the new.html.erb file so it looks like
This will render the partial _form and pass it the local variables and the variable label_text with either the value "Create" or "Update". Let's see if it works by editing one of our existing movies.
Things look the same as before, which is exactly what we wanted.
Creating a new movie also continues to work as before
Making a list, checking it twice
let's make the listing look a little more organized. Edit the index.html.erb file so it looks like this:
<div id="movie-list">
<h1>Movie Listing</h1>
<table cellpadding="5" cellspacing="0">
<% for movie in @movies %>
<tr valign="top" class="<%= cycle('list-line-odd', 'list-line-even') %>">
<td>
<img class="list-one-sheet" src="<%= movie.one_sheet_url %>"/>
</td>
<td width="60%">
<span class="list-title"><%= h(movie.title) %></span><br />
<%= h(truncate(movie.description, 80)) %>
</td>
<td class="list-actions">
<%= link_to 'Show', movie %>
<%= link_to 'Edit', edit_movie_path(movie) %>
<%= link_to 'Destroy', movie, :confirm => 'Are you sure?', :method => :delete %>
</td>
</tr>
<% end %>
</table>
</div>
<br />
<%= link_to 'New movie', :action => 'new' %>
Then add a section to the exchange.css file for our movie-list div id tag that looks like
#movie-list .list-title {
color: #244;
font-weight: bold;
font-size: larger;
}
#movie-list .list-one-sheet {
width: 60px;
height: 70px;
}
#movie-list .list-actions {
font-size: x-small;
text-align: right;
padding-left: 1em;
}
#movie-list .list-line-even {
background: #ffccff;
}
#movie-list .list-line-odd {
background: #d0d0d0;
}
The index.html.erb file should now render a page that looks like
here are a few one sheet images for you to play with (you might have to rename them).
Copies of these images should be placed in the /exchange/public/images/ directory.
What has been done so far?
Next time I'll look at the controller and a Rails 2.0 trick for adding new columns in a migration.