Pro Yearly is on sale from $80 to $50! »

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. Writing Ruby Gems Liz Abinante • @feministy Senior Software Engineer,

    New Relic labinante@newrelic.com
  2. View slides online! tinylinx.biz/ruby

  3. View example gem tinylinx.biz/sample_gem

  4. Requirements

  5. You have familiarity with: •Ruby •Command line tools •git

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

    ̣ Ruby 2.3.1 ̣ bundler 1.12 ̣ rspec 3.0 ̣ git
  7. If you want to follow along… •Accounts and command line

    access for: ̣ Rubygems.org ̣ GitHub
  8. Overview & Goals

  9. CLI Tools for Development Overview of Bundler

  10. Gem dependencies What are they? How do they work?

  11. File structure WHY ARE THERE SO MANY FOLDERS

  12. Versioning & licensing Semantic versioning overview & OSS rant

  13. Testing patterns & CI Happy code is tested code

  14. Publishing versions (and yanking versions)

  15. Configuration blocks Letting your users configure your gem

  16. What’s next Ideas for small gems you can write today!

  17. Slide formatting

  18. Terminal > this is text printed out this is a

    command you type in
  19. Ruby code def wee_method(i_am) if i_am == "SUPER COOL" puts

    "✨ ✨" end end
  20. LET’S GO!

  21. CLI Tools

  22. Bundler •Why bundler? •Creating a new gem •Creation options •Caveats

  23. Why bundler? •provides a simple, well documented CLI •supportive community

    •used with Gemfiles
  24. Why bundler? •helps you get started with best practices •prompts

    you to think about testing •prompts you to think about contribution guidelines
  25. Create a new gem bundler gem cryptozoologist

  26. Create a new gem bundler gem cryptozoologist bundler gem cryptozoologist

    bundler gem my_cool_gem
  27. Create a new gem > Do you want to generate

    tests with your gem? rspec
  28. Create a new gem > Do you want to license

    your code permissively under the MIT license? y
  29. Create a new gem > Do you want to include

    a code of conduct in gems you generate? y
  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
  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!
  32. Commit your baby gem! git add . git commit -m

    “✨ gem scaffold”
  33. Gem creation commit tinylinx.biz/new_gem

  34. Gem dependencies

  35. Gemfiles How you list dependencies and pin versions for Ruby

    applications
  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
  37. Gemfiles for gems In a Ruby gem, you also have

    a Gemfile! ... but it's used differently
  38. Gemfile source 'https://rubygems.org' # Specify your gem's dependencies in cryptozoologist.gemspec

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

    gemspec the only part that matters
  40. Gemfile source 'https: //rubygems.org' # Specify your gem's dependencies in

    cryptozoologist.gemspec gemspec ok this matters, too
  41. View Gemfile tinylinx.biz/gemfile

  42. Gemspec Gemspecs cover a lot of things, we’re only looking

    at dependencies right now.
  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
  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
  45. add_development_dependency This does not require the listed dependency if you're

    running the gem in your application!
  46. File structure

  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
  48. Folders bin

  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
  50. Folders lib

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

    in your lib root folder: ̣ cryptozoologist.rb ̣ cryptozoologist/
  52. lib/cryptozoologist.rb •requires all of your other files •where you create

    your gem namespace (Cryptozoologist) using parent module
  53. lib/cryptozoologist •subfolder! •where you put everything you want to use

    in your gem •everything we add will go here - except for tests
  54. lib/cryptozoologist/version.rb •sets the VERSION constant for your gem •required by

    your gemspec to set your gem version number
  55. Folders spec

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

    folder patterns a lib •add _spec to the end of your file name
  57. lib/cryptozoologist.rb spec/cryptozoologist_spec.rb FILE: TESTS:

  58. lib/cryptozoologist/dictionary.rb spec/cryptozoologist/dictionary_spec.rb FILE: TESTS:

  59. Versioning

  60. semver semantic versioning

  61. Version levels •major: 1.0.0 •minor: 1.1.0 •patch: 1.1.1

  62. Other types of versions •release candidate: rc ̣ 4.2.5.rc2 ̣

    4.2.5.rc.2
  63. Other types of versions •alpha: alpha ̣ 1.1.0.alpha.1 ̣ 1.1.0.alpha1

  64. Other types of versions •beta: beta ̣ 1.1.0.beta.1 ̣ 1.1.0.beta1

  65. Patch •1.1.3, 2.19.6, etc •should be small •usually does not

    introduce new functionality
  66. Patch •small bug fixes ̣ backwards compatible ̣ do not

    break existing functionality •added documentation
  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!
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  78. What should your next version be? 2.0.0

  79. Licensing

  80. IANAL I am not a lawyer.

  81. By default, all gems have an MIT license.

  82. MIT licenses are a core foundational of OSS.

  83. MIT licenses let people do anything, as long as you

    get credit & are not liable.
  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
  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
  86. Common alternatives •GPL, version 2 or 3 ̣ “copyleft” •Apache

    2.0 ̣ similar to MIT but talks about patents •see choosealicense.com for more
  87. Use a license. I don’t care which one. Just do

    it.
  88. Testing patterns & CI

  89. CI with Travis

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

    and merge •free for open source projects! •highly configurable
  91. Sample .travis.yml language: ruby rvm: - 2.3.1 before_install: gem install

    bundler -v 1.12.3 notifications: email: false
  92. Testing patterns

  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
  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
  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
  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
  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
  98. Publishing

  99. Before publishing •update your version number •commit and push your

    changes to GitHub
  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
  101. Yanking

  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!
  103. Yanking gem yank cryptozoologist -v 2.0.0

  104. Configuration blocks

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

    keys, usernames, passwords ̣ callback URLs •change usage options ̣ test order in RSpec!
  106. Configuration for our sample gem •exclude word lists •specify delimiter

    type •include quantity words •order of words
  107. Example configuration Changing delimiter

  108. Applying configuration Cryptozoologist.configure do |config| config.include = [:quantity] config.delimiter =

    “_” end in your application somewhere…
  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
  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
  111. Configuration tutorials tinylinx.biz/gem_config tinylinx.biz/gem_errors

  112. What next?

  113. Make a gem! Wait, really? Yes, really.

  114. Ideas •random strings •long password generator •fake address creator •convert

    hex colors to RGB ̣ tinylinx.biz/hex_to_rgb
  115. Your gem does not have to provide unique functionality.

  116. tinylinx.biz/ruby Liz Abinante • @feministy Senior Software Engineer, New Relic

    labinante@newrelic.com