Alex Coplan Alex Coplan - 4 months ago 10
Ruby Question

How should I be trying out my gem during development?

Let's say I have a gem called foo, with a file structure like so:

foo.gemspec
test_foo.rb
lib/foo.rb
lib/foo/file1.rb
lib/foo/file2.rb


The file
test_foo.rb
contains some code which I use to try out my gem. It accesses the gem's code with the following line:

require './lib/foo'


Then,
lib/foo.rb
acceses the other files required for the gem like so:

require './lib/foo/file1'
require './lib/foo/file2'


Since
test_foo.rb
is in the root of the gem directory, it is necessary to require the files in
lib/foo
with the complete path from the root directory.

This all works fine, and allows me to immediately play around with my gem by changing the code used in
test_foo.rb
.

However, if I then want to build the gem, I then have to change my calls to
require
, like so:

require 'foo/file1'
require 'foo/file2'


instead of

require './lib/foo/file1'
require './lib/foo/file2'


Which is kind of tedious to do every time I want to build the gem.

So, I thought of another way of trying it out, which was to use
rake
to automate the build and install of the gem, something like this:

task :build do
`gem build foo.gemspec`
`gem uninstall foo`
`gem install ./foo-0.0.0.gem`
end


and then when I made changes to my code, and wanted to try it out, just run
rake build
, and call
require 'foo'
in
test_foo.rb
.

But that is quite a slow process, and feels a bit like it defeats the point of ruby being a language where you don't have to build your code before trying it out.

So, my question is, what's the best workflow to use when actively developing a gem, and testing it out?

Answer

It's error-prone and confusing having to mess with require paths within your source files in order to make the code run in different situations (local testing, "production" as a gem etc.).

There are some tricks you can use to "write once, run anywhere" (sorry Java, for stealing). This is also what you should aim for, using these standard practices makes it easier for others to understand your code or help you out if something doesn't work as expected.

Your code already follows the common gem directory layout, nothing needs to be changed there. But, as a general rule of thumb, you should use require to load "libraries" and require_relative (if you are on Ruby >1.9) to load source files relative to the one you are working on.

1) Your file test_foo.rb should only contain a line:

require 'foo'

That is you omit the './lib' part from the require path. The entry point for the gem (the "require_path") is commonly chosen to be "lib". More on that later (3).

2) Next, in foo.rb, use require_relative (again assuming Ruby 1.9) to pull in the relative source files:

require_relative 'foo/file1'
require_relative 'foo/file2'

3) To set the require_path to lib for your gem, it's considered good practice to add a gemspec file.

To test your code (for example with your test_foo.rb), you have several options:

4) You can run test_foo.rb with ruby's -I option to include the lib/ directory in its load path:

ruby -I lib test_foo.rb

This is much less obtrusive than adding additional includes directly in your code.

5) Bundler is an even better way to manage your code/gem in order to test it under "real-life" conditions, especially if you develop two or more gems at once that rely on each other. Bundler gives you the option to reference these gems locally, so there's no need to deploy them formally in order to test their functionality during development. To use Bundler, you would add a Gemfile to the root directory of your gem code.

6) Finally, for convenience, it makes sense to integrate common steps in your development process into a Rakefile, using rake to drive the processes.

I use these techniques quite a bit in my own projects, here are some links for your reference to give you some concrete examples:

1) Test files just contain a "require 'foo'"

2) Use "require_relative" from your "main" file to pull in subsequent parts

3) Example gemspec with require_path set to "lib"

5) Example Gemfile

6) Example Rakefile

Of course you don't need to use all of these techniques, you'll probably be fine with using steps 1-4, but as your project grows larger, the additional effort will definitely pay off.