Shiva Bhusal
Shiva's Blog

Follow

Shiva's Blog

Follow

Convert your Ruby Script to a Ruby-Gem

Shiva Bhusal's photo
Shiva Bhusal
·Aug 30, 2019·

6 min read

Play this article

What is a gem?

Gem is basically a library of reusable ruby codes or simply ruby plugins. One should be thinking of creating ruby gems for some of the reasons

Want to make generic solutions quickly achievable to public or within organization Share newer functionality with others Organize reusable codes in larger projects

Introduction to gem development

Gems has actually no size limitations, it can go to any extent. For beginners, a gem could be a small code library that adds some helper methods into the existing ruby classes such as String class. In this tutorial we will try to add some helper methods in String class so that this methods could be used in testing or in Models of Rails framework.

Note: Be careful while giving name to your gem. Make sure no naming conflict arises by checking name you want to take in RubyGems.org or github. Make sure name is in small letters and use underscores for multiple words. See the cart below for more details.

GEM NAME REQUIRE STATEMENT MAIN CLASS OR MODULE
ruby_parser require 'ruby_parser' RubyParser
rdoc-data require 'rdoc/data' RDoc::Data
net-http-persistent require 'net/http/persistent' Net::HTTP::Persistent
net-http-digest_auth require 'net/http/digest_auth' Net::HTTP::DigestAuth

Generating a New Gem

There are four ways to create a new gem

  • Create the directory tree with required files in proper way (not recommended)
  • Rubymine's new 'Ruby gem' project
  • Using Bundler in terminal
  • Using available frameworks such as newgem, hoe, echoe, gemhub, bones

Using Bundler

Making it a gem, you will make it easier to install and use. Any body can benefit from your scripts.

We will use the famous tool bundler to generate a new gem project. This will generate files necessary.

bundle gem erika

will generate the files and dir like:-

~/projects/erika(master) tree
.
├── CODE_OF_CONDUCT.md
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── bin
│   ├── console
│   └── setup
├── erika.gemspec
├── lib
│   ├── erika
│   │   └── version.rb
│   └── erika.rb
└── spec
    ├── erika_spec.rb
    └── spec_helper.rb

4 directories, 12 files

Update GemSpec file

lib = File.expand_path("lib", __dir__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require "erika/version"

Gem::Specification.new do |spec|
  spec.name          = "erika"
  spec.version       = Erika::VERSION
  spec.authors       = ["Shiva Bhusal"]
  spec.email         = ["youremail@gmail.com"]

  spec.summary       = %q{TODO: Write a short summary, because RubyGems requires one.}
  spec.description   = %q{TODO: Write a longer description or delete this line.}
  spec.homepage      = "https://shivab.com"
  spec.license       = "MIT"

  spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"

  spec.metadata["homepage_uri"] = spec.homepage
  spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
  spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."

  # Specify which files should be added to the gem when it is released.
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
  spec.files         = Dir.chdir(File.expand_path('..', __FILE__)) do
    `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
  end
  spec.bindir        = "exe"
  spec.executables   = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
  spec.require_paths = ["lib"]

  spec.add_development_dependency "bundler", "~> 2.0"
  spec.add_development_dependency "rake", "~> 10.0"
  spec.add_development_dependency "rspec", "~> 3.0"
end

add production / runtime dependencies like

spec.add_runtime_dependency 'example', '~> 1.1', '>= 1.1.4'

gems mentioned with add_development_dependency method are only installed in development; I mean using bundle install in development mode of this gem-project.

Gem consumers will execute gem install erika or bundle install in other app; which will only install runtime dependencies.

spec.bindir

By default exe will be your home for your executable script files. If your gem is a CLI tool then only you need to create any script in exe folder.

What is the use of Gemfile in gem?

# Gemfile
source "https://rubygems.org"

# Specify your gem's dependencies in erika.gemspec
gemspec

Well, it makes possible to run bundle install command to install gem dependencies mentioned in erika.gemspec file. You can see gemspec method used in Gemfile. It will get the list of all gems mentioned in gemspec file.

Useful commands

rake build            # Build rubytutor-0.1.0.gem into the pkg
                        directory
rake clean            # Remove any temporary products
rake clobber          # Remove any generated files
rake install          # Build and install rubytutor-0.1.0.gem into
                        system gems
rake install:local    # Build and install rubytutor-0.1.0.gem into
                        system gems withou...
rake release[remote]  # Create tag v0.1.0 and build and push
                        erika.gem to RubyGems
rake test             # Run tests

Moving your scripts to Gem

My lib dir


lib> (master) tree
.
├── erika
│   ├── audio.rb
│   ├── config.rb
│   ├── image.rb
│   ├── runner.rb
│   └── video.rb
├── erika.rb
├── hash.rb
└── string.rb

1 directory, 8 files
  • move your lib directory to lib dir of your new gem.
  • move your config files
  • setup your Gemfile
  • create an executable the exe folder is where you wish to put scripts which users will like to use via CLI. If your gem is not going to be used via CLI, no need to create anything in exe folder.

Build Script executible from anywhere

I create exe/erika file with following content

#!/usr/bin/env ruby
require File.absolute_path('../lib/erika', __dir__)

Erika::Slideshow.new

then installed the gem locally

rake install

------------
erika 0.1.0 built to pkg/erika-0.1.0.gem.
erika (0.1.0) installed.

then tried

erika

Traceback (most recent call last):
        4: from /Users/john/.rvm/gems/ruby-2.6.3/bin/erika:23:in `<main>'
        3: from /Users/john/.rvm/gems/ruby-2.6.3/bin/erika:23:in `load'
        2: from /Users/john/.rvm/gems/ruby-2.6.3/gems/erika-0.1.0/exe/erika:3:in `<top (required)>'
        1: from /Users/john/.rvm/rubies/ruby-2.6.3/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
/Users/john/.rvm/rubies/ruby-2.6.3/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require': cannot load such file -- /Users/john/projects/lib/erika (LoadError)

Reason

Since the script file erika is bin-stubbed in some bin directory by bundler/installer, so ruby cannot properly locate the ruby file like ../erika/config, so we need to modify the ruby load-path.

Solution

#!/usr/bin/env ruby
require 'pry'

# Adding the lib directory to the Load Path of Ruby
$:.unshift File.expand_path("../../lib", __FILE__)

require 'erika'

erika = Erika::SlideShow.new
erika.start

Build CLI option handlers

You also want to support CLI options like

 erika g -s happy -S=5 -t=4 -o=opt/mymovie.mp4

We will use Thor support CLI options

#!/usr/bin/env ruby
require 'pry'
require 'thor'

# Adding the lib directory to the Load Path of Ruby
$:.unshift File.expand_path("../../lib", __FILE__)
require 'overrides/hash'
$erika_options = {}
class Options < Thor
  desc "g", "Generate movie"
  method_option :output,
                :aliases => "-o",
                :desc => "Output path; where to generate output movie"
  method_option :source,
                :aliases => "-s",
                :desc => "Input path; folder path where the images are located"
  method_option :audio,
                :aliases => "-a",
                :desc => "Audio path; the path to bg audio"
  method_option :transition_duration,
                :aliases => "-t",
                :desc => "Transition animation duration between two images"
  method_option :slide_duration,
                :aliases => "-S",
                :desc => "Slide duration between two images"
  def g
    # $erika_options.merge({output_dir: File.expand_path(options[:o])}) if options[:o]
    # $erika_options.merge({source_dir: File.expand_path(options[:s]),
    #                       source_files: ''}) if options[:s]
    # $erika_options.merge({audio: File.expand_path(options[:a])}) if options[:a]

    $erika_options = options.to_o
    require 'erika'
    erika = Erika::SlideShow.new
    erika.start
  end
end


Options.start(ARGV)

this will build beautiful CLI API. Try

$ exe/erika help g
Usage:
  erika g

Options:
  -o, [--output=OUTPUT]                            # Output path; where to generate output movie
  -s, [--source=SOURCE]                            # Input path; folder path where the images are located
  -a, [--audio=AUDIO]                              # Audio path; the path to bg audio
  -t, [--transition-duration=TRANSITION_DURATION]  # Transition animation duration between two images
  -S, [--slide-duration=SLIDE_DURATION]            # Slide duration between two images

Generate movie

Publishing Gem

First you need to signup @ https://rubygems.org. Then you need to signin from CLI

$ gem signin

Enter your RubyGems.org credentials.
Don't have an account yet? Create one at https://rubygems.org/sign_up
   Email:   hotline.shiva@gmail.com
Password:

Enter password and you are in.

Then, build and release

$ rake release

erika 0.1.0 built to pkg/erika-0.1.0.gem.
Tag v0.1.0 has already been created.
Pushing gem to https://rubygems.org...
Successfully registered gem: erika (0.1.0)
Pushed erika 0.1.0 to https://rubygems.org

Summary

  • either you can generate a skeleton gem using bundle gem command or move exising ruby project to it.
  • You then, refactor the library so that files can be required my ruby properly.
  • Build a CLI interface if required
  • configure your gemspec properly
  • signup to rubygems.org
  • publish/release your gem.
 
Share this