Sunday, December 12, 2010

Devise conflicts with session.rb model

Sessions vs "sessions"

Wikidiscography.com is a session-based discography. As such, it has a model called session.rb along with the corresponding controllers and views. These sessions are not internet/computer/user/browsing sessions, but recording sessions. So I already have a model/controller/view set called "sessions."

When I installed the Devise authentication system for the first time, I got an error when I tried to sign in after creating a new user.

Sign in error

To sign in as a user, one navigates to /users/sign_in. This is a route supplied by Devise to sign in using the credentials already existing in the database. When we click okay, the following error appears: 'Routing Error No route matches "/sessions/user"'.

After signing in with valid credentials

Notice the URL that it redirected to after the attempted sign-in: /session/user. I already have a session model and a sessions_controller, so as I have the application currently set up, this will cause Rails to look for a "user" action in the file sessions_controller.rb. Devise is hitting a resource conflict.

Solution

One way to figure this out is to rename MY session resources to "music_session." I go over how to do this in a separate post. With "session" renamed to "music_session" and "session_type" renamed to "music_session_type" everywhere in the application, Devise is now able to sign in an existing user successfully.

This is not necessarily the best or only way to solve this problem. A more advanced Rails user (not me) might be able reconfigure the routes that Devise uses so that it avoids the "session" part of the url. But by doing it this way I was able to let Devise have its default settings. Now everything "just works." Thanks Rails. Thanks Plataformatec (makers of Devise).

Saturday, December 11, 2010

Adding Authentication with Devise 1.2.rc to a Rails 3.0.3 Application

NB: This post describes my experience following Ryan Bates' Railscast on installing Devise. He uses Rails 3.0.0 with Devise 1.1.rc0; I use Rails 3.0.3 with Devise 1.2.rc. As such my experience differs slightly from the screencast: I change one script command and experience two fewer problems.

Introduction: Choosing Devise

I want wikidiscography.com data to be available to anyone without requiring a log-in, but I only want registered users to be able to add, remove, and edit data. I'm not that this is the best idea; after all, wikipedia doesn't require registration. One good reason may be that correctly entering discographical data may be difficult, so I might want to have some control over who gets editing privileges. For no rigorous reason besides reading a few blog posts and checking out ruby-toolbox.com, I chose Devise. In this blog post, I'll go over how I install it to wikidiscography.com. The first thing I did was check out the ASCIIcasts on Devise and on customizing Devise. Both of these are based on Ryan Bates' railscasts.com screencasts. Thank you Ryan.

Create a new git branch

Before I change any application code, I want to create a new branch on git:
$ git branch devise
$ git checkout devise

Check that you're in the correct branch with
$ git branch
and see that the asterisk is next to the "devise" branch. Now we can safely delete files and destroy the application, and if we make irrecoverable mistakes, we can just delete the branch and be back to where we were.

Remove previous authentication code

Since I've been going through Michael Hartl's Ruby on Rails tutorial which includes a section on creating a custom authentication solution from scratch, the first thing I have to do is go through the application and remove all that code. This includes the following places:

  • user.rb
  • users_controller.rb
  • specs
  • helpers
  • Gemfile (remove unnecessary gems)
  • routes
  • layouts (remove signup link from _footer)
  • database tables (probably a user table and the associated routes; remove them by migrating down and the deleting the migrations)
Once that is done, restart the server and poke around the application to make sure all the code from the previous authentication solution has been removed. From the command line, run $ Rspec spec for one more check. If those tests pass, it's time to move on to installing Devise.

Installing Devise

From here on, I'll be following the Devise ASCIIcast, but making it specific to wikidiscography.com.

Which version?

First, which version should we install? Since this is a Rails 3 application, we should probably go with the latest version currently available. If it were a Rails 2.3 app, you should use Devise 1.0.6, according to Ryan Bates. Since a few months have passed since the Railscast was released, we should look at the Devise changelog to see what the latest version offers. The latest release as of this writing is 1.2.rc. We'll go with that for now, and if it breaks something we may need to downgrade later. (Update: It didn't break anything.)

Add it to the Gemfile

Add the appropriate line to the Gemfile, and then let Bundler do the work of finding and installing it: Gemfile:
gem 'devise', '1.2.rc'

Command line from within wikidiscography folder:
$ sudo bundle install
(This should take about 30 seconds.) Among all the rest of the output that this generates, I get the following lines:

Installing bcrypt-ruby (2.1.2) with native extensions  Using bundler (1.0.0)  Installing orm_adapter (0.0.3)  Installing warden (1.0.2)  Installing devise (1.2.rc) 

This tells me that Bundler has installed not only Devise, but its dependencies, which are Warden, orm_adapter, and bcrypt-ruby. I don't know what these are, nor do I care. According to the ASCIIcast, the next step is to run the installation generator.

$ rails generate devise_install
For me, this returned the following error:

Could not find generator devise_install
This is why it's a good idea to use the same version of software that the tutorial uses. It turns out that the newer version of Devise that I installed (1.2.rc) has a slightly different installation script, as noted in the installation notes on Devise's GitHub page. The new install command is:

$ rails generate devise:install
This creates the files config/initializers/devise.rb and config/locales/devise.en.yml. It also presents the user with the following instructions:

===============================================================================

Some setup you must do manually if you haven't yet:

  1. Setup default url options for your specific environment. Here is an
     example of development environment:

       config.action_mailer.default_url_options = { :host => 'localhost:3000' }

     This is a required Rails configuration. In production it must be the
     actual host of your application

  2. Ensure you have defined root_url to *something* in your config/routes.rb.
     For example:

       root :to => "home#index"

  3. Ensure you have flash messages in app/views/layouts/application.html.erb.
     For example:

       <p class="notice"><%= notice %></p>
       <p class="alert"><%= alert %></p>

===============================================================================
Those are pretty good instructions, so before moving on, we follow them.

Follow the 3 installation steps

1. Copy the line in the instructions into the file config/environments/development.rb:

config.action_mailer.default_url_options = { :host => 'localhost:3000' }

2. Check the file config/routes.rb to see if there is a
root :to => ... root :to => "pages#home"
, so step 2 is already complete.
3. Check the file app/views/layouts/application.html.erb to see if we have flash messages there. Wikidiscography has the following line:

<%= flash[:notice] %>

but it doesn't have an "alert" line. I don't know if alert is redundant or new to Rails 3 or what, but I'm going to add the line to my application layout anyway because I want Devise to work. I'll remove code later if I can. So now my application layout has the following two lines:

<%= flash[:notice] %>
<%= alert %> 

As far as I can tell, we have satisfied the 3 requirements presented by the Devise installation script.

Let Devise Create a User Model

(I'm still following the ASCIIcast.) Now that Devise has been properly installed (I hope), from the command line type:

$ rails generate devise User
For wikidiscography, this creates the following output:

invoke  active_record
      create    app/models/user.rb
      invoke    test_unit
      create      test/unit/user_test.rb
      create      test/fixtures/users.yml
      create    db/migrate/20101213002243_devise_create_users.rb
      insert    app/models/user.rb
       route  devise_for :users



Notice that instead of generating Test::Unit files as in the ASCIIcast, it invoked Rspec and generated Rspec specs. That's because I previously set up Rspec as my testing environment. Besides that difference, everything seems to be working the same. It also generated a user.rb file at /app/models/user.rb, and a database migration at /db/migrate/20101211053242_devise_create_users.rb. These files give us four modules by default: database_authenticatable, recoverable, rememberable, and trackable. I don't really know or care what these do at the moment, so I'm going to accept the defaults and run the migration.

Migrate the database

From the command prompt, type:

$ rake db:migrate
This produces the following output, showing a new users table, and indexes on two columns:

==  DeviseCreateUsers: migrating ==============================================
-- create_table(:users)
   -> 0.0145s
-- add_index(:users, :email, {:unique=>true})
   -> 0.0327s
-- add_index(:users, :reset_password_token, {:unique=>true})
   -> 0.0024s
==  DeviseCreateUsers: migrated (0.0502s) =====================================

Once you've migrated the database, restart the server.

Check out the new routes

At the command line, type in

$ rake routes

to see a list of all routes available to the application. Notice the routes associated with the controller "devise":

new_user_session GET    /users/sign_in(.:format)       {:action=>"new", :controller=>"devise/sessions"}
            user_session POST   /users/sign_in(.:format)       {:action=>"create", :controller=>"devise/sessions"}
    destroy_user_session GET    /users/sign_out(.:format)      {:action=>"destroy", :controller=>"devise/sessions"}
           user_password POST   /users/password(.:format)      {:action=>"create", :controller=>"devise/passwords"}
       new_user_password GET    /users/password/new(.:format)  {:action=>"new", :controller=>"devise/passwords"}
      edit_user_password GET    /users/password/edit(.:format) {:action=>"edit", :controller=>"devise/passwords"}
                         PUT    /users/password(.:format)      {:action=>"update", :controller=>"devise/passwords"}
cancel_user_registration GET    /users/cancel(.:format)        {:action=>"cancel", :controller=>"devise/registrations"}
       user_registration POST   /users(.:format)               {:action=>"create", :controller=>"devise/registrations"}
   new_user_registration GET    /users/sign_up(.:format)       {:action=>"new", :controller=>"devise/registrations"}
  edit_user_registration GET    /users/edit(.:format)          {:action=>"edit", :controller=>"devise/registrations"}
                         PUT    /users(.:format)               {:action=>"update", :controller=>"devise/registrations"}
                         DELETE /users(.:format)               {:action=>"destroy", :controller=>"devise/registrations"}
                    root        /(.:format)                    {:action=>"index", :controller=>"welcome"}

Where did all these routes come from? Check out the /config/routes.rb file and notice the line that the devise generator added at the top:

devise_for :users
Again, I don't recognize that syntax, I don't understand how it works, but I'll accept the routes it gives me and happily move on. If you really want to dig into it, check out the file /usr/lib/ruby/gems/1.8/gems/devise-1.2.rc/lib/devise/rails/routes.rb. It's well commented with detailed instructions on how to read and modify routes. Good luck.

Use the new sign_up route

(Still following the ASCIIcast.) At this point, Devise is ready to go. Navigate to /users/sign_up and we'll see a sign-up form. But wait a minute: The Devise generator did not generate any view files, and I certainly never made a sign-in form. So where is this coming from? To see the answer, take a look at the server console output:


Started GET "/users/sign_up" for 127.0.0.1 at Sun Dec 12 19:29:51 -0500 2010
  Processing by Devise::RegistrationsController#new as HTML
Rendered /usr/lib/ruby/gems/1.8/gems/devise-1.2.rc/app/views/devise/shared/_links.erb (1.9ms)
Rendered /usr/lib/ruby/gems/1.8/gems/devise-1.2.rc/app/views/devise/registrations/new.html.erb within layouts/application (56.0ms)
Completed 200 OK in 65ms (Views: 62.6ms | ActiveRecord: 0.0ms)

This shows us a few things: First, it says "Processing by Devise::RegistrationController#new". I never made a "registration controller", but apparently by pointing the application to /users/sign_up it called the "new" method on this phantom controller. Second, the penultimate line gives us more information: "Rendered /usr/lib/ruby/gems/1.8/gems/devise-1.2.rc/app/views/devise/registrations/new.html.erb". There's the answer: The sign-up form is in the gem subdirectory. So if I navigate to usr/lib/ruby/gems/1.8/gems/devise-1.2.rc/app/, I see all the files that Devise uses to work its magic. At this point I'm content to let it be magical without deeply understanding it.

Try signing up

I try out the sign-up form by putting in my name and email address and submitting the form. It works! And it displays a flash message that reads "Welcome! You have signed up successfully." I notice that is also displayed an empty flash[:notice] message, but that is probably something in my own code, and something to debug later. For now, since we know user registration is functioning properly, we move forward and test out other features.

Try signing out

Now that I'm registered and signed in, I click around a few pages, and then try signing out. There's no "sign out" button because I never created one, but as Ryan Bates points out via the ASCIIcast, you can sign out by pointing the browser to the sign-out route. Which route is that, exactly? Find it by referencing the routes output from

$ rake routes
The line we need is "/users/sign_out", so type that into the address bar and see what happens.

After visiting /users/sign_out
Voila, we are signed out and given a friendly flash message: "Signed out successfully."

Sign in again

Since we now have a user in the database, we go back to /users/sign_in to sign in using the credentials we supplied when signing up. At this point in the ASCIIcast, Ryan Bates describes an error caused by Rails and how to fix it. Since that episode, the bug has been fixed, and everything just works. Devise is now able to sign in an existing user successfully.

Git commit; git push

Now that everything seems to be working properly, I want to commit my changes and and merge back into the master branch.

$ git commit -am "installed and configured Devise"
$ git checkout master
$ git merge devise
$ git branch -d devise

In the code above, I first committed my changes and added a custom message; then I moved from the devise branch to the master branch using the "checkout" command; then from the master branch I called for the devise branch to merge into the master branch; then I deleted the devise branch.

Done. This got us most of the way through the ASCIIcast, but with considerably fewer problems. This is because we are using a newer version of Rails (3.0.3 instead of a 3 release candidate) and a new version of Devise (1.2.rc instead of 1.1.rc0).

Next post will deal with actually using Devise and adding real user authentication.

When and how to create and merge branches on git

The Scenario

Here's the scenario: I want to add a new and different authentication system (Devise) to wikidiscography.com. Should I just commit any outstanding changes I've made, then do the entire switchover, then do another commit, so that all my Devise additions are in one neat little commit? No, probably not. Why? Because I'm not a great coder or a great user of Rails, so my attempt to install and configure Devise will probably take several days. Sometime in the middle of the switchover I might find that I have to make other changes, so those changes would be rolled into the Devise git commit. My understanding of git best practices is that changes should be committed as logical groups. So what's the answer? I think the answer is to create a new branch, make the Devise changes there until it works properly, then merge that new branch back into the master branch. Simple enough.

NB: For a great explanation git and how to use it, see http://gitref.org/

Make a new git branch

Much of this is learned from http://gitref.org/branching/
From the command, from within the application folder, create a new git branch and call it "devise":

$ git checkout -b devise


(The -b flag creates the new branch and immediately switches over to it). Check that you're in the correct branch with $ git branch, and see that the asterisk is next to the "devise" branch. Now we can safely delete files and destroy the application, and if we make irrecoverable mistakes, we can just delete the branch and clone the original repository from Git.

But now that I've been working on the Devise configuration for a while, I find that I have to make another major change that again really deserves its own separate commit or even its own branch. I have to rename two sets of models/controllers/views from "session" and "session_type" to "msession" and "msession_type," respectively. But I'm already in the Devise branch; can I just make another branch, make the renaming changes, and then merge back? Do I merge it back to the Devise branch, or back into the master branch? Let's just move forward and see what happens.


Make another new git branch

From the terminal again, type
$ git checkout -b rename_session_to_msession

and ensure you're in the new branch with git branch. Now go through all the code and replace "session" with "music_session" everywhere. I also did this with migrations, which probably wasn't the best way to do it. I think the Rails way would have been to create another migration in which I renamed the tables and the table columns. This might cause problems later when I try to push everything to Heroku. But for now, it's enough for me to get my development environment working.

After going through the application and making sure everything still worked now that "session" has been replaced with "music_session", I commit my changes.
$ git commit -am "changed 'session' to 'music_session' everywhere".

Now I have a branch with a committed set of changes.


Merge the branch

Now I want to merge this branch back into the "devise" branch. First switch to the "devise" branch:
$ git checkout devise

Then tell Git to merge the "rename_session_to_msession" branch into the "devise" branch:

$ git merge rename_session_to_msession

Now I'm done with the "rename_session_to_msession" branch, so I can delete it:

$ git branch -d rename_session_to_msession

Now my "devise" branch shows the renamed session items, so I can continue working on the Devise configuration.

Done.

Friday, December 10, 2010

Ruby on Rails Tutorial: Learn Rails by Example

A major aid in my attempted progress in understanding Rails and Ruby programming has been Michael Hartl's excellent Ruby on Rails Tutorial. He takes the reader through literally every step of building a database-backed web application in Ruby on Rails, and he assumes virtually no prior knowledge. Three things make this tutorial especially valuables. The tutorial:
  1. is based on Rails 3, but includes a parallel course on Rails 2.3;
  2. includes steps on how to deploy on Heroku; and
  3. includes test-driven development with Rspec.
 There are many more reasons why this tutorial is great, but those three should be enough to merit a look by most of us would-be developers. Thanks Michael.

http://railstutorial.org

How to upgrade from Rails 3.0.0 to Rails 3.0.3.

On November 15, 2010, the Rails team released Ruby on Rails 3.0.3. If you've already upgrade to 3.0.0, here's how to upgrade to the newest release.

First, check what version of rails you're currently running. From your application folder (mine is ~/wikidiscography), type rails -v. For my application that returns Rails 3.0.0.

Now check your Gemfile (mine is at ~/wikidiscography/Gemfile), and find the line that should read something like gem 'rails', '3.0.0'.

First, try removing the version argument (everything after the comma), so the new line should just read gem 'rails'. Then, from the command line, run $ sudo bundle install and see if it installs the latest version of Rails. My truncated result: ... Using rails (3.0.0) ... Your bundle is complete! .... So removing the version argument does nothing.

Next try adding the version argument back in to your Gemfile with the latest version of rails, 3.0.3: gem 'rails', '3.0.3'. Note that we've changed "3.0.0" to "3.0.3". Now save the file and run $ sudo bundle install again. This takes about 30 seconds to run.

My result:

andy@andy-laptop:~/wikidiscography$ sudo bundle install
Fetching source index for http://rubygems.org/
You have requested:
  rails = 3.0.3

The bundle currently has rails locked at 3.0.0.
Try running `bundle update rails`
Fair enough. Let's do what it suggests, and run 'bundle update rails'. I'm also interested in why and how my bundle has locked the application at 3.0.0. From the command line: $ sudo bundle update rails. Again, this takes about 30 seconds. My result:
andy@andy-laptop:~/wikidiscography$ sudo bundle update rails
Fetching source index for http://rubygems.org/
Using rake (0.8.7)
Using abstract (1.0.0)
Installing activesupport (3.0.3)
Using builder (2.1.2)
Installing i18n (0.5.0)
Installing activemodel (3.0.3)
Using erubis (2.6.6)
Using rack (1.2.1)
Using rack-mount (0.6.13)
Installing rack-test (0.5.6)
Using tzinfo (0.3.23)
Installing actionpack (3.0.3)
Using mime-types (1.16)
Using polyglot (0.3.1)
Installing treetop (1.4.9)
Installing mail (2.2.12)
Installing actionmailer (3.0.3)
Installing arel (2.0.6)
Installing activerecord (3.0.3)
Installing activeresource (3.0.3)
Using bundler (1.0.0)
Using diff-lcs (1.1.2)
Using factory_girl (1.3.2)
Installing thor (0.14.6)
Installing railties (3.0.3)
Installing rails (3.0.3)
Using factory_girl_rails (1.0)
Using gravatar_image_tag (0.1.0)
Using nokogiri (1.4.3.1)
Using rspec-core (2.1.0)
Using rspec-expectations (2.1.0)
Using rspec-mocks (2.1.0)
Using rspec (2.1.0)
Using rspec-rails (2.1.0)
Using sqlite3-ruby (1.3.1)
Using webrat (0.7.1)
Using will_paginate (3.0.pre2)
Your bundle is updated! Use `bundle show [gemname]` to see where a bundled gem is installed.

So this has installed the 3.0.3 version of all the modules that make up rails (activerecord, activeresoruce, actionmailer, etc). Now if you run from the command line $  rails -v you should get the return Rails 3.0.3.

Now try restarting the server with $ rails server. The result:

andy@andy-laptop:~/wikidiscography$ rails server
=> Booting WEBrick
=> Rails 3.0.3 application starting in development on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
[2010-12-10 12:23:05] INFO  WEBrick 1.3.1
[2010-12-10 12:23:05] INFO  ruby 1.8.7 (2010-01-10) [i486-linux]
[2010-12-10 12:23:10] INFO  WEBrick::HTTPServer#start: pid=4872 port=3000
According to the output, I am now running a Rails 3.0.3 application.

To recap:
  1. Check your Rails version from the command line: $  rails -v
  2. Modify your Gemfile to the latest rails version: gem 'rails', '3.0.3'
  3. Run Bundler's updater: $ sudo bundle update rails
  4. Restart your application's server.
  5. Done (if all your test are passing).