Rails 2.0 and following DHH's build-a-blog screencast

Now that I had gotten Rails 2.0 installed, it was time to make a Rails 2.0 app.  I decided to go back to the original screencasts (tons of more recent ones at railscasts) and see if I could follow DHH‘s original “build a blog in 15 minutes” screencast.  There’s some things that don’t work any more, and more importantly, a good number of things that are significantly better/easier in 2.0 now.  Here’s a list based on following the video:

File extensions

While you can still use index.rhtml, the preferred naming separates the mime-type from the rendering mechanism, so you’d be better off using index.html.erb.

http://weblog.rubyonrails.org/2007/12/7/rails-2-0-it-s-done

We’ve separated the format of the template from its rendering engine. So show.rhtml now becomes show.html.erb, which is the template that’ll be rendered by default for a show action that has declared format.html in its respond_to. And you can now have something like show.csv.erb, which targets text/csv, but also uses the default ERB renderer.

Database needed even for Hello World

In the video, he didn’t set up the database until after he already had a controller created, handling an index action (that did a render :text), and then the index.rhtml view.  In Rails 2.0, you can’t do that any more, because you’ll immediately get an error on that /blog hit. 

No such file or directory – /tmp/mysql.sock

You have to have the database setup before you have a working controller, even if no database access is involved at all.  I’m fine with that, of course, since you need to get the database config fixed eventually for anything interesting.

Database server setup

While I don’t know for sure whether this is a Rails 2.0 thing or not, the database.yml file in the video doesn’t have a socket: line, so I’m leaning towards this being a 2.0 thing, although I’m happy to hear otherwise if that’s not the case :)

From the above error, it’s relatively obvious that rails is trying to talk to a mysql server via a socket located at /tmp/mysql.sock.

  • First step, actually go install mysql-server (I didn’t have it installed already – d’oh! – so sudo apt-get install mysql-server) and start it (sudo /etc/init.d/mysql start)
  • Confirmed that it was listening on localhost port 3306 (not really needed, but a sanity check)
    • % sudo netstat -tunap|grep -i 3306
      tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN     6309/mysqld
  • Restarted WEBrick hoping it would at least contact mysql via that port if the socket failed, but no such luck, even though “host: localhost” is in the database.yml file
  • While I could have dug into it to figure out how to get the tcp port working, the google searches on mysql.sock made it clear that I could add socket: with the local path to the mysql.sock to get that to work. Who knows, maybe someone out there thinks it’s a good idea based on a security concern, even though mysql’s default TCP bind is localhost-only.
  • /tmp/mysql.sock didn’t exist, so I checked /etc/mysql/my.cnf to see where the socket was getting opened
    • [client]
      port            = 3306
      socket          = /var/run/mysqld/mysqld.sock
  • So, I added “socket: /var/run/mysqld/mysqld.sock” to each of the development/test/production stanzas in database.yml, restarted WEBrick (script/server), and I was past that error and rails could connect to the mysql server.
  • I learned later on (when running rails to create the app after mysql was installed and running), that the socket: line is already in place and I wouldn’t have had to do this by hand if I had already installed MySQL first :)

Database creation

In the video, you see him using a MySQL gui a lot for creating the database and tables and then turning around and creating the model based on it.  As many others apparently agree, that’s pretty ugly that with a database abstraction layer in place, we’re still having to interact directly with the database (especially since ActiveRecord Migrations exist)

Thankfully, that’s gone in 2.0, thanks to Matt Aimonetti, and you can create the current database or all of them in a single rake command (or drop, or drop and re-create).  I chose to create all 3 of them and ran rake db:create:all.  Very nice!

Table creation

Also in the video, you see him use the MySQL gui for the creation of a table, as mentioned above.  When I got to the “generate model Post” step, I realized this isn’t necessary any more.

Now generating the model will also generate the migration script to create the relevant table!  That’s awesome!  You just go edit the migration script that was generated for creating the table, which already has a shell create_table in self.up (with t.timestamps already in place!  nice!) and the necessary drop_table in self.down.

% script/generate model Post
      create  app/models/
      create  test/unit/
      create  test/fixtures/
      create  app/models/post.rb
      create  test/unit/post_test.rb
      create  test/fixtures/posts.yml
      create  db/migrate
      create  db/migrate/001_create_posts.rb

% cat db/migrate/001_create_posts.rb
class CreatePosts < ActiveRecord::Migration
  def self.up
    create_table :posts do |t|

      t.timestamps
    end
  end

  def self.down
    drop_table :posts
  end
end

Yes, of course, this is using the new Sexy Migrations syntax, so all I had to add to the create_table was:

t.string :title
t.text :body

(UPDATE: I now know that I could have just added title:string and body:text onto the end of the generate model command line)

Then, just run db:migrate and I had a great posts table in my database!  I really like the 2 columns that t.timestamps gives you – that’s a nice touch.  And notice that I didn’t have to create the id column like he did in the video.

mysql> desc posts;
+————+————–+——+—–+———+—————-+
| Field      | Type         | Null | Key | Default | Extra          |
+————+————–+——+—–+———+–
————–+
| id         | int(11)      | NO   | PRI | NULL    | auto_increment |
| title      | varchar(255) | YES  |     | NULL    |                |
| body       | text         | YES  |     | NULL    |                |
| created_at | datetime     | YES  |     | NULL    |                |
| updated_at | datetime     | YES  |     | NULL    |                |
+————+————–+——+—–+———+—————-+
5 rows in set (0.01 sec)

Most importantly, I never had to interact with MySQL directly – heck, except for the database.yml settings, I don’t even *know* that the backend is MySQL.

However, I ended up removing this migration and model due to the change in scaffolding, which I’ll cover now:

Scaffolding

In the video, he gets the scaffolding (wiki page, API doc) going in the blog controller with “scaffold :post” in the blog controller.  That doesn’t work in Rails 2.0 any more.  If you try to use it, you’ll get:

undefined method `scaffold' for BlogController:Class

This wasn’t covered in the 2.0 blog posts I ran across, but some other hits thought that scaffold had been removed in 2.0.  Rather than guess, I checked out the changelog located at /usr/lib/ruby/gems/1.8/gems/actionpack-2.0.1/CHANGELOG to confirm, and sure enough it was intentionally removed by DHH himself.

* Removed ActionController::Base.scaffold — it went through the whole idea of scaffolding (card board walls you remove and tweak one by one).
Use the scaffold generator instead (it does resources too now!) [DHH]

So, checking the output of “script/generate scaffold” I see our example in there already:

Description:
    Scaffolds an entire resource, from model and migration to controller and
    views, along with a full test suite. The resource is ready to use as a
    starting point for your restful, resource-oriented application.

    Pass the name of the model, either CamelCased or under_scored, as the first
    argument, and an optional list of attribute pairs.

    Attribute pairs are column_name:sql_type arguments specifying the
    model’s attributes. Timestamps are added by default, so you don’t have to
    specify them by hand as ‘created_at:datetime updated_at:datetime’.

    You don’t have to think up every attribute up front, but it helps to
    sketch out a few so you can start working with the resource immediately.

    For example, `scaffold post title:string body:text published:boolean`
    gives you a model with those three attributes, a controller that handles
    the create/show/update/destroy, forms to create and edit your posts, and
    an index that lists them all, as well as a map.resources :posts
    declaration in config/routes.rb.

Examples:
    `./script/generate scaffold post` # no attributes, view will be anemic
    `./script/generate scaffold post title:string body:text published:boolean`
    `./script/generate scaffold purchase order_id:integer amount:decimal`

Since it generates the model, migration, and controller, I go ahead and rake db:drop:all and just start the app over from scratch.  As per the above, I didn’t need to add the socket: lines this time around, so right after running “rails”, I ran the above “generate scaffold” and then “rake db:create:all” and “rake db:migrate”  – I end up with the same table as the first time, except now I have another column (of mysql type tinyint(1)) called “published” since I included that from the example.

Since the “generate scaffold” also generates the controller, I don’t need to create a controller myself, but it’s called PostsController instead of BlogController, so when we start the server again and hit the url, it’s /posts and not /blog.  However, hitting it with the Firefox I already had running ends up with an error:

CGI::Session::CookieStore::TamperedWithCookie

We can see one of the Rails 2.0 security measures in action here – nice!  I’m sure this is because my Firefox has an existing cookie from the previous incarnation of the rails app, so I’ll just clear the cookie.  Tools -> Options -> Show Cookies, and I remove the cookie there:

image

Now I can hit /posts again and it works!

image

Then, adding a new post has a form now:

image

And clicking create shows the new post (at /posts/1), including the flash of “successfully created” – yay!

image

Clicking Back to get to the full list:

image

Adding comments

As with the first time we generated a model (even though we threw that one away), when we “script/generate model Comment”, we automatically get the migration file for adding the comments table. All we need to add is:

t.text :body
t.integer :post_id

(UPDATE: I now know that I could have just added body:text and post_id:integer onto the end of the generate model command line)

Since the screencast manually inserts the first comment, I follow that same pattern here, just for the sake of it.  It’s not needed, since the next step includes creating the form that allows for inserting new comments, but I do it anyway :)

mysql> insert into comments (body, post_id) values (‘my first comment!’, 1);
Query OK, 1 row affected (0.02 sec)

Following that and the edits to app/views/posts/show.html.erb, I have my comment shown and the enter-a-comment form!

image

Running unit tests

In the video, he mentions that tests are created when models get created and he runs “rake test_units” to run the existing unit tests, which runs fine with 2 tests, 0 failures.  That fails in 2.0, unfortunately.

Don’t know how to build task ‘test_units’

Fortunately, I’ve noticed a pattern in other places where underscores were replaced with colons, since “rake db_migrate” is now “rake db:migrate”, and “rake test:units” works fine with the expected results.

Finished in 0.346111 seconds.

2 tests, 2 assertions, 0 failures, 0 errors

I went to change the test_truth test in test/unit/post_test.rb like his and came to realize a few of things:

  1. since test/test_helper.rb defaults to use_instantiated_fixtures = false, I probably needed to use a local variable instead of a class variable to hold the post.
  2. Since test/test_helper.rb defaults to use_transactional_fixtures = true, running the test wouldn’t actually commit a new comment.  This is certainly best-practice for database-backed (non-mocked) tests these days, and it’s great to see.

One thing I noticed after doing the initial run of the tests is that running the test:units created the tables and inserted the fixture data already – nice!

mysql> select * from posts;
+———–+———-+——–+———–+———————+———————+
| id        | title    | body   | published | created_at          | updated_at          |
+———–+———-+——–+———–+———————+———————+
| 953125641 | MyString | MyText |         0 | 2007-12-14 14:51:48 | 2007-12-14 14:51:48 |
| 996332877 | MyString | MyText |         0 | 2007-12-14 14:51:48 | 2007-12-14 14:51:48 |
+———–+———-+——–+———–+———————+———————+
2 rows in set (0.00 sec)

This data comes from the default fixture file created back when I did the scaffolding:

% cat test/fixtures/posts.yml
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html

one:
  title: MyString
  body: MyText
  published: false

two:
  title: MyString
  body: MyText
  published: false

Nice – so, I pick the id of one of the posts that was inserted and use that for my test:

def test_adding_comment
  post = Post.find(953125641)
  post.comments.create :body => ‘new comment’
  post.reload
  assert_equal 1, post.comments.length
end

Run the tests again, and it passes.  I change the post id to 1 for a moment just to make sure it fails, and it does :)

script/console

I try ./script/console like in the screencast, only to get an error of irb: command not found.  Easy enough to fix with sudo apt-get install irb :)

I do the same interaction with doing a Post.find to get the post, modifying its title, saving it, then checking /posts/1 to see that the change is reflected.  Then I also did the p.comments.create :body => ‘this comment comes via console’, and that showed up fine as well (didn’t need to p.save – I guess the save is implied if you’re making a change through a relationship?  i’ll figure that out later).  Nice.

That’s it!

Ok, this blog post has gone on long enough.  Hopefully someone finds it useful, even if only as a brain dump from me going through this process. :)

About these ads

6 thoughts on “Rails 2.0 and following DHH's build-a-blog screencast

  1. Nice post. That exact video got me going back then with 1.0, and now you are helping me get going with 2.0. I just don’t get how the bloody hell Mr. Thomas doesn’t want to do AWDWR 3. Thanks again!

  2. I was going through AWDWR with Rails 2.0 installed (not recommended) and couldn’t generate the scaffolding. This article helped a ton, thanks! I like migrations and how 2.0 works although I might go back to a rails version more congruent with the book.

    I created my table, then successfully added a migration (to add a column), then raked it but I don’t know how to regenerate my scaffolding to include the new column. I’m so lame, any ideas?

  3. Maybe you’ll be interested in taking a look at my website. I’ve recorded both a screencast doing the dhh’s blog demo (but with all the fancy new features) and I wrote a 2 part tutorial describing each step. Here are the links:

    http://www.akitaonrails.com/2007/12/10/the-first-rails-2-0-screencast-english

    http://www.akitaonrails.com/2007/12/12/rolling-with-rails-2-0-the-first-full-tutorial

    http://www.akitaonrails.com/2007/12/12/rolling-with-rails-2-0-the-first-full-tutorial-part-2

    There you go.

  4. | 953125641 | MyString | MyText | 0 | 2007-12-14… |
    | 996332877 | MyString | MyText | 0 | 2007-12-14…

    Any idea why the ids are set to 953125641 and 996332877 instead of 1 and 2?

Comments are closed.