Step By Step of Creating a Ruby Gem

Step By Step of Creating a Ruby Gem

I’m going to create a ruby gem to handle the accents in a string. The gem will replace all the accents to it’s corresponding ascii character. And will achieve this by extending the class String to provide an instance method #accent_to_ascii.

For example:

require 'accent_to_ascii'
"Thìs ìs ā strìng wïth āccēnts".accent_to_ascii
#=> "This is a string with accents"

The initialtive is that those non-ascii codes in the string sometime is not compatible with systems, we need clear them up. But it’s too crude to just remove them with something like gsub(/[^\x20-\x7e]/, ''). And there are a lot of names are with accents in American. So replace those accents with the corresponding ascii characters is a better solution. ( Will make them happy 🙂 )

So in this article will create a gem accent_to_ascii to do this specific task.

Let’s get started:

Steps (story in short)

  1. Create the files for the gem. It’s as simple as just running: bundle gem accent_to_ascii. This will create the gem files from templates into directory ./accent_to_ascii. I prefer the rspec option for testing.
  2. Develop the gem functionality.
    1. Update the gemspec file accent_to_ascii.gemspec with information of the gem.
    2. Write tests in the spec/accent_to_ascii_spec.rb (Test first right?)
    3. Write the core methods in the lib/accent_to_ascii.rb to make tests green
  3. Publish it into rubygems.org, if you already have rubygems account, this is simple as just run rake release

Now let’s diving into the details of the above steps:

1. Gem Creation

Create gem is as simple as just a line of command:

$ bundle gem accent_to_ascii

By running this command, the bundler will create the gem in the directory accent_to_ascii as:

$ tree accent_to_ascii
accent_to_ascii
├── CODE_OF_CONDUCT.md
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── accent_to_ascii.gemspec
├── bin
│   ├── console
│   └── setup
├── lib
│   ├── accent_to_ascii
│   │   └── version.rb
│   └── accent_to_ascii.rb
├── pkg
│   └── accent_to_ascii-0.1.0.gem
└── spec
    ├── accent_to_ascii_spec.rb
    └── spec_helper.rb

accent_to_ascii.gemspec

The accent_to_ascii.gemspec is the description file of this gem, we can modify it first.

lib/accent_to_ascii/version.rb

The lib/accent_to_ascii/version.rb defined the version of the gem.

module AccentToAscii
  VERSION = "0.1.0"
end

The version is defined with a Major.Minor.Patch style:

  • The Major version is for updates will break the backward compatibility;
  • The Minor version is for updates with backward compatibility;
  • The Patch version is for bug fixes with backward compatibility.

spec/accent_to_ascii_spec.rb

This is the test file, we modify this file with the expected behaviors of the gem and make it green by developing the code.

lib/accent_to_ascii.rb

This is the main code file.

2.1 Update the gemspec file

Just provide the informations in the TODO section, and I make it as:

# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'accent_to_ascii/version'

Gem::Specification.new do |spec|
  spec.name          = "accent_to_ascii"
  spec.version       = AccentToAscii::VERSION
  spec.authors       = ["uniEagle"]
  spec.email         = ["unieagle@gmail.com"]

  spec.summary       = %q{Rubygem to replace accents in string into the corresponding ascii chars}
  spec.description   = %q{Replace the non ascii accents in a string}
  spec.homepage      = "https://github.com/unieagle/accent_to_ascii"
  spec.license       = "MIT"

  spec.files         = `git ls-files -z`.split("\x0").reject do |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", "~> 1.13"
  spec.add_development_dependency "rake", "~> 10.0"
  spec.add_development_dependency "rspec", "~> 3.0"
end

2.2 Write the tests

The in the test file /spec/accent_to_ascii_spec.rb, added several tests to reflect the expected behavior of this gem:

require "spec_helper"

describe AccentToAscii do
  it "has a version number" do
    expect(AccentToAscii::VERSION).not_to be nil
  end

  it { expect("São Paulo".accent_to_ascii).to eq "Sao Paulo" }
  it { expect("Tẽst".accent_to_ascii).to eq "Test" }

  it "does not change original string" do
    str = "São Paulo"
    expect(str.accent_to_ascii).to eq "Sao Paulo"
    expect(str).to eq "São Paulo"
  end

  context "hungarian accents" do
    specify { expect("fejlődő".accent_to_ascii).to eq "fejlodo" }
    specify { expect("FEJLŐDŐ".accent_to_ascii).to eq "FEJLODO" }
    specify { expect("fű".accent_to_ascii).to eq "fu" }
    specify { expect("FŰ".accent_to_ascii).to eq "FU" }
  end

  context "accent_to_ascii!" do
    let(:str) { "São Paulo" }

    it "changes original string" do
      expect(str.accent_to_ascii!).to eq "Sao Paulo"
      expect(str).to eq "Sao Paulo"
    end
  end
end

And of course, if you run rake, almost all the tests will fail:

$ rake
... some test errors ...
Finished in 0.0024 seconds
9 examples, 8 failures

2.3 Write some real code and make the tests green

The file /lib/accent_to_ascii.rb is the target file. I just made it as the following:

require "accent_to_ascii/version"

module AccentToAscii
  ACCENTS_MAPPING = {
    'E' => [200,201,202,203],
    'e' => [232,233,234,235,7869],
    'A' => [192,193,194,195,196,197],
    'a' => [224,225,226,227,228,229,230],
    'C' => [199],
    'c' => [231],
    'O' => [210,211,212,213,214,216,336],
    'o' => [242,243,244,245,246,248,337],
    'I' => [204,205,206,207],
    'i' => [236,237,238,239],
    'U' => [217,218,219,220,368],
    'u' => [249,250,251,252,369],
    'N' => [209],
    'n' => [241],
    'Y' => [221],
    'y' => [253,255],
    'AE' => [306],
    'ae' => [346],
    'OE' => [188],
    'oe' => [189]
  }

  def self.accent_to_ascii(string)
    string.tap do |s|
      ACCENTS_MAPPING.each { |letter, accents| replace(s, letter, accents) }
    end
  end

  private

  def self.replace(string, letter, accents)
    packed = accents.pack('U*')
    regex = Regexp.new("[#{packed}]", nil)
    string.gsub!(regex, letter)
  end

end

class String
  def accent_to_ascii(string = String.new(self))
    AccentToAscii.accent_to_ascii(string)
  end

  def accent_to_ascii!
    accent_to_ascii(self)
  end
end

Special thanks to the Github project accentless, reused the ACCENTS_MAPPING in that project.

Then the tests are green now:

$ rake
AccentToAscii
  has a version number
  should eq "Sao Paulo"
  should eq "Test"
  does not change original string
  hungarian accents
    should eq "fejlodo"
    should eq "FEJLODO"
    should eq "fu"
    should eq "FU"
  accent_to_ascii!
    changes original string

Finished in 0.00283 seconds
9 examples, 0 failures

3. Package and Publish

If you already have rubygems account registered and configured well, it’s just as simple as a single command:

$ rake release

Or, just sign up in the RubyGems.org, then run the following command:

$ gem push pkg/accent_to_ascii-0.1.0.gem

The first time run this command will ask you input your account information for RubyGems.org.

And for the following up changes, just update the version and run rake release.

References

[Engineering Lunch Series] Step-by-Step Guide to Building Your First Ruby Gem

Official Guide of Publish Gems onto RubyGems.org

Typhoeus : a gem for http requests in rails.

Typhoeus



Single request

request = Typhoeus::Request.new(
  "www.example.com",
  method: :post,
  body: "this is a request body",
  params: { field1: "a field" },
  headers: { Accept: "text/html" }
)

# handle the response as follows
request.on_complete do |response|
  if response.success?
    # hell yeah
  elsif response.timed_out?
    # aw hell no
    log("got a time out")
  elsif response.code == 0
    # Could not get an http response, something's wrong.
    log(response.return_message)
  else
    # Received a non-successful http response.
    log("HTTP request failed: " + response.code.to_s)
  end
end

# this will actually execute the request
request.run



Parallel requests
Typhoeus doing well in the parallel requests, just doing this:

hydra = Typhoeus::Hydra.hydra

first_request = Typhoeus::Request.new("www.example.com/posts/1.json")
first_request.on_complete do |response|
  third_request = Typhoeus::Request.new(www.example.com/posts/3.json)
  hydra.queue third_request
end
second_request = Typhoeus::Request.new("www.example.com/posts/2.json")

hydra.queue first_request
hydra.queue second_request
# this is a blocking call that returns once all requests are complete
hydra.run



Referrence
For more information, looking:
Typhoeus on Github