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.
Facets of Ruby
Presented by Dave Thomas, OSCON 2005
I'm sitting in Dave Thomas's session on Intro to Ruby at
the Oregon Convention Center. It looks like someone finally figured out the
main problem with conferences - lack of power outlets. Kudos to O'Reilly
- they've put power strips at the base of every table in this room. With
the high-speed wireless and unlimited power, this conference is getting off
to a great start.
Is programming still fun?
Round about 2000, programming started getting tedious for Dave - after having
fun for the past 25 years. When we program, we combine all the problems of
the artistic side of the race with all of the problems of the scientific
side of the race. The only way to be successful at it is to enjoy doing it.
Is programming productive?
It has to be to enjoy it. The most satisfying thing about programming
is watching it run. That's why scripting languages are so great - because
you can see it run now. Myth: a good programmer can be
a good programmer in any language. Language and tools make a difference
- a good programmer knows which language to choose for a particular problem.
This session is not going to be a syntax session. Damn, sounds like
I won't really learn how to program Ruby in this session.
Ruby, the Language
Born in Japan in 1994. Father: Invented by Yukihiro Matsumoto (Matz). Mother:
Ada, Smalltalk, CLU, Perl, Lisp. Grew very rapidly in 2000, outpaced Python
in 2000. Became international star in 2004.
Dave and Andy are language freaks and downloaded Ruby 1.4 shortly after finishing
The Pragmatic Programmer. It passed the 5-minute test, the 1/2-hour
test and Dave ended up playing with it all morning. The first Pickax book
was 500+ pages long, and they wrote it because there wasn't much English-language
documentation on Ruby. Ever since Rails, Ruby's adoption has grown exponentially.
Ruby is a multi-paradigm language: procedural, object-oriented, functional,
meta-programming. You can write
procedural code, but you'll be using OO concepts at the same time. You can
do all of these at the same time.
Ruby code example:
# Generate Fibonacci numbers up to a given limit
def fib_up_to(max)
f1, f2 = 1, 1
while f1 <= max
puts f1
f1, f2 = f2, f1+f2
end
end
fib_up_to(1000)
Methods start with def and end with end. The parenthesis
around the method arguments are optional.
Now Dave is ragging on Java Programmers and how they discount Ruby because
of its duck typing. In a Java program, most things are dynamically
typed too. This is because most objects are stored in collections and whenever
you pull things out of a collection - you have to cast from Object to the
real type. The argument is that you don't have to have static typing.
Dave hates Generics because he thinks they should've just done automatic
casting.
The basic gist of Dave's argument is that we use dynamic typing
(using casting) in Java all the time and you don't see Runtime exceptions
all of the place. So the biggest proponents of static typing are actually
using dynamic typing all of the place.
Back to the code: in Ruby, you don't need parenthesis around conditionals
(for instance in the while statement above). The main reason we
have parenthesis is because of Fortran. There's no reason for them.
You can put parenthesis and semi-colons in your code, but you don't need
to. In this code example, the variables are scoped for the duration
of the method. puts (pronounced "put s") just prints the value of
a variable to the console.
class Song
def initialize(a_title)
@title = a_title
end
def title
@title
end
end
Instance variables in Ruby start with an @ sign. The first time you use them,
they spring into existence. If you access an instance variable and it's value
hasn't been set - it's value is nil. Using the return keyword
at the end of a method is optional - the default is to return the last line
of a method. You can change the "title" method to use
attr_reader :title
The attr_reader call is actually a method of Class:class. The attr_reader
will dynamically add an accessor (that looks like the title method
above). To create a setter, you can use attr_accessor and it'll
create both a getter and setter.
Ruby is a single Inheritance language.
class KaraokeSong < Song
attr_reader :lyric
def initialize(a_title, a_lyric)
super(a_title)
@lyric = a_lyric
end
end
Unlike Java, the super call can happen in any line, or not at all.
To solve the single-inheritance problem, you can use mixins and
apply them to any class.
Blocks and iterators are pervasive in Ruby, and look to be very easy to use.
3.times { puts "Ho!" }
hash.each { |key, value|
puts "#{key} -> #{value}"
end
To do method calls with blocks, you use the yield keyword.
You can use blocks to simplify Resource Management and automatically close
resources.
File.open("myfile.dat") do |f|
name = f.gets
# whatever
end
When the above code hits end, the file is automatically closed.
Duck Typing
Strongly-typed objects, untyped variables and methods. Types are implicitly
determined by the things that an object can do. Duck typing is great for
testing, refactoring and maintenance. This is very similar to concepts in
Smalltalk. There is a strong commitment to unit testing in Ruby - which
makes duck typing even easier to use. Duck typing makes things very easy.
For example, you can have a method that takes a file as a parameter - and
writes data to it. You can test this method by passing in a String (which
also supports << for appending) and verify that your method's logic works.
Duck typing is useful in regular code for reducing coupling and increasing
flexibility. Ruby community now differentiates the type (what it
can do) of an object and the class (what generated it) of an object.
The Road to Metaprogramming
Metaprogramming is really, really powerful in Ruby. Library writers use it,
but most developers don't use it. The ActiveRecord framework is an example
of metaprogramming. Rather than being an O/R Mapping tool, it's more of a database
table wrapper. The belongs_to, has_one, has_many and
other validation_presence_of method calls can be written by you. Allowing
you to write DSL (domain-specific languages) that appear to be a part of
the Ruby language.
4 steps to metaprogramming:
- Classes are open: in Ruby, you can always add methods,
attributes, etc. to existing classes. For example, you can add an encrypt() method
to the
String
class. Isn't
this dangerous? What if someone changes the logic of + for math expressions. No,
because if one of your programmers overrides methods that breaks things
- you take them out in the parking lot and beat them with a rubber hose!
The language shouldn't prohibit us from doing powerful things. - Definitions are active: code executes during what would
normally be compilation. As the class if being defined, the code is being
executed. For example, you can check if an environment variable is set
- and create methods (i.e. log.debug()) accordingly. This can
be great for caching.
- All method calls have a receiver: Methods are executed
on objects. There's always a current object: self. Methods with
no explicit receiver are executed on current object.
- Classes are objects too: You can easily add methods
to classes.
Many more Ruby features: Reflection and ObjectSpace, Marshalling and Distributed
Ruby, Tk, Gtk, Fox, networking, databases, etc. Garbage collection, Threads
(like Java green threads), Modules and mixins. ObjectSpace - allows
you to reflect on all of the objects that exist at runtime. Marshalling allows
you to serialize into binary or text formats. No XML - uses YAML instead.
Unlike XML, it's readable and looks more like a properties file. Modules
(and their methods) can be easily included into a class simply by using "include
ModuleName".
Now Dave is going to write a program to extract book sales ranks from Amazon
pages, publish them as an RSS Feed, store them in a database, and access
via a web application (using Rails). Since this is likely to involve a lot
of live coding, I probably won't blog the code Dave writes.
Web Applications in Ruby can be done with Simple CGI, FastCGI, mod_ruby and
frameworks (like Rails). Iowa, CGIKit, Nitro and Ruby on Rails are all web
frameworks in Ruby. Iowa is a Ruby implementation of Apple's WebObjects.
Dave's quote: "Apple really screwed up with WebObjects, they could've owned
the market on web frameworks."
Summary
- Use Ruby because it is Lightweight. A Ruby download is under
10 MB. Ruby Gems allows easy package management for downloading libraries
and documentation.
- Use Ruby is Transparent. It's nice and easy to read - and it
only takes a couple of hours to learn its syntax.
- Portable - runs on PDAs and Mainframes.
- Open Source - MIT/Artistic style license. 1.8.3's regular expression
engine is LGPL, 1.9's engine will be BSD-style license.
- Easy to Learn - uses the Principle of Least Surprise. Things
seem to work as you'd expect. Dave knows people that've downloaded Ruby
and put web applications on line in the same morning - w/o any prior
knowledge of Ruby.
- Fun! It's enjoyable to program in.
Ruby Resources