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:
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.
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
- % sudo netstat -tunap|grep -i 3306
- 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
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 :)
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!
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
% cat db/migrate/001_create_posts.rb
class CreatePosts < ActiveRecord::Migration
create_table :posts do |t|
Yes, of course, this is using the new Sexy Migrations syntax, so all I had to add to the create_table was:
(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:
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:
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.
`./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:
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:
Now I can hit /posts again and it works!
Then, adding a new post has a form now:
And clicking create shows the new post (at /posts/1), including the flash of “successfully created” – yay!
Clicking Back to get to the full list:
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:
(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!
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:
- 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.
- 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
Nice – so, I pick the id of one of the posts that was inserted and use that for my test:
post = Post.find(953125641)
post.comments.create :body => ‘new comment’
assert_equal 1, post.comments.length
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 :)
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.
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. :)