ruby on rails - one step at a time

Want to start at the beginning?

It’s finally time to add some bookmarks to our web bookmark database.

In the final version of this app, I expect bookmarks to be fetched on a per-folder basis (or as a result of a search) via some AJAX interaction with the server. Since we’re postponing the fancy javascript for later, this iteration will involve setting up the bookmark support stuff - models and controllers - and we’ll put some stub code in the views just to show the bookmarks for now. Once all the pieces are in place, we’ll be able to devote some attention to the look and feel of the interface and integrate some javascript to smooth out the user interactions.

 
10.1

Our first step is to get rails to help us get started with some bookmark code. The scaffold generator will set up most of what we need to start off with.

$ cd $HOME/projects/webmarks
$ ./script/generate scaffold bookmark

This will give us a new file in db/migrate called something like “20090102163151_create_bookmarks.rb”. Open up this file in your editor and make it look like this:

$HOME/projects/webmarks/db/migrate/20090102163151_create_bookmarks.rb
class CreateBookmarks < ActiveRecord::Migration
  def self.up
    create_table :bookmarks do |t|
      t.integer :user_id,            :null => false
      t.integer :folder_id,          :null => false
      t.integer :position
      t.string :name
      t.string :url,                 :limit => 1024
      t.string :notes,               :limit => 4096
 
      t.timestamps
 
      t.foreign_key :user_id, :users, :id, :on_delete => :cascade, :on_update => :cascade
    end
 
    add_index :bookmarks, :user_id
    add_index :bookmarks, :folder_id
    add_index :bookmarks, :position
  end
 
  def self.down
    drop_table :bookmarks
  end
end

Much of this new table is self-explanatory. A few pieces bear elaboration: “position” helps us set an arbitrary order on the bookmarks, so the user can re-order them at will. “user_id” and “folder_id” establish a parent-child relationships so we can track ownership and hierarchy. “notes” is a field that I’ve learned is useful from my prior bookmark application. It’s often useful to add some comment to a bookmark - a hint to the login credentials, or a note about how the bookmark is only valid until such-and-such a date, etc.

Next, implement the migration:

$ rake db:migrate

If you’re paranoid, you can log into your database and verify that the new table is in place before you continue.

 
10.2

The position column in our new table will be used to help us order the bookmarks within each folder. There is a standard rails plugin that can help us with that: acts_as_list. This plugin is pretty basic so in future steps will probably need to add some more methods to our model, but that doesn’t mean we can’t take advantage of what functions the plugin does provide.

A word of caution, though: acts_as_list used to be a plugin that was installed by default in any rails app when you say “rails someAppName”. However, since Rails 2.0, those plugins are not installed to cut down on application bloat, and the developer must install them by hand when needed. Also, it appears that the usual method of “./script/plugin install acts_as_list” doesn’t work either since this particular plugin has moved out of the standard repository and is exclusively served from github. You can install a plugin directly from a git repository, but only if your rails version is recent enough (circa 2.0.2 or higher). Here’s the command:

$ cd $HOME/projects/webmarks
$ ./script/plugin install git://github.com/rails/acts_as_list.git

This will install “acts_as_list” in your “vendor” directory and it’s ready for use.

One other note: The “acts_as_list” plugin has a quirk that you should know about. When a list item is deleted, the first thing that happens is it is removed from the list. The remove_from_list code looks like this:

# Removes the item from the list.
def remove_from_list
  if in_list?
	decrement_positions_on_lower_items
	update_attribute position_column, nil
  end
end

Notice anything interesting? The position column is set to null in the database. This means we can’t set a “NOT NULL” constraint on our position column, or this code will throw active record exceptions all over the place. So, while it might make sense that the position column should never be null, if you’re going to use acts_as_list, you should not set that constraint. Note that the migration code above follows this recommendation.

 
10.3

Now we can update the stock model code that the scaffold generator created for our bookmark object. The new code looks like this:

$HOME/projects/webmarks/app/models/bookmark.rb
class Bookmark < ActiveRecord::Base
  belongs_to :user
  belongs_to :folder
  acts_as_list :scope => :folder
 
  validates_presence_of     :name, :url, :folder_id
  validates_format_of       :url,    :with => /\A[a-zA-Z]+\:\/\/[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)*(\:\d+)?(\/.*)?\Z/,  :message => "invalid URL format"
  validates_length_of       :name, :maximum => 255
  validates_length_of       :url, :maximum => 1024
  validates_length_of       :notes, :maximum => 4096
 
end

Notes:

  • The Bookmark object “belongs_to” user and also to folder. This will allow us to easily make associated references from either perspective later.
  • The “acts”as_list” declaration adds the options hash for :scope, which tells the list management code that each folder has an independed counter for bookmarks.
  • There are several data validations included: some required fields are designated, a fairly simple-minded URL formatting regex is used, and then some limits are set on text field lengths.
  • We don’t dictate any rules for “position” since acts_as_list will be managing that column for us.

Since we establish that a bookmark belongs to a user and a folder, we need to update those respective models to show the other side of the association.

$HOME/projects/webmarks/app/models/user.rb
require 'digest/sha1'
class User < ActiveRecord::Base
  has_many :folders
  has_one :root_folder, :class_name => 'Folder', :conditions => "parent_id IS null"
  has_many :bookmarks
$HOME/projects/webmarks/app/models/folder.rb
class Folder < ActiveRecord::Base
  belongs_to :user
  has_many :bookmarks, :order => 'position'

Note the folder model includes a definition for “order” - this tells rails that, when it selects a bookmarks collection for a folder, it should order the results by the given column. In our case, that’s “position”.

There’s one other change we can make to the folder model. Our “empty?” method should take bookmarks into account when determining emptiness. Here’s the new method:

$HOME/projects/webmarks/app/models/folder.rb
  # Return true if this folder has no contents - either folders
  # or bookmarks.
  def empty?
    self.leaf? && self.bookmarks.count == 0
  end
 
10.4

Next we need to modify the bookmarks controller. The changes we’re making are the same kinds of things we’ve updated in the folders controller already: Remove the “index” and “show” methods; Remove XML format responders; Set all methods to find/build bookmark through current_user association; Do current_user lookups within rescue blocks; On success, redirect back to “my” controller. Here’s the new controller code:

$HOME/projects/webmarks/app/controllers/bookmarks_controller.rb
class BookmarksController < ApplicationController
  before_filter :require_active
 
  # GET /bookmarks/new
  def new
    @bookmark = Bookmark.new
  end
 
  # GET /bookmarks/1/edit
  def edit
    begin
      @bookmark = current_user.bookmarks.find(params[:id])
    rescue Exception => exc
        logger.error("Invalid bookmark ID lookup: #{exc.message}")
        flash[:error] = 'Invalid bookmark ID.'
        redirect_to :controller => "my", :action => "index"
    end
  end
 
  # POST /bookmarks
  def create
    # create new bookmark through association without saving it to DB right away
    @bookmark = current_user.bookmarks.build(params[:bookmark])
    begin
      # ensure that parent folder ID is really owned by current_user before saving
      @parent_folder = current_user.folders.find_by_id(params[:bookmark][:folder_id])
    rescue Exception => exc
        logger.error("Invalid parent folder lookup: #{exc.message}")
        flash[:error] = 'Invalid parent folder.'
        render :action => "new"
    end
 
    if @bookmark.save
      flash[:notice] = 'Bookmark was successfully created.'
      redirect_to :controller => "my", :action => "index"
    else
      render :action => "new"
    end
  end
 
  # PUT /bookmarks/1
  def update
    begin
      @bookmark = current_user.bookmarks.find(params[:id])
    rescue Exception => exc
        logger.error("Invalid bookmark ID lookup: #{exc.message}")
        flash[:error] = 'Invalid bookmark ID.'
        redirect_to :controller => "my", :action => "index"
    end
    begin
      # ensure that parent folder ID is really owned by current_user before saving
      @parent_folder = current_user.folders.find_by_id(params[:bookmark][:folder_id])
    rescue Exception => exc
        logger.error("Invalid parent folder lookup: #{exc.message}")
        flash[:error] = 'Invalid parent folder.'
        render :action => "edit"
    end
 
    if @bookmark.update_attributes(params[:bookmark])
      flash[:notice] = 'Bookmark was successfully updated.'
      redirect_to :controller => "my", :action => "index"
    else
      render :action => "edit"
    end
  end
 
  # DELETE /bookmarks/1
  def destroy
    begin
      @bookmark = current_user.bookmarks.find(params[:id])
    rescue Exception => exc
        logger.error("Invalid bookmark ID lookup: #{exc.message}")
        flash[:error] = 'Invalid bookmark ID.'
        redirect_to :controller => "my", :action => "index"
    end
    @bookmark.destroy
    redirect_to :controller => "my", :action => "index"
  end
end
 
10.5

Next we can update the views from the scaffold-created code and some of our existing pages. Some of the default functions won’t be needed in our interface model. For instance, the bookmarks will be displayed in the “my” views, so we won’t need “show” or “index” views that the scaffold generator gave us. Our “new” and “edit” views will still be used, though, at least until we replace them with some AJAX forms in the “my/index” view, so we should check those two out and make sure they’re doing what we need.

$HOME/projects/webmarks/app/views/bookmarks/new.html.erb
<h1>New bookmark</h1>
 
<% form_for(@bookmark) do |f| %>
  <%= f.error_messages %>
 
  Name: <%= f.text_field :name %> <br />
  URL: <%= f.text_field :url %> <br />
  Notes: <%= f.text_field :notes %> <br />
 
  Parent folder: <%= select("bookmark", "folder_id", current_user.root_folder.full_set.collect {|fol| [ fol.name, fol.id ] }) %>
 
  <p>
    <%= f.submit "Create" %>
  </p>
<% end %>
 
<%= link_to 'Back', :controller => "my" %>
$HOME/projects/webmarks/app/views/bookmarks/edit.html.erb
<h1>Editing bookmark</h1>
 
<% form_for(@bookmark) do |f| %>
  <%= f.error_messages %>
 
  Name: <%= f.text_field :name %> <br />
  URL: <%= f.text_field :url %> <br />
  Notes: <%= f.text_field :notes %> <br />
 
  Parent folder: <%= select("bookmark", "folder_id", current_user.root_folder.full_set.collect {|fol| [ fol.name, fol.id ] }) %>
 
  <p>
    <%= f.submit "Update" %>
  </p>
<% end %>
 
<%= link_to 'Back', :controller => "my" %>

These two screens are nearly identical. The parent folder chooser isn’t terribly efficient, but it’s already scheduled for deprecation due to AJAX, so we won’t spend any more energy on it now. Note that the “Back” links have been updated to return to our “my/index” page instead of the scaffold default bookmark view.

 
10.6

We also need to update the “my” views - the index and partials - to incorporate our new bookmark data. First, the index page:

$HOME/projects/webmarks/app/views/my/index.html.erb
<h1>My Stuff</h1>
 
<div style="border:1px; padding:10px;">
    <ol id="folderlist_<%= @root.id %>_subfolders" class="folderlist">
    <%= render :partial => "subfolder", :collection => @folders %>
    </ol>
 
    <!-- Replace with AJAX display -->
    <% marks = @root.bookmarks %>
    <% if marks.length > 0 %>
      <ul id="bookmarklist_<%= @root.id %>" class="bookmarklist">
        <% marks.each() do |bookmark| %>
            <li class="bookmark">
              <a href="<%= bookmark.url %>" class="bookmark" target="bookmarkWindow"><%= bookmark.name %></a>
              (<%= link_to 'Edit', edit_bookmark_path(bookmark) %>)
              (<%= link_to 'Destroy', bookmark, :confirm => 'Are you sure?', :method => :delete %>)
            </li>
        <% end %>
      </ul>
    <% end %>
</div>
 
<br />
 
<%= link_to 'New folder', new_folder_path %> <br />
<%= link_to 'New bookmark', new_bookmark_path %>
<br />
 
 
<%= link_to 'Edit', edit_user_path(@user) %> |
<%= link_to 'Back', users_path %>

There are a couple of additions here. First, the bookmarks are displayed in the block that begins with the “Replace with AJAX display” comment. Each folder will handle the display of its own bookmark set, so this page is only displaying the bookmarks owned by the root folder. Each bookmark has a “target” set that will cause each bookmark to open in a second window, a window which will be re-used by other bookmark clicks (instead of opening a new window for each bookmark). Each bookmark link also includes a link to the Edit and Destroy actions. Finally, at the end, we add a link to the New Bookmark page. None of this code is final, it’s only there so we can see our system working and run it through its paces. We’ll move things around in the interface later.

You might have noticed that there’s no interface for moving a bookmark to a new spot in the order within its folder. As with our folder views earlier, this is an interface task that I’d rather accomplish through drag and drop, so we’re postponing it for the time being.

Next, we add a similar bookmarks list block to our _subfolder partial:

$HOME/projects/webmarks/app/views/my/_subfolder.html.erb
<li id="folderlist_<%= subfolder.id %>" >
    <% if !subfolder.leaf? -%>
      <% if subfolder.open? -%>
        [ <%= link_to " - ", close_folder_path(subfolder), :method => :post %> ]
      <% else -%>
        [ <%= link_to " + ", open_folder_path(subfolder), :method => :post %> ]
      <% end -%>
    <% end -%>
 
    <%= subfolder.name %>
 
    (<%= link_to 'Edit', edit_folder_path(subfolder) %>)
    <% if subfolder.empty? -%>
      (<%= link_to 'Destroy', subfolder, :confirm => 'Are you sure?', :method => :delete %>)
    <% elsif subfolder.open? -%>
      <% if !subfolder.leaf? -%>
        <ol id="folderlist_<%= subfolder.id %>_subfolders" class="folderlist">
        <%= render :partial => "subfolder", :collection => subfolder.children %>
        </ol>
      <% end -%>
 
      <!-- Replace with AJAX display -->
      <% marks = subfolder.bookmarks %>
      <% if marks.length > 0 -%>
        <ul id="bookmarklist_<%= subfolder.id %>" class="bookmarklist">
          <% marks.each() do |bookmark| -%>
            <li class="bookmark">
              <a href="<%= bookmark.url %>" class="bookmark" target="bookmarkWindow"><%= bookmark.name %></a>
              (<%= link_to 'Edit', edit_bookmark_path(bookmark) %>)
              (<%= link_to 'Destroy', bookmark, :confirm => 'Are you sure?', :method => :delete %>)
            </li>
          <% end -%>
        </ul>
      <% else -%>
      [ no marks ] <br />
      <% end -%>
    <% end -%>
</li>
 
10.7

The routes.rb file was already updated by the scaffold generator, which added the line “map.resources :bookmarks”, enabling all the REST-oriented URL handling we need for our actions. That done, our application should be ready for one last test before we commit. So it’s time to fire up the server again and create some bookmarks in the site.

$ cd $HOME/projects/webmarks
$ ./script/server

Try making bookmarks in different levels of the folder hierarchy and then edit them, moving them around. You can also test out the URL validation by trying to create bookmarks with bogus URLs (like “http://google.”). At this point, you may find yourself wishing you could just type “google.com” in the URL bar, and expect the application to change it to “http://google.com/” instead of spitting out an error. That’s a convenience feature worth adding, but we’ll do it in another iterative step. For now, we just want bookmarks modeled and functional within our system - we’ll make them nice and easy later.

Is everything working? Then it’s time to do a commit. First, let’s check subversion status:

$ cd $HOME/projects/webmarks
$ svn status -u

Look through the list of new and modified files. One entry that stood out for me was “scaffold.css”. This is a CSS stylesheet that was created by our scaffold generator, but we don’t need it. It’s easier and cleaner to get rid of it now so it doesn’t clutter up our version control codebase. Don’t forget to “svn add” all the other new files to version control tracking.

$ rm public/stylesheets/scaffold.css
$ svn add test/unit/bookmark_test.rb
$ svn add test/functional/bookmarks_controller_test.rb
$ svn add test/fixtures/bookmarks.yml
$ svn add app/helpers/bookmarks_helper.rb
$ svn add app/models/bookmark.rb
$ svn add app/controllers/bookmarks_controller.rb
$ svn add app/views/bookmarks/
$ svn add db/migrate/[SomeTimestampHere]_create_bookmarks.rb
$ svn add vendor/plugins/acts_as_list/

Now we’re ready to commit.

$ svn commit -m "Added bookmark object class with db migration, model, controller, views; Updated my/index views to display bookmarks; Added acts_as_list plugin"
 
10.8

Now we have a functional bookmark tracking application. In some ways that app is still minimal - we’ve intentionally postponed or ignored several useful features - but in other ways the application is surprisingly advanced given the small codebase. Have a look at your project, in the models, controllers, and views folders, and you’ll realize that we haven’t typed in that much code, especially when you consider how much was given to us in scaffolding and how much we copy/pasted from other places. In fact, the descriptions in this tutorial are quite a bit longer than the code itself. (Sorry.)

Now is a good time to evaluate where we stand and make up a list of items we want to tackle to take this web app from “functional” to “how did I ever live without it”.

  • Rearrange my/index interface - different display panes for folders and bookmarks, make room for future features (see below)
  • Tags for bookmarks
  • Bookmarks search box (substring search on title, url, notes, tags)
  • Click to select a folder, making it “active” - the active folder is the one whose bookmarks are showing. Active state is preserved across sessions.
  • Replace New and Edit views with in-page popup panels, connected to server through AJAX, for both folders and bookmarks.
  • Make folders and bookmarks both draggable to change order, hierarchy.
  • “Remember search” feature: saves the search terms, not the result set, for later use
  • “Sort by name” option for bookmarks in a folder - orders a folder’s bookmarks by name all at once
  • Import/export bookmarks in popular browser formats (Firefox, IE, Safari, Chrome)
  • Open/Close All option for folder tree
  • Improved display of folder tree: small image for open/close toggle (little triangle?), nicer tree view

Those are the things that come to mind at the moment. I’m sure more will occur to me as the app progresses. Above all, though, the interface should remain clean, uncluttered, and highly utilitarian. This web app is not a high-bandwidth graphics showcase, it’s a utility that a serious user would refer to a hundred times a day. So it needs to stay clean and present the most-used interface elements front and center. I’ll probably work on the my/index layout next since that’s a prerequisite to implementing any AJAX features. Stay tuned.

Want to start at the beginning?

We ended Step Eight planning to create some bookmarks for our bookmark web app, but when I got into the coding I realized there was one other update I wanted to make to our Folders: keeping track of open and closed states. I was starting to think ahead to the final interface, with AJAX functions and so on. The idea is to show a folder tree in one panel and the bookmarks for a selected folder in another panel. To show the subfolders for a given folder would be to “open” it. Clicking on a folder name itself would select it and cause its bookmarks to display in the other panel. Adding this functionality was pretty easy, so this will be a short step getting that done, then bookmarks come next, I promise.

One other design decision I should mention: I’d like to remember the “open/closed” state in a persistent way across visits to the site. This way, a commonly-used folder could be left open and would still be that way when you come back to it tomorrow. It’s easy enough to open a folder only for the current session - that’s how my old version of this web app works - but I’ve found that I’d rather have the application remember, which means we need to store the state in the database.

 
9.1

To keep track of whether folders are open or closed, we need a new column in the “folders” table. Let’s call it “state”. We won’t need to use acts_as_state_machine for this - that’s overkill for a simple toggle - but the column name is appropriate and it will be easy enough to switch to state machine code later if we find it useful.

To do this, we make a small database migration file to add the new column to the table. We can get rails to generate a stub migration for us.

$ cd $HOME/projects/webmarks
$ ./script/generate migration add_state_to_folders state:string

This will give us a new file in db/migrate called something like “20090103030209_add_state_to_folders”. Open up this file in your editor and make it look like this:

$HOME/projects/webmarks/db/migrate/20090103030209_add_state_to_folders
class AddStateToFolders < ActiveRecord::Migration
  def self.up
    add_column :folders, :state, :string, :default => "closed"
 
    say_with_time "Setting default state values..." do
      Folder.reset_column_information
      Folder.find(:all).each do |folder|
        # Default all "root" folders to "open", others to "closed"
        if folder.root?
          folder.update_attribute :state, "open"
        else
          folder.update_attribute :state, "closed"
        end
      end
    end
  end
 
  def self.down
    remove_column :folders, :state
  end
end

There are a couple of things going on here. The basic purpose of this migration is to add the new column. That’s taken care of right at the top. Note the default value of “closed”, which will populate that value automatically into any new folder we create, saving us the need to include it in our new folder code. It also saves us having to update our existing new folder code to deal with this new data column.

Next is a block of code (starting with “say_with_time”) that is designed to set reasonable values for this column for any existing records we may already have in the database. This way, when the new column is added, the migration will ensure the rest of our older data is consistent with any data we create from here on. The block works this way:

  1. say_with_time is a migration command that causes a message to be printed to the console when the migration runs. Without this, the code block would execute silently.
  2. Folder.reset_column_information is a critical step that forces the system to refresh the data structure of the Folder class in memory. Without this, the Folder class would not include the new column we just added. “add_column” only affects the database, not the in-memory data structures.
  3. The Folder.find… block fetches each folder record from the database and sets the state. The default state is normally “closed”, as we set in our add_column line, but the Root folders are a special case: they should be set to “open”. In fact, they will never be closed, as we’ll see later. So we test to see whether a folder is a Root folder, then set the value accordingly. The “root?” method is provided by the better_nested_set plugin.

Now we just need to implement our migration.

$ rake db:migrate

If you’re paranoid, you can log into your database and verify that the new column is in place before you continue.

 
9.2

Now that we have a data column prepared, we just need to modify our model, controller, and views for Folders. First up: the model.

I mentioned in 9.1 above that our Root folders will always be open. This is because we’ve designed our views so far such that the Root folder isn’t listed. If the Root folder doesn’t show on screen, there won’t be any way for a user to open or close it. Therefore its state should be fixed, and always open (so our display logic can work consistently for this folder and all its children).

To update our model, we’ll add some state-related methods and some data validations. We want to ensure only allowed “state” values make it into the database, and we’ll use the model to keep the Root folder open with a validation line. To accomplish these things, add these two validation lines near the top of folder.rb, so the top of the file looks like this:

$HOME/projects/webmarks/app/models/folder.rb
class Folder < ActiveRecord::Base
  belongs_to :user
 
  acts_as_nested_set
 
  validates_format_of :state, :with => /\A(open|closed)\Z/
  validates_format_of :state, :with => /\Aopen\Z/, :if => :root?, :message => "Root folder must always be open"

The regular expression uses “\A” and “\Z” in place of “^” and “$” as suggested by the Rails API.

Next, we’ll add some useful methods to the end of our folder model. Add this code to the bottom of the file, just before the final “end”.

$HOME/projects/webmarks/app/models/folder.rb
  # Return true if this folder has a state of "open"
  def open?
    self.state == "open"
  end
 
  # Set folder state to "open"
  def open
    update_attributes(:state => "open")
  end
 
  # Set folder state to "closed"
  def close
    update_attributes(:state => "closed")
  end

The comments should explain the code well enough - it’s pretty simple. Note that “update_attributes” will only complete successfully if validation passes, so there’s no need to do any data checking here. If you prefer, you might instead (or in addition) define methods like “open!” and “close!”, using “update_attributes!(…)”, to get methods that would raise exceptions on failure instead of returning false.

 
9.3

Next, we can add some new methods to our controller. These will be the triggers for opening and closing folders as a result of clicks in the web page. I’m also going to tip my hand a bit and use some code that I’ll be talking about a little later on, just because I don’t want to put busted code here only to fix it farther on down the page. So, with that in mind, open up the folders_controller for editing and add these two methods to end end of the class:

$HOME/projects/webmarks/app/controllers/folders_controller.rb
  def open
    begin
      @folder = current_user.folders.find(params[:id])
    rescue Exception => exc
        logger.error("Invalid folder ID lookup: #{exc.message}")
        flash[:error] = 'Invalid folder.'
        redirect_to :controller => "my", :action => "index"
    end
    if !@folder.open
      flash[:error] = "Folder could not be opened: #{@folder.errors.on "state"}"
    end
    redirect_to(:controller => 'my')
  end
 
  def close
    begin
      @folder = current_user.folders.find(params[:id])
    rescue Exception => exc
        logger.error("Invalid folder ID lookup: #{exc.message}")
        flash[:error] = 'Invalid folder.'
        redirect_to :controller => "my", :action => "index"
    end
    if !@folder.close
      flash[:error] = "Folder could not be closed: #{@folder.errors.on "state"}"
    end
    redirect_to(:controller => 'my')
  end

In general, this code works much the same as most other methods in the controller. First, look up the folder object through the “current_user” object, which ensures the user only messes with his own folders. Next, attempt to open or close the folder. If the open/close method returns false, there was some error so flash it to the user. In either case, refresh the “my” index page.

Note: Later on, when we implement some AJAX functionality, we will be eliminating the redirect statement and just updating the state in the database. Javascript in the page will handle updating the “my” display on the fly.

I’m ignoring the begin/rescue/end block for now, since that’s the part I’m going to talk about later. For now, just trust me.

 
9.4

Finally, we need to update our views to give the user something to click to open or close a folder. I’m going to present it as a plus/minus sign next to each folder name. Since the root folder isn’t displayed, there are no changes to make in my/index.html.erb. We only need to update the _subfolder.html.erb partial. Remember, this is the partial that is rendered for each separate folder in the tree (below root). Add this new code to the beginning, above “subfolder.name”, so now the top of the file looks like this:

$HOME/projects/webmarks/app/views/my/_subfolder.html.erb
<li id="folderlist_<%= subfolder.id %>" >
    <% if !subfolder.leaf? -%>
      <% if subfolder.open? -%>
        [ <%= link_to " - ", close_folder_path(subfolder), :method => :post %> ]
      <% else -%>
        [ <%= link_to " + ", open_folder_path(subfolder), :method => :post %> ]
      <% end -%>
    <% end -%>
 
    <%= subfolder.name %>

The “subfolder.name” line is included above as a point of reference - make sure you don’t have it in your file twice. The check for “leaf?” means we will only show the open/close toggle for folders that have children. Note, we’re using a “method” of “post” since the open/close actions will change state of the data.

Okay, I lied - this is the final step: Adding some extra text to routes.rb so rails will recognize “close_folder_path” and “open_folder_path” when it sees them. Update the “map.resources” line for folders so it looks like this:

$HOME/projects/webmarks/app/views/my/_subfolder.html.erb
  map.resources :folders, :member => {:open => :post, :close => :post}

The “member” option tells rails to map additional RESTful routes for open and close, using the folder ID.

 
9.5

Before we finish up, I said I’d talk about the begin/rescue/end blocks in the sample code above. I realized as I was coding this step that I had failed to include some bulletproofing in the folders controller to this point. Here’s the problem. In a line like this:

@folder = current_user.folders.find(params[:id])

We load the folder by ID through the current_user object to ensure that we only select folders owned by the user as a security precaution. The problem is that if the folder ID is invalid (doesn’t exist or is owned by some other user), this assignment throws an exception. It’s not enough just to test that @folder got a value - the code will never get that far because rails throws the exception first. It’s very bad form to allow users to do things that end up with the stack trace screen, so we need to trap that exception.

The begin/rescue/end block is how exceptions are caught and handled in rails. We don’t need to do anything very fancy with this scenario since it should only occur when the user has deliberately done something bad, like use a hand-edited URL to mess with some other folder ID. When the @folder assignment fails, we log the error, set a flash error message to show the user, and redirect to the “my” index page, which short-circuits this action.

If the assignment works, the “rescue” block won’t be called and the rest of the action can proceed normally.

This block should be applied everywhere in the folders controller where that @folder assignment occurs. For completeness, here is the full controller code. I refactored a couple of things that looked like they could be tweaked, and placed @parent_folder lookups inside rescue blocks, too. These lookups happen when a user is creating or editing a folder and chooses a parent folder from a list - this parent folder should also be verified for user ownership.

$HOME/projects/webmarks/app/controllers/folders_controller.rb
class FoldersController < ApplicationController
  before_filter :require_active
 
  # GET /folders/new
  # GET /folders/new.xml
  def new
    @folder = Folder.new
 
    respond_to do |format|
      format.html # new.html.erb
      format.xml  { render :xml => @folder }
    end
  end
 
  # GET /folders/1/edit
  def edit
    begin
      @folder = current_user.folders.find(params[:id])
    rescue Exception => exc
        logger.error("Invalid folder ID lookup: #{exc.message}")
        flash[:error] = 'Invalid folder ID.'
        redirect_to :controller => "my", :action => "index"
    end
  end
 
  # POST /folders
  # POST /folders.xml
  def create
    # create new folder through association without saving it to DB right away
    @folder = current_user.folders.build(params[:folder])
    begin
      @parent_folder = current_user.folders.find_by_id(params[:folder][:parent_id])
    rescue Exception => exc
        logger.error("Invalid parent folder ID lookup: #{exc.message}")
        flash[:error] = 'Invalid parent folder ID.'
        render :action => "new"
    end
    if @folder.save
      @folder.move_to_child_of(@parent_folder.id)
      @parent_folder.open
      flash[:notice] = 'Folder was successfully created.'
      redirect_to :controller => "my", :action => "index"
    else
      render :action => "new"
    end
  end
 
  # PUT /folders/1
  # PUT /folders/1.xml
  def update
    begin
      @folder = current_user.folders.find(params[:id])
      raise "Unable to modify Root folder" if @folder.root?
      @parent_folder = current_user.folders.find_by_id(params[:folder][:parent_id])
    rescue Exception => exc
        logger.error("Invalid folder ID lookup: #{exc.message}")
        flash[:error] = 'Invalid folder ID.'
        render :action => "edit"
    end
 
    if @parent_folder.id != @folder.id && !@parent_folder.descendant_of?(@folder)
      if @folder.update_attributes({:name => params[:folder][:name]})
        # Tree moves can be expensive. Only move folder if a new parent_id is requested.
        if @folder.parent_id != params[:folder][:parent_id]
          @folder.move_to_child_of(params[:folder][:parent_id])
          @parent_folder.open
        end
        flash[:notice] = 'Folder was successfully updated.'
        redirect_to(:controller => 'my')
      else
        render :action => "edit"
      end
    else
      flash[:error] = 'Invalid parent folder'
      render :action => "edit"
    end
  end
 
  # DELETE /folders/1
  # DELETE /folders/1.xml
  def destroy
    begin
      @folder = current_user.folders.find(params[:id])
    rescue Exception => exc
        logger.error("Invalid folder ID lookup: #{exc.message}")
        flash[:error] = 'Invalid folder ID.'
        redirect_to :controller => "my", :action => "index"
    end
 
    if @folder.empty?
      @folder.destroy
    else
      flash[:error] = 'Folder was not empty. Could not delete.'
    end
    redirect_to :controller => "my", :action => "index"
  end
 
  def open
    begin
      @folder = current_user.folders.find(params[:id])
    rescue Exception => exc
        logger.error("Invalid folder ID lookup: #{exc.message}")
        flash[:error] = 'Invalid folder.'
        redirect_to :controller => "my", :action => "index"
    end
    if !@folder.open
      flash[:error] = "Folder could not be opened: #{@folder.errors.on "state"}"
    end
    redirect_to(:controller => 'my')
  end
 
  def close
    begin
      @folder = current_user.folders.find(params[:id])
    rescue Exception => exc
        logger.error("Invalid folder ID lookup: #{exc.message}")
        flash[:error] = 'Invalid folder.'
        redirect_to :controller => "my", :action => "index"
    end
    if !@folder.close
      flash[:error] = "Folder could not be closed: #{@folder.errors.on "state"}"
    end
    redirect_to(:controller => 'my')
  end
 
end
 
9.6

Time to fire up the server, check that everything works, and commit our changes.

$ cd $HOME/projects/webmarks
$ ./script/server

You may need to create a hierarchy of folders to help you test the new functionality. Once it’s working correctly, we can check everything into version control.

$ svn status -u
$ svn add db/migrate/20090103030209_add_state_to_folders.rb
$ svn commit -m "Added state to folders model; updated interface with folder open/close toggle"

Next up: bookmarks. I promise.

JavaScript sandbox page

January 10th, 2009 Posted in javascript | 869 Comments »

As a training aid for a javascript tutorial session I’m giving to other developers at work, I put together a javascript testing and experimentation page. This sandbox is a page where anyone writing javascript can test things out quickly and easily without the overhead of dealing with a separate editor and browser.

I expect I’ll use the page frequently myself to work out new code snippets - tweaking regular expressions, messing with scriptaculous effects, and so on - so I thought I’d post the page here in case anyone else will find it helpful.

JavaScript Sandbox

Some notes about the page:

  • The page loads Prototype and Scriptaculous libraries from the Google Code repository. The page itself will report the versions of the libraries.
  • All the code for the page - including the css and javascript - is inline in the page. The only remote files are the javascript libraries.
  • Since the page is fully self-contained, it should be easy to copy and save on your own hard drive in case you want to modify it to use JQuery, for example, or want to add some more HTML entities to the page to play with.
  • It’s just a plain HTML page. There is no application server on the back end. So, if you want to test out certain AJAX functionality, you may need to refer to your own app server or secondary data files.

Want to start at the beginning?

We left ourselves another short To Do list at the end of step 7. Here’s a reminder:

  • Clean out folders_controller.rb
  • Folder actions: update the views (new, edit, delete) and controllers

This will be a shorter iteration than that last one, but before we get started I need to say a word or two about what we won’t be doing.

My vision for the final product is an interactive web application that operates primarily via AJAX actions, not through multiple screens dedicated to separate functions. For instance, to add a new folder, I’d like to be able to right-click an existing folder and choose “add new subfolder”. At that point, there’s no need for a “parent folder” selection, since that’s already known. The only thing we’d need is the name. For editing, the name would be editable through the same right-click interface, but the folder would be moved around in the hierarchy with click-and-drag, not using the edit screen we’ll work on today. We still need an edit screen since we need a way to make these changes to the folder structure while we work, but we won’t spend a great deal of time on the interface since we’ll just end up abandoning it later.

In general, parts of this interface will be left in rough, barely functional form, since that’s all the development time they deserve - they’ll just be deleted in the end.

 
8.1

First on our list is to clean up folders_controller.rb. Since our users primarily interact through the /my controller to see the folders, some default methods in our folders_controller are unneeded. We can delete the “index” and “show” methods. The top of our folders_controller file now looks like this:

$HOME/projects/webmarks/app/controllers/folders_controller.rb
class FoldersController < ApplicationController
  before_filter :require_active
 
  # GET /folders/new
  # GET /folders/new.xml
  def new
    @folder = Folder.new
 
    respond_to do |format|
      format.html # new.html.erb
      format.xml  { render :xml => @folder }
    end
  end

The other methods in our controller will still be used, and we’ll keep them here since this is the logical place to handle interaction with the Folders model.

 
8.2

Next we need to finish up the various folder actions so we can manage them as we work. This means making sure Edit and Delete work as desired. So give Edit a try, using a link in the /my page folder list.

Uh oh - the page is basically empty. Our scaffold didn’t give us any form to use here. It could have, if we had given the appropriate options when we generated this code in the first place, but it isn’t hard for us to do ourselves. In fact, the form is essentially the same as the New Folder form. So let’s copy and paste the code. Our new edit.html.erb file will look like this:

$HOME/projects/webmarks/app/views/folders/edit.html.erb
<h1>Editing folder</h1>
 
<% form_for(@folder) do |f| %>
  <%= f.error_messages %>
 
  Folder name: <%= f.text_field :name %> <br />
 
  Parent: <%= select("folder", "parent_id", current_user.root_folder.full_set.collect {|fol| [ fol.name, fol.id ] }) %>
  <p>
    <%= f.submit "Update" %>
  </p>
<% end %>
 
<%= link_to 'Back', :controller => "my" %>

Before we give this a try, let’s think about what it’s going to do. It’ll post its data to the folders controller, using the “update” method. Remembering our experience with new folders, our update will need to happen in two parts: one standard record update for the folder name, then a “move_to_child_of” if the user picks a new parent folder. Finally, we will need to do some error checking to make sure the user doesn’t choose a child (or descendant) as the new parent - that wouldn’t work. Any folder that’s below this particular one in the tree is off limits as a parent.

It would be nice to also have a way to change the ordered position of this folder in its level of the tree, but I know from experience that doing that with a web form is ugly and hard, so we’ll skip it for now. This is one place where a live, AJAX-style interface is very much the simpler solution.

So this leaves us with a couple of things to think about: the controller needs some validation code and it might be nice if our edit screen disallowed choosing invalid parent folders in the select. I know I said I wouldn’t spend a lot of time on that interface, but this can be an instructive problem, so I’ll show how to do it with a partial.

 
8.3

First let’s walk through the process and make sure the right data is used throughout. First: the folders controller, in the “edit” method. This is the method that gets called when the user views the Edit screen. As in other cases we’ve seen, the default code loads up a Folder object into @folder using a plain find. We want to restrict it to only folders owned by current_user. So this change should be familiar. Change this:

$HOME/projects/webmarks/app/controllers/folders_controller.rb
  # GET /folders/1/edit
  def edit
    @folder = Folder.find(params[:id])
  end

to this:

$HOME/projects/webmarks/app/controllers/folders_controller.rb
  # GET /folders/1/edit
  def edit
    @folder = current_user.folders.find(params[:id])
  end

Then, when the data is POSTed to the “update” method in the controller, our original scaffold code doesn’t do much validation at all:

$HOME/projects/webmarks/app/controllers/folders_controller.rb
  # PUT /folders/1
  # PUT /folders/1.xml
  def update
    @folder = Folder.find(params[:id])
 
    respond_to do |format|
      if @folder.update_attributes(params[:folder])
        flash[:notice] = 'Folder was successfully updated.'
        format.html { redirect_to(@folder) }
        format.xml  { head :ok }
      else
        format.html { render :action => "edit" }
        format.xml  { render :xml => @folder.errors, :status => :unprocessable_entity }
      end
    end
  end

There’s that “Folder.find” again, to start with, and just a “was it successful?” test when the attributes are updated. Here’s some new code. I’ll cover the details below.

$HOME/projects/webmarks/app/controllers/folders_controller.rb
  # PUT /folders/1
  # PUT /folders/1.xml
  def update
    @folder = current_user.folders.find(params[:id])
    @parent_folder = current_user.folders.find_by_id(params[:folder][:parent_id])
    if @parent_folder && @parent_folder.id != @folder.id && !@parent_folder.descendant_of?(@folder)
      respond_to do |format|
        if @folder.update_attributes({:name => params[:folder][:name]})
          if @folder.parent_id != params[:folder][:parent_id]
            @folder.move_to_child_of(params[:folder][:parent_id])
          end
          flash[:notice] = 'Folder was successfully updated.'
          format.html { redirect_to(:controller => 'my') }
          format.xml  { head :ok }
        else
          format.html { render :action => "edit" }
          format.xml  { render :xml => @folder.errors, :status => :unprocessable_entity }
        end
      end
    else
      flash[:error] = 'Invalid parent folder'
      render :action => "edit"
    end
  end
  • @folder is set from the current_user’s folders collection.
  • @parent_folder is looked up using the ID from the edit page’s form, again using the current_user’s collection. If the parent folder doesn’t exist in current_user, this object will be null.
  • If @parent_folder was successfully found and the folder itself wasn’t chosen as the new parent and if the user didn’t try to move the folder inside its own sub-tree, then proceed. Otherwise, we return to the edit page with an error. Our edit page should prevent users from making these mistakes, but it’s important to bullet-proof the application even against seemingly impossible data.
  • Try and update the folder’s “name” attribute. If this succeeds, and if the new parent folder’s ID is different from before, do a “move_to_child_of”.
  • Finally, redirect to /my page and say “I did it.”

You may have noticed the “descendant_of?” method in one of the “if” statements. This method doesn’t exist in BetterNestedSet, I added it as a utility method in the model since I found myself in a couple of different situations trying to tell whether a given folder was nested inside another or not. Here are the methods I added to the folder model. The internal comments explain the new code.

$HOME/projects/webmarks/app/models/folder.rb
class Folder < ActiveRecord::Base
  belongs_to :user
 
  acts_as_nested_set
 
  # A leaf folder does not have any children. Return true if no
  # children exist for this folder.
  # This method does not require a database query.
  def leaf?
    self.all_children_count == 0
  end
 
  # Return true if this folder has no contents - either folders
  # or bookmarks.
  def empty?
    self.leaf?
  end
 
  # In the statement:
  #   @folder.ancestor_of(@other_folder)
  # The result is true if @other_folder is within the sub-tree of
  # @folder.
  # The result is false if @other_folder is outside @folder's sub-tree or if
  # @folder == @other_folder.
  # This method does not require a database query.
  def ancestor_of?(descendant)
    self.lft &lt; descendant.lft && self.rgt &gt; descendant.rgt
  end
 
  # In the statement:
  #   @folder.descendant_of(@other_folder)
  # The result is true if @other_folder is a direct ancestor of @folder.
  # Otherwise, false.
  # This method does not require a database query.
  def descendant_of?(ancestor)
    self.lft &gt; ancestor.lft && self.rgt &lt; ancestor.rgt
  end
 
end
 
8.4

So now we should be all ready to handle the data coming into our controller from an edit page. Let’s take a look at the edit page itself. The last thing we did with this page was to copy and paste code from the New folder page. That code will work, but the select dropdown isn’t as helpful as it could be: it doesn’t illustrate the nested hierarchy of the folders in the list, and it allows the user to choose invalid parent folders. We can solve these display issues with another recursive partial view, similar to the one we used for our /my page. Using that code as a model makes this partial easier to construct. Here’s the new version:

$HOME/projects/webmarks/app/views/folders/edit.html.erb
<h1>Editing folder</h1>
 
<% form_for(@folder) do |f| %>
  <%= f.error_messages %>
 
  Folder name: <%= f.text_field :name %> <br />
 
  Parent:
  <select id="folder_parent_id" name="folder[parent_id]">
    <option value="<%= current_user.root_folder.id %>">Root</option>
    <%= render  :partial => "subfolder_option",
                :collection => current_user.root_folder.children,
                :locals => {:level => 1} %>
  </select>
  <p>
    <%= f.submit "Update" %>
  </p>
<% end %>
 
<%= link_to 'Back', :controller => "my" %>

There are a couple of differences in the way this partial is used compared with our /my page. First, we want to show the Root folder in this case, since that’s a legal parent for any of our folders. Then we add a new local variable, “level”. The “:locals” hash is a way to pass in specific local variables to the partial. We’ll use “level” to keep track of the depth within our tree. The BetterNestedSet library provides a method to get the level of any given object, but it requires hitting the database every time. That isn’t necessary when it’s so easy for us to keep track of the value ourselves. The value for “level” starts at 1, so every folder rendered by the partial will be nested at least one level deep below Root.

Now let’s see the partial:

$HOME/projects/webmarks/app/views/folders/_subfolder_option.html.erb
<option value="<%=  subfolder_option.id %>"
    <%- if @folder.parent_id == subfolder_option.id -%>
        selected="selected"
    <%- elsif (@folder.id == subfolder_option.id) || subfolder_option.descendant_of?(@folder) -%>
        disabled
    <%- end -%> >
    <%= "&nbsp;&nbsp;"*level %> <%=  subfolder_option.name %>
</option>
<%- if !subfolder_option.leaf? -%>
  <%= render :partial => "subfolder_option", 
  	:collection => subfolder_option.children,
  	:locals => {:level => (level + 1)} %>
<%- end -%>

Remember, this partial will be rendered for each folder in the collection of Root’s children. For each folder, display an “option” tag, then check to see if this folder is a leaf node. If not, there are subfolders to render, so call the partial again with this folder’s children. Note that when the partial is called recursively, the “level” value is incremented by one. That new value will be valid in the recursive call, but will be discarded when the recursion backs out and returns to this point. Also note that “level” is used to show leading spaces before the subfolder’s name with this code:

    <%= "&nbsp;&nbsp;"*level %> <%=  subfolder_option.name %>

That’s ruby for ‘render the string “&nbsp;&nbsp;” (level) times’.

Finally, pay special attention to the “option” tag section. There’s a big if-then-else block inside designed to toggle between a couple of different situations:

  • If this subfolder’s id matches the original parent_id, say “selected”. That makes this the default parent folder in the drop down - the one that’s current for this object.
  • Otherwise, check to see if this subfolder equals the folder being edited. In that case, this folder shouldn’t be choosable as a parent, so it shows “disabled”. Also, if this subfolder is a descendant of the folder being edited, we can choose that as a parent either, so again say “disabled”.

And that will take care of our select tag. Let’s save everything and have a look. You might experiment with making several more subfolders, nested a few levels deep, and move them around by choosing different parent folders. You should be able to move a folder and see all its children move with it, and you can test that the options are indented and disabled where appropriate.

 
8.5

Only one thing left to do, and that’s to handle our “delete” action.

There’s no delete page; the action is handled directly from a link in the /my page. A javascript popup gives the user a chance to back out of the operation - this is code that’s installed by the scaffold generator and it works fine. The only code we need to modify is the controller method that actually deletes the record from the database.

As before, we need to make sure the only records that get deleted are ones belonging to current_user. Also, we face a dilemma: to allow a user to delete a folder that isn’t empty (contains subfolders and/or bookmarks) or to only allow empty folders to be deleted. For now, I’m inclined to only allow empty folders to be deleted. This can make it more of a pain for the user to delete a folder full of outdated bookmarks, but we should be able to revisit the decision later if we want. Enforcing this restriction isn’t hard, we just need to check the folder before deleting it to see if it contains anything. I added an “empty?” method to the model to help with just this situation. Right now, it only checks for subfolders, but when we add bookmarks to the system we’ll need to update it to check for those, too.

So with that in mind, here’s the new “delete” method in the folders controller:

$HOME/projects/webmarks/app/controllers/folders_controller.rb
  def destroy
    @folder = current_user.folders.find(params[:id])
    if @folder.empty?
      @folder.destroy
 
      respond_to do |format|
        format.html { redirect_to(:controller => 'my') }
        format.xml  { head :ok }
      end
    else
      flash[:error] = 'Folder was not empty. Could not delete.'
      redirect_to :controller => "my", :action => "index"
    end
  end
end

You’ll should recognize the familiar “current_user.folders.find…” code by now. I’ve added the “empty?” test in an if statement with an error message to display if the delete couldn’t proceed.

Speaking of which, we should probably clean up the /my view - there’s no reason to show a Delete link for folders that we’re not allowed to delete anyway. In practice, only leaf folders can be deleted since those are the only ones that are empty. It should be simple to add a “leaf?” test to the view. The Delete link is coded in our “subfolder” partial, so that’s the file we need to edit. It turns out that partial already includes a leaf? test (where it decides whether to do a nested recursion call or not), so it’s just a matter of tweaking that into an if-then-else that suits our needs. Here’s the new partial code:

$HOME/projects/webmarks/app/views/my/_subfolder.html.erb
<li id="folderlist_<%= subfolder.id %>" >
    <%= subfolder.name %>
    (<%= link_to 'Edit', edit_folder_path(subfolder) %>)
    <% if subfolder.leaf? %>
    (<%= link_to 'Destroy', subfolder, :confirm => 'Are you sure?', :method => :delete %>)
    <% else %>
      <ol id="folderlist_<%= subfolder.id %>_subfolders" class="folderlist">
      <%= render :partial => "subfolder", :collection => subfolder.children %>
      </ol>
    <% end %>
</li>

A quick test with the server confirms that the Delete link is correctly appearing only for folders without any subfolders.

 
8.6

So let’s double-check ourselves: We can view the folder list in /my and the nested hierarchy appears as it should. We can create a folder any place in the hierarchy. We can edit the folder and rename it or move it. We can delete the folder. We’ve done some bulletproofing and we can be confident that the user can only perform those operations on his/her own folder records, and we’ve closed the most obvious holes for mistaken or malicious abuse of the processes. Some more subtle openings may turn up later, but this is enough for now.

Looks like we’re ready to commit our changes to version control and tie off this iteration step.

$ svn status -u
$ svn add app/views/folders/_subfolder_option.html.erb
$ svn commit -m "Finished folder actions; cleaned up folder interface; created partial for folder select"

We only added one new file, _subfolder_option.html.erb, so we just need to tell subversion about it and commit.

There’s one folder function that I still want to include in the final product: to move a folder around within the same level of the hierarchy, effectively to re-order the folder display. As I mentioned early on in this post, I’ve done that kind of interface with static pages before and it’s just ugly. This time, I’m going to save it for when we have AJAX enabled to give us a cleaner way to represent the problem.

At this point everything looks stable and ready to add some bookmarks to this system. We’ll start that in our next step.

Want to start at the beginning?

In this step we’ll deal with the To Do list from the end of Step Six. As a reminder, here’s the list again:

  • validate ownership of folder == current_user on save
  • show folders in nested hierarchy
  • create new folder

We’ll also do one other thing: apply authentication and authorization rules to our various controllers. Since we implemented the restful_authentication system, we haven’t really taken advantage of it by marking sections of our site as restricted. We’ll take care of this first.

 
7.1

To restrict areas of the site with the authentication system, we need to set up some filters. We haven’t used filters yet in this application. It’s a technology that’s widely used in the web application framework world. In general, a filter can be defined so that an incoming request will automatically trigger the execution of a subroutine. That subroutine can do any number of things like (initializing variables, converting output from plain text to compressed data, etc) but here we’ll use filters to test whether the user is authenticated and, if not, short-circuit the request and redirect the user to the site’s root home page.

In Rails, there are several different kinds of filters which operate on different stages of the request/response cycle (user requests something … server responds with data). For our authentication filter, we’ll use the “before_filter” since it executes before the server does any processing on the request. There are two ways this can be implemented:

  • At the application level, demand authentication for all pages. For certain pages, disable this requirement.
  • Demand authentication only for the pages that need it.

The Rails filter system provides even more granularity than that - you can dictate filter behavior for each separate method in a controller class. The two methods listed above are functionally equivalent. The only difference is in maintenance. I opted for the first since most of the application should be protected by the login, and there are only a few exceptions, like our public front page, for example. This way, when we add more model/view/controller sets to our application, they’ll be protected by default and we won’t need to remember to enable authentication for them.

So the first step is to establish a filter at the application level. Open the application.rb controller for editing and add a “before_filter” line like so:

$HOME/projects/webmarks/app/controllers/application.rb
class ApplicationController < ActionController::Base
  before_filter :login_required

The “:login_required” is a label referring to a method that’s defined in the restful_authentication plugin. By default, when the user isn’t logged in, it calls an “access_denied” method which redirects users to the login page. I’d rather send people to the home page, so I want to override (you might say, re-define) the “access_denied” method. The code here is mostly copy/pasted from the plugin source, with a new redirect. Add this method to the end of application.rb:

$HOME/projects/webmarks/app/controllers/application.rb
  # Redirect as appropriate when an access request fails.
  #
  # The default action is to redirect to the login screen.
  #
  # Override this method in your controllers if you want to have special
  # behavior in case the user is not authorized
  # to access the requested action.  For example, a popup window might
  # simply close itself.
  def access_denied
    respond_to do |format|
      format.html do
        store_location
        redirect_to root_url
      end
      format.any do
        request_http_basic_authentication 'Web Password'
      end
    end
  end

We just need to make a couple more changes before we test. Our new filter will operate on every page request for the whole site, but there are several pages that users should be able to get to even though they’re not logged in:

  • site home page
  • log in page
  • forgot password page
  • new user signup page
  • new user activation page

All these are exceptions that we need to make against our filter. These exceptions are declared in the controllers. There are three files we need to edit: public_controller.rb, sessions_controller.rb, and users_controller.rb. These are the controllers that govern the pages in our exceptions list.

$HOME/projects/webmarks/app/controllers/public_controller.rb
class PublicController < ApplicationController
  skip_before_filter :login_required
$HOME/projects/webmarks/app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
  skip_before_filter :login_required, :except => [:destroy]
$HOME/projects/webmarks/app/controllers/users_controller.rb
class UsersController < ApplicationController
  # Protect these actions behind an admin login
  # before_filter :admin_required, :only => [:suspend, :unsuspend, :destroy, :purge]
  before_filter :find_user, :only => [:suspend, :unsuspend, :destroy, :purge]
  skip_before_filter :login_required, :only => [:create, :activate, :forgot_password, :reset_password]

In these examples, you can see some different usage cases of the filter. By setting a filter (or a skip_…) in a controller, it applies to every method in that controller by default. If you give an “:except” list, some methods will be exempted from the filter setting. If you give an “:only” list, you apply the filter only to certain methods within the controller. Using the appropriate option can save you some effort later on. For example, it makes sense that any controller method and view found in our “public” area should be exempted from the login requirement. So a broad “skip_before_filter” setting will help us here - we can add more public pages later and we won’t have to change the filter.

That should be enough for us to test. Fire up the test server and we can have a look.

$ cd $HOME/projects/webmarks
$ ./script/server

Open up http://localhost:3000/ in a web browser. If you’re still logged in from a previous session, log out. You should see the site’s home page. Now try http://localhost:3000/my. The site should redirect you straight back to the site home page since you haven’t logged in.

 
7.2

You may notice we haven’t set up any special filter rules for our other two controllers: my_controller and folders_controller. This is because we’ll need a slightly different restriction for these areas of the site. Access to these parts of the system should be restricted not just to users with logins, but to active users with logins. Remember, a user might be disabled, or may not have completed the activation process. So we need a new filter with this extra requirement. Since we need the filter in more than one place, we’ll define once it at the application level and then use it where we need it. So dig up the application controller again and add this new method:

$HOME/projects/webmarks/app/controllers/application.rb
  def require_active
    (logged_in? && current_user.active?) || access_denied
  end

“logged_in?” returns a boolean value, as does “current_user.active?”. If they’re both true, the final “||” won’t trigger and the filter will quit. If either test fails, then the rest of the “||” will execute, causing the “access_denied” method to redirect the user just as before. We’ll set up this filter for use in our remaining two controllers.

$HOME/projects/webmarks/app/controllers/my_controller.rb
class MyController < ApplicationController
  before_filter :require_active
$HOME/projects/webmarks/app/controllers/folders_controller.rb
class FoldersController < ApplicationController
  before_filter :require_active

This is a more difficult one to test - you’ll need to create a new user and, before using the activation link, try visiting the “my” page to see what happens.

 
7.3

Now that our site is protected against unauthorized access, it’s time to get back to our original to-do list. The first item we need to tackle is creating new folders. Once that code is working, we’ll have a list of folders to use in our next steps.

The first thing we can try is to use the “New folder” link that we already see in our /my page. Rails created that for us when we generated the scaffold code for the folders class, so let’s see if it works.

The default “New Folder” screen is simple, but enough for now. If you fill in the fields and click the button, though, you get an error:

PGError: ERROR:  null value in column "user_id" violates not-null constraint
: INSERT INTO "folders" ("name", "updated_at", "lft", "user_id", "parent_id", "rgt", "created_at") VALUES(E'error test', '2008-09-18 12:25:30.293602', 25, NULL, NULL, 26, '2008-09-18 12:25:30.293602') RETURNING "id"

What is it saying? That when the new folder was saved to the database, the “user_id” column wasn’t given any data - it was NULL - and that violated the “not null” condition we set on our database table. The underlying message here is that, when the folder is saved, it isn’t sending the current user’s user_id along with the record. So that’s the first thing we need to fix.

The default source code looks like this:

$HOME/projects/webmarks/app/controllers/folders_controller.rb
  # POST /folders
  # POST /folders.xml
  def create
    @folder = Folder.new(params[:folder])
 
    respond_to do |format|
      if @folder.save
        flash[:notice] = 'Folder was successfully created.'
        format.html { redirect_to(@folder) }
        format.xml  { render :xml => @folder, :status => :created, :location => @folder }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @folder.errors, :status => :unprocessable_entity }
      end
    end
  end

The “@folder = …” line will create a new folder object with its values initialized to the ones passed from the form parameters. This is nice, but not enough as our form doesn’t include the user_id. We will not fix this by adding a user_id field to the form. That’s an invitation to malicious users to create form data for any arbitrary user by changing that user_id. A better solution is to add “current_user.id” to the new @folder object before it gets saved. That way the folder is guaranteed to be owned by the logged-in user and nobody else.

The best solution, though, is to take advantage of Rails built-in hierarchical data models. The “:has_many” and “:belongs_to” labels we attached to our models give us access to some nice shorthand ways to solve this problem. Here’s the new version:

$HOME/projects/webmarks/app/controllers/folders_controller.rb
    @folder = current_user.folders.build(params[:folder])

By referencing “folders” as a sub-class of current_user, the folder class methods inherit some key current_user properties like the user_id. A similar syntax could be used in an Admin-level function to act on another user’s folder using the very same web form.

If you try the new code, it works… but only somewhat. The Nested Set settings are not quite right. In particular, this object doesn’t have a parent. In the database, it looks just like a Root folder, which we don’t want. Why? We had a pulldown where we got to select the parent of the folder, so why didn’t it get stored that way?

The answer lies in the BetterNestedSet library. One of the tradeoffs in the nested set data model is that adding new records is a two-step operation.

  1. Add record to database. The INSERT statement will give the record an ID and a place to live in the table.
  2. Move the record to the right spot in the hierarchy. This UPDATE will modify all necessary records’ “lft” and “rgt” fields as well as setting the parent_id of the record we moved.

Unfortunately, a two-step operation introduces a chance for an unexpected error to ruin the process, so we need to add some error checking in our code just in case. Here’s the final version:

$HOME/projects/webmarks/app/controllers/folders_controller.rb
  # POST /folders
  # POST /folders.xml
  def create
    @folder = current_user.folders.build(params[:folder])
    if @parent_folder = current_user.folders.find_by_id(params[:folder][:parent_id])
      if @folder.save
        @folder.move_to_child_of(@parent_folder.id)
        flash[:notice] = 'Folder was successfully created.'
        redirect_to :controller => "my", :action => "index"
      else
        render :action => "new"
      end
    else
      flash[:error] = 'Invalid parent folder'
      render :action => "new"
    end
  end

The key line in this code is “@folder.move_to_child_of(…” This is the line that places our new folder in the hierarchy. Before doing that move, though, we first make sure the parent folder really exists by doing a “find” on the parent_id. Note that this uses the “current_user.folders…” syntax, which will force the “find” operation to look only at folders owned by “current_user”. This insures us from accidentally saving a folder into someone else’s hierarchy. If that parent_folder exists, and if the @folder.save operation is successful, then we move the folder. Unfortunately, the BetterNestedSet library doesn’t provide a useful return value for the “move_to_child_of”, so I’m not testing for success there. Later, it will be worthwhile to add some exception handling around that code to protect us from database errors and such. If any of our tests fail, the user is returned to the New Folder page. On success, the user is redirected to the /my page, which should show the new folder in the list.

Go ahead and start your server (if it isn’t already running) and give this a try. Your folders will still show up in a flat list, since we haven’t taught the display page how to do anything better, but you should test anyway to make sure you haven’t made any typing errors along the way.

$ cd $HOME/projects/webmarks
$ ./script/server
 
7.4

While we’re messing with the New Folder piece of the application, let’s make one change to that page to make our lives a little easier for the time being: the “Back” link is wrong. It should take us back to our /my page, but it doesn’t. To fix it, use this line for the link instead:

$HOME/projects/webmarks/app/views/folders/new.html.erb
<%= link_to 'Back', :controller => "my" %>
 
7.5

Now it’s time to update the folder listing in our /my page so it shows the hierarchy of the nested set. I went round and round on this looking for the most efficient way to handle it, so I’m going to take a minute to summarize that process so you’ll know why I ended up where I did.

First of all, there’s a limitation in the BetterNestedSet library, a limitation I didn’t know about until I started watching my log while I was working on this piece. For Linux users out there, the best way to keep an eye on your log while you’re working is like this:

$ cd $HOME/projects/webmarks/log
$ tail -f development.log

This will show the bottom end of the development.log file and it will keep the display open so any updates to that file will also display on your console. To exit, hit Ctrl-C. The development.log file is more verbose (by default) than other logs and it will show you all kinds of useful information like the actual SQL statements that are executed on your database. If you’re trying to streamline your DB usage, this is invaluable.

So what did I learn? I learned that although BetterNestedSet does provide a quick and easy way to fetch an entire sub-tree from the database (using methods like “all_children” and “full_set”), it does not return the data in a tree structure. Instead, you get an array of objects. These objects include all the necessary data to discover the tree structure inside (using parent_id fields and such), but you still need to do that in code. In fact, all the methods that return sets of objects return them in array form. This is fine for some cases, but when I want to display a tree on a web page, it’s not very helpful.

There are other methods like “children” which gives the tree level immediately below a given object, but every call to “children” results in a separate query to the database. Other helpful methods like, “children_count” and “level”, also require separate database queries. If you’ve ever written code to parse through a tree structure you’re already groaning because you see that this means we need one or two database queries for every node of our tree. For a bookmarks collection like mine, with dozens of folders, that’s horribly inefficient use of the database.

Some investigation into the source code of the library revealed one hidden gem: the “all_children_count” method does not require its own database query. It can be answered based on data already present in the record, the “lft” and “rgt” values, because of the way nested sets work mathematically. I was able to use this to my advantage in my final tree render routine. By the way, “children_count” tells how many immediate children exist for a given record. “all_children_count” gives a count of all nodes descended from the given record - children, grandchildren, etc. It’s useful because I needed some way to know whether a given node was a “leaf node” in the tree, the end of the line, so to speak, or if the node had children of its own, which would mean another layer of the tree to process. Being able to identify these two cases is critical in parsing tree structures: “I’m looking at a node. Am I done, or do I have to go further down?”

I thought about writing some code that would translate my “all_children” array into a tree of folder objects, but decided I’d save that for later if it looked necessary. I decided the efficiency gains would probably not be all that significant, and I’ll explain why after I show the code.

It’s possible to display a tree structure using a while loop, but it’s a pain and it’s not easy to read or understand. It’s much, much easier to do it with a recursive process. If you’ve never encountered recursion before, I recommend the wikipedia entry on the topic. To use recursion in this case, we need to make use of a Rails construction called a “partial”.

A “partial” is a snippet of “view” code. Our “view” files are generally HTML files with some ruby stuff mixed in. A partial is like a little piece of a web page that can be called on and inserted in a page. They can come in handy in a variety of situations, but we’ll use them in this case to allow us to write a little recursive folder display. If we have a partial that can take a Folder object and first display that folder then call itself for each child Folder, then all we need is to start off the process with our root folder from the /my page and watch the whole thing unfold. So first we’ll make a new file, the partial. In Rails, partials are named with a leading underscore (”_”). This is a convention that Rails uses to reference partials automatically from identifiers in the ruby code. Here’s our new file:

$HOME/projects/webmarks/app/views/my/_subfolder.html.erb
<li id="folderlist_<%= subfolder.id %>" >
    <%= subfolder.name %>
    (<%= link_to 'Edit', edit_folder_path(subfolder) %>)
    (<%= link_to 'Destroy', subfolder, :confirm => 'Are you sure?', :method => :delete %>)
    <% if subfolder.all_children_count > 0 %>
      <ol id="folderlist_<%= subfolder.id %>_subfolders" class="folderlist">
      <%= render :partial => "subfolder", :collection => subfolder.children %>
      </ol>
    <% end %>
</li>

If you ignore the HTML list tags, what you see is embedded ruby code to: display the folder name (”subfolder.name”), give some action links for the folder, and then do our recursion. If this folder object has any children, then call the partial again on each of those children. I’ll go into more detail on the syntax a little later. First, we need to also see the initial call to this partial. The new /my page uses the partial in place of the original table that showed all the folder data.

$HOME/projects/webmarks/app/views/my/index.html.erb
<h1>My Stuff</h1>
<div style="border:1px; padding:10px;">
    <ol id="folderlist_<%= @root.id %>_subfolders" class="folderlist">
    <%= render :partial => "subfolder", :collection => @folders %>
    </ol>
</div>
<br />
<%= link_to 'New folder', new_folder_path %>
<br />
<%= link_to 'Edit', edit_user_path(@user) %> |
<%= link_to 'Back', users_path %>

One last thing to show. To make this new index page work, we need to set up some new local variables in our controller.

$HOME/projects/webmarks/app/controllers/my_controller.rb
class MyController < ApplicationController
  before_filter :require_active
 
  def index
    @user = current_user
    @root = current_user.root_folder
    @folders = @root.children
  end
end

So let’s start at the top and analyze what’s happening. Our controller stores a reference to “current_user” as “@user”. Then it creates a reference to the top-level folder in the next line (”@root = …”). Finally, it stores an array of the root folders immediate children as “@folders”. This is enough data to kick start our tree display.

The index.html.erb view starts off an ordered list and gives it an id value that incorporates the top-level folder’s ID. This is a little forward-thinking on my part, since I hope to add some nice javascript interactivity to this display later and giving everything an ID helps make that possible. The contents of the ordered list are filled out by the partial. This is the critical line:

    <%= render :partial => "subfolder", :collection => @folders %>

First of all, you use the “<%= … %>” syntax. The partial will execute as a subroutine that returns a string value. This is the embedded ruby directive that will cause the result to show up in the page. Next, the “render” command gets some important options. The “:partial” label takes the name of the partial to show. Rails will take this name, put a _ at the front and look for a file by that name in the same folder as the main view. This is why we named the file “_subfolder.html.erb”. Finally, the last option says to render the partial for the :collection “@folders”. This bit of code will cause the “render” directive to operate like an “each” loop: for each object in the collection @folders, the partial will be called, passing each individual folder in succession. Remember: the entire @folders array is not passed to the partial - just one Folder at a time will be. By Rails convention, that Folder object will have a local variable name of “subfolder”, the same name as the partial itself. There are lots of conventions at play here, but if you follow them it makes your code much more concise.

So now we will be calling the “subfolder” partial like it’s a subroutine inside an “each” loop. In our partial code, you’ll see the “subfolder” variable being used to access Folder object data, and then if the code determines that there are more levels beneath this Folder, the same kind of render statement is called again:

      <%= render :partial => "subfolder", :collection => subfolder.children %>

The collection in this case is the array of children of our “subfolder” Folder. This is just like the initial case where we came into the sequence with a set of children already in hand.

Now, I said I’d go a little more into the efficiency of this algorithm. If you watch the logs while this code operates, you’ll see a sequence of database hits:

  1. Get the root folder for current_user
  2. Get the children of the root_folder
  3. For each subfolder that has children, get the children of that subfolder

The first two steps will always happen exactly once. We didn’t need to ask the database whether there are any subfolder-children or not - that’s a mathematical operation. Then the other children arrays are fetched as needed from the database. That last step is the one that we could avoid by re-writing this to work out the tree structure in Ruby in advance. I elected not to based on some assumptions of how our data is likely to appear. In a tree that’s very deep, but not very wide, this algorithm requires many database calls, since there’s a query every time you dive into the next level down the tree. However, in my experience, bookmarks (this is a web bookmarks site, remember?) are not usually that way - the tree is more likely to be wide and shallow. The root may have many subfolders, and some of those may have subfolders, but it’s unlikely that the structure will go more than a few levels deep. Also, many if not most of the folders will be leaf nodes (no children). In this scenario, the database won’t be needed as much since most of the work will happen when we get the children of the root folder. After that, only a few more queries should fill out our tree.

If that assumption turns out to be false, then I can write the code and solve the problem, but I think that what we have now will be good enough.

So go ahead and save all your files, fire up the server, and load the /my page to see how it looks. You might need to make some new folders to get a good test, and be sure to make some folders as subfolders of others to show off the tree. The display may not be as pretty as we want for our final version, but it works.

$ cd $HOME/projects/webmarks
$ ./script/server
 
7.6

I did one piece of unrelated cleanup at this stage, so I’ll mention it here so our files will be in synch. In the application layout file, “views/layouts/application.html.erb”, I updated a couple of links. I realized in testing that there’s no navigation element that takes the user directly to the “/my” page. The “Webmarks” banner goes back to the site root page, so we need a new link to go to “/my”. I added it to the links that show for logged-in users in the top right corner. Here’s a snippet:

$HOME/projects/webmarks/app/views/layouts/application.html.erb
                <% if user_logged_in? %>
                  Logged in: <span class="login_name"><%= current_user.login %></span>
                  &nbsp;&nbsp;<strong>|</strong>&nbsp;&nbsp;
                  <%= link_to "Home", :controller => 'my', :action => 'index' %>
                  &nbsp;&nbsp;<strong>|</strong>&nbsp;&nbsp;
                  <%= link_to "Log out", logout_url %>
                <% else %>

Finally, I updated the link to the root page. In my layout file, that link used a hard-coded path to the root page, which is bad form in Rails. It’s more proper (because it’s easier to maintain) to use a label for that link instead. Then, if the link path ever changes, you can just change it in routes.rb and you’re done. Here’s the new version:

$HOME/projects/webmarks/app/views/layouts/application.html.erb
              <span id="title"><%= link_to "WebMarks", root_url %></span>
 
7.7

That’s it for step 7. It’s time to click through your web site one last time, make sure everything works as intended, and check in the new code.

$ svn status -u
$ svn add app/views/my/_subfolder.html.erb
$ svn commit -m "Updated folder display for /my/index; added auth filters"

So that was a lot of explanation for not so much code. I hope it was helpful to see some of the thinking behind the final version. Before we add bookmarks to the site - we’ll get there, I promise! - there are a couple of housekeeping things we should take care of next to make sure our Folder code is robust and ready before we move on.

  • Clean out folders_controller.rb: The controller file that the Rails scaffold generator made for us needs to be cleaned out since we won’t be using most of it.
  • Folder actions: update the views (new, edit, delete) and make sure our controller works as planned

Once that’s done, then we should be ready to add some bookmarks to our folders, and then we can start polishing and adding the fancy stuff.

Want to start at the beginning?

If you read the interlude, then you have an idea of where we’re heading. To get there, we’ll need data models (and controllers and all the rest) for folders and for bookmarks. There’s no need to rush through too much at once, though. For this step, we’ll work on getting folders implemented, and tying them to users.

The folder data isn’t all that trivial since they can be nested within one another. They should be representable as a tree structure and ideally the table that tracks all the folders should keep enough data to represent this tree. As it turns out, there are a few acts_as… plugins that can help with this. Moreover, we don’t just want a tree, we want an ordered tree since we want users to be able to re-order the folders at will. The data model that fits the bill, then, is the nested set (excellent summary by the folks at MySQL). For implementation, I chose the “betternestedset” plugin from Jean-Christophe Michel, Symétrie, which implements an “acts_as_nested_set” interface with most of what we need already built in.

Representing a nested set in a database requires a few extra integer columns to track the contextual relationship of each record with respect to the others. From there the plugin handles the grunt work.

Our folder data model has one other very important requirement: folders are owned by users. In Rails terms, the User “has_many” Folders and each Folder “belongs_to” a User. In our database, this simply means we’ll need a “user_id” field in our folders table which will connect each folder record to a record in the users table.

As a matter of personal preference, I like to implement as much as I can in the database, even if it’s implemented in code, too. Here, that means creating foreign-key relationships between tables even though Rails will keep track of that for me. To me, it’s another level of data integrity security, and in such cases redundancy is a good thing. Your mileage may vary according to preference and by your database of choice. In PostgreSQL, foreign keys are no trouble. In some other databases, this may not be the case. So feel free to skip the foreign-key stuff if you want.

All right then. Time to install a couple of new plugins and get going.

 
6.1

This first plugin will provide us with a nice clean way to define foreign-key relationships within a rails db:migrate file. It’s from redhillonrails.org, their “core” plugin:

$ cd $HOME/projects/webmarks
$ ./script/plugin install http://redhillonrails.rubyforge.org/svn/branches/stable-2.0/vendor/plugins/redhillonrails_core

Please note that, since we’re using Rails version 2.x, we’re installing the 2.x version of this plugin.

Next we’ll install the “betternestedset” plugin. We’ll be using the functionality provided here to manage our folder structure.

$ ./script/plugin install svn://rubyforge.org/var/svn/betternestedset/tags/stable/betternestedset

Neither of these plugins will do anything to change any files we’ve already touched - they only provide extra classes that can be called upon when needed, like when doing a database migrate or when declaring a data model to be a nested set.

 
6.2

We’ll ask Rails to make us a scaffold for the folders. We’ll end up throwing away some of it and over-writing most of the rest, but it’s a quick way to get the standard file structure in place and it won’t hurt anything. Plus it makes for much shorter instructions in this tutorial. :)

$ cd $HOME/projects/webmarks
$ ./script/generate scaffold folder

I need to take a second here to talk about Rails 2.1, which was released while I was working on this stage of development. In Rails 2.0, the database migration files were typically named things like “002_create_folders.rb”, where the leading “002″ was indicative of the migrate iteration. My example file name indicates step 2 in a sequence of migrate steps. In Rails 2.1, however, this system was changed, at least where the “generate” script was concerned. The “rake db:migrate” command is still happy with the old way - any old sequential integer value will do - but it means the new “create_folders.rb” file was created on my system as “20080625023825_create_folders.rb”. The leading number is now a timestamp value, which makes plenty of sense, really, but unfortunately means that when you run the “generate scaffold” command, you’ll get a different filename based on your current timestamp.

So the upshot of all this is that I will describe the database migrate file with a name like this “[timestamp]_create_folders.rb” and you should expect to find a file with a long numeric sequence at the front. If you’re still using Rails 2.0, you’ll just get a file like “002_create_folders.rb” instead.

 
6.3

Open the new database migrate file for editing. We’re going to replace everything inside so that it looks like this instead:

$HOME/projects/webmarks/db/migrate/[timestamp]_create_folders.rb
class CreateFolders < ActiveRecord::Migration
  def self.up
    create_table :folders do |t|
      t.integer :user_id,            :null => false
      t.integer :parent_id
      t.integer :lft,                :null => false
      t.integer :rgt,                :null => false
      t.string :name
 
      t.timestamps
 
      t.foreign_key :user_id, :users, :id, :on_delete => :cascade, :on_update => :cascade
    end
 
    add_index :folders, :user_id
    add_index :folders, :parent_id
    add_index :folders, :lft
    add_index :folders, :rgt
  end
 
  def self.down
    drop_table :folders
  end
end

Note that we’re creating a “user_id” field, which cannot be null, to refer each folder record back to a user. The next three fields, “parent_id”, “lft”, and “rgt” are all needed for the nested set structure. “parent_id” will hold another folder’s id number in cases where this folder is a sub-folder of another, or the value may be null if the folder is at the root of a set. In our model, as you’ll see shortly, we’ll expect each user to have a single Root folder (where the parent_id is NULL). The “t.foreign_key” line comes to us courtesy of redhillonrails and hooks this table to the users table, joined by user_id.

Next, we define some indexes. This is an important step for performance in your web application. In general, it’s almost always useful to create an index on any column containing an ID - here, the User ID and the Folder’s Parent ID - because database table joins and references are typically implemented with these columns and creating indexes here will show huge performance gains. The “lft” and “rgt” columns are less obvious places for indexes unless you’ve read up on how nested sets work. These values are used to quickly identify all descendants of an object (direct children, plus grandchildren, great-grandchildren, ad infinitum). Internally, this is accomplished with an SQL clause like “…WHERE lft BETWEEN(ancestor.lft, ancestor.rgt)…” In short, these values will be integers and it will be common for the whole table to be scanned to evaluate “less than”/”greater than” comparisons - a scenario that will be helped considerably by a database index.

It would be easier if good web application development didn’t require a basic knowledge of things like database model design and performance, web server efficiency and performance issues, usable page design techniques, aesthetics, database security (SQL injection), javascript security (XSS), web application security, concurrency issues for apps hosted on multiple servers… oh, and, you know, actual programming in the language of the moment - perl, php, ruby, python, java, whatever. However, knowledge of all these things (and more, I’m sure) are truly relevant - not just in an occasional, background kind of way, but really importantly relevant - to any well-constructed web application that people out in the Big Bad World will interact with. So it goes.

Anyway, having edited the migrate file, let’s run it against our database to get our folders table created:

$ cd $HOME/projects/webmarks
$ rake db:migrate

When complete, it would be prudent to log into your database and double check that it all looks as it should. You should see “cannot be null” restrictions on the appropriate fields and the foreign key relationship should be listed. If you use PostgreSQL from the command line, you can see all this with a “\d folders” command, short for “describe folders”.

 
6.4

We need to edit the User model to let Rails know that the User object now could own Folders. Edit the file so the beginning looks like this (the “has_many” and “has_one” lines are new):

$HOME/projects/webmarks/app/models/user.rb
require 'digest/sha1'
class User < ActiveRecord::Base
  has_many :folders, :include => [:user]
  has_one :root_folder, :class_name => 'Folder', :conditions => "parent_id IS null"
 
  # Virtual attribute for the unencrypted password
  attr_accessor :password

In the “has_many” line, the :include option helps tell Rails how to pre-load the associated data. The “has_one” line is a little non-intuitive. How can we have a “has_many” and a “has_one” relationship defined against the same Folder class? The “has_many” is the “real” relationship that truly describes what’s happening with the data. The “has_one” is a kind of virtual mapping that we’ll use for convenience.

Every time a user logs in and views his/her bookmarks, the application will need to find the user’s Root folder to use as a starting point, a kind of handle the app can use to grab onto the User’s whole set of folders. Each User will have one and only one Root folder, so we can create a “has_one” relationship, called anything we like (”root_folder” in the code above), and as long as we give Rails enough information to connect the User object to the right Folder object, it’ll work nicely and allow us to say things like “user.root_folder” whenever we want to reference the thing. So we give a :conditions option with an SQL snippet that clues Rails as to how to identify the folder record we want, and it’s ready for use. We could just as easily have defined a “root_folder” method in the User model with a “find” statement, but this seemed more elegant to me.

 
6.5

The default Folder model needs to be updated so Rails will know that Folders belong to Users. The following is the whole model file:

$HOME/projects/webmarks/app/models/folder.rb
class Folder < ActiveRecord::Base
  belongs_to :user
 
  acts_as_nested_set
 
end

We define the relationship to User and we tell Rails that we’d like to use nested set functionality (from our betternestedset plugin) for the Folder class.

 
6.6

With that, we have enough code in place to try our web app and make sure it’s doing what it should. This should look familiar:

$ cd $HOME/projects/webmarks
$ ./script/server

Open up http://localhost:3000/ in a web browser. Log in if your test user still exists, otherwise work through the user creation process. When you’re logged in with an active user, go here: http://localhost:3000/folders/. You should see a page with not much useful information, but there should be no errors. This page is using the default routing and index view created by the “generate scaffold” command we ran a few steps ago. If it appears without errors, that means our database table is being read by Rails and all is going smoothly so far.

 
6.7

The Folder class is up and running, but our application’s business logic isn’t. In particular, our app expects that every user will have a Root folder in the folders table. The best time to make this happen is at the point of user creation. You might think “activation” instead, but not so: an account might be activated, de-activated, and activated again. The “create” action, however, is a one-time operation. Our foreign-keys will help us keep garbage down to a minimum by destroying any folder records when the user that owns them is deleted.

So we need to create a new Root folder when each new user is created. To accomplish that, we just add a bit of code to the “create” method in the users_controller file.

$HOME/projects/webmarks/app/controllers/users_controller.rb
  def create
    cookies.delete :auth_token
    @user = User.new(params[:user])
    @user.register! if @user.valid?
    if @user.errors.empty?
      self.current_user = @user
      @root_folder = Folder.new(:user_id => @user.id, :name => 'Root');
      @root_folder.save;
      flash[:notice] = "Thanks for signing up!"
      redirect_back_or_default('/')
    else
      render :action => 'new'
    end
  end

The new lines start with “@root_folder”. That’s where we create a new folder object without specifying any parent ID and save it to the database.

Since our existing test user didn’t have the benefit of this code, it’s missing a root folder. We could create one manually in the database, but we need to try out our code anyway, so it’s just as well to clear out the test user and re-create it. You can do this in the database by simply deleting all records from the Users table, or you can ask rake to do it for you:

$ cd $HOME/projects/webmarks
$ rake db:migrate:redo STEP=2

We’ve used this command before, but now there’s an extra Database update iteration in there, so we need to redo two steps instead of one. That should drop the folders table, drop the users table, then create the users and folders again. You’ll need to go through the creation process again and, once the new user has been created (even before activation, if you’re curious), you can check in the database to make sure there’s a Root folder with the user’s ID.

 
6.8

For this next step, I tried out a few ideas in my head before continuing. I don’t know if I picked the best way, but it is a way that works and it has some advantages. Let me explain…

When a user logs in, the user should see his/her bookmarks. The default page after login should be a kind of console or dashboard view where all the most common operations and options are presented. A heavy user of the site will live on this page - it will be the most-viewed page in the whole site. All the other pages are special-use pages that will only be seen rarely.

In the typical Rails model, the user’s own page might be the page found at “[server-name]/users/368″ where “368″ is the user’s ID. This is the basic “show” URL in the default RESTFUL setup. However I don’t really want use that as a “home” page for the user: it would be a different URL for every user; it would invite poking into areas better left untouched (like other users’ pages), and it’s ugly.

A nicer solution might be to provide a short, simple URL that will be the Home URL for every user. The web app will dynamically generate the appropriate content according to the “current_user” value for that particular web session. We could set up something like that using our “routes.rb” file (as we did with some of the user creation-related URLs), but that’s unnecessary and a slippery slope, besides. It leads us down the path of creating special purpose routes for every page in our site, which is not an efficient way to run a web app.

What then? Answer: a controller. A plain old controller, one without a model, but with all the required views. Our controller could easily be coded to respond to certain actions by rendering data only for the current user, and this way the routing will be automatic - if a controller method and corresponding view exist to go with an incoming URL, Rails will just ship the request straight off to the controller. Just the way we like.

So I created a controller called “my”. We can use the controller’s “index” view as the Home page, giving us a URL like “something.com/my”, which is short enough to remember and use and will indicate immediately to the user that his/her own private data is being displayed here.

$ cd $HOME/projects/webmarks
$ ./script/generate controller my
 
6.9

The generate script will make a few empty files for us that we need to fill in. Rails could have helped us out more if we’d given some more parameters on the command line, but it’s not hard to do this work ourselves. And it builds character and stuff.

Find the new file “my_controller.rb” in the controllers directory and add an “index” method to it. The resulting file is still pretty small. Here it is:

$HOME/projects/webmarks/app/controllers/my_controller.rb
class MyController < ApplicationController
  def index
    @user = current_user
    @folders = current_user.folders
  end
end

We’re just creating a couple of instance variables (think “temporary variables for the web page”) that the view can use to show user data and folder data independently. The “user.folders” line calls on one of Rails’ useful default methods to fetch all the “folder” records owned by this user (according to the user_id field). We don’t explicitly render a page in this method since Rails’ default behavior is what we want: to find the web page with the same name as the action (”index”) and render it.

 
6.10

Speaking of which, we need an index page. We’ll need to create a file called “index.html.erb” in the “app/views/my” directory.

$HOME/projects/webmarks/app/views/my/index.html.erb
<h1>My Stuff</h1>
 
<table>
 
<% for folder in @folders %>
  <tr>
    <td><%= folder.name %></td>
    <td><%= link_to 'Edit', edit_folder_path(folder) %></td>
    <td><%= link_to 'Destroy', folder, :confirm => 'Are you sure?', :method => :delete %></td>
  </tr>
<% end %>
</table>
 
<br />
 
<%= link_to 'New folder', new_folder_path %>
<br />
 
 
<%= link_to 'Edit', edit_user_path(@user) %> |
<%= link_to 'Back', users_path %>

Much of this code was copied and pasted from pre-fabricated code in other views: the show-user view and the list-folders view. I substituted our instance variables where appropriate. Note that I’m not doing anything fancy with the “nested set” construction yet - I just want to show the folders in a plain list so I can verify that the right folders, and only the right folders, are being shown at all. We’re taking this in steps, remember?

 
6.11

Let’s give it a try.

$ cd $HOME/projects/webmarks
$ ./script/server

Open up http://localhost:3000/ in your web browser, log in, and then go to http://localhost:3000/my. You should see the “My Stuff” page with a Root folder listed and a few action links.

Is it working? Great - we’re almost done with this stage. It would be best if the web app would redirect us to this home page on login and also on activation (which is essentially equivalent to a login). This means finding a couple of places where the app redirects back to the default (”/”) and instead direct it to our new Home. One place is in the sessions controller (”create” method) and the other is in the users controller (”activate” method).

$HOME/projects/webmarks/app/controllers/sessions_controller.rb
  def create
    self.current_user = User.authenticate(params[:login], params[:password])
    if logged_in?
      if params[:remember_me] == "1"
        self.current_user.remember_me
        cookies[:auth_token] = { :value => self.current_user.remember_token , :expires => self.current_user.remember_token_expires_at }
      end
      flash[:notice] = "Logged in successfully"
      redirect_to :controller => "my", :action => "index"
    else
      flash[:error] = "Invalid username or password"
      render :action => 'new'
    end
  end
$HOME/projects/webmarks/app/controllers/users_controller.rb
  def activate
    self.current_user = params[:activation_code].blank? ? :false : User.find_by_activation_code(params[:activation_code])
    if logged_in? && !current_user.active?
      current_user.activate!
      flash[:notice] = "Signup complete!"
      redirect_to :controller => "my", :action => "index"
    end
    redirect_back_or_default('/')
  end

In your web browser, Log out. Then restart the server (may not always be necessary in Rails development environment, but it never hurts).

$ ./script/server

Go back to your web browser and log in again. This time, when you’ve logged in, you should be taken immediately to the Home page.

If it’s working, then we’re going to call it another little milestone and commit all this new stuff we’ve done to version control.

 
6.12

Time to commit everything to subversion. First, we’ll remove a couple of files that the scaffold generator made for us but that we don’t need. Since subversion doesn’t know about them yet, we’ll use a regular “rm” command. Then we call “svn status” to get a list of all the new files we need to “svn add” to version control.

$ rm app/views/layouts/folders.html.erb
$ rm public/stylesheets/scaffold.css
$ svn status -u
$ svn add test/unit/folder_test.rb test/functional/folders_controller_test.rb test/fixtures/folders.yml
$ svn add app/helpers/folders_helper.rb app/models/folder.rb app/controllers/folders_controller.rb app/views/folders/
$ svn add test/functional/my_controller_test.rb app/helpers/my_helper.rb app/controllers/my_controller.rb app/views/my
$ svn add vendor/plugins/redhillonrails_core/ vendor/plugins/betternestedset/
$ svn add db/migrate/[timestamp]_create_folders.rb

I went back and forth on the design for this section a little bit, so this list of files may not be exactly the same as what you see, but it should be close. Basically, you run the “svn status” command and look for entries marked with a “?”. All of those need to be added to version control, and you can add more than one thing in a single “svn add” command if you want. I’ve done it in several steps here so it’ll be more readable, grouping the adds into categories.

Important: Note the last “svn add” is the DB migration file named with a timestamp. You’ll need to be sure to use the filename that’s actually on your system instead of pasting in my command directly.

Now we can do one more “svn status” to check for any more “?” entries that we left out. When that’s all looking good, commit.

$ svn status -u  (check any ? in list)
$ svn commit -m "Added vendor plugins betternestedset, redhillonrails_core; added Folders with DB migration and scaffold; Added My controller to display logged-in user and associated folders"

Phew. That was a fair bit of thinking, coding, then talking about it afterwards. Our work isn’t done, however, and not just because we don’t have any actual bookmarks in our Web Bookmarks web app. Here’s a short list of the primary features we should add to make this iteration solid before we move on:

  • validate ownership of folder == current_user on save
    Whenever a folder is created or edited and then saved back to the database, we should have some failsafes in place that make certain a user only modifies his own folders and no one else’s. The Rails framework and our interface screens will only present options that are valid, but that won’t prevent someone bad from inventing user_id values and messing with other people’s stuff. It should be simple enough to do - before saving anything, just check that the user_id for the object is the same as the “current_user” user_id. If not, abort.

  • show folders in nested hierarchy
    Our folders are being displayed as a flat list, with no indication of the robust hierarchical structure they’re capable of. We should display the folders’ relationships in the /my page.
  • create new folder
    We could really use a working Create action for our Folder class, one that respects the nested-set-ness of it all. We’ll probably do this one first since without it, we won’t have any other folders around to test our interface with.

So we have a plan for the next iteration. After that, it’ll be time to add bookmarks into the mix, and then we can go back and add polish, tie off loose ends, and maybe introduce some AJAX-based interface features.

Want to start at the beginning?

At this point I’d like to take a short break from coding to do a little planning. We won’t be doing anything so formal as write a design specification, but we should have an idea of where we’re headed before we take our next steps. If you don’t care about such things and just want to get to the code, then feel free to skip to Step 6.

The next several steps of the project will involve creating a structure and an interface for our bookmarks. To plan out what we need and how all the pieces will interact, we need to have a general idea of what our interface will look like, and then design our system to accommodate the interface in such a way that the data is organized efficiently.

It would also be nice if our data storage model provided for some flexibility in the interface since it’s very likely that the interface will be updated before the data model is, and we’d like that process to not be painful. So here’s the thought process I went through when working out the next few steps…

One: the original

If you’ve read all these tutorial pages and have a good memory, you might recall that I wrote a similar web application several years ago, in Perl. In that web app, the bookmarks (links) could be stored in folders, and folders could contain a mix of bookmarks and other folders. Moreover, the display order was completely at the user’s discretion: folders and bookmarks could be mixed like so:

  • Link one
  • Link two
  • Folder one

    • Folder one.one
    • Link one.one
  • Link three

This afforded the greatest flexibility to the user, but required some messy and/or ugly modeling behind the scenes. The only reasonable way I could think of to accomplish it was to keep folders and bookmarks together in the same database table. That way, they all could be ordered together. The ugly part is that each record could contain one data type or the other, so it needed a column to say what type of data was in the record. Plus, each record needed columns for both data types: folder records had useless URL columns; bookmark records had useless “open or closed” data… you get the idea. It worked, but it just seemed, well, messy.

Two: hmm

Ruby on Rails is designed to deal very well with nice, hierarchical arrangements. This order has several line items. This person has two phone numbers. Et cetera. On the face of it, this looks like just such an arrangement: a user has folders; each folder has bookmarks (and possibly other sub-folders). The tricky part would be implementing the mixed ordering. What to do? After thinking on it for a bit, I took a step back and asked if that interface model was really a requirement, or just a feature of version 1.0… a feature that might be improved upon or even replaced for version 2. Dynamic representation of web pages (i.e., AJAX and such) has come a long way since I wrote version 1, so many more things are possible to do on the display end, even with a more restrictive model behind the scenes.

Three: tried and true… or not

In nearly every graphic representation of a file system I’ve every seen, each directory lists all the sub-directories together, followed by all the directory’s files. This model has an immediate advantage of familiarity for users. But it isn’t necessarily the most convenient. For an instance with many bookmarks, the user might need to scroll up and down just to interact with the interface. I would much prefer the side-by-side model that seems to be going out of fashion these days, with the folder tree on the left and the contents on the right. Actually, with a little bit of CSS and a touch of AJAX, this would be pretty easy to implement.

Three: a man, a plan, a canal: panama

Let’s assume the model exists the way Rails would like to have it, with a nice clean arrangement of “belongs_to” and “has_many” linking the data models together. If I list the folders, in tree form, in one panel, and the folder’s bookmarks in another panel, then we can stay faithful to the data model - making Rails happy - and we’ll have all kinds of cool display options we could implement in the interface, all without needing any changes to the underlying data model - making me happy.

 

Before I cast this plan in stone, though, I want to do a little review in my head and make sure I don’t have any interface features in mind that will be impossible (or even really really hard) with this setup…

  • Search box - allows user to search for a bookmark when he can’t remember what folder he stuck it in. This has never happened to me. Ever. I swear.
  • Tags - I’m not a huge fan of tagging, but in some cases it can be handy. In particular, it allows a single item to be categorized in more than one place. And the rest of planet Earth seems to think it rocks, so why not?
  • Bookmark listings by frequency/last time of use - It would be nice to get to see the most-clicked links in a page, or perhaps the ones most recently clicked. That would require the links be redirects through the app so the clicks can be tracked, but… Well, implementation can be worked out later.

That’ll do for now. I don’t see any major difficulties with any of those. They would either replace or supplement the folders area with another bookmark group selection mode. When these features are implemented, maybe the folders area would get tabs across the top to switch between Folders, Tags, and so on. In any case, it should be feasible without any wholesale code rewrites down the line, which is all I care about at this point.

So…

Time to go write some code.

Let’s say you have some files in your subversion repository - a Rails application, a Java app, you name it - that you’d like to be able to re-use. It’s common to have a single file that functions as a starter template to save you some time getting a new object Class or makefile or whatever under way, but what if you want to do the same for a whole collection of files, not just one? If you have those files stored in a directory in a subversion repository, it’s easy to set them up as templates you can re-use in future projects.

The trick, and it’s not much of a trick, is to use subversion’s “copy” function. The “svn copy” command will operate equally well on local files on your filesystem as it will on files or directories in a remote repository. This function of subversion is typically used to create Tags or Branches of project code, like savepoints within a project, but subversion will happily put a copy anywhere you like as long as it’s all within the same overall repository.

$ svn help copy
copy (cp): Duplicate something in working copy or repository, remembering history.
usage: copy SRC DST

  SRC and DST can each be either a working copy (WC) path or URL:
    WC  -> WC:   copy and schedule for addition (with history)
    WC  -> URL:  immediately commit a copy of WC to URL
    URL -> WC:   check out URL into WC, schedule for addition
    URL -> URL:  complete server-side copy;  used to branch & tag

    [ command line switches have been edited to fit your screen ]

Let’s say you have a project in a subversion repository at “svn://server.domain.com/myproject”. The project is at a point where you’d like to save what you have as a template for future use. You’ve installed and set up libraries, made buildfiles, and all the other time-consuming setup work that you’d rather not have to repeat. First, we’ll make a “templates” directory in the repository:

$ svn mkdir svn://server.domain.com/project_templates -m "Created directory for project starter templates"

When you issue an “svn mkdir” or “svn copy” command on a remote address (a URL), the action is committed right away, so we supply a commit message with the command.

Now we’re ready to store a copy of our project.

$ svn copy svn://server.domain.com/myproject svn://server.domain.com/project_templates/mytemplate -m "Saved copy of myproject as a template"

Now, I’d hope that in real life you’d choose better names for your project and template than this, but you should get the idea from this example. Only one question remains: Now that I’ve saved this template, how do I use it for a new project? That’s easy: you copy it again and checkout.

$ svn copy svn://server.domain.com/project_templates/mytemplate svn://server.domain.com/newproject -m "Began newproject from the template mytemplate"
$ svn co svn://server.domain.com/newproject ./newproject

The first command makes a copy of the template as a new project. The second command checks out the project to a local directory so you can begin work.

So there you have it. In three commands (well, four, but only the first time), you can save an existing project as a template, create a new project from that template, and check it out to start customizing your new project.

Want to start at the beginning?

In this step we will add some help for users who’ve forgotten their passwords and can’t log in. This will be the last generic user-authentication piece we need before diving into specifics for a web bookmarks site.

 
5.1

We have a working login system already and we want to tack on another user action to the existing process. Before doing that, it’s worth taking a minute to think about how it will work and what we’ll need to add or modify in our web app to make it happen.

Dealing with a forgotten password is a common problem for any web authentication system. The more worried you are about the contents of the web site, the more security checks you can demand before allowing an unauthenticated user to reset a password. One basic precaution that’s relatively easy to apply is the one we’ll use here: We will rely on the email address for the user account that was provided when the account was created. We assume for our purposes that this data is clean - that only the real owner of the account could have access to this email account prior to the password reset.

So here’s our model: The user has forgotten his password, so he clicks on a link to get some help. Our web app doesn’t know who the user is yet, so it must ask the user for some identifying token. This could be the username or the email address. In our app we’ll use the email address, but either would work. When the user provides his email, our app will look up the record in the Users table. If a match is found, an email is sent to the user with a special password reset URL included. This URL includes a random token that will be used to verify the legitimacy of the password request later. In effect, this token acts as a one-time password that allows an anonymous user to set a password on a given account. This random token is stored in the Users table in that user’s record. The process is nearly identical to the user activation model we used earlier.

Finally, the user receives his email and clicks the link or pastes it into a browser. The web app takes the random token, finds the user’s record, and generates a password reset page, allowing the user to provide a new password for the site. Once that form is filled out and submitted, the new password is stored, the user is logged in, and a final confirmation email is sent to the user notifying of the change. This is one last security measure just in case a bad guy was able to initiate the password change somehow.

So that’s the model. How do we accomplish it in Rails?

We need several pieces:

  • We’ll need two new pages with web forms for the user to fill out: a page that prompts for the user’s email address and a page where the user fills out a new site password.
  • The Rails routing file needs some new URL forms that will be used to reach these two new pages.
  • The users controller needs some new actions to deal with these page requests.
  • The user model needs a couple of methods to deal with the special password reset code and to help keep track of state as the user goes through this process.
  • The user observer needs to have some triggers added that will send out the password reset emails.
  • The user_mailer model needs some new email settings for our password reset emails.
  • Some user_mailer views need to be created. These are the views that render the body of the email messages.
  • Last but not least, we need to add a link to the forgotten password form in our existing site.

These are the changes we’ll be making in the remainder of Step Five.

 
5.2

First we’ll create the web form pages. Each page needs a new “view” file with a web form. Open up your favorite editor and paste in this code, then save the two files using the filenames given at the top of each. The file should be saved to the app/views/users directory inside your Rails project.

$HOME/projects/webmarks/app/views/users/forgot_password.html.erb
<% form_for :user, :url => {:action => 'forgot_password'} do |form| %>
  <h4>Password Reset Request</h4>
  <p>Enter the email address on file at this site and you will
  receive a message with a link to reset your password.</p>
 
  <p>
    <label for="user_email">Email address:</label><br />
    <%= form.text_field :email, :size => 35 %>
  </p>
  <%= submit_tag 'Send', :class => "submit"  %>
<% end %>
$HOME/projects/webmarks/app/views/users/reset_password.html.erb
<% form_for :user, :url => {:action => 'reset_password'} do |form| %>
  <h2>Reset Password</h2>
  <p><label for="Password">Password</label><br />
  <%= form.password_field :password %></p>
 
  <p><label for="password_confirmation">Confirm Password</label><br />
  <%= form.password_field :password_confirmation %></p>
 
  <p><%= submit_tag 'Reset your password', :class => "submit" %></p>
<% end %>
 
5.3

Next, those :action statements in the web forms need some routes that will answer the action requests. Find your config/routes.rb file and add the password routes so the top of your file looks like this:

$HOME/projects/webmarks/config/routes.rb
ActionController::Routing::Routes.draw do |map|
  map.resources :users
  map.resource :session
 
  map.activate '/activate/:activation_code', :controller => 'users', :action => 'activate'
  map.signup '/signup', :controller => 'users', :action => 'new'
  map.login '/login', :controller => 'sessions', :action => 'new'
  map.logout '/logout', :controller => 'sessions', :action => 'destroy'
  map.forgot_password '/forgot_password', :controller => 'users', :action => 'forgot_password'
  map.reset_password '/reset_password/:id', :controller => 'users', :action => 'reset_password'
 
  map.root :controller => 'public', :action => 'index'

Note that the reset_password route expects a url like “[web-app-URL]/reset_password/some-id-token”. The “:id” placeholder is where we’ll be putting the random password reset ID token.

 
5.4

Time to give our users controller some actions to deal with the forgot and reset URLs. Find the users controller file and open it in your editor. We’ll be adding two new methods at the end of the public area, just before the “protected” declaration. I’m including the existing “protected” code as a reference point.

$HOME/projects/webmarks/app/controllers/users_controller.rb
  def forgot_password
    return unless request.post?
    if @user = User.find_by_email(params[:user][:email])
      @user.forgot_password
      @user.save
      flash[:notice] = "A password reset link has been sent to your email address"
      redirect_back_or_default('/')
    else
      flash[:alert] = "Could not find a user with that email address"
    end
  end
 
  def reset_password
    if (params[:id])
      @user = User.find_by_password_reset_code(params[:id])
      return if @user unless params[:user]
      if (params[:user][:password] && (params[:user][:password] == params[:user][:password_confirmation]))
        self.current_user = @user
        current_user.password_confirmation = params[:user][:password_confirmation]
        current_user.password = params[:user][:password]
        @user.reset_password
        flash[:notice] = current_user.save ? "Password reset successfully." : "Password not changed."
        redirect_back_or_default('/')
      else
        flash[:alert] = "Password and repeated password required."
      end
    else
      flash[:alert] = "Password reset token missing."
      redirect_back_or_default('/')
    end
  end
 
protected
  def find_user
    @user = User.find(params[:id])
  end

These two new actions have a lot going on, so let’s take them one piece at a time.

forgot_password
The very first line, “return unless request.post?” is a common rails trick. The idea is that you have a web form that the user needs to fill out and then the system should take that submitted data and do something with it. Logically, your action name applies to both events: displaying the blank form and also responding to the action. They both have to do with “forgot_password”. The solution used here is to define one action that handles both situations.

If the request type is a GET, we assume it was the initial request for the blank form. At that first line, the method will halt (since it’s not a POST) and since we didn’t tell it to do anything special, Rails will, by default, render the view that matches the action name. We already created the “forgot_password” view, so that’s what the user will see.

When the user fills out the form and clicks submit, that web action is a POST request to “forgot_password”, so the first line will recognize that and continue with the rest of the forgot_password method. The rest of the method is pretty straightforward: Look up the user by the email address; Do a “forgot_password” on the user (we haven’t set this up yet in the user model - that’s next); Save the record; Send the user back to the home page with a message saying it was done.

reset_password
This method is a little different. You need to remember how the user will be triggering this action: by clicking a URL in an email. That will be a GET request that should have the special password reset key in it (with the parameter name of “id”). If that value isn’t there, this is a malformed request, so we spit out an error and quit. Otherwise, we look up the user by this token and then quit. This is the same construct as we just used in forgot_password. The extra step was to fetch the user information so it could be included in the password reset form when the view generates it.

When the form is submitted, the form “action” will be the same URL as we used to get to this form, which means the password reset token will still be in the URL. When we’re done, you can look at the HTML source for this page in your browser to see all the values. We refer to :id as a “param”, even though it’s not present as a form input object. How does this work? It works because of the way we defined our route for this action. The route uses “:id” as a placeholder in the URL, so Rails will pluck it out of the URL and set it up as a param so that we can access it in our code. Convenient, eh?

So the form is submitted and this time the HTTP request is a POST, so again we continue with the method past that “return” statement. There’s a bit of password validation code there which probably should be shifted out to the user model in the data validation area. Some cleanup for later - right now, let’s just get it working. The validation checks that there is a password and that the user typed the password the same way twice. If that’s true, then we’re ready to do the password reset.

First, “current_user” is set to be the user we fetched from the database. This will effectively log the user in. Next, we do a “reset_password” on the user - this is more user model stuff that we haven’t done yet. Finally, we save “current_user” to the database. Thanks to our user model, the password will be encrypted for us before it’s written to the database, so we don’t have to worry about that here. In other words, we trust the user model to know how passwords should be stored. The controller just says “save it, please” and it’s up to the model to decide how and where. Finally, we set a message saying whether it worked or not and send the user back to the home page.

 
5.5

As we said in step 5.4, we need to add some code to the User model. We referenced some model methods and need to cover a couple of pieces of our password reset system here.

Firstly, the User model needs to know how to attach a password reset private token to the user’s record in the database. Then, when the user clicks the URL in the email, the controller will be able to look up the user by this code. Secondly, we need to set a couple of user object variables that we can use to keep our automatic emails under control.

Remember when we first got the web app to the point that we could create a new user (at the end of Step Three), when we did so we got two emails about it? We fixed that problem (in Step Four) by creating a variable and a boolean test method to tell when the user had been “recently_activated”. We’ll do the same thing here, or else risk that every “save” action on the user object might trigger a password reset-related email. So we’ll need two variables and methods, one for “recently forgot” and another for “recently reset”.

Our first item is the “forgot_password” method that we referenced in step 5.4. To add this method to your code, you’ll need to open app/models/user.rb in your favorite editor…

$HOME/projects/webmarks/app/models/user.rb
  def forgot_password
    @forgotten_password = true
    self.make_password_reset_code
  end

Make sure you add this method to the “public” area of the class - if it’s in the “protected” section, it won’t be usable by the rest of the framework. Note the instance variable “@forgotten_password”. This is the variable we’ll use for our email trigger. Also note the other line references a method we haven’t created yet - the method that will create our special password reset token. We’ll add that method to the “protected” area of the class:

$HOME/projects/webmarks/app/models/user.rb
    def make_password_reset_code
      self.password_reset_code = Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join )
    end

You may notice that this is basically a copy of the code used to generate the activation code. If it already works, why not use it again?

Next, we’ll add the “reset_password” method we referenced in step 5.4. I put it right next to the “forgot_password” method in the user model.

$HOME/projects/webmarks/app/models/user.rb
  def reset_password
    update_attributes(:password_reset_code => nil)
    @reset_password = true
  end

The first line wipes the reset password code from the user record. The next line sets our variable for the email trigger.

Last but not least, we’ll set up a couple of boolean methods that our mailer can use to decide when it’s appropriate to send out the email. They can go right next to the very similar “recently_activated?” method in the user model.

$HOME/projects/webmarks/app/models/user.rb
  def recently_forgot_password?
    @forgotten_password
  end
 
  def recently_reset_password?
    @reset_password
  end

That’s it for our user model.

 
5.6

Now we can update our user_observer file to watch out for “recently_forgot_password?” and “recently_reset_password?” and trigger an email when either of those comes back true. Both of these will be within the “after_save” method in the observer, so that block should now look like this:

$HOME/projects/webmarks/app/models/user_observer.rb
  def after_save(user)
    UserMailer.deliver_activation(user) if user.recently_activated?
    UserMailer.deliver_forgot_password(user) if user.recently_forgot_password?
    UserMailer.deliver_reset_password(user) if user.recently_reset_password?
  end
 
5.7

Time to set up our emails. Remember the user_mailer model is where the basic structure of each email is defined, then we use views to construct the actual body of each email. So first we’ll add our two new methods to the user_mailer model which will answer to the “deliver…” actions in our user observer:

$HOME/projects/webmarks/app/models/user_mailer.rb
  def forgot_password(user)
    setup_email(user)
    @subject    += 'You have requested a password change'
    @body[:url]  = "#{SITE}/reset_password/#{user.password_reset_code}"
  end
 
  def reset_password(user)
    setup_email(user)
    @subject    += 'Your password has been reset'
  end

Note that, just as with the activation email, we’ll define a special URL for use in the email body that includes the password reset token.

 
5.8

Next we need two new views for the two different emails we might send. We create these in the directory “app/views/user_mailer”.

$HOME/projects/webmarks/app/views/user_mailer/forgot_password.html.erb
A password reset has been requested for your account, <%= @user.login %>.
 
To set a new password, visit the following web address:
 
<%= @url %>
$HOME/projects/webmarks/app/views/user_mailer/reset_password.html.erb
You have successfully reset your password.
 
	Username:	<%= @user.login %>
	Password:	<%= @user.password %>
 
5.8

Only one thing left for us to do: We need a link so we can get to our new code! All the pieces are in place, so we’re ready to make a link to our new “I forgot my password” rescue page. There are a couple of different places where this link could go, so it’s up to you where to put it. You could put the link in the Log In page, but I’ll be putting it in the banner area alongside the “Log in” and “Create account” links. This means I’ll need to edit the application layout file:

$HOME/projects/webmarks/app/views/layouts/application.html.erb
                <% else %>
                  <%= link_to "Log in", login_url %>
                  &nbsp;&nbsp;<strong>|</strong>&nbsp;&nbsp;
                  <%= link_to "Create account", signup_url %>
                  &nbsp;&nbsp;<strong>|</strong>&nbsp;&nbsp;
                  <%= link_to "Forgot password", forgot_password_url %>
                <% end %>
 
5.9

That’s it! We’re ready to make sure we saved all the files we modified or created and fire up a web server so we can try it out. Do you still have your test user? For the first time, we don’t want to wipe our test user first. Our user creation code is solid, so now we’re testing functionality for existing users. If you’ve already erased your test user, you’ll need to create a new one so we can test resetting its password.

$ cd $HOME/projects/webmarks
$ ./script/server

Open up http://localhost:3000/ in a web browser. Click the new “Forgot password” link, enter the test user’s email address, and check to see that you got your password reset message. If it arrived as planned, click or copy/paste the URL and you should see a Congratulations message and the banner area of the site should indicate that you’re now logged in as the test user.

If everything worked as planned, save that app to the repository. (Don’t forget to add the new files we’ve created before you commit.)

$ svn add app/views/user_mailer/* app/views/users/*
$ svn commit -m "Added forgotten password functions to site."
 

At this stage, our web app is completely generic and could be used for almost anything. It includes a basic user authentication scheme with some extra password-related functionality. Nothing we’ve done so far was terribly complicated, but it did take some time and a long series of small changes in many places - that means lots of opportunities for typos and subtle mistakes. So it could definitely be worth saving this web app at this stage for use as a staging ground in future web sites. Subversion makes that really easy, so we’ll talk about how to do that in our next step.

Want to start at the beginning?

In step four we’ll clean up some of the messy output we were left with at the end of step three. This will involve updating the automatic email process, adding some new routes to our application, and setting up a default layout and new index page.

 
4.1

When we left off, we had just created a new test user and confirmed that the user existed in the database. There’s a good chance we’ll be repeating that step several times, so we need an easy way to wipe our data clean and start from scratch for each test.

Rails includes a system for doing this kind of thing in the “test” environment, but not in “development”, which is where we need it right now. In addition, all the procedures for resetting data in “test” involve dropping and re-creating the database. As I mentioned early on (in Step 2.5), this is known to be broken for remote PostgreSQL databases, so we can’t even copy and re-use the test code for this purpose. However, there is a built-in task we can use here: rake db:migrate.

The db:migrate task allows for several actions including rolling back and re-doing any number of steps in your database migration setup. This solution wouldn’t be the best for a database with many existing steps already completed, but in our case we’re just getting started so this will work just fine.

First, we ask rails to tell us how many migrate steps we’ve been through already. Then we’ll tell our database to repeat those steps.

$ cd $HOME/projects/webmarks
$ rake db:version
$ rake db:migrate:redo STEP=1

The “db:version” command tells us we’re in version 1, so that means only one step to repeat. The “db:migrate:redo” command will drop the users table and recreate it.

 
4.2

Next we’ll fix the double email problem. The culprit is the file apps/models/user_observer.rb. The problem code is here:

$HOME/projects/webmarks/app/models/user_observer.rb
  def after_save(user)
    UserMailer.deliver_activation(user) if user.pending?
  end

When a new user signs up, the state of the user is almost immediately updated to “pending” where the record sits until it’s validated. Thus, when the new user record is saved with the “pending” state, the activation email is sent out prematurely. What we’d like is for this email to only be sent out once the user has completed the activation process. Several solutions to this have been suggested in different venues online, but the method I’m going to use here is to set a variable when the activation is processed, and then to only send the email when that variable is set. To accomplish this, we’ll make some small changes in a couple of places.

First, we add the “recently_activated?” method to models/user.rb:

$HOME/projects/webmarks/app/models/user.rb
  .
  .
  .
  def forget_me
    self.remember_token_expires_at = nil
    self.remember_token            = nil
    save(false)
  end
 
  def recently_activated?
    @recent_active
  end

Then, we add a line to the “do_activate” method at the bottom of the same file:

$HOME/projects/webmarks/app/models/user.rb
    def do_activate
      @recent_active = true  # Remember that we have just activated this user
      self.activated_at = Time.now.utc
      self.deleted_at = self.activation_code = nil
    end

Finally, we make use of our new method and variable in the user_observer:

$HOME/projects/webmarks/app/models/user_observer.rb
  def after_save(user)
    UserMailer.deliver_activation(user) if user.recently_activated?
  end
 
4.3

Now we can test our new code.

$ cd $HOME/projects/webmarks
$ ./script/server

Open a browser and go to http://localhost:3000/users/new

Fill out the form as you did before (we erased our first test user, so you can use the same login if you want). When you finish, you should only receive one email - the one with the activation code in it. Don’t erase that email - we’ll be needing it in a minute.

Did it work? If so, then we’re ready to commit these changes.

$ svn commit -m "Fixed activation email notice when user is created"
 
4.4

Now it’s time to get account activation working. In our current system, using the activation code URL from the email produces an error since rails doesn’t know what to do with a URL that says “activate”. Our first step, then, is to define a “route” which will tell rails how to handle these requests. Find the file config/routes.rb and add the “map.activate” line as shown below:

$HOME/projects/webmarks/config/routes.rb
ActionController::Routing::Routes.draw do |map|
  map.resources :users
  map.resource :session
  map.activate 'activate/:activation_code', :controller => 'users', :action => 'activate'

Saying “map.activate” will define “activate” as a keyword you can use in several ways, including auto-generating URLs. This way you won’t ever need to spell out the “blah/activate/code” URL in your views, you can just use a variable instead and rails will do the dirty work based on the information in this route. The stuff at the end of the line tells rails what action to take when it sees the “activate” URL.

As it turns out, the “activate” action is already in the “users” controller and it’s ready to go. So take the last email you received from your app and try using the activation link to see what happens. If you don’t still have that email, you’ll need to wipe your users table clean and create a test user again.

You should get an email telling you that your account is now active, but don’t trust it. You should check in your database to verify that your test user is now active. An active user should have a STATE of ‘active’, ACTIVATED_AT should have a timestamp in it, and the ACTIVATION_CODE column should be empty. If that all checks out, we’re ready to commit again.

$ svn commit -m "Added route for user activation"
 
4.5

If we had a real web site to look at, we’d be able to see status messages and errors and all kinds of things that would let us know whether things were working or not. So it’s time to get rid of this default Welcome to Rails web page and make our own instead.

First we’ll rename that default home page to something else so it’s still available if we want it. But wait…

Warning: Whenever you delete or rename files in a version-controlled directory, you should always, always, ALWAYS do it through the version control system. It’s not hard to to, but it can be hard to remember to do, until you get in the habit.

Okay, so where were we? The file we want is in the “public” directory, called “index.html”. The public directory holds all the plain web files which will be served in our application. There’s room here for javascript files, cascading style sheets, images, HTML files, and anything else you want to toss in. We’ll rename index.html to index.original.html, effectively disabling it as the default web page for our server.

$ cd $HOME/projects/webmarks
$ svn rename public/index.html public/index.original.html
 
4.6

We need to do a little prep work that will help us present some useful links on our new front page later. Namely, we’d like to have a “log in” or “log out” link be available at the top of every page in the site. To do that well, we should detect whether the user is already logged in and provide the appropriate option. A “logged_in?” method already exists in the restful_authentication library (lib/authenticated_system.rb), but it’s not sufficient for our needs. Specifically, that method will return True for a user that’s just been created, but hasn’t been activated yet. We won’t be changing that method because doing so could have unintended side effects. Instead, we’ll just create our own method that does what we want. This is an excellent candidate for a Helper method.

Since we want this method to be accessible to all the models and controllers in our site, we’ll add it to the Application Helper, “app/helpers/application_helper.rb”. That file is essentially empty to start with, so here’s the whole file with our new method:

$HOME/projects/webmarks/app/helpers/application_helper.rb
# Methods added to this helper will be available to all templates in the application.
module ApplicationHelper
  def user_logged_in?
    logged_in? && current_user.active?
  end
end

Our new “user_logged_in?” method borrows the logic of the existing “logged_in?” method, but adds the stipulation that the current user should have a state of “active”. In some other more advanced implementations that have more user states that may be considered “logged in”, this method may need to be updated or even replaced with a series of helper methods that deal with all those states. This is all part of a sophisticated authorization scheme that we won’t need for our current web app.

 
4.7

Next, we want to add a bit of feedback help to one of the pre-fabricated methods in the sessions controller. When a user logs in but mis-types a password, the default code just returns the user to the login screen with no indication as to why. Fixing that is easy. We’ll add a line to the controller that makes use of the “flash” rails construct to give feedback to the user. In the code below, the “flash[:error]” line has been added.

$HOME/projects/webmarks/app/controllers/sessions_controller.rb
  def create
    self.current_user = User.authenticate(params[:login], params[:password])
    if logged_in?
      if params[:remember_me] == "1"
        self.current_user.remember_me
        cookies[:auth_token] = { :value => self.current_user.remember_token , :expires => self.current_user.remember_token_expires_at }
      end
      redirect_back_or_default('/')
      flash[:notice] = "Logged in successfully"
    else
      flash[:error] = "Invalid username or password"
      render :action => 'new'
    end
  end

Personally, I don’t like getting the “Logged in successfully” feedback as a special alert on the page, but it’s comforting to see while developing so I’m leaving it in for now. We can always remove it later (or not - it’s your choice).

 
4.8

Here I’m going to divert a little from some of the other screencasts and tutorials on rails authentication. What we want at this point is a new home page. The home page should be visible to the public without requiring authentication. There could potentially be many pages like this that we want to expose on the outside of our protected area (an About page, for example), so we’re going to set up a new controller that will be dedicated to public access web pages in our site. In my code, I’m calling this the “public” controller. Creating it is easy, thanks to the generator scripts that come with rails:

$ cd $HOME/projects/webmarks
$ ./script/generate controller Public index

The “generate” script will generate many things for us. The command above says to make a new controller called Public and to set up a new controller method and corresponding view called index. Later, we’ll configure rails (in the routes.rb) file to use this as the default home page for the site, a replacement for the “index.html” file we renamed earlier.

That’s all we need to do with this for now. The auto-generated code will work for us until we want to come back later and add some real web content to that home page.

 
4.9

Next, we’ll make a page template that will apply for all of our pages. In a typical web site, the top and bottom of the page are consistent throughout the site, so it pays to define that web code in only one place. This makes future maintenance a much less painful process. In a plain web world, you’d likely accomplish this with Server-Side Includes. In rails, we can do it with one file that has the whole default page layout in it, with a placeholder for the main page content. Rails will always display this default layout (unless we override it in a specific case), and will plug in the appropriate content in the middle. This web code is located “app/views/layouts”.

The layouts directory is empty by default. We can put whatever layouts we want in here and, if we name them correctly, rails will find and use the right one to go with whatever view is being rendered. The overall rails naming convention applies here, too: to create a layout that applies for every page, we just name it “application.html”. In our case, we’d like to include some ruby code to intelligently alter that layout content (remember our “log in” vs. “log out” links?), so we’ll name the file “application.html.erb” instead. The “.erb” stands for “embedded ruby” and it’s a signal to the web server that there will be internal ruby code to process.

So go ahead and create this new file:

$HOME/projects/webmarks/app/views/layouts/application.html.erb
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
  <head>
    <title><%= @page_title || 'Page Title Here' %> <% if ENV['RAILS_ENV'] != 'production' %> <%= " : #{ENV['RAILS_ENV']}" %> <% end %></title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <%= stylesheet_link_tag 'root' %>
    <%= javascript_include_tag :defaults %>
  </head>
  <body>
      <div id="banner">
          <div id="banner-inner">
              <div id="menu-top">
                <% if user_logged_in? %>
                  Logged in: <span class="login_name"><%= current_user.login %></span>
                  &nbsp;&nbsp;<strong>|</strong>&nbsp;&nbsp;
                  <%= link_to "Log out", logout_url %>
                <% else %>
                  <%= link_to "Log in", login_url %>
                  &nbsp;&nbsp;<strong>|</strong>&nbsp;&nbsp;
                  <%= link_to "Create account", signup_url %>
                <% end %>
              </div>
              <span id="title"><%= link_to "WebMarks", "/" %></span>
          </div>
      </div>
      <div id="main">
        <% flash.each do |key,value| %>
        <div id="flash_<%= key %>" class="flash">
            <%= value %>
        </div>
        <div class="clear"><br /></div>
        <% end %>
        <%= yield :layout %>
        <div class="clear"><br /></div>
      </div>
  </body>
</html>

There’s a lot of ruby in this file for those not familiar with embedded ruby files. I’ll just touch on a few key things:

  • The Page Title area is designed to be dynamic so we can customize the title on different pages if we want. Also, there’s a bit of debug code in there to display the current ruby environment if we’re not in production. This is a helpful and reassuring reminder that you’re working in “development” or “test” mode.
  • We make use of our new “user_logged_in?” helper method to show log in and log out links. Also, the Create Account link should not appear for logged-in users.
  • There’s a fairly standard implementation of a display of the “flash” construct. This will allow us to see feedback messages on our pages.
  • The “yield :layout” line is where the page’s main content gets plugged in.

The basic layout of this page is not too complicated and should be flexible enough to get us going for now.

 
4.10

The default layout above includes a reference to a cascading stylesheet file named “root”, which rails will interpret to mean “root.css” in the “public/stylesheets” directory, by convention. This file doesn’t exist, so we need to create it.

$HOME/projects/webmarks/public/stylesheets/root.css
/* Root Style sheet */
 
body {
  background-color:#777777;
  font-family:Verdana,Arial,Helvetica,sans-serif;
  padding:0;
  margin:0;
  font-size:10pt;
}
 
 
/* ======================= Banner ======================= */
#banner {
  background-color:#444444;
  margin:0;
  padding:0;
  border-bottom:4px solid #cccccc;
}
#banner a:link, #banner a:visited {
  color:#eeeeee;
  text-decoration:none;
}
#banner a:hover {
  text-decoration:underline;
}
#banner-inner {
  width:700px;
  margin:0 auto;
  padding:10px 6px 6px 6px;
}
#title {
  font-size:2.0em;
  font-weight:bold;
  background-color:#444444;
  color:#eeeeee;
}
#title a:hover {
  text-decoration:none;
}
#menu-top {
  text-align:right;
  float:right;
  color:#eeeeee;
  margin-top:14px;
}
span.login-name {
  font-weight:bold;
}
 
/* ======================= Main body ======================= */
#main {
  width:700px;
  margin:0 auto;
  background-color:#aaaaaa;
  color:#333333;
  border-right:1px solid #cccccc;
  border-bottom:1px solid #cccccc;
  border-left:1px solid #cccccc;
  padding:6px;
}
 
#main a:link, #main a:visited {
  color:#333333;
  text-decoration:none;
}
#main a:hover {
  text-decoration:underline;
}
 
div.flash {
  background-color:#eeeeee;
  padding:4px;
  margin:8px 0;
  text-align:center;
  vertical-align:baseline;
}
 
#flash_error {
  border:2px solid #e0a0a0;
}
#flash_notice {
  border:2px solid #a0e0a0;
}
 
/* ======================= Errors ======================= */
div.errorExplanation {
  border:2px solid #e09090;
  background-color:#eeeeee;
  margin:6px;
  padding:6px;
}
div.errorExplanation h2 {
  font-size:1.1em;
  font-weight:bold;
  border-bottom:1px solid #999999;
  margin-bottom:0;
  padding-bottom:2px;
}
div.errorExplanation ul {
  margin:0;
  padding-left:20px;
}
div.errorExplanation p {
  margin:2px 0;
}
 
/* ======================= General ======================= */
.right {
  float:right;
}
.left {
  float:left;
}
.clear {
  clear:both;
  font-size:0.1em;
  height:1px;
  line-height:0.1em;
}
 
h4 {
  font-size:1.2em;
  font-weight:bold;
  margin:0;
  padding:0;
}
form {
  margin:0;
  padding:0;
}
input {
  background-color:#cccccc;
  padding:2px;
  border:1px solid #444444;
}
.smaller {
  font-size:0.8em;
}
 
4.11

Just one more thing to do. We need some more routes defined in our application to give us some shortened and more convenient links, plus we want to set the new default page. We just need to add a few lines to the routes.rb file. This is the whole new file:

$HOME/projects/webmarks/config/routes.rb
ActionController::Routing::Routes.draw do |map|
  map.resources :users
  map.resource :session
  map.activate 'activate/:activation_code', :controller => 'users', :action => 'activate'
  map.signup '/signup', :controller => 'users', :action => 'new'
  map.login '/login', :controller => 'sessions', :action => 'new'
  map.logout '/logout', :controller => 'sessions', :action => 'destroy'
 
  map.root :controller => 'public', :action => 'index'
 
  # Install the default routes as the lowest priority.
  map.connect ':controller/:action/:id'
  map.connect ':controller/:action/:id.:format'
end

You’ll see that we set “map.root” to call the “public” controller with the “index” action and we made some new URL forms that we can use to get to the create user, log in, and log out pages.

 
4.12

Now we’re ready to try it all out and see how it looks. For a complete test of all our new pages and flash notices and so on, we’ll blow away our existing test user and create a new one, activate it, then try logging in and out.

$ cd $HOME/projects/webmarks
$ rake db:migrate:redo STEP=1
$ ./script/server

Go to http://localhost:3000/ in your web browser. It should look like this:

webmarks home page

Click “Create account” and go through the user creation process. You should see feedback messages in the web page at each step. Play around with it some - verify that you can log out and back in again (once you’re activated). When you’re satisfied that everything is working as it should, we can commit all this new stuff we’ve added.

Remember: When you create new files in a directory under version control, you need to tell subversion to add them to the repository. The most reliable way to make sure you get everything is to ask subversion for a status report. Any new files that are not yet in the repository will show up with a “?” next to them.

$ cd $HOME/projects/webmarks
$ svn status -u

We need to do “svn add” for everything that subversion doesn’t know about yet. If you’ve followed the tutorial to the letter so far, this command will do the trick. Otherwise, you may need to change the file listing for your setup.

$ svn add app/controllers/public_controller.rb app/helpers/public_helper.rb app/views/public app/views/layouts/application.html.erb test/functional/public_controller_test.rb public/stylesheets/root.css

Next, enter that “svn status -u” command one more time. Check through the output and verify that there are no more files flagged with a “?”.

$ svn status -u

All okay? Then we’re ready to commit.

$ svn commit -m "Added new authentication routes; added default layout and style sheet"
 

It’s finally starting to look like a real web site. In the next stage, we’ll add one more piece of authentication functionality before we get to the rest of the application: an “I forgot my password” link that triggers a password reset email. The passwords are stored in a one-way crypted fashion, so we can’t email the user the actual password. What we can do, though, is generate a special code (very much like the activation code) that will let the user get to a password reset screen. This function could be added to the application at almost any stage, since it’s not critical for development, but we’ll do it now so that we’ll have a fully functional Web Application With Authentication that could be used as a model for future web apps.