The Rails Tutorial

I really like the Ruby on Rails Tutorial by Michael Hartl. I started working my way through it some time ago, and am now starting it all over again. My hat is definitely off to the author, not just for writing such an excellent tutorial and keeping it up to date, but for making it available freely online.

As I work my way through it, I found myself eager to make my own notes, which are what you are reading this very moment. My intention was for these notes to serve as a sort of cheat-sheet as I built my own rails apps, but I find myself documenting ugly issues as they arise, as well as making comments of my own on rails topics.

Before you get started, consider doing a multi-terminal setup like I do to make you live more civilized jumping between the MVC directories (and others).

And I will make another up-front suggestion. The author drags you kicking and screaming into TDD (Test Driven Development), not to mention version control. I strongly recommend ignoring all the TDD material on the first time through the tutorial. TDD occupies over half of the tutorial and frankly gets in the way of learning rails. It is too much to ask of the student to be climbing all these learning curves in parallel. You absolutely can do rails without TDD and version control (just like you can do any software project without them). The continuity of rails learning experience is really disrupted by all the TDD material.

What I recommend is making two passes through the tutorial. The first time just do the rails stuff and ignore git and TDD. Then make a second pass and add the others, if you like. You are in charge as the student after all. I honestly think that this would greatly improve the rails learning experience. I will wager the author contemplated this, but said something like: "Yeah, but the lazy scumbags will never come back and do the second pass". Not all of us eat our daily portions of fruits and vegetables either. Actually he probably didn't say exactly that -- he seems like a nice guy for the most part.

A link to my heroku demo: My demo app at heroku.

Another side note - when I first started the tutorial I found that the tutorial site itself "locked up" now and then. I suspect javascript bugs in the site itself, in fact firebug complained about some fishy JSON business. All this seemed to get fixed and get better later, and a reload was usually sufficient to fix things.

Chapter 1

October 26, 2012

As near as I can tell, the goal in Chapter 1 is just to get you to install and try out all the bits and pieces you will be needing to get through the rest of the tutorial. Even though I already was running rails on the machine I intended to use for the tutorial, I hit a number of serious snags. I am sure that at the time the tutorial was written, the author did all this on his system, with his set of packages, and this all worked. At least I hope so.

I am running a Fedora 17 linux system. Ruby 1.9.3 is installed along with Rails 3.2.6. I have Git already installed and have been using it for other things. I use vim already as my editor of choice. Since I will be using "vi", I need to mentally substitute "vi" for subl which is what the author uses as his editor of choice.

I decided to take the plunge and install RVM (the ruby version manager), as recommended. I merrily typed "yum install rvm" and there it was (or so I thought). However it turns out that the "rvm" package under fedora is actually something called the "recoverable virtual memory" library, not the ruby version manager.

So rvm and fedora is a knot yet to be untied. I am concerned about how rvm might interact with fedora RPM packages, gem packages, and all that. Perhaps someday.

I have gem 1.8.24, and he suggests freezing at this version via "gem update --system 1.8.24", but I don't. I place all the tutorial files into /u1/rails/tutorial (next to my current rails files, just to be tidy).

cd /u1/rails/tutorial
rails new my_project
As noted in the tutorial, app/assets is new (this is rails 3.2 after all) I edit the Gemfile to freeze package versions. The tutorial would like me to use rails 3.2.8, and I specify this in the Gemfile, then I must do bundle update (rather than bundle install), but it does bring in the newer version of rails, which is impressive.

When I type "rails server", it blows up looking for a javascript runtime (bummer). Something called Node.js is the recommended thing, but Fedora already has an existing package called "node" which contains a file /bin/node which would conflict with Node.js. No clue what the fedora "node" package is or does (and it is not installed on my system). There is a site that is unofficially supporting nodejs for Fedora:

I do this:
yum localinstall --nogpgcheck http://nodejs.tchol.org/repocfg/amzn1/nodejs-stable-release.noarch.rpm
yum install nodejs-compat-symlinks npm
This pulls in 33 dependent packages (a flock of nodejs packages as well as "v8").
Note: As of 10/27/2012 this will pull in nodejs packages with some kind of nasty bug (from the fedora-updates respository), see the notes in chapter 2 about getting nodejs from another repository.

And after this we can type "rails server" to get a demo server on localhost:3000. Great!

The tutorial dives into a nice introduction to the MVC (model/view/controller) concept in rails. I suppose at some point we will get a dose of propoganda about "REST" as well. Then we get a serious lesson about using git (which is fine by me since I am in transition from svn to git and I like git). I am going to bypass the business of using GitHub (so I can ignore the git remote add and git push commands in the tutorial).

Just so you know, git is a distributed version control system. This means that you always have a local repository that you add, commit, and checkout from. You may also have one (or none or several) remote repositories also. Quite different than SVN for example, but I like it a lot. Here is a cheatsheet of some git commands:

git init
vi .gitignore
git add .
git commit -m "initial"
git status
git branch my_branch
git commit -a -m "message"
git checkout master
git merge my_branch
git branch -d my_branch
The final step in chapter 1 is deployment, which he does using a service called "Heroku". I am a total fan of what he says about deploying early and often. So, I set up an account on heroku, get the "toolbelt" (which goes to /usr/local/heroku), I set up a symbolic link from /usr/local/bin/heroku to /usr/local/heroku/bin/heroku (rather than putting the heroku bin directory on my path). Then I do heroku create -- which tells me:
Uploading SSH public key /home/tom/.ssh/id_dsa_cholla.pub... done
Creating pure-forest-8703... done, stack is cedar
http://pure-forest-8703.herokuapp.com/ | git@heroku.com:pure-forest-8703.git
Git remote heroku added
Then I do "git push heroku master" and a lot of stuff happens, followed by the message:
 !     Detected sqlite3 gem which is not supported on Heroku.
 !     http://devcenter.heroku.com/articles/how-do-i-use-sqlite3-for-development
 !
 !     Heroku push rejected, failed to compile Ruby/rails app

 To git@heroku.com:pure-forest-8703.git
Well, how about that!! The bottom line is that they recommend using: PostgreSQL and do not support sqlite3 (and they recommend against using sqlite3 for development and PostgreSQL for deployment).
(Just a note - we hate mixed case names like POSTgresql).

Well, I think the best answer to this is to ignore their excellent recommendation (this is just a tutorial after all) and use sqlite3 for development and pOsTgReSQL for deployment. Basically following the advice along these lines given in:

What they say to do is to edit your Gemfile and replace the line:
gem 'sqlite3'
With this:
group :development, :test do
   gem 'sqlite3'
end
group :production do
   gem 'pg'
end
Then, once again type "bundle install --without production". What the --without production does is to allow you to avoid installing the pg gem on your local system. This would also involve you in installing pOSTgreSql, which would infuriate you just by the number of times you had to reach for your shift key.

After this, commit and push to heroku -- and it works!!

git commit -a -m "no sqlite3 on production"
git push heroku master

And I can visit http://pure-forest-8703.herokuapp.com/ via my browser, and there is the app.

Only chapter 1 of the tutorial and we are already involved in several new and unexpected learning curves, and jumping through flaming hoops already! Rails has always been dynamic and exciting!

Chapter 2

October 27, 2012

This chapter aims to lead the user through a quick application set up using scaffolding. I decided to use a different machine to work through this one (Also a Fedora 17 linux machine, but one that I have never installed rails on). Because of this, I have to repeat some steps from chapter one (as well as things I did long ago and forgot about).

yum install rubygem-rails
The install of rubygem-rails pulls in 30 packages, but gives me rails 3.0.11. To get the newer rails version as used in the tutorial, I do:
gem install rails
This pulls in version 3.2.8, which is great, and now I do "rails new demo_app" It blows up during the bundle install (while building json) with this message:
        /usr/bin/ruby extconf.rb 
	mkmf.rb can't find header files for ruby at /usr/share/include/ruby.h
On a hunch, I do "yum install ruby-devel", then try again and I am OK.

Now I edit the Gemfile to lock down versions as instructed in the tutorial. It turns out I must have missed something in Chapter 1, as this has the changes to use PostGresql on the production site. After this:

bundle install --without production
Works fine, I put things under git and procede to creating a user model by typing the command "rails generate scaffold User name:string email:string". (I note that this is new, rails will generate the database schema as part of the "generate" -- though this could quickly get cumbersome for anything complex). This blows up with "Could not find a JavaScript runtime", which we know about from Chapter 1. We install node.js for fedora via:
yum localinstall --nogpgcheck http://nodejs.tchol.org/repocfg/amzn1/nodejs-stable-release.noarch.rpm
yum install nodejs-compat-symlinks npm
(It is handy to keep a root window open for all of this kind of thing, I never use sudo). When this finishes, I repeat the command (it works now) and "migrate the database", whatever the heck that means (the tutorial doesn't explain and tells me that it might give me a clue in chapter 6).
rails generate scaffold User name:string email:string
bundle exec rake db:migrate
rails server
And indeed, after this I have my useless application talking to firefox at localhost:3000 -- Great! Note that running rake, prefixed by "bundle exec" ensures that the same gem package set is used by rake as has been selected for the application. Things aren't as nice as they seem though. As soon as I try to visit "/users" as instructed, I get this:
ExecJS::RuntimeError in Users#index 
Showing /u1/rails/tutorial/demo_app/app/views/layouts/application.html.erb where line #6 raised:
nodejs: symbol lookup error: nodejs: undefined symbol: _ZN2v82V837AdjustAmountOfExternalAllocatedMemoryEi
line #6 is: <%= javascript_include_tag "application" %>
Apparently this is some kind of Node.js bug (and some google searching on the symbol lookup error yields a lot of information). The bug is reputed to be in the "v8" package - v8 is the javascript engine from Google. My evidence indicates that it is in Node.js itself (see below). One could complain about Google (they have a dubious reputation in the developer community), or one could complain about Rails dragging in every package besides the kitchen sink. The fix is apparently to get "v8" (and node.js) from a different repository. The buggy version of v8 I now have is from fedora-updates. The recommendation is to remove it, add the nodejs-stable repository, make sure v8 will not get installed from "updates" and reinstall. To ensure that v8 doesn't get pulled from fedora-updates, add the line "exclude=v8*" to the [updates] section of that repo control file.
yum -y remove v8
edit /etc/yum.repos.d/fedora-updates.repo and exclude "v8"
yum localinstall --nogpgcheck http://nodejs.tchol.org/repocfg/fedora/nodejs-stable-release.noarch.rpm
yum install npm
This seems to fix things, although I note that v8 gets installed from the "fedora" repository, not the nodejs-stable repository. I am content that things work, although I might feel better by adding the exclude=v8* line to the fedora.repo file as well and repeating this process. Actually I tried this, and it caused other problems with broken package dependencies, so go with what works.

I will also note that this is probably (hopefully) a transitory situation. As fedora packages get updated, the buggy node.js packages will get replaced by less buggy versions, new bugs will appear elsewhere, and so forth.

The tutorial now launches into some more discussion of MVC (it passed over "CRUD" - Create, Read, Update, Delete), though it did mention the list of database operations without the acronym. The tutorial gets five gold stars for discussing rails routing (which often does not get mentioned because it doesn't fit neatly into the MVC scheme and embarrases purists, or something like that). There is a brief mention of the all-too-popular "REST" acronym (Representational State Transfer), though without any useful discussion of what it is or why we should worry about it. And then the tutorial moves on to adding a Micropost "resource".

rails generate scaffold Micropost content:string user_id:integer
bundle exec rake db:migrate
Although the tutorial doesn't come right out and say it, it presents tables that show how the four http actions "POST, GET, PUT, and DELETE" along with URL's are mapped to controller actions by the rails routing code.

Note that while the database table (resource) is named "Micropost", the controller actions and URL's use the name "microposts". This is a standard rails convention. The tutorial goes on to introduce data validation and many to one mapping:

validates :content, :length => { :maximum => 140 }
has_many :microposts
belongs_to :user
When I try to run "rails console", I get:
/usr/local/share/gems/gems/activesupport-3.2.8/lib/active_support/dependencies.rb:251:in `require': cannot load such file -- minitest/unit (LoadError)
Once again, google to the rescue. Apparently this is a fedora specific issue. The brute force fix is to add minitest to the Gemfile (so they say). But with Ruby 1.9 this should not be necessary as minitest is now part of the standard ruby library, there is just some fedora specific brain-damage that is causing this problem. See this discussion. As someone in the discussion says, "And people wonder why nobody uses the system package managers when installing Ruby..." I add the line "gem "minitest" to my Gemfile, run "bundle install --without production" and I can run "rails console", which suits me, see the discussion for other possible "cleaner" solutions.

And now, I would like to "deploy" from my home machine, so I need to get the heroku "thing" going on my home machine:

su
wget -qO- https://toolbelt.heroku.com/install-other.sh | sh
cd /usr/local/bin
ln -s /usr/local/heroku/bin/heroku .
heroku keys:add
git remote add heroku git@heroku.com:pure-forest-8703.git
git push heroku master
heroku run rake db:migrate
The above push fails because the git repository at heroku holds to project from chapter one, and it makes no sense to just pushd the entirely different chapter two repository onto it, what I need to do is just nuke the repository at heroku and start over. We will get to that, but here are some nice heroku links: I will note that the "thin" webserver is recommended over the default "webrick" on heroku. You can switch to it via the following lines in the Gemfile:
group :production do
  gem 'thin'
end
I actually switched to it for everything and like it better, it starts much faster than webrick.

I will note that my app on heroku is using the generated name "pure-forest-8703", so I can do things like:

heroku info --app pure-forest-8703
=== pure-forest-8703
Addons:        heroku-postgresql:dev
Git URL:       git@heroku.com:pure-forest-8703.git
Owner Email:   ---@---
Repo Size:     6M
Slug Size:     9M
Stack:         cedar
Web URL:       http://pure-forest-8703.herokuapp.com/
The magic to force my new git repository on top of the old one is to add a plus sign in front of master, as follows:
git push heroku +master
Probably it would be better at some point to just destroy the application on heroku and start fresh. Looking ahead to chapter 3, it would seem that we will be starting over fresh there anyway, so we will start fresh with Heroku then.

A final question: where is the sqlite3 database file? As near as I can tell, this is simple, it is a single fine at db/development.sqlite3 Of course the deployment database is something different, a pOSTgresQl thing.

Chapter 3

October 27, 2012 To start fresh with the deployment on heroku:
heroku destroy pure-forest-8703 --confirm pure-forest-8703
This chapter promises to get us involved in automated testing. The verb "to refactor" is defined (it means to change the form of code without changing its function -- something I do all the time without putting a fancy name on it).
rails new sample_app --skip-test-unit
edit Gemfile (see below)
bundle install --without production
rails generate rspec:install
git init
git add .
git commit -m "get started"
heroku create
git push heroku master
I edit the Gemfile to lock versions (and I skip ahead and add all the gems as they hint that I might want to do as per chapter 9). I see my old friend will_paginate from a previous project. I also add "use 'thin'" since I found I like it better than "webrick" (not only do I like it better, but it doesn't have a ridiculous name). I also include the 'minitest' gem to get around the Fedora issue I tripped over in Chapter 2. It tells me I am using thin 1.5.0 (and minitest 4.1.0) and the bundle install goes cleanly.
gem 'thin'
gem 'minitest'

My application is now called: sheltered-beach-2619. I can use "git push heroku" whenever I feel good about things (and let heroku be both my git remote repository and deployment site). The command "heroku logs" can be useful in case of trouble.
All this is going entirely smoothly (knock on wood).

And now they are teaching me a new trick! I can type "vi ." and open a directory in vi, then use vi to navigate around the directory tree. I have done this now and then by accident, but never realized it could be useful. Given that when editing rails code you are always jumping from view to controller to model, this could be very nice.

After creating a static "hello.html" in public as directed, I make a git branch:

git checkout -b static-pages
rails generate controller StaticPages home help --no-test-framework
And I get to learn some new lingo (CamelCase and snake_case) for some conventions for typing names that I have used for years, but never knew there were names for. Isn't this tutorial great! And I learn that by substituting (with some care) the word "destroy" for "generate", I can undo things done by rails generation (which can involve many files). Nice to know if I want to change the name of a model or controller sometime. The word "rollback" also reverses a database "migrate". Nice to know, but I will appreciate it more when I understand what a migration is -- probably.

And a lesson on "REST" (the tutorial feeds this to us a bit at a time, lest we choke). The classic database CRUD actions map to HTML verbs as follows (more or less, kinda):

C - Create	http "post"
R - Read	http "get"
U - Update	http "put"
D - Delete	http "delete"
The way rails controllers work, is that when an action is being handled, first the method with the same name as the action in the controller is executed (for a static page this will usually be a stub method), then the view is rendered. For an action like "home" the view will be home.html.erb
git config --global user.name "Tom Trebisky"
git config --global user.email tom@mmto.org

git add .
git commit -m "add static pages controller"
git push heroku

rails generate integration_test static_pages
edit spec/requests/static_pages_spec.rb as described
bundle exec rspec spec/requests/static_pages_spec.rb
edit config/routes.rb
edit app/controllers/static_pages_controller.rb
create app/views/static_pages/about.html.erb

Now we dive into test driven development, learning about RSpec, Capybara, and down the road Cucumber. Amazing names (I like them much better than "webrick", excepting maybe RSpec which kinda falls flat). Along with passing tests, when I run "rails server", I can use URL's like: http://localhost:3000/static_pages/about.html

Heroku is not with me though. When I do "git push heroku", I keep getting "Everything up-to-date", which is bogus. I suspect this has something to do with branching (heroku is up to date with the master branch). I do this and it works. Heroku even starts up my new application version. I am told that Heroku will try to start the application in a new "dyno" (virtual cloud machine) first, and only switch to it if it starts properly. If it fails to start, the prior version of the application continues to run as if nothing happened.

git checkout master
git merge static-pages
git push heroku
git checkout static-pages
The above sequence does the trick. I switch to the master branch, merge in my changes, send them to heroku, and switch back to the static-pages branch for further work.

The chapter winds up with a discussion of "erb" (embedded ruby) and the methods "provide" and "yield". The advanced setup section though discusses "Guard" and "Spork" (more cool names) and automated testing, and ways to eliminate having to type "bundle exec" by using rvm. More than I have energy for today.

Chapter 4

October 28, 2012

I was able to go through this pretty quickly, since I am already a veteran ruby programmer. But it is indeed worth going through, even for veterans. If nothing else I picked up the new Ruby 1.9 hash idiom of "key: value".

The tutorial adds an application helper module using more embedded ruby. A fair bit of hands on fiddling with the rails console to illustrate ruby concepts, and then on to:

Chapter 5

November 2, 2012

The lesson now involves augmenting the layout using stylesheets, and in particular a thing called Bootstrap that was first invented for use with twitter. We also get to learn about something called sass which stands for "Syntactically Awesome Style Sheets", and claims to "make CSS fun again". Was it ever fun? Looks like we don't just get a rails tutorial, but we get to learn about everything but the kitchen sink (Can't we just learn rails without getting into stylesheets and server side javascript via Node, etc. etc. ?) Reminds me of a far-side comic where a guy is in the dentist chair with a dozen things in his mouth, and the doctor is holding a tennis ball, and says: "hope you don't mind, but we have a bet going on whether we can get this in there too".

We learn the trick/convention of using "#" as a target URL that has not been filled in yet. And then we learn (or rote copy) a lot of CSS and Bootstrap savvy markup. We learn about partials to augment and clean up views.

Rails assets consist of the off-to-the-side stuff like CSS, Javascript, and images. Now there is an official place to put this stuff. It used to have an official place (under public) and can still go there, but now there are 3 places: app/assets, lib/assets, and vendor/assets. A gem called "sprockets" now treats special css comments in a way that allows "master" css files to be "manifest files" that include all the others. Rails also recognizes several official file extensions to trigger preprocessors (.erb for embedded ruby, .scss for sass, and .coffee for coffeescript (whatever it is). Part of the game here is optimization. A collection of developer friendly files get lumped together into one file to be efficiently sent to the browser.

Sass adds a number of things to make CSS more pleasant. The tutorials discusses mixins, variables, and nesting via sass. We learn that we can replace "static_pages/about' with about_path (depending on the information in routes.rb). Lines like this:

match '/help',    to: 'static_pages#help'
root to: 'static_pages#home'
Define help_path and help_url (as well as root_path and root_url) as handy by-products.

After all of this, the demo application appears at localhost:3000 without needing to include "static_pages" in the URL. Then we learn to clean up our rspec files and move on to creating a user controller.

rails generate controller Users new --no-test-framework
rails generate integration_test user_pages
bundle exec rspec spec/requests
bundle exec rspec spec
git commit -am "xxx"
git checkout master
git merge this_branch
git push heroku
This gives us a user controller with a new action (which will fiddle routes.rb to be located at /signup).

We also delete public/index.html

It all was smooth until I deployed to Heroku. The application starts, but on the first access, it blows up with (Missing partial layouts/shim). It turns out this was some kind of mischief with git (none of the partials had been commited).

Chapter 6

November 3, 2012

Finally - this tutorial gets to creating a model and a database. It turns out "migrations" are the rails way of setting up (and making changes to) the database structure; something I have always done outside of rails using MySQL scripts and other ugly business. I much like the idea of not dealing with SQL directly, as well as not caring if SQLite or MySQL is behind my rails project (I am going with SQLite for the first time while following this tutorial and am liking it).

git checkout -b user-model
rails generate model User name:string email:string
bundle exec rake db:migrate
The migration creates the user table with 3 automatically generated columns. The "id" field that rails expects is generated, along with two timestamps (created_at and updated_at), which is nice. The database itself is at: db/development.sqlite3.

The tutorial next instructs me to run the sqlite browser, which I do not have by default on my machine, and which does not appear to be available as an RPM for Fedora. So I need to build it, which is OK I guess:

yum groupinstall 'KDE Software Development'
download source tarball
tar xvf sqlitebrowser_200_b1_src.tar.gz
cd trunk/sqlitebrowser
qmake
make
It is based on the C++ "qt" libraries, which is why I need the KDE development bundle. I also fetch the source tarball sqlitebrowser_200_b1_src.tar.gz. The tarball contains "trunk/sqlitebrowser". My system does not have qmake, even after installing the KDE bundle, and I tend to loathe any software that does not just use vanilla make, so I am abandoning this for now at least. I may also need the sqlite-devel packages (or is it sqlite3-devel?).

As instructed, I add the annotate gem to my Gemfile and run "bundle install". This allows me to do:

bundle exec annotate
This does not get automatically done when you change the model and do migrations, you have to remember to run it whenever you do a migration that changes the model structure.

The tutorial discusses how to avoid a mass assignment security hole. Then we dive into playing with the model using the rails console, which goes just as the tutorial says it should. I note that when you destroy and then create, the destroyed id does not get recycled -- a find on that id will yield a RecordNotFound exception. The console is handy for exploring the ActiveRecord API, and when run with --sandbox you are guaranteed not to alter the database.

Now we are on to validations (for presence, length, format, uniqueness, and confirmation).

After editing spec/models/user_spec.rb it is necessary to:

bundle exec rake db:test:prepare
bundle exec rspec spec/
What this does is to initialize the test database from the development database prior to running tests. After monkeying around adding a number of validations, the tutorial discusses a uniqueness scenario that can only be solved at the database level, and this involves modifying the database by creating a migration. The modification is to add an index to the email column, which has the important side benefit (apart from enforcing uniqueness) of avoiding full table scans each time we do a lookup by email address.
rails generate migration add_index_to_users_email
edit db/migrate/xxx_add_index_to_users_email
bundle exec rake db:migrate

The final thing is to add a password feature, leveraging the rails "has_secure_password" functionality. To do this, a column with the mandatory name "password_digest" needs to be added to the database (model). Note that there are virtual attributes "password" and "password_confirmation" that hold the plaintext passwords, but they never get saved to the model. Also these are automatically generated by has_secure_password

rails generate migration add_password_digest_to_users password_digest:string
bundle exec rake db:migrate
bundle exec rake db:test:prepare

Done with chapter 6. This is the meat and potatoes I have been waiting for. This chapter got me up to speed with migrations (which are great). When done, we have a working model with validations and password authentication. What we are dying to see is the form to set up new users from the web, as well as the controller code to handle users signing in. This will come along in Chapter 7 and following.

Chapter 7

November 6, 2012

Now things start getting interesting as we start building forms that can add stuff to our database. Just for the record, Rails has a "rails" object, with interesting attributes like an "env" hash. Also note that "heroku run console" will give you a rails console on heroku for your production server.

Next we add one line (and comment out another) in routes.rb:

#get "users/new"
resources :users
And we get a diatribe on "REST", and I get a chance to grind an axe. Every rails book and resource I read on one hand dismisses REST as a complex topic that is too deep to delve into. My take on this is that most rails writers don't have a clue what it is all about. My take goes even further that it is mostly a bunch of smoke and mirrors wrapped around basic concepts we all know and take for granted. After telling us how complex and impossible to understand it is, they then tell us that it is the only way to do things, will feed the starving millions, right all wrongs, wax our car, etc. etc. I grow weary and irritated, and I go beyond that when I find some rails writer scolding the use of non-REST-ful practices. The fabricated word / bad pun makes me nauseous, or nauseated, or maybe both. As if MVC dogma wasn't bad enough, we have REST on top of it. Just show me how to get things done without making it all a pain in the a$$ and I'll be convinced.

Anyway, the new "resources" in the routes.rb file gives us a whole flock of useful actions as a package deal, which I can like. And we create the file app/views/users/show.html.erb with a single line, and then load up the @user variable in app/controllers/users_controller.rb and voila -- show/1 works!

With us feeling good about this bit of success, we dive back into TDD, and will learn about something called "factories" which apparently will load users into the database, which can then be tested. In fact a gem with the entertaining name "factory_girl_rails" will save the day. And we add a users_helper to inject a "gravatar" image for each user. We further dress up the user "show" page using CSS.

Now the real deal: a form to generate new user entries! The following two lines flush out any junk we have previously loaded into the database. The second line may not be needed (and I skipped it).

bundle exec rake db:reset
bundle exec rake db:test:prepare
In the TDD world, Capybara augments Rspec and allows tests to simulate filling in forms, including clicking on buttons. A bunch of new stuff goes into spec/requests/user_pages_spec.rb and the form itself is built with the help of the "form_for(:user)" method in app/views/users/new.html.erb and we add the line "@user = User.new" to the new method in the user controller. There is some nice discussion about forms and how they ultimately yield a POST action to /users - which will end up looking for a create method in the user controller. A partial called app/views/shared/_error_messages.html.erb handles the display of error messages when a form is rejected. The pluralize method is a nice thing I have always wished for to avoid things like "you have 1 errors".

The style sheet keeps growing and growing ...

And then we get to the subject of the rails flash, along with a bogus statement that "the flash, which operates like flash memory in that it stores its data temporarily". Sorry Michael, but flash memory will store its data forever (until new data is written) and in no way does it act like a rails flash. Anyway, a bit of code in app/views/layouts/application.html.erb allows the contents of the flash to displayed as needed (the flash gets displayed once, then goes away). Once this is done, we can add lines like this:

flash[:success] = "Welcome to the Sample App!"
which is just what we do in the user controller create action.

To use SSL in production (which would be reasonable to do to avoid session hijacking and to allow secure password transmission), you add one line to config/environments/production.rb

# Force all access to the app over SSL, use Strict-Transport-Security, 
# and use secure cookies.
 config.force_ssl = true

Chapter 8

November 7, 2012

The first topic is implementing sessions via cookies. And the whole business is discussed as being within a "RESTful" framework, making clear to me what I have suspected all along, namely that the rails community is clueless about what REST is. They are using words like "RESTful" in their own peculiar way, and it will serve us well to figure out what that is, entirely apart from what REST itself is. (cookies are the most notorious violation of REST principles on the web, not that I care personally, but ...).

rails generate controller Sessions --no-test-framework
rails generate integration_test authentication_pages
Then we add routing to new/create and destroy (a subset of the usual REST actions). We add new, create, and destroy stub methods to the new session controller. We add a view for new and we can pass the first round of tests.

Now we create a form by hand (in the sessions/new view), whereas for users, it was basically built for us from the model (we have no sessions model).

And now we see a violation of the MVC paradigm as a view helper needs to be available to a controller. So, don't feel bad if to get the job done your work doesn't fit neatly into the MVC scheme. The tutorial discusses one option (storing the user ID in a cookie that expires after the browser session is done), but then opts for another option, namely storing a "remember_token" in the user database. This means that sessions persist forever (until the user explicitly signs out), but also means we get to modify and migrate the user table.

rails generate migration add_remember_token_to_users
-- edit migration file
bundle exec rake db:migrate
bundle exec rake db:test:prepare
bundle exec rspec spec/models/user_spec.rb
Note that the migration file needs to be edited to add the new column and index.

We then add a private method "create_remember_token" to the user model and set up a before_save callback to it to generate the user token. We add the sign_in method to the file app/helpers/sessions_helper.rb.

I find it ironic to find the following quote: "much of the elegance of Rails ultimately derives from the malleability of the underlying Ruby language." All this in the midst of rails being opinionated software. Well, the rails developers are a bunch of hypocrites, what can we say.
Note that without site-wide SSL, using tokens stored in a cookie would make us vulnerable to session hijacking via a replay attack.

We add a current_user() and current_user=() method to the session_helper.rb file, and are exposed to a ruby idiom "a ||= b", which sets to value of a only if it is nil.

We have to add bootstrap to app/assets/javascripts/application.js Now things are more or less working, but any users currently in the database do not have remember tokens set. We use the rails console to fix this. I only had one user in my database, so I could do:

User.first.save(validate: false)
With more than one user, you might need to do:
User.all.each { |user| user.save(validate: false) }

To view cookies under firefox, right click on the website, then select "view page info", select the Permissions tab and click the view cookies button.

Signout blows up with a missing template for sessions/destroy -- this is fixed by this method in sessions_controller.rb (this involves adding a sign_out method alongside sign_in in app/helpers/sessions_helper.rb).

  def destroy
      sign_out
      redirect_to root_url
  end
The final section in chapter 8 introduces "cucumber", which is an alternate way to write tests in a very human readable language (called gherkin).

Chapter 9

November 15, 2012

The tutorial is moving along much faster and more nicely now that I have a rational way of hopping around in the rails directory structure.

We add tests, then an edit action to the users controller. Note that when we run tests with the "-e" switch as follows:

bundle exec rspec spec/requests/user_pages_spec.rb -e "edit page"
We get an error "Unknown switches '-e'", so clearly the tutorial is out of synch with this aspect of the current rails.

The tutorial describes a subtle issue. The same form_for(@user) is used to edit the user as is used to create a new user. Yet the create form asks for a plain old POST for the create, but fakes a PUT for the edit. It does this dynamically by detecting whether or not the record exists using the new_record? boolean method in ActiveRecord, which is cool.

After adding the user edit form, we move on to authorization. Authentication is deciding if a user is who he says he is, authorization is limiting what an authenticated user can do. Now that I have added the sign_in(user) method to spec/support/utilities.rb I am getting the error:

undefined method `visit' for #
# ./spec/support/utilities.rb:11:in `sign_in'
So, I am blowing off tests for the rest of the tutorial (I can come back for a second pass and try to straighten this out, .... maybe). Actually there are more darn tests than there are rails tutorial and it is starting to get on my nerves.

We add a before filter to the users controller, and a private method called signed_in_user() to implement the filter. Now we get the error:

NoMethodError (undefined method `signed_in?' for #):
This is defined in app/helpers/sessions_helper.rb, so what is the beef? The beef is we need to add these lines to users_controller.rb
  protect_from_forgery
  include SessionsHelper
This gets us back on the air. The next step is to add another before filter to ensure, not just that any user is logged in, but that the correct user is. The correct_user method in the before filter does the "find" on the user id, so that method can be removed from the edit and update actions.

Now we implement friendly forwarding via a couple of methods in app/helpers/sessions_helper.rb In particular, a variable :return_to in the session_hash is set to the URL we were attempting to visit when it was discovered we were not authenticated. Then we add some code to the create method in the user controller.

Now we add a method to show all users. This gets a users/index.html.erb and an index method in the users controller. And the tutorial blows up with a complaint about 2 arguments when I should have one in

ActionView::Template::Error (wrong number of arguments (2 for 1)):
    4: 
Apparently "gravatar_for" will accept only one argument, so we ditch the size: 52 argument. Nothing we like better than an unstable API (and rails including a dozen packages with unstable API's). Yep, that fixes it.

Now something interesting. Via the "faker" gem, we are going to add a bunch of users to the database quickly. We put a file into lib/tasks/sample_data.rake, with this in place, we do:

bundle exec rake db:reset
bundle exec rake db:populate
bundle exec rake db:test:prepare
The problem now is signing into the flused out database. Use "example@railstutorial.org" with password "foobar". This works great, and I have an index of 100 users! Nice!

Now we are on to using my old friend will_paginate. We zip along fast since we are now wisely blowing off all the test stuff in the tutorial (and you absolutely should the first time through). We add two lines with will_paginate to the index view, then (since will_paginate adds some methods to active record), we go the the index method in the users controller and change the find all to:

@users = User.paginate(page: params[:page])
Wow! two lines of code added and one changed and we are paginating. To paginate with other than the default of 25 items per page, add the per_page argument as follows:
@users = User.paginate(page: params[:page], per_page: 20)
To set up a special class of users with administrative priveleges involves adding an "admin" boolean to the database, which requires a migration:
rails generate migration add_admin_to_users admin:boolean
We edit the migration to add ", default: false". Note that rails adds the admin? method upon seeing a boolean.
bundle exec rake db:migrate
bundle exec rake db:test:prepare
We edit lib/tasks/sample_data.rake to turn on the admin attribute for the first user. Note that since "admin" is not an accessible attribute, we cannot just set it in the initialization has, but need to use the toggle! method. After this:
bundle exec rake db:reset
bundle exec rake db:populate
bundle exec rake db:test:prepare
Now we add the destroy action to the user controller, with a before filter so only admin users can invoke the destroy action.

Now I get into trouble because I skipped the refactoring that set up the _user.html.erb "partial", so I need to go back and do that since the delete links get set up in the partial. This gets the partial working, but I trip over the API change to gravatar_for once again.

After this the method current_user? is not defined. Somehow I missed this, it belongs in app/helpers/sessions_helper.rb Note that the admin user does not have a button to delete himself.

Chapter 10

November 16, 2012

We generate a micropost model, and edit the migration file to add and "add_index" line as instructed. Then we do the migration. We add a validation line to the model file.

Now we learn about adding belongs_to/has_many properties to th user and micropost models. This produces some "automatic" rails methods for us:

micropost.user			Return the User object associated with the micropost.
user.microposts			Return an array of the user's microposts.
user.microposts.create(arg)	Create a micropost (user_id = user.id).
user.microposts.create!(arg)	Create a micropost (exception on failure).
user.microposts.build(arg)	Return a new Micropost object (user_id = user.id).
We are told to check for this:
config.active_record.whitelist_attributes = true
in config/application.rb (it is there).

To get the proper ordering, we add this line to the micropost model.

default_scope order: 'microposts.created_at DESC'
To ensure that a users posts get destroyed when a user does, we need to just modify one line in the user model:
has_many :microposts, dependent: :destroy
Getting the tests to work for this is more involved and gets the tutorial into an essay about ruby array references, dup, and deep copies.

We add a validation line to the micropost model:

validates :content, presence: true, length: { maximum: 140 }
You really get a sense of the elegance of rails when you omit all the test stuff. Rails does a lot for you, the testing you have to build for yourself. This causes the tutorial to get into a "cannot see the forest for the trees" mode, where the description of tests obscures what rails can do.

Rather than have an index page just for microposts, we show them on the user show page. So we edit app/views/users/show.html.erb. Note that <%= render @microposts %> implies the existence of a microposts partial named: microposts/_micropost.html.erb (and yes, we need to create the microposts directory to hold the partial). Then we add this line to the users controller show method:

@microposts = @user.microposts.paginate(page: params[:page])
Now we load some dummy posts using the data faker in lib/tasks/sample_data.rake
bundle exec rake db:reset
bundle exec rake db:populate
bundle exec rake db:test:prepare
The disk clatters away while this is being done, and a browser shows a paginated collection of posts.

Microposts do not have their own controller (they could, but that just isn't how things are done in this application). They get screwed with via actions in the user controller and static pages. We move the signed_in_user method from the user controller to the session helper (so it is more globally available). Then we create a microposts_controller.rb as follows (did we miss using a generate to produce this?).

class MicropostsController < ApplicationController
  before_filter :signed_in_user

  def create
  end

  def destroy
  end
end
Next we overhaul app/views/static_pages/home.html.erb and since every render implies a partial, we add app/views/shared/_user_info.html.erb and app/views/shared/_micropost_form.html.erb Remembering the tutorial bug where gravatar_for only takes one argument, we fix that. We add a line to define @microposts to the static_pages controller. We generatlize the app/views/shared/_error_messages.html.erb partial so that it will work with any form, using the f.object gizmo that supplies the model object associated with a given form. This implies changes to users/edit.html.erb and users/new.html.erb which also share this partial.

To test all this, we type rails server and visit the home page, only to get:

undefined method `signed_in?' for #
Well, we moved this to the session helper file to make it more globally available, but there is more to it than that (we have tripped over this before). Recall that this file app/helpers/sessions_helper.rb defines a module SessionsHelper which we can include here and there as needed via these two lines (at the top of each controller, or so it would seem):
protect_from_forgery
include SessionsHelper
Now we get:
undefined method `errors' for nil:NilClass
From the error_messages partial. This is my error, I missed changing once instance of @users to object when I generalized this partial. Fixing this puts us on the air. But, we get another missing method when we try to post a micropost:
undefined local variable or method `signed_in_user' for #
Looks like we need to add the usual two lines to the controller to pull in the SessionsHelper. Yep!

Now we add a "feed" method to the user model (which gets used in the home method of the static pages controller). We add _feed.html.erb as a partial which gets rendered in the home static view.

All done and seems to work -- the feed needs CSS, it walks off the page to the right.

Chapter 11

November 16, 2012

The final lap. The game now is for users to follow other users. This will be implemented via a relationships table which has two columns (one holds the followers user_id, the other the followed users user_id).

rails generate model Relationship follower_id:integer followed_id:integer
-- edit the creates_relationship migration
bundle exec rake db:migrate
bundle exec rake db:test:prepare
We add the following lines to the user model:
has_many :relationships, foreign_key: "follower_id", dependent: :destroy
has_many :followed_users, through: :relationships, source: :followed
We edit the relationship model, making sure only followed_id is accessible and adding the lines:
belongs_to :follower, class_name: "User"
belongs_to :followed, class_name: "User"

validates :follower_id, presence: true
validates :followed_id, presence: true
We add a follow! method (where the bang indicates it always works, and raises an exception when it doesn't). Also a following? method as well as an unfollow! method. These go into the user model alongside feed().

To set up followers we add these lines to the user model:

has_many :reverse_relationships, foreign_key: "followed_id",
   class_name:  "Relationship", dependent:   :destroy
has_many :followers, through: :reverse_relationships, source: :follower
Now lib/tasks/sample_data.rake gets reworked with 3 methods being added to clean things up, a new one adds relationships. The function to make the relationships is pretty nice.
def make_relationships
  users = User.all
  user  = users.first
  followed_users = users[2..50]
  followers      = users[3..40]
  followed_users.each { |followed| user.follow!(followed) }
  followers.each      { |follower| follower.follow!(user) }
end  
Now we fiddle with routes and set up a _stats.html.erb partial. We render the partial in home.html.erb (static_pages). The home page blows up with a nil object not having method feed until I edit the static pages controller and make both assignments conditional on being logged in (how did this not bite us back in Chapter 9?).

Next we create follow and unfollow forms (which consist only of a button) which is done via several partials. We render the partial to follow in the user show view.

The user controller needs two new actions: following and followers. We add a whole new view show_follow.html.erb, as it is explicitly rendered by the new actions. The stupid gravatar_for size bug shows up again. We go on to craft a relationships controller out of whole cloth with create and delete methods.

We are told that it is common for forms to use Ajax, and best of all that rails makes ajax easy, just by adding remote: true to the end of a form_for line, you have it. This is all about unobtrusive javascript. We add a respond_to section to the relationships controller and we are there. Now we get tangled up in JQuery (a big topic of its own that involves the world of javascript and ajax). The thing we need to know is that rails allows xxx.js.erb files and we need create.js.erb and destroy.js.erb in the views/relationships directory. The whole point of javascript (and ajax) here is that it seems so silly to redirect back to the original page after processing a form -- the page is already displayed, so why not stick with it.

Now we are on to section 11.3 and the status feed, and I am ready for a break.

We do learn a cool bit of ruby shorthand. We can replace map { |x| x.to_s } with map { &:to_s } --- how about that!

Essentially the application is now a junior version of Twitter, and the tutorial goes on to suggest various exercises that will add more twitter-esque features. His guide to futher rails resources is worth a look.


Feedback? Questions? Drop me a line!

Ruby on Rails notes / tom@mmto.org