An aggregation of all the Rock Solid Knowledge Blogs
Now that we have a Rails app up and running, all be it a simple one, it's time to think about testing. When you generate an application you also get a test structure created for us. This structure lets us create unit, functional and integration tests. Each kind of test has a different scope and I'll start, as we should, with unit tests.
Looking in the test/unit directory there's a single ruby source file for each model created previously, each of these files looks like this
class BlogEntryTest < ActiveSupport::TestCase # Replace this with your real tests. def test_truth assert true end end
Pretty straightforward, assert that true is true. (If this is your first glance at Ruby code the first line says that BlogEntryTest derives from TestCase and the 'def' statement defines a method).
Before running the test I create the test database
mysqladmin -u root create rblog_test
There are two ways (at least) to run this test, running
ruby -I test test/unit/blog_entry_test.rb
(-I test here includes the test directory in the search path) or
Running the first command line on my machine gives this:
Loaded suite test/unit/blog_entry_test Started E Finished in 0.033476 seconds. 1) Error: test_truth(BlogEntryTest): ActiveRecord::StatementInvalid: Mysql::Error: Table 'rblog_test.users' doesn't exist: DELETE FROM `users`
This shows us that the database tables don't exist. Running
fixes this and re-running the test now succeeds.
Started . Finished in 0.095619 seconds.
It's also possible to run
This will run all the unit tests
Started ... Finished in 0.056533 seconds. 3 tests, 3 assertions, 0 failures, 0 errors
Now that there's some confidence that the testing framework is in place it's time to start thinking about real tests.
The user class represents a user of the system, either a user with a blog or a user posting comments. This user must have a username, email and password. The user class looks like this
class User < ActiveRecord::Base validates_presence_of :name validates_presence_of :email validates_uniqueness_of :email validates_confirmation_of :password validate :password_non_blank
This suggest some tests. Does the user have a name and email, is the emil unique and does the password have some data! The first test checks that the user is valid
class UserTest < ActiveSupport::TestCase def test_empty_user_is_invalid user = User.new assert !user.valid? assert !user.errors.empty? assert user.errors.invalid?(:name) assert user.errors.invalid?(:email) assert_equal "Missing password", user.errors.on_base end
The test creates a User then calls the valid? method (if you're new to Ruby the ? on the end of a method is part of the method name and indicates that the method returns a boolean, a ! on the end indicates that the method mutates data) . The test asserts that the user is not valid and then asserts that the appropriate errors have been added to the errors collection.
The code has other tests for the user, checking that the password and password_confirmation match and that the password is not blank. There is also a test that a User is valid if all the fields are set correctly, none of these are shown here but the code is available here not that there's much to see at the moment!
The final test for the moment checks that the User must have a unique email. The code looks like this:
def test_user_unique_email user = User.new(:name => "Test Name", :email => users(:kevin).email, :password => "wibble", :password_confirmation => "wibble") assert !user.save assert_equal "has already been taken", user.errors.on(:email) assert_equal ActiveRecord::Errors.default_error_messages[:taken], user.errors.on(:email) end
The thing of interest in this code is the line
:email => users(:kevin).email,
This loads a fixture named :kevin. Fixtures are test data defined in a yml file (YAML Ain't a Markup Language) file. Fixtures have names and cen be loaded by name in the test code. The fixture looks like this:
kevin: email: firstname.lastname@example.org hashed_password: hash name: Kevin
The fixture data is loaded into the database, then the line 'users(:kevin).email' loads the fixture and gets its email value. This means that the test tries to save a user with the same email address as one that already exists, and that should fail.
I wanted to make this a series about Rails and about Ruby. To that end I'm going to write a series of entries on building an MVC application. As I said in the previous post I am a Ruby and Rails neophyte, so I'll be learning as I go along, regard this as a developer's journal. The aim being that you can learn from the mistakes that I make. If this really gets going I also hope to do some screencasts along the way.
What am I going to write - a blog of course (is there any other sort of web app out there today :) ). I chose blogging software because
I'm going to rely heavily on the 'Agile Development With Rails' book, and hopefully any feedback I get. I'm learning Ruby at the same time as Rails and Ruby is a very idiomatic language, I know I'll get some of the idioms wrong, so again feedback is welcome. Oh and initially I'm doing this on MacBook using TextMate as the editor. At some point I'm going to play with NetBeans and Eclipse as tools and so try out JRuby, not that I like a challenge of learning 17 new things at once!
So the beginning of a Rails app is the code generator, to create the application I fire up a terminal make sure I'm in the right directory and call
This gives the well known directory structure of
app config db doc lib log public script test tmp vendor
Most of the time I'll be in the app directory (that's where the models, controllers and views are), but I'll also use stuff from the db directory where the 'migrations' are stored, in the public directory where public the web files are and in config, where various setup files live.
Once the application has been created I can fire up another terminal (I usually have three open, one for my commands, one for the server and another to see the log file), change the the project directory and run the server with the command 'script/server'
This starts the server on port 3000, the server I'm using here is Mongrel, you can also start WEBrick. There's plenty of discussion on these servers out on the web so I won't go into the differences here.
Once the server is running you can point your browser at http://localhost:3000 and you should see something like:
I'm going to use MySql for the database and this needs to be configured. To do this I've edited config/database.yml to look like this:
development: adapter: mysql encoding: utf8 database: rblog_development username: root password: socket: /tmp/mysql.sock
Adding similar entries for test and production databases. Once that's done I created the development database
mysqladmin -u root create rblog_development
Now that all the necessary structure is in place I can create the first controllers and models. What do I know about the blog? I know it's going to have users, users can have blogs and blogs will have entries and comments. There may be more things eventually, such as tags, categories, pingbacks etc, but for now that's enough. So not to get too far ahead of myself I decided to create scaffolding for users, blogs and blogentries.
For the users model I'm going to user a similar approach to that used in the Rails book, so the user will have an email address and password, the password will be stored as a hash will be salted. A user will have a name that can be displayed on comments or on a blog and users may also own blogs. For now I'm going to limit this to one blog per user, but in the future this may expand to multiple blogs.
script/generate scaffold user email:string hashed_password:string name:string blog_id:integer
gives me what I want. Doing something similar for blogs and blogentries
script/generate scaffold blog title:string sub_title:string owner_id:integer admin:boolean script/generate scaffold blog_entry title:string entry:text author_id:integer entry_added:datetime entry_last_edited:datetime
creates those models and their corresponding controllers and migrations.
Notice that as in my previous post I'm using singular names for the model components, so user, blog and blog_entry.
Now that's done it's then onto setting out some of the UI and writing some unit testing code.
I've been looking at Ruby and Rails for a long time, when I say looking at here what I really mean is staring through the glass and wondering what the fuss was about. Now that I finally have a MacBook I decided to actually sit down and learn Rails and Ruby at the same time. This means that on my hour long commute to and from my current contract I'm writing web code, and much fun it is. The commute is split over two trains so each part is about 30 minutes which gives me a very fractured learning experience but I'm still getting something done.
I wanted to use the web app I'm working on (it's only a toy) as a learning tool. This means that a) I wanted to minimize the new concepts and b) not be led by the hand too much. Because of that I decided that I would initially not use REST based routing as I would need to understand more about REST and using the built in routing would hide the routing system from me. So taking my routes in hand I initially did things the 'hard' way (actually, it's not that hard)
I finally figured that I knew enough to then try the RESTian approach, so firing up my trusty console, and TextMate I tried adding a new controller and model to the application.
I ran a command, something like,
script/generate scaffold foo
pointed the browser at http://localhost:3000/foos and everything seemed good. I added a new foo, life was good. Then I tried to browse to http://localhost:3000/foo/1 and I get an error:
Unknown action No action responded to 1
After much Googling and running of
I couldn't find an answer.
Eventually after much re-reading of the routing chapter in The Book something must have lodged in my head and I decided to kill the web server and restart it, and suddenly the routes started working. The moral is "you do sometimes need to re-start the rails web server"!
The second issue was even more stupid. After fix the above problem, and getting my head more around the REST idea I decided I needed a bunch of controllers, so I duly run
script/generate scaffold bars
restart the server and immediately get strange errors along the lines of
undefined method `edit_bars_result_path' for #<ActionView::Base:0x255a508>
Rails experts will be laughing right now, or thinking 'been there, done that.' Rails adopts naming conventions, in this case models and controllers are singular. RESTian routing is based around the ideas of things and collections of things, the things are singular and the collections are plural. I tried to generated things that were already plural (bars), and the RAILs pluralization had no idea what to do with it, so the naming broke (at least that's what I think happened :) )