This tutorial requires the following RubyGems to be installed:
aruba bson_ext builder bundler cucumber facets gli io-console json mongo nori rake reek rspec settingslogic simplecov sinatra webrick xml-simple yard
To list the versions of your installed gems, execute
gem list
(For testing this tutorial, the versions of the gems that I have installed are listed here. I am using ruby 2.0.0p647 (2015-08-18) [x86_64-linux].)
Exercise 1 covers the following topics.
When you browse source code on GitHub you quickly get accustomed to the organizational layout of a typical Ruby application.
A project's root directory has a Rakefile
, a .gemspec
, and a README.rdoc
.
Unit tests are in the test
or spec
subdirectory;
production code is in lib
;
and executables are in bin
.
Innovation in file location is not appreciated by developers.
They seek conformity,
a project structure that blends with the crowd.
This makes things easy to find.
In this exercise you will practice project setup. Your directory structure will be organized to make it easy for you to come back and pick up where you left off. Your file locations will make it simple for newcomers to come aboard and reuse your code.
From your working directory create a new RubyGem project to brew India Pale Ale:
bundle gem ipa
Specify "rspec" for tests.
Take a look at each of the files that are generated.
The directory structure and the scaffolding in ipa/ipa.gemspec
are particularly helpful.
If you are writing a simple Ruby code library (without a command line interface), a REST server, or something else fairly small, then
bundle gem
is a great way to start a project.
For a new command line application, however, it is better to use the GLI RubyGem instead.
Remove the ipa
directory and everything in it.
Use GLI (which stands for "Git-Like Interface") to create a command line application called StatOwl
that will handle the commands mean
, median
, variance
, and all
.
gli init statowl mean median variance deviation all cd statowl
The terminal output shows the new files and directories that were created.
As you saw earlier, convention puts production code in the project's lib
directory
and executables in bin
. Take a look at
lib/statowl/version.rb
.
It keeps track of the current project version.
In the future when you are ready to begin version 0.0.2 you will only have to change the
version number here.
Usually there is only one file in the lib
directory. Read that file now to find out why it exists.
The rest of your production code will go into the lib/statowl
directory.
The lib
directory structure reflects the project's module hierarchy.
For a large project with multiple modules and many classes like
ModuleA::ModuleA1::MyClass ModuleA::ModuleA2::AnotherClass
the classes should be defined in
lib/statowl/module_a/module_a1/my_class.rb lib/statowl/module_a/module_a2/another_class.rb
This is similar to Java, where the directory structure corresponds to the package hierarchy.
One exception to the module-directory paradigm applies to the project you are working on. You will be extending Ruby's Array class by adding methods that compute statistics. Computing the mean value of an array, for example, looks like the following.
class Array def mean reduce(:+) / size.to_f end end my_average = [1,2,3,4].mean # -> 2.5
Since Array
is not a new class defined for the Statowl module,
it would be confusing to put it in a file called
lib/statowl/array.rb
so by convention, extensions to existing classes are put into extension files.
Your project, for example, will extend Array
in a file called
lib/statowl/extensions.rb
If you make a lot of modifications to many core classes (maybe an idea worth reconsidering...) you can
create an entire directory for them called extensions
, as in
lib/extensions/array.rb lib/extensions/enumerable.rb
The bottom line: if you want to modify core Ruby, you should use extension files. This helps developers find modifications that might conflict with other code. You will draw their wrath if you randomly extend core classes somewhere else.
A README file is important to introducing your project to new users. It contains everything needed to begin working on a project, but nothing more. This includes
Remove the statowl.rdoc
file. Read through the following RDoc text. Then replace the contents
of README.rdoc
with this text:
= StatOwl Convenient ways to compute statistics for a bunch of numbers. == Description This project computes the mean, median, variance, and standard deviation of numerical data. It also stores data in a MongoDB database for retention and future use. StatOwl has three basic interfaces: a Ruby API that extends the Array class, a command line application, and a Sinatra-based REST service. The output format can be plain text, HTML, XML, or JSON. == Synopsis statowl [options] command == Basic Command Line Usage Compute the mean: $ echo '1,2,3,4' | statowl mean Read input data from the console and output statistics in plain text to the console: $ echo '1,2,3,4' | statowl all Format the output as HTML: $ echo '1,2,3,4' | statowl -f html all == Installing the Gem Simply type this at the command prompt: $ sudo gem install statowl
A README file does not completely document a project. It provides an overview and a quickstart to get code running. A README file that has too much detail can quickly become obsolete.
Read again the Description, Synopsis, and Basic Command Line Usage sections.
This is a high-level overview of what you will be implementing in the exercises to come.
You will start by extending the Array
class to compute statistics,
thereby creating a typical Ruby API library.
Then you will wrap the API in a command line application, so that non-Rubyists can
compute statistics from the command line, and a web service to provide statistics for web applications.
Next you will write a Ruby API to enable both numbers and statistics to be stored and retrieved in a database.
Then you will integrate it with your web services.
This is difficult to justify for a statistics application, but important to round out these exercises, because a
web service interface to a database is an essential element of create, read, update, and delete (CRUD) applications.
Read the following code that creates a WEBrick webserver. Then insert
this code into a new file bin/webrick
.
#!/usr/bin/env ruby require 'webrick' include WEBrick puts "start" dir = Dir::pwd + '/doc' host = 'localhost' # or the domain or IP address of your choice port = 8080 # or another port of your choice puts "serving up http://#{host}:#{port}/" s = HTTPServer.new( :Port => port, :DocumentRoot => dir ) trap("INT") { s.shutdown } s.start
This enables you to view the project's documentation as it appears to users.
Make the file executable via
chmod +x bin/webrick
Execute this command to invoke Yard, a Ruby documentation tool:
yard doc
and then execute
bin/webrick
Once the WEBrick server has started up, navigate to http://localhost:8080/
(or your specified host and port) in your browser.
The information is from your README.rdoc file. Yard translates this into a web page at
doc/index.html
.
Notice how your command line examples are automatically formatted to look like they were typed on a console.
Yard marks them up as
<pre class="code ruby"><code class="ruby">$ YOUR_COMMAND</code></pre>
and adds some CSS to make this happen.
Your project is ultimately designed to be executed as a user-installed RubyGem will all of its required gems specified in statowl.gemspec
.
As a developer, however, you need a way to run your executables directly from the working directory
without having to completely install the gem. This is accomplished by invoking bundle exec
.
Try this:
bundle exec bin/statowl help
Ultimately users of your RubyGem will be able to execute statowl help
directly,
without bundle exec
or the full path to the executable file.
In development, however, it is common practice to use bundle exec
.
It is much faster than packaging and installing a gem every time you test new code.
Take a look at bin/statowl
to observe the scaffolding that has been set up.
GLI distinguishes between global flags and switches, which appear before the command,
and command flags and switches, which appear after the command.
This is the command line syntax:
statowl [global options] command [command options] [arguments...]
You will use global flags that apply to all commands.
Global switches and command options will not be needed.
Edit bin/statowl
to remove unnecessary code
and write appropriate comments where needed. The file should look like this:
#!/usr/bin/env ruby require 'gli' require 'statowl' include GLI::App program_desc 'A command line app that computes statistics' version Statowl::VERSION subcommand_option_handling :normal arguments :strict desc 'Describe some flag here' default_value 'the default' arg_name 'The name of the argument' flag [:f,:flagname] desc 'Computes the mean' command :mean do |c| c.action do |global_options,options,args| end end desc 'Computes the median' command :median do |c| c.action do |global_options,options,args| end end desc 'Computes the variance' command :variance do |c| c.action do |global_options,options,args| end end desc 'Computes the standard deviation' command :deviation do |c| c.action do |global_options,options,args| end end desc 'Computes all statistics' command :all do |c| c.action do |global_options,options,args| end end exit run(ARGV)
Now try this:
bundle exec bin/statowl help
Notice how GLI still includes command options and arguments in the synopsis, even though you have not defined any. This has the potential to be confusing to users of your gem. Next, execute the following:
bundle exec bin/statowl help mean bundle exec bin/statowl help deviation bundle exec bin/statowl help all
If you were to deploy your project as a RubyGem in its current state, then users would be able to execute
statowl help
to get general help information and statowl help all
to get help
with the statowl all
command. bundle exec
and the path to the executable
would not be required.
So far your project has no functionality, other than generic help messages. Good practice dictates, however, that no new features be added before writing tests.
What happens if you execute the command rake
without any arguments?
Ouch! Lots of errors! It is important to note, however, that the default setup for Rake tries to run tests by default.
Testing is deeply ingrained in Ruby culture.
Take a look at the extensive unit tests in test/default_test.rb
.
Then remove the entire test
subdirectory. Instead
of running Ruby unit tests you will use RSpec and Cucumber.
RSpec is a popular library for Test Driven Development.
Cucumber is an Acceptance Testing environment that runs on RSpec.
You will use Yard to generate RDocs, not the existing RDoc task specified in Rakefile
.
Edit your Rakefile
to delete Rake::RDocTask
and Rake::TestTask
.
Then set up tasks for verbose Cucumber and RSpec tests.
The file should now look like this:
require 'rake/clean' require 'rubygems' require 'rubygems/package_task' require 'cucumber' require 'cucumber/rake/task' require 'rspec/core/rake_task' require 'yard' spec = eval(File.read('statowl.gemspec')) Gem::PackageTask.new(spec) do |pkg| end desc 'Run RSpecs' RSpec::Core::RakeTask.new do |t| t.rspec_opts = ["--color", "--format", "documentation"] end CUKE_RESULTS = 'doc/cuke_results.html' CLEAN << CUKE_RESULTS desc 'Run features' Cucumber::Rake::Task.new(:features) do |t| opts = "features --format html -o #{CUKE_RESULTS} --format pretty -x" opts += " --tags #{ENV['TAGS']}" if ENV['TAGS'] t.cucumber_opts = opts t.fork = false end YARD::Rake::YardocTask.new do |t| end task :default => :features
Execute the command rake --tasks
to see the tasks that you have at your disposal.
Remove the file features/statowl.feature
.
You will be writing your own features in a later exercise.
Execute rake
to observe that you are testing Cucumber scenarios by default.
It is good practice to ensure that your project can be packaged and deployed as a RubyGem from the very beginning. Moreover, periodically verifying that you have a working gem can help identify deployment issues early, particularly problems with file paths.
Packaging is controlled by the Gem::Specification
class,
which by convention is in a file in your project root directory with a .gemspec
extension.
Your gemspec
file, for example, is statowl.gemspec
.
Read through the following configuration for packaging your project as a RubyGem.
require File.join([File.dirname(__FILE__),'lib','statowl','version.rb']) spec = Gem::Specification.new do |s| s.name = 'statowl' s.version = Statowl::VERSION s.description = 'convenient ways to compute statistics' s.authors = ['YOUR NAME'] s.platform = Gem::Platform::RUBY s.summary = 'Statistics utilities and services' s.files = Dir["lib/**/*"] s.require_paths << 'lib' s.has_rdoc = true s.extra_rdoc_files = ['README.rdoc'] s.rdoc_options << '--title' << 'statowl' << '--main' << 'README.rdoc' << '--ri' s.bindir = 'bin' s.executables << 'statowl' s.add_development_dependency('rake') s.add_development_dependency('cucumber') s.add_development_dependency('aruba') s.add_runtime_dependency('gli') end
Use the code to replace the contents of statowl.gemspec
.
Then execute the following:
rake clobber rake features rake package gem install pkg/statowl-0.0.1.gem
Execute gem list
and look for your statowl
gem.
You should now be able to execute statowl help
using the installed gem, just like an ordinary user.
The most common problem at this point is failure to package all the necessary files.
This line in the statowl.gemspec
file includes every file in the lib
subdirectory:
s.files = Dir["lib/**/*"]
Its success can be verified using the command
gem contents --no-prefix statowl
You don't want the installed gem to interfere with the one you are developing, so uninstall it in preparation for the next exercise. Answer "Y" to the prompt.
gem uninstall statowl
In this exercise you learned about Ruby conventions for organizing a project.
You also learned how files and directories relate to Ruby modules and classes. The class
Statowl::Utilities::Logger
, for example, should be defined in the
lib/statowl/utilities/logger.rb
file.
You configured Yard for automated code documentation and setup WEBrick to verify the resulting RDocs. Yard has many advanced features that were not covered during this exercise but are worth exploring as your projects grow in popularity. It can be configured for automatic updating, for example, and is easy to customize and extend.
You learned what belongs in a README file and Ruby's convention of keeping it short.
You used the GLI gem to create scaffolding for a command line application.
For simple applications you could use bundle gem my_killer_app
to create a new project directory structure.
Next you learned how to write new Rake tasks. Rake, in essence, is Ruby Make. It uses Rakefiles in the same way that Make uses Makefiles.
You also tested the packaging of your new RubyGem.
In the next exercise you will use Behavior Driven Development to write production code.
Copyright © 2005-2022 Amp Books LLC All Rights Reserved