Thursday, October 05, 2006

Ruby Project Tree

I was recently asked: How would you structure a (non-Rails) Ruby project?

Here's a list of the things I generally do when creating a new project:
  • Create lib and test folders to house the code and tests respectively.
  • Create a rakefile.rb with a task for running all the tests:
    task :default => :test

    task :test do
    require File.dirname(__FILE__) + '/test/all_tests.rb'
    end
  • Create a test file that runs all tests (all_tests.rb):
    Dir['**/*_test.rb'].each { |test_case| require test_case }
  • Create a test_helper file that handles common require calls:
    require 'test/unit'
    require 'mocha'
    require 'stubba'
    require File.dirname(__FILE__) + '/../lib/[project name]'
  • Create a file with the same name as my project in the lib folder. This file contains all the requires necessary to run the application/use the library:
    [example: sqldsl.rb]

    lib = File.dirname(__FILE__)

    require lib + '/object.rb'
    require lib + '/symbol.rb'
    require lib + '/array.rb'
    require lib + '/string.rb'
    require lib + '/numeric.rb'
    require lib + '/time.rb'
    require lib + '/sql_statement.rb'
    require lib + '/where_builder.rb'
    require lib + '/select.rb'
    require lib + '/insert.rb'
    require lib + '/update.rb'
    require lib + '/delete.rb'
I find this structure makes my life easier.

Of course, if you want this automated, you could always just use tree_surgeon.rb
require 'fileutils'

project_name = ARGV[0]
lib = project_name + "/lib"
test = project_name + "/test"
rakefile = project_name + '/rakefile.rb'
all_tests = test + "/all_tests.rb"
main = project_name + "/lib/" + project_name + ".rb"
test_helper = test + "/test_helper.rb"

FileUtils.rm_rf project_name

puts "creating: " + project_name
Dir.mkdir project_name

puts "creating: " + lib
Dir.mkdir lib

puts "creating: " + test
Dir.mkdir test

puts "creating: " + rakefile
File.open(rakefile, 'w') do |file|
file << <<-eos
task :default => :test

task :test do
require File.dirname(__FILE__) + '/test/all_tests.rb'
end
eos
end

puts "creating: " + all_tests
File.open(all_tests, 'w') do |file|
file << "Dir['**/*_test.rb'].each { |test_case| require test_case }"
end

puts "creating: " + main
FileUtils.touch main

puts "creating: " + test_helper
File.open(test_helper, 'w') do |file|
file << <<-eos
require 'test/unit'
require File.dirname(__FILE__) + '/../lib/#{project_name}'
eos
end
Also, if you are creating a RubyGem it is recommended that you create README, TODO, and CHANGES files. Additionally, if your gem uses executables it is suggested that you store the executables in a bin folder and create a install.rb file that calls your executables.

Do you have other suggestions?

9 comments:

  1. Badasssssss

    Bookmarked

    Uhh, as for suggestions, maybe a Rake task to generate your documentation from your /lib folder? Or does Rake have that built in already, I forget.

    ReplyDelete
  2. Anonymous11:19 AM

    Nice post. You can shorten some of the requires by saying something like:


    files = %w[object symbol array string numeric time sql_statement where_builder select insert update delete]

    files.each {|f| require lib+"/#{f}.rb" }

    ReplyDelete
  3. I was just thinking I needed a guide like this the other day, thanks!

    ReplyDelete
  4. Quick question:
    Why do you create a file to run all the tests - why not just run all the tests directly from the Rake task itself?

    ReplyDelete
  5. I think I read somewhere that when you distribute a gem you should have a file that runs all the tests.

    Also, you could run that file in textmate. Perhaps it's possible to run a specific rake task in textmate also, but I'm not sure how.

    Honestly though, there isn't much need for it.

    ReplyDelete
  6. Jay, that was a great script. With that as a base, I created a newgem gem to allow ppl to create new gems easily.

    http://drnicwilliams.com/2006/10/11/generating-new-gems/

    So, thanks!

    Nic

    ReplyDelete
  7. Chris Rucci8:43 PM

    Hey, Man. It's been a while since I've read you (or talked to you). Hope things are going well. This was exactly what I needed. Thanks for the great post.

    ReplyDelete
  8. > Also, you could run that file in textmate. Perhaps it's
    > possible to run a specific rake task in textmate also,
    > but I'm not sure how.

    It asks you to choose if you give descriptions with "desc". I got a little annoyed at this, though, so I defined another command to always run the default (testing for me).

    > Honestly though, there isn't much need for it.

    I actually use it quite a bit. It's nice to not have to go to the shell just to generate docs, etc.

    ReplyDelete
  9. Ben...10:54 AM

    Hi Jay
    This has been a really interesting and has served as a foundation for understanding RubyGems. I've written an article which others might find useful:

    http://blog.emson.co.uk/2008/06/an-almost-fix-for-creating-rubygems-on-windows/

    Keep up the good work,

    Ben Emson...

    ReplyDelete

Note: Only a member of this blog may post a comment.