Skip to content

Instantly share code, notes, and snippets.

@ericboehs
Last active October 27, 2016 16:51
Show Gist options
  • Save ericboehs/461439da9a1416d9c6ca139a389a226c to your computer and use it in GitHub Desktop.
Save ericboehs/461439da9a1416d9c6ca139a389a226c to your computer and use it in GitHub Desktop.

How to create a Ruby Gem

Why create a gem?

  • For code reuse
  • For isolation (easy to test, black box input/output)
  • For important and/or complex code
  • For community features and feedback (if open sourced)

Example code to turn into a gem

pry
require_relative 'month_ordinals'
month_ordinals = MonthOrdinals.for 2016
month_ordinals[:first][:friday]
_[7]

month_ordinals = MonthOrdinals.for 2016..2017
month_ordinals

Scaffold Gem:

bundle gem month_ordinals
cd month_ordinals
ls -1A

This adds a gemspec, Rakefile (for testing and releasing), stub for your library's code, semver version.rb, minitest, travis.yml, bin/setup and bin/console, an MIT license and a git repository.

Edit gemspec summary, description, homepage, remove private gem protection, add gems:

vi *gemspec

Edit bin/console and bin/setup (add .git/safe and pry)

vi bin/setup bin/console

Commit scaffold:

git commit -am 'Initial commit'

Add code:

mkdir lib/month_ordinals/ext
vi lib/month_ordinals.rb
git add -p
git commit -v -S
git create ericboehs/month_ordinals
git push -u

Play and Test

bin/console
rake
# or
m test/month_ordinals_test.rb:5

Publish

rake -T
rake release

Updating

vi -p lib/month_ordinals.rb lib/month_ordinals/version.rb
git add -p
git commit -v
rake release

Resources

Create this gist

gist _README.md month_ordinals.rb
require "minitest/autorun"
require "date"
class MonthOrdinals
ORDINALS = %i(first second third fourth fifth last)
WEEKDAYS = %i(sunday monday tuesday wednesday thursday friday saturday)
def self.for years
years = years..years unless years.is_a? Range
ORDINALS.map { |ordinal| { ordinal => years_map(years, ORDINALS.index(ordinal)) } }.reduce Hash.new, :merge
end
private
def self.years_map years, ordinal
years.map { |year| weekdays_map year, ordinal }.reduce Hash.new, :deep_array_merge
end
def self.weekdays_map year, ordinal
WEEKDAYS.map { |weekday| [weekday, months_map(year, ordinal, WEEKDAYS.index(weekday))] }.to_h
end
def self.months_map year, ordinal, day_of_week, months=1..12
months.map do |month|
date =
if ordinal == 5
last_day_of_month day_of_week, year, month
else
ordinal_day_of_month ordinal, day_of_week, year, month
end
date if date.month == month
end.compact
end
def self.last_day_of_month day_of_week, year, month
date = Date.new year, month, -1
offset = date.wday - day_of_week
date -= offset % 7
end
def self.ordinal_day_of_month ordinal, day_of_week, year, month
date = Date.new year, month, 1
offset = day_of_week - date.wday
date += offset % 7
date += 7 * ordinal
end
end
class ::Hash
def deep_array_merge second
merger = proc do |key, v1, v2|
if Hash === v1 && Hash === v2
v1.merge v2, &merger
elsif v1.is_a?(Array) && v2.is_a?(Array)
v1 + v2
end
end
self.merge second, &merger
end
end
class MonthOrdinalsTest < MiniTest::Test
def test_first_sundays
expected_month_ordinals = [
Date.new(2001, 1, 7), Date.new(2001, 2, 4), Date.new(2001, 3, 4),
Date.new(2001, 4, 1), Date.new(2001, 5, 6), Date.new(2001, 6, 3)
]
month_ordinals = MonthOrdinals.for 2001..2002
assert_empty expected_month_ordinals - month_ordinals[:first][:sunday]
end
def test_third_tuesdays
skip
end
def test_last_fridays
expected_month_ordinals = [
Date.new(2001, 1, 26), Date.new(2001, 2, 23), Date.new(2001, 3, 30),
Date.new(2001, 4, 27), Date.new(2001, 5, 25), Date.new(2001, 6, 29)
]
month_ordinals = MonthOrdinals.for 2001..2002
assert_empty expected_month_ordinals - month_ordinals[:last][:friday]
end
end
@ericboehs
Copy link
Author

ericboehs commented Aug 5, 2016

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment