Writing Ruby Gems

3d65a0bc911de24fde5e58d84b0276af?s=47 Liz
June 17, 2016

Writing Ruby Gems

Thanks to some lovely people in the Ruby and Bundler communities, creating your first Ruby gem is a lot more approachable than it used to be. Even with this helpful documentation, creating your first gem can be daunting: what are all of these files, and why are they organized this way? How do you test a gem? What are some basic best practices? During this talk, I'll walk you through creating a small Ruby gem and publishing it for others to use.

3d65a0bc911de24fde5e58d84b0276af?s=128

Liz

June 17, 2016
Tweet

Transcript

  1. 6.

    If you want to follow along… •A computer that has:

    ̣ Ruby 2.3.1 ̣ bundler 1.12 ̣ rspec 3.0 ̣ git
  2. 7.

    If you want to follow along… •Accounts and command line

    access for: ̣ Rubygems.org ̣ GitHub
  3. 21.
  4. 24.

    Why bundler? •helps you get started with best practices •prompts

    you to think about testing •prompts you to think about contribution guidelines
  5. 27.

    Create a new gem > Do you want to generate

    tests with your gem? rspec
  6. 28.

    Create a new gem > Do you want to license

    your code permissively under the MIT license? y
  7. 29.

    Create a new gem > Do you want to include

    a code of conduct in gems you generate? y
  8. 30.

    Create a new gem create cryptozoologist/Gemfile create cryptozoologist/.gitignore create cryptozoologist/lib/cryptozoologist.rb

    create cryptozoologist/lib/cryptozoologist/version.rb create cryptozoologist/cryptozoologist.gemspec create cryptozoologist/Rakefile create cryptozoologist/README.md create cryptozoologist/bin/console create cryptozoologist/bin/setup create cryptozoologist/.travis.yml create cryptozoologist/.rspec create cryptozoologist/spec/spec_helper.rb create cryptozoologist/spec/cryptozoologist_spec.rb create cryptozoologist/LICENSE.txt create cryptozoologist/CODE_OF_CONDUCT.md
  9. 31.

    Caveats •bundler will only ask these questions once •gem names

    must be unique •alternative licenses are available •bundler doesn't set a Ruby version ̣ but we can do this ourselves!
  10. 36.

    Quick run down •lists dependencies for apps •pins versions for

    dependencies •lists sources for dependencies •can include ruby version! •paired with a Gemfile.lock
  11. 37.

    Gemfiles for gems In a Ruby gem, you also have

    a Gemfile! ... but it's used differently
  12. 40.

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

    cryptozoologist.gemspec gemspec ok this matters, too
  13. 43.

    Gemspec # coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless

    $LOAD_PATH.include?(lib) require 'cryptozoologist/version' Gem::Specification.new do |spec| spec.name = "cryptozoologist" spec.version = Cryptozoologist::VERSION spec.authors = ["Liz Abinante"] spec.email = ["me@liz.codes"] spec.summary = "Generates random strings from animal, clothing item, and color pairings." spec.description = "Cryptozoologist generates random strings from animal, clothing item, and color pairings." spec.homepage = "https://github.com/feministy/cryptozoologist" spec.license = "MIT" spec.files = Dir["CODE_OF_CONDUCT.md", "CHANGELOG.md", "LICENSE.txt", "README.md", "lib/**/*.rb"] spec.test_files = Dir["spec/**/*.rb"] spec.require_paths = ["lib"] spec.add_development_dependency "bundler", "~> 1.12" spec.add_development_dependency "rake", "~> 10.0" spec.add_development_dependency "rspec", "~> 3.4" spec.add_development_dependency "pry", "~> 0.10.3" spec.add_development_dependency "pry-nav", "~> 0.2.4" end
  14. 44.

    Gemspec # blah blah blah Gem::Specification.new do |spec| # stuff

    we don’t care abut (right now) spec.add_development_dependency "bundler", "~> 1.12" spec.add_development_dependency "rake", "~> 10.0" spec.add_development_dependency "rspec", "~> 3.4" spec.add_development_dependency "pry", "~> 0.10.3" spec.add_development_dependency "pry-nav", "~> 0.2.4" end
  15. 47.

    Remember all these files? create cryptozoologist/Gemfile create cryptozoologist/.gitignore create cryptozoologist/lib/cryptozoologist.rb

    create cryptozoologist/lib/cryptozoologist/version.rb create cryptozoologist/cryptozoologist.gemspec create cryptozoologist/Rakefile create cryptozoologist/README.md create cryptozoologist/bin/console create cryptozoologist/bin/setup create cryptozoologist/.travis.yml create cryptozoologist/.rspec create cryptozoologist/spec/spec_helper.rb create cryptozoologist/spec/cryptozoologist_spec.rb create cryptozoologist/LICENSE.txt create cryptozoologist/CODE_OF_CONDUCT.md
  16. 49.

    bin •contains executables •things you’d run in the command line

    •console automatically starts IRB •setup automatically runs bundle install •we won’t be using these today
  17. 51.

    lib •actual files for the gem! •only 2 things go

    in your lib root folder: ̣ cryptozoologist.rb ̣ cryptozoologist/
  18. 52.

    lib/cryptozoologist.rb •requires all of your other files •where you create

    your gem namespace (Cryptozoologist) using parent module
  19. 53.

    lib/cryptozoologist •subfolder! •where you put everything you want to use

    in your gem •everything we add will go here - except for tests
  20. 56.

    spec •where your tests go! •follows the same naming and

    folder patterns a lib •add _spec to the end of your file name
  21. 66.

    Patch •small bug fixes ̣ backwards compatible ̣ do not

    break existing functionality •added documentation
  22. 67.

    Patch •people rarely pin to patch versions •this makes it

    easier for people to get your new patches without thinking about it •patches are great!
  23. 68.

    Patch •patches are also great for: ̣ security fixes you

    want everyone to have ̣ small improvements or additions that aren’t required ̣ adding incremental upgrades and deprecation messaging for future versions
  24. 69.

    Minor •1.3.10, 2.18.0, etc •range in size from small to

    large •add new features or functionality without: ̣ breaking previous versions ̣ changing behavior of current version
  25. 70.

    Minor •can include: ̣ small: adding one new method that

    establishes a new pattern ̣ large: adding a new feature, such as covering a new API endpoint
  26. 71.

    Minor •it’s not uncommon to see a lot of minor

    bumps in less popular gems as features get added •larger, more stable projects like Rails don’t use as many minor versions
  27. 72.

    Minor •you can also you minor changes to help with:

    ̣ slow, early deprecation warnings alongside your new changes or features ̣ easier upgrade process for your next major version
  28. 73.

    Major •1.10.0, 3.0.2, etc •usually very big changes that: ̣

    are not backwards compatible ̣ change your API contract ̣ require code changes to upgrade
  29. 74.

    Major •don’t be hesitant to make breaking changes ̣ but

    if you do, be sure you label them as breaking! •starting your gem on a major version of 0 makes it difficult to bump your major version
  30. 75.

    API contracts •you should be providing your users with a

    consistent interface •if your public interface (aka your gem’s API) is not consistent, people won’t use your gem
  31. 76.

    API contracts •your gem will be easier to use if

    you: ̣ don’t change the type of object returned ̣ establish a consistent pattern ̣ document your code
  32. 77.

    Example version •your gem provides a method that tells someone

    if a user is logged in ̣ version 1.3.5 returns a boolean ̣ your next version returns a User object if they’re logged in, and nil if they’re not
  33. 79.
  34. 83.
  35. 84.

    Reasons you might not want MIT •you’re creating a gem

    for: ̣ work and it’s internal use only ̣ you’re using a data source or API that doesn’t allow for a permissive license ̣ you don’t want others profiting off of your free labor
  36. 85.

    If you don’t use MIT… •your code can’t be used

    in closed source, for profit things •your code can only be used in other free, open source projects that have the same, or more restrictive, license •I won’t judge you because OSS lets big companies with deep pockets profit off of great code for free instead of investing in community software
  37. 86.

    Common alternatives •GPL, version 2 or 3 ̣ “copyleft” •Apache

    2.0 ̣ similar to MIT but talks about patents •see choosealicense.com for more
  38. 90.

    Running tests with CI •Travis runs with every push, PR,

    and merge •free for open source projects! •highly configurable
  39. 91.
  40. 93.

    How to test? •the more object oriented your code is,

    the easier it will be to test •testing the underlying private API is just as important as the public one •sometimes your public tests are really small
  41. 94.

    Public API module Cryptozoologist def self.generate string = "" #

    determine string order # determine any special configs that need to be added order.each do |library| # get a word, add a delimiter # squish together a string end return string end end /lib/cryptozoologist.rb
  42. 95.

    Testing the public API describe Cryptozoologist do # some other

    tests context '#generate' do it 'returns a string' do expect(Cryptozoologist.generate).to be_instance_of(String) end it 'returns a string with the delimeter' do expect(Cryptozoologist.generate.match('-')).to be_instance_of(MatchData) end end end /spec/cryptozoologist_spec.rb
  43. 96.

    Private API module Cryptozoologist def self.generate string = "" #

    determine string order order.unshift(:quantity) if @configuration.include_quantity? order.each do |library| # get a word, add a delimiter # squish together a string end return string end end /lib/cryptozoologist.rb
  44. 97.

    Testing the private API describe Cryptozoologist ::Configuration do context ‘#include'

    do it 'sets valid inclusions' do Cryptozoologist.configure do |config| config.include = [:quantity] end expect(Cryptozoologist.configuration.include_quantity?).to be true end end end /spec/cryptozoologist/configuration_spec.rb
  45. 100.

    Publishing gem build cryptozoologist.gemspec Successfully built RubyGem Name: cryptozoologist Version:

    2.0.0 File: cryptozoologist-2.0.0.gem gem push cryptozoologist-2.0.0.gem
  46. 101.
  47. 102.

    What does yanking do? •removes the gem from rubygems.org ̣

    users can’t install it using gem install ̣ removes the gem file completely •permanently removes that version number: you cannot republish using that version number!
  48. 105.

    Why configuration? •allows things to be set once ̣ API

    keys, usernames, passwords ̣ callback URLs •change usage options ̣ test order in RSpec!
  49. 106.

    Configuration for our sample gem •exclude word lists •specify delimiter

    type •include quantity words •order of words
  50. 109.

    Configuration module Cryptozoologist class << self attr_accessor :configuration end def

    self.configuration @configuration ||= Configuration.new end def self.configure yield(configuration) end end /lib/cryptozoologist.rb
  51. 110.

    Configuration module Cryptozoologist class Configuration attr_reader :delimiter def initialize @delimiter

    = "-" end def delimiter=(string) raise Errors::Configuration, "delimiter must be a a string" unless string.is_a?(String) @delimiter = string end end end /lib/cryptozoologist/configuration.rb
  52. 112.