intertwingly

It’s just data

Rails Confidence Builder


rails I don’t know about you, but taking a look at a new framework is a daunting task. Particularly if there are code generators and multiple directories involved.  You don’t know what is assumed, and what is required.  Sure, the videos make it look easy, but somehow the person making the presentation always seems to already know all the answers; they know exactly where to put every file, the syntax of every command and statement, and have a detailed knowledge of all the relevant class libraries.

What I like about PHP, JavaScript, Python, Perl, and a number of other languages is that you can start with a real example that you can understand that fits comfortably in a single text file.  For this reason, the first task I take when approaching a new language or framework is to distill down an equally simple example to it’s essence.  Only after I ‘grok’ the example, do I then move on to bigger and better things.  In this case, that presumably involves splitting the file up into chunks, seeing where the chunks belong, and then adding test cases, function, html pages, etc.

Herewith is a single example, annotated stanza by stanza below.  Note that this example is designed for Ubuntu Linux, some small modifications may be required to run in other environments.

mysql function

Ruby makes it easy to interact with the command line, piping in input, capturing output and the like.  But since we are going to interact with mysql three separate times in this script, let’s make it even easier by defining a function that accepts two parameters: the first is the options to be passed on the command line, and the second is the stream of data to be piped in.

def mysql options, stream
  IO.popen("mysql #{options}", 'w') { |io| io.puts stream }
end

This function makes use of a number of Ruby idioms.  First, there generally is no need to enclose parameters in parentheses unless you really want to.

IO.popen opens a pipe, in this case for writing.  The name of the “file” is simply the command to be executed.  Note that double quoted strings enable you to make use of embedded Ruby, which I use to insert the options into the command.

While IO.popen can be used as a traditional function, returning a stream that you can write to and ultimately are responsible for closing, I’m making use of another idiom: passing a block to the function.  IO.popen will bind the file handle to the argument you specify, and take care of ensuring that the pipe is closed properly when you exit the scope of the block.  Sweet!

create database

Now, lets make use of this function, first as root.

mysql "-u root -p", <<-END
  drop database if exists weblog_development;
  create database weblog_development;
  grant all on weblog_development.* to #{`id -un`.chop}@localhost;
END

Here we are invoking mysql, specifying user as root, and requesting that there be a prompt for the password.

The stream of commands is passed in what is commonly known as a here document.  Note that embedded Ruby is also available.  In this case, we shell out to the command line and get the name of the user running the script.  The result is a string, and therefore we have access to all the normal methods that are available on a string.  In this case, we wish to chop off the trailing newline.

On non-Unix based systems, you may need to substitute another command, or simply hardcode your user id.

Create table

Now, create a table named entry with id, title, updated_on, and content columns.  The suffix _on is a little Rails magic that ensures that the field will get initialized to Time::now.  I haven’t decided whether that one line of code savings is worth it for my application, but for now, I’m including it in the demo.

mysql "weblog_development", <<-END
  drop table if exists entries;
  create table entries (
    id         int not null auto_increment,
    title      varchar(100),
    updated_on datetime,
    content    text,
    primary key(id)
  )
END

Pretty standard SQL stuff, except for the fact that the way you specify the notion of auto_increment appears to vary across databases.  Grrr...

Requirements

Next we pull in the necessary Rails functionality

require 'rubygems'
require_gem 'activerecord'

Not much to this.  Note that Kernel::require is simply a function (don't let the absense of parentheses fool you), and can be invoked any place in a program — similar constructs in other languages must appear at the top.  The rubygems library includes a Kernel::require_gem function, which is used in the next statement.

The net effect of all this is that one can readily extend the Ruby language with domain specific grammar. Rails makes extensive use of this in ActiveRecord, though this isn't demonstrated in this example.

Establish a Connection

Now we connect to the database.

ActiveRecord::Base.establish_connection(
  :adapter  => "mysql",
  :database => "weblog_development",
  :socket   => "/var/run/mysqld/mysqld.sock"
)

Pretty straightforward, particularly as this call uses named parameter association.  Note that the value you need to specify for the socket may vary across systems.  I found the correct value for Ubuntu systems using Google.

Generating Code

Now, lets generate code for the entries table:

class Entry < ActiveRecord::Base
end

Not much there, eh?  That’s because ActiveRecord::Base already knows about your database, and when it sees a class named Entry, it looks for a table named entries and infers all it needs to know about what attributes it needs to create.

First Post

Now lets create and save a row:

post = Entry.new
post.title = "First post!"
post.content = "Hello from Ruby on Rails!"
post.save

Again, not much there.  Create a new entry.  Set title.  Set content.  Save.

Checking to make sure

There is an ancient Russian saying, “trust, but verify”.  Let’s check to make sure that mysql is convinced that this data is there.

mysql "-t weblog_development", <<-END
  select * from entries;
  update entries set content='Right back-atcha from MySQL!';
END

We use the trusty mysql function we defined earlier, this time passing the “-t” option (otherwise, mysql “knows” that we are running it batch, and the output isn’t quite as pretty).  We select all from entries, and sure enough, everything is there (see sample output below).  Just be doubly sure, we update the entry with some new text.

Full Circle

Now, lets make sure that Rails can retrieve this data from the database.

puts Entry.find(:first).content

Find the first (and in this case, only) entry, get the content from that entry, and put it to the screen.  Again, nothing to it.

Output

Here’s the output from running this application:

Enter password:
+----+-------------+---------------------+---------------------------+
| id | title       | updated_on          | content                   |
+----+-------------+---------------------+---------------------------+
|  1 | First post! | 2005-08-09 17:06:05 | Hello from Ruby on Rails! |
+----+-------------+---------------------+---------------------------+
Right back-atcha from MySQL!

If you want to try it for yourself, the full script can be found here.