It is not that easily believable. But yes, occasionally, a Rails developer might have to write a simple Ruby commandline script. It is a fact, and all facts should be respected.
A Ruby commandline script is not a gem. But its folder structure can be similar to that of a gem.
For the commandline functionality, you can use any of the many available utility gems. Popular choices are
optparse. I prefer
thor as its API doesn’t deviate much from the standard PORO.
Here’s the bare minimum directory structure of such a program.
$ tree . ├── Gemfile ├── Gemfile.lock ├── bin │ └── the_command ├── lib │ ├── commandline_ruby │ │ ├── cli.rb │ │ └── main.rb │ └── commandline_ruby.rb └── test 3 directories, 7 files
The main file
lib/commandline_ruby.rb can be used for 2 purposes:
- requiring all the relevant files that are present in other dirs in the lib folder
- Defining the main module of this library, and defining common utility classes and functions in this module. This is how it can look:
module CommandlineRuby extend self # utility functions def logger end end require_relative 'commandline_ruby/main' require_relative 'commandline_ruby/cli'
It’s crucial that the files are loaded using
require_relative. Only then the executable file `bin/ can be invoked from anywhere.
Next is the
lib/commandline_ruby/cli.rb file. This is the file where the classes and code related to your chosen commandline library will go. In my case, it would be place where the Thor main class is defined. It can look like this:
require 'thor' module CommandlineRuby class CLI < Thor desc "process", "do something useful!" def process Main.new.process! end end end
I like to extract out the actual business logic out of these utility classes and put them in their own classes. That’s why the
CLI class seems to be doing nothing much. It delegates the meat of the task to the
lib/commandline_ruby/main.rb file is where this script’s main work is being done. It can look like this:
module CommandlineRuby class Main include Module1 include Module2 def process! # The MEAT! end end end
From this class, you can delegate the work to many other classes and modules, all of which will be already loaded in the `lib/commandline_ruby.rb file.
The last, but the most important file is the actual executable file. This will expose this script’s functionality as a command that you can run from the terminal. It can look like this:
#!/usr/bin/env ruby require_relative "../lib/commandline_ruby" CommandlineRuby::CLI.start(ARGV)
Since we follow a convention (which is industry standard) of requiring all dependencies in 1 file before the actual work ever starts, all of our requires would be in the
lib/commandline_ruby.rb file. (Actually, they are ‘require_relative’s, not ‘require’s.)
This executable file in the bin directory just requires the main file, and just calls the main method of the commandline library’s code. In my case here, it would be the Thor class’s
start method, passing in the arguments we got from the commandline.
This is a simple way to structure your non-rails, commandline programs. I’ve used them in many client projects. They run perfectly even in Production environment. Let me know if I can improve it by any means.