One thing that I like most about Ruby is the ease of contributing to OpenSource. You want to share code? Just create a gem and upload it to Rubygems. The problem is, there are a lot of implicit rules that make it hard to do it right in the first place.
Since Ruby 1.9 Rubygems is bundled with every Ruby distribution. If you are using RVM you get Rubygems with the older versions too. Setting the RUBYOPT environment variable will enforce it even for 1.8 versions:
# no need for 'require "rubygems"' in your code export RUBYOPT=rubygems
The Gem command
Since the merge of Gemcutter and Rubygems there have been a lot of improvements to the gem shell command. Sharing your code is now as easy as:
# create a gem from your local source configured in your gemspec gem build your.gemspec # upload the created gem gem push your-0.0.1.gem
There are some more new commands that handle the Rubygems API communication, which are very well documented on RubyGems.org.
It is also worth taking some time to get an overview of the old command reference. Commands like which or cleanup come in handy if you know them!
I use RVM and Gemsets extensively and I do not want to have all that duplicated RDoc and RI on disc (I’d rather search the web instead of using local generated doc), so I changed my .gemrc file to skip generating the doc on gem install:
# .gemrc --- :update_sources: true :sources: - http://gems.rubyforge.org/ - http://gems.github.com gem: --no-ri --no-rdoc :verbose: true :bulk_threshold: 1000 :backtrace: false :benchmark: false
Building a Gem
Jeweler is a great tool for scaffolding and releasing new Gems. If you don’t care about getting to know the basics, just move right over to their documentation. The problem is, that it makes things more complicated than they really are…
Naming and Versioning
There are some simple rules when it comes to naming:
- Use lowercase letters only! Users of case insensitive file systems will be thankfull.
- Use ASCII characters only! You know, those US guys just don’t know there is UTF-8.
- Use _ underscores for parts of the name and - dashes for extensions of existing Gems.
- Name your Gem like the file to require in your code. If not, put a big, fat warning in your documentation!
Choose the Gem name with care, because you know: naming is everything!
It became popular lately to start the development cycle with a version 0.0.1 instead of 1.0.0. I think this is just a matter of taste. More importantly you should handle your MAJOR.MINOR.FIX versions carefully. Increment your FIX version on bugfixes or additions to the API that won’t brake any existing code. Increment your MINOR version if you added features with an noticable impact to the API or it’s usage. Push a new MAJOR version if you changed your API completely or if you want to emphasize that the latest version is a major improvement or a new stable release. In an ideal world all those decisions and changes are documented in a CHANGELOG file.
In addition to these rules, Rubygems provides a mechanism to roll out beta versions of a Gem. In order to do this, just append a .beta etc. to the end of your version. These Gems can then be installed with the —pre flag:
gem install rails --pre # might install rails 3.1.0.beta some day
One thing that is essential for creating a Gem is the .gemspec file. Put one in the root of your project folder and name it alike. The .gemspec file contains plain old Ruby and simply specifies all important attributes for your Gem. For an example, have a look at the gem_best_practice.gemspec:
# coding: utf-8 lib = File.expand_path('../lib/', __FILE__) $:.unshift lib unless $:.include?(lib) require 'gem_best_practice' spec = Gem::Specification.new do |s| s.name = s.rubyforge_project = 'gem_best_practice' s.version = GemBestPractice::VERSION s.author = 'Peter SchrÃ¶der' s.description = 'A Gem that shows best practice for Gem creation.' s.email = 'firstname.lastname@example.org' s.homepage = 'http://github.com/phoet/gem_best_practice' s.summary = 'If you ever wondered how to create a Gem and what best practice should be applied, this project is for you.' s.has_rdoc = true s.rdoc_options = ['-a', '--inline-source', '--charset=UTF-8'] s.files = Dir.glob('lib/**/*.rb') + Dir.glob('bin/*') + %w(README.rdoc) s.test_files = Dir.glob('test/**/*.rb') s.executables = %w(gbp) s.default_executable = 'gbp' s.add_dependency("erubis", "~> 2.6.0") end
After the .gemspec file is done, the Gem may be built via the gem command:
gem build your.gemspec
Or with a custom Rake task:
spec = eval(File.new("your.gemspec").readlines.join("\n")) Rake::GemPackageTask.new(spec) do |pkg| pkg.need_zip = true pkg.need_tar = true end
Ruby is an open language with very little restrictions. You can place your code anywhere, but there are some guidelines of how code should be structured so that nobody has to mess with the $LOAD_PATH in order to use it. This is an example of standard Gem folders:
gem_best_practice/ |- bin |- example |- lib |- test
Your actual Ruby code should go under lib and subfolders. It’s a best practice to put just one file there and the other stuff right into another subfolder in order do avoid name clashes or the like. Extensions to the standard Ruby libraries should be separated out, so that they are easier to spot:
|- lib | |- gem_best_practice | | |- core_ext | | | |- kernel.rb | | |- core_ext.rb | | |- gbp.rb | |- gem_best_practice.rb
All the tests should go into the test folder ( spec / feature folder respectively). Make sure the Gem is well tested, otherwise chances are nobody will use the it nor contribute to the codebase. As an awesome Ruby developer, one would add some (working) examples in the example folder to demonstrate the API of your Gem in action. Additional executables for your Gem should be put into bin and don’t forget to add a proper shebang:
There are a lot of great libraries available for Ruby nowadays and one would be a fool not to use them. That’s why most of the Gems depend on other libraries. Different Gems may share common dependencies, so it’s elementary for a good Gem to be as loose about the own dependencies as possible to reduce versioning conflicts. Tools like Bundler do a great job on resolving those conflicts and they depend on the versions that are wired in the .gemspec file of a Gem. You can easily tell Bundler to use your .gemspec the main configuration source for your gems. A tradeoff between hard wiring Gem versions and providing no version restrictions at all, is to use the ~> range operator, that will allow newer dependency versions within the same MINOR version:
s.add_dependency("erubis", "~> 2.6.0") # >= 2.6.0 and < 2.7.0
There is still one problem left. It’s currently not possible to define optional dependencies in your .gemspec file. This is a big problem for libraries that implement some kind of adapter pattern (Have a look at HTTPI which provides an interface to different HTTP libraries) and don’t want to force the user to install all underlying implementations (This is also a problem for bundler).
Until this problem is fixed in Rubygems, a simple approach to this problem is to programmatically rescue from LoadErrors:
# require optional dependency begin require 'ap' rescue LoadError puts "awesome_print would be awesome, please 'gem install awesome_print'" end
Documentation and Examples
An awesome Gem deserves a decent documentation. If you are too lazy to document your code, you should at least bundle a README file with your source. GitHub does a great job at pushing a project to provide a README (rdoc/textile/markdown/…), because this file will be prominently rendered on the projects start page. The README should at least include installation instructions and basic usage examples.
Using YARD / RDoc in the source code is another great way to help others understand the code, provide bugfixes or extend existing functionality. Even for yourself, having a good inline documentiont will save time when returning to a piece of code you did not had your hands on for month.
Generating the documentation can easily be done with Rake:
Rake::RDocTask.new(:rdoc_dev) do |rd| rd.rdoc_files.include("lib/**/*.rb", "README.rdoc") rd.options + ['-a', '--inline-source', '--charset=UTF-8'] end
If the project is hosted on GitHub, rdoc.info may generate the documentation automatically from the source-repository. GitHub also provides a service-hook so that the documentation is updated on every push! A .document file in the project will give rdoc.info the right pointers which files to include in your docs:
Depending on the project, it might be a good idea to include some usage examples with the code. This might be the case if one can not put all the necessary information in the inline comments or the README file. Bye the way, (especially behavior driven) tests serve very well as executable examples.
Another thing that is overlooked quite often is the documentation of Ruby runtime compatibility. Does the Gem work with MRI 1.8 / 1.9 / JRuby / REE / … ? Since RVM is a defacto standard in the Ruby environment, there is no good reason not to test at least Ruby 1.8.7 and 1.9.2.
After finishing the work on the first version of a Gem and pushing it to Rubygems, what comes next?
It’s not easy to reach a wide range of users, but if you want to promote your Gem over the interwebs there are a lot of services like DZone or Forrst that allow you to post something about the Gem or to link to articles on your blog or Twitter posts. Another great resource for Ruby developers to discover new Gems is The Ruby Toolbox.
The Gem Best Practice Gem
More best practice, less fail!