[OSCON] Monday Afternoon
Ruby on Rails - Enjoying the ride of programming
Presented by David Heinemeier Hansson, OSCON 2005
About David: started doing Ruby in June 2003. Involuntary programmer of need, served 5 years in PHP. Spent 7 months in a Java shop.
Prerequisites of play: Ruby 1.8.2, dated December 25th. A database, pick one of 6. The RubyGems miner. Some gems called Active and Action.
Directory structure that Rails creates is more for convenience than anything. By picking conventions for you, it makes things easier. It might feel like flexibility is being ripped away from you - but you can change the defaults. However, by following the default settings, things will just work and life will be much easier for you as a developer.
I did a bit of playing on my PowerBook while listening to David's talk. I have Tiger installed, but found that Ruby 1.8.1 was installed on my machine (in /sw/bin/) thanks to Fink. My running "rm -r /sw/bin/ruby" and restarting iTerm, the default changed to /usr/bin/ruby, which is 1.8.2. From there, I downloaded and installed the Rails Installer.
I hate to admit it, but this talk is pretty boring so far. Probably because I've read David's blog for the past 6 months and watched most of the Rails videos. I haven't really learned a whole lot in the first 45 minutes of this talk. To be fair, the content of the talk seems to be properly targeted - there's been a fair amount of questions and everyone seems to be interested. Almost all of the seats are filled in the room; 3-4x as many folks as Dave Thomas's Ruby talk.
One interesting thing I've learned today is many features of Rails (i.e. Webrick) are actually a part of Ruby, not Rails. In addition, Ruby seems to have frequent releases and more features are added to the language each time. I guess that's the advantage of having a language that's not developed by committee.
When creating model objects in Rails, the default is to use a plural form of the object for the database table name. For example, a comment model will map to a comments table. Dave Thomas did mention in this morning's session that Rails isn't smart enough to figure out "sheep" - it gets maps to "sheeps". Apparently, you can easily override this behavior by specifying use_plurals=false somewhere. Another convention built-in to the framework is that the primary key is named "id" and its an auto-incrementing field.
"The database is a data bucket. I don't want any logic in my database, I want it all to be in my data model."
Rails doesn't handle composite primary keys. Rails is mostly designed for green-field development, where you get to control your database and its schema.
There are a number of key properties you can use in your database tables (a.k.a. your model objects) that will automatically get updated if you name them properly. Their names are created_at (datetime), created_on (date), updated_at and updated_on. There are also a number of time-related helpers, i.e. distance_of_time_in_words_to_now(date) » less than a minute ago.
Rails also has the concept of filters, which you can apply to a group of controllers. To use a filter, you define the filter method in controllers/application.rb and then you have to add a before_filter clause in each controller you want it to be applied. While it's cool that Rails has filters, it would be nice if you didn't have to configure the controllers that filters are applied to in the controller. To me, it seems more appropriate to be able to configure the where the filters are applied externally to the controllers. It seems more natural to me that you'd put something like apply_to_controller => { :controller1, :controller2 } in application.rb.
For doing page decoration with Rails (i.e. SiteMesh), you simply create a decorator in views/layouts. If you want a particular decorator to apply to a particular controller, you just name the file the same as the controller's URL. For example, if you have a posts controller (really a PostController.rb file), you'll create a decorator named posts.rhtml to decorate all the HTML rendered from the PostController - regardless of whether you're rendering from a method or from a view template. To have a decorator apply to all controllers, you can simply create a file named view/layouts/application.rhtml. This seems like something that SiteMesh could easily do as well - for example defaulting to /decorators/default.jsp (or something similar).
One thing I like about Rails is it's flash concept and how easy it makes it to display success messages. In my experience with Java web frameworks - many make this more difficult than it should be.
Testing Rails Applications
When running tests, Rails automates the creation of a test database instance that mirrors the schema of your development database. One slick thing in a Rails project's Rakefile is that you can run all the tests that you've touched in the last 10 minutes. I think one of the most unique thing about Rails/Ruby vs. Java is all that almost all of the files (Rakefile, code generation scripts, etc.) are written in Ruby.
Controller tests have a "mini-language" for simulating a browser when testing controllers. For example:
def test_login get :login assert_response :success< assert_template "login" post :login, :password => "secret!" assert_response :success assert !session[:authorized] post :login, :password => "secret" assert_response :redirect assert session[:authorized] end
In the Controller tests, you can set cookies, parameters and mimic almost everything the browser can do. You can also test that your model objects have been manipulated appropriately. For example:
def test_create_post post :create, :post => { :title => "This is my title", :body => "1" } assert_response :redirect assert_kind_of Post, Post.find_by_title("This is my title") post :create, :post => { :title => "", :body => "1" } assert_response :success # something was rendered, regardless of error messages assert_equal "don't leave me out", assigns(:post).errors.on(:title) #or assert_equal 1, assigns(:post).errors.count end
The find_by_title method is a dynamic finder, where ActiveRecord creates find_by methods for each attribute of the model object. Another cool feature of testing is you can add a line with "breakpoint" in it - and the test will stop executing there - giving you access to all the variables at that point.
Ajax
The main reason for integrating JavaScript into Rails is so developers don't have to write JavaScript. For most developers, writing JavaScript is a pain because of browser incompatibilities and such. Rails ships with 4 JavaScript libraries, including Prototype and Script.aculo.us. It's easy to include the default JavaScript libraries in Rails:
<%= javascript_include_tag :defaults %>
Both the link_to and form_tag methods have a "remote" equivalent (i.e. link_to_remote) that allows you to hook into Ajax, and by defining a :complete callback, you can call fade effects and the like. You can override many of the lifecycle stages of Ajax, but the most common is the :complete callback. In a Controller, it's easy to distinguish Ajax calls from non-Ajax calls using:
if request.xml_http_request? # do logic, for example rendering partials end
Partials seem to be a pretty cool feature in Rails. They're actually just parts of a page that you include in a parent page with render :partial => "viewName". The slick thing about partials is you can actually populate their model and return them in a controller after an Ajax call.
The Ajax demos that David just showed are pretty cool. He was able to easily show how to delete a comment in his "weblog app", as well as add a new comment - w/o refreshing the page. The slick part of the add was he was easily able to add the new comment id to the Ajax response header, and then grab it in a callback and use the id to reference a <div> and use the yellow fade technique to highlight and fade the new comment.
That's the end of Dave's talk, and the first day at OSCON. Thanks to Dave and David for showing me the cool features of Ruby and Rails.
Posted by Steve Mayzak on August 02, 2005 at 02:32 AM MDT #
Posted by Sebastiano Pilla on August 02, 2005 at 07:07 AM MDT #
Posted by Tamer Salama on August 02, 2005 at 08:40 AM MDT #
Posted by Scott Schram's Blog on August 03, 2005 at 11:27 PM MDT #
Posted by George Moschovitis on August 05, 2005 at 06:33 PM MDT #