Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
May 29, 2012 07:08 pm GMT

Gem Creation with Bundler

Building a gem used to be a complex task that would require either a precise knowledge of the gem format, itself, or some dedicated tools to generate a suitable boilerplate. These days, we can use the excellent Bundler to remove this complexity and keep the amount of generated code to a minimum.


What We're Building

The test gem we're going to build is a dummy content generator you might use during development. Instead of generating "lorem ipsum" sentences, it uses Bram Stoker's Dracula to generate an arbitrary amount of sentences taken from the book. Our workflow will start by generating the gem, testing and implementing the minimum amount of code necessary to get our gem ready, and then publishing it on RubyGems.


Generating a Skeleton

I'm going to assume that you have a Ruby environment already setup. For this tutorial, we will use Ruby 1.9.3 as a baseline. If you plan, however, to develop a real gem, it might be good to also test it against Ruby 1.8 and other interpreters. For that purpose, a tool like Travis CI is a godsend; with a solid test suite in place, Travis will let you test your gem against a wide variety of platforms without any hassle. Let's start by generating the skeleton:

bundle gem bramipsum

I'm really sorry if you don't like the name I chose, as a matter of fact one of the hardest tasks in developing a gem is finding the right name. The command will create a directory, called bramipsum with a few files:

Gemfile

The Gemfile is very minimal:

source 'https://rubygems.org'# Specify your gem's dependencies in bramipsum.gemspecgemspec

Note that it clearly tells you to move your gem dependencies to bramipsum.gemspec, in order to have all the relevant data for your gem in the file that will be used to populate the metadata on Rubygems.

bramipsum.gemspec

The gemspec file contains a good deal of information about our gem; we can see that it relies heavily on Git to assign the right values to all the variables that involve file listing.

# -*- encoding: utf-8 -*-require File.expand_path('../lib/bramipsum/version', __FILE__)Gem::Specification.new do |gem|  gem.authors       = ["Claudio Ortolina"]  gem.email         = ["[email protected]"]  gem.description   = %q{TODO: Write a gem description}  gem.summary       = %q{TODO: Write a gem summary}  gem.homepage      = ""  gem.executables   = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }  gem.files         = `git ls-files`.split("\n")  gem.test_files    = `git ls-files -- {test,spec,features}/*`.split("\n")  gem.name          = "bramipsum"  gem.require_paths = ["lib"]  gem.version       = Bramipsum::VERSION  gem.add\_development\_dependency 'rake'end

Next, we can run bundle to install Rake. As it was added as a development dependency, it won't be installed by Bundler when someone uses our gem.

A few interesting notes about the file:

  • It includes the Ruby 1.9 opening comment that specifies the file encoding. This is important, as some data in the file (like the email or the author name) can be a non-ascii character.
  • description and summary need to be changed to be correctly displayed on Rubygems.
  • The version is defined inside the lib/bramipsum/version file, required at the top. It defines the VERSION constant, called right before the end of the file.

The lib folder

The lib folder contains a generic bramipsum.rb file that requires the version module. Even if the comment in the file suggests that you add code directly to the file itself, we will use it just to require the separate classes that will form our small gem.


Updating the Base Data and Adding a Test Framework

Let's start by updating the data in bramipsum.gemspec:

...gem.description   = %q{Random sentences from Bram Stoker's Dracula}gem.summary       = %q{Generate one or more dummy sentences taken from Bram Stoker's Dracula}...

Very simple stuff. Next, let's add support for proper testing. We will use Minitest, as it's included by default in Ruby 1.9. Let's add a test directory:

mkdir test

Next, we need a test_helper.rb file and a test for the presence of the Bramipsum::VERSION constant.

touch test/test_helper.rbmkdir -p test/lib/bramipsumtouch test/lib/bramipsum/version_test.rb

Let's open the test_helper.rb file and add a few lines:

require 'minitest/autorun'require 'minitest/pride'require File.expand_path('../../lib/bramipsum.rb', __FILE__)

It requires both Minitest and Pride for colored output; then it requires the main bramipsum file.

The version_test.rb file needs to be updated with the following code:

require_relative '../../test_helper'describe Bramipsum do  it "must be defined" do    Bramipsum::VERSION.wont\_be\_nil  endend

We use the expectation format for our tests. The test itself is fairly self-explanatory and can easily be run by typing:

ruby test/lib/bramipsum/version_test.rb

You should have a passing test!

Let's now update the Rakefile to have a more comfortable way to run our tests. Erase everything and paste the following code:

#!/usr/bin/env rakerequire "bundler/gem_tasks"require 'rake/testtask'Rake::TestTask.new do |t|  t.libs << 'lib/bramipsum'  t.test_files = FileList['test/lib/bramipsum/*_test.rb']  t.verbose = trueendtask :default => :test

This will let us run our tests by typing rake from the gem root folder.


Adding Functionality

As the focus of this tutorial is creating a gem, we will limit the amount of functionality we're going to add.


Base Class

Bramipsum is still an empty shell. As we want to use Dracula's book to generate sentences, it's time to add it to the repository. I have prepared a version of the book where I have removed any content, except the story itself: let's add it to the project.

mkdir -p bookcurl https://raw.github.com/cloud8421/bundler-gem-tutorial/master/book/dracula.txt -o book/dracula.txt

Let's now create a Base class, where will add all the methods needed to extract data from the book.

touch lib/bramipsum/base.rbtouch test/lib/bramipsum/base_test.rb

The test file will have only a few expectations:

require_relative '../../test_helper'describe Bramipsum::Base do  subject { Bramipsum::Base }  describe "reading from file" do    it "must have a source" do      subject.must\_respond\_to(:source)    end    it "must have the dracula file as a source" do      subject.source.must\_be\_instance\_of(String)    end  end  describe "splitting into lines" do    it "must correctly split the file into lines" do      subject.processed_source.must\_be\_instance\_of(Array)    end    it "must correctly remove empty lines" do      subject.processed_source.wont_include(nil)    end  endend

Running rake now will show an exception, as the base.rb file is still empty. Base will simply read the content of the file and return an array of lines (removing the empty ones).

The implementation is very straightforward:

module Bramipsum  class Base    def self.source      @source ||= self.read    end    def self.processed_source      @processed_source ||= self.source.split("\n").uniq    end    private    def self.read      File.read(File.expand_path('book/dracula.txt'))    end  endend

We define a series of class methods that hold the original text and a processed version, caching the results after the first run.

We then need to open lib/bramipsum.rb and add the right require statement:

require_relative "./bramipsum/base"

If you save and run rake now, you should see all tests passing.


Sentence Class

Next, we need to add a new class to generate sentences. We will call it Sentence.

touch lib/bramipsum/sentence.rbtouch test/lib/bramipsum/sentence_test.rb

As before, we have to open lib/bramipsum.rb and require the newly created file:

require_relative "./bramipsum/base"require_relative "./bramipsum/sentence"

This class will inherit from Base, so we can keep the implementation minimal. The test will need only three expectations:

require_relative '../../test_helper'describe Bramipsum::Sentence do  subject { Bramipsum::Sentence }  it "must return a random sentence" do    subject.sentence.must\_be\_instance\_of(String)  end  it "must return 5 sentences by default" do    subject.sentences.size.must_equal(5)  end  it "must return the specified amount of sentences" do    subject.sentences(10).size.must_equal(10)  endend

The idea is that we can call Bramipsum::Sentence.sentence or Bramipsum::Sentence.sentences(10) to generate what we need.

The content for sentence.rb is also very concise:

module Bramipsum  class Sentence < Base    def self.sentence      self.processed_source.sample    end    def self.sentences(n=5)      self.processed_source.sample(n)    end  endend

As we are on Ruby 1.9, we can use the sample method to return a random element from an array.

Once again, running rake should show all tests passing.


Building and Distributing the Gem

If you run gem build from the command line, a local copy of the gem will be built and packaged for you. If you don't need to distribute it, or if you need to keep it private, you can stop here. But if it's a project you can open-source, I encourage you to do that.

The obvious step is to add our brand new gem to RubyGems.org.

After creating an account on the site, visit your profile. You will find a command you need to run to authorize your computer. In my case it was:

curl -u cloud8421 https://rubygems.org/api/v1/api_key.yaml > ~/.gem/credentials

You're now only one step away to publish the gem on Rubygems, however don't do it unless you really want to. The command you would run is:

gem push bramipsum-0.0.1.gem

Conclusion

Congratulations! You now know how to create a gem from scratch just using Bundler. There are however, other things to take into account:

  • Compatibility: you may want to support Ruby 1.8 as well. That will require refactoring all the require_relative calls; additionally, you will need to use the Minitest gem as it's not included by default in Ruby 1.8
  • Continuous Integration: you can add support to Travis CI and your gem will be tested in the cloud against all major Ruby releases. This will make it simple to be sure that there are no issues with platform-specific behavior changes.
  • Documentation: this is important, it's good to have RDoc comments that can help in generating automatic docs and a good README file with examples and guidelines.

Thanks for reading! Any questions?



Original Link: http://feedproxy.google.com/~r/nettuts/~3/RbosoSr4z6o/

Share this article:    Share on Facebook
View Full Article

TutsPlus - Code

Tuts+ is a site aimed at web developers and designers offering tutorials and articles on technologies, skills and techniques to improve how you design and build websites.

More About this Source Visit TutsPlus - Code