advertisement

Print

How to Build Simple Console Apps with Ruby and ActiveRecord
Pages: 1, 2, 3, 4

Writing a Rakefile by Hand

One thing Rails users are sure to be familiar with is using rake for various project automation tasks. However, if you haven't written your own custom tasks before, you might not be familiar with how to create a Rakefile from scratch.



You can learn a whole lot by taking a peek at the Rakefile that is generated by Rails, but one of the most simple needs you'll have that's easy to handle with Rake is creating a test runner.

The following code shows what I'm using in EarGTD.

File: Rakefile
require "rubygems" 
require "rake" 
require "rake/testtask" 

task :default => [:test]

Rake::TestTask.new do |test|
  test.libs << "test" 
  test.test_files = Dir[ "test/test_*.rb" ]
  test.verbose = true
end

This should be fairly intuitive just by reading it. This uses libraries built into Rake to set up a test task that will run anything in the test/ dir that has a file name like test_something.rb

The code above sets the test task as default, so running rake anywhere within your project will run your test suite.

Though this is all fairly pedestrian stuff, I'd like to mention that the Dir class rocks. It's part of core ruby and lets you treat file pattern matches as Enumerable objects. Below is a quick irb session that shows why this is interesting.

Fun Ruby stuff: Dir

>> Dir["EarGTD/*"]
=> ["EarGTD/data", "EarGTD/earGTD", "EarGTD/lib", "EarGTD/Rakefile", "EarGTD/test"]
>> Dir["EarGTD/*"].map { |f| f.reverse }
=> ["atad/DTGraE", "DTGrae/DTGraE", "bil/DTGraE", "elifekaR/DTGraE", "tset/DTGraE"]
>> Dir["EarGTD/*"][0]                   
=> "EarGTD/data"

Code for Console Apps Doesn't Need to Be Ugly

It's really tempting to build little console apps in a way similar to how you'd write a shell script: as a procedural series of commands that form a ball o' code. Though this works fine in a pinch, it makes the code difficult to test and prevents it from playing well with others.

Still, how much is too much? You probably don't want a massive class heirarchy for a simple tool like EarGTD. Usually in cases like this, I find myself leaning towards a simple structure that borrows some ideas from functional programming.

Using module_function to Create a "Bag o' Functions"

You may have seen modules in Ruby used as mix-ins. This is when you define some functionality in a module and then include it in a class for use.

The following example will add the instance method b to the class C.

module A
  def b
    puts "this is b" 
  end
end

class C
  include A
end

d = C.new
d.b #=> "this is b"

Still, if you aren't really dealing with maintaining any state, why bother with classes? By using module_function, the following code could be simplified so that the method may be called directly on the module.

module A          

  module_function

  def b
    puts "this is b" 
  end

end

A.b #=> "this is b"

Since all the state for EarGTD is stored in the database, this is the approach we use. The result is a very clean modular interface. In fact, the EarGTD script itself becomes quite minimal in part because of this.

File: EarGTD
#!/usr/bin/env ruby
require "lib/ear_gtd"        

EarGTD.connect
EarGTD.process_command(ARGV)

You can see that all of the heavy lifting is essentially being done by a single method, process_command.

Creating a Simple Command Processor

An approach that helps keep things clean when building console apps in Ruby is to build some simple command processing. This could either be a method or a class that manipulates the data that is passed in from the command line.

In the case of EarGTD, all that needs to be handled are the arguments passed to the script. In Ruby, this is stored in a special variable called ARGV.

Above, you saw it used like this:

EarGTD.process_command(ARGV)

To set up my command processor, I simply peel off the first argument and store the rest of the arguments in an array, as Example 3 shows.

File: lib/ear_gtd.rb
def process_command(cmd)
  args = Array(cmd)
  command = args.shift
  case(command)
  when "+" 
    add_task(args[0])     
  when "@" 
    t = tasks 
    puts t.empty? ? "Looks like you have nothing to do.\n" : t

  # ... other commands omitted

  else
    puts "Que?" 
  end
end

Above is a simplified version of EarGTD's command processor. You can see that it's just using a case statement to figure out what command is being called and then delegating to some functions to do the actual tasks.

This allows you to treat the rest of your coding task as if you were writing a small function library, and cleanly separates that code from the user interface. Any interface that manipulates the input into something the command processor understands will work fine.

Pages: 1, 2, 3, 4

Next Pagearrow