Extracting a Gem from your Rails app

Montreal.rb 2018

So you got that big pile of useful code.

Benefits of extracting

open-source code

  • Cleaner code and architecture
  • External collaborators
  • Fresh and new ideas
  • Reusable and sharable code
  • Test in isolation
  • Give back to the community

First gem?

  • Knowledge on requirement, use cases and edge cases
  • Implementation already exists
  • You already have an app to test it
  • Focus on the interface

Where to find useful code to extract?

About anywhere!

 

Are you solving common problems?
Have some cool scripts?

Where to find useful code to extract?

  • Custom validations
  • Test matchers
  • Wrapper around an external API
  • Additions or changes to Rails
  • PORO modeling common things
  • And many more!

This talk is a step by step guide to extract a gem from existing code

Step 1:

Identify and move the key parts of your code.

Problem solved

Scope

Interface

Step 2: 

Refactor your code to move as much as possible to lib/

Decouple

Test in isolation

Break dependencies

Step 3:

Setup of the gem

Bundle gem [gemname]

  • Code of conduct
  • RSpec (minitest available)
  • Travis-CI ready

Rails plugin new [gemname]

  • Dummy rails app
  • Minitest only
  • Rake tasks setup
  • Useful for Rails Engines

VS.

Small number of variations Gemspec, licence, readme, etc

I did both commands

# Make dir and git init commands

> bundle gem has_prerequisite


> cd has_prerequisite

> git commit -Am "init the gem"

> cd ..


> rails plugin new has_prerequisite --skip-test --dummy-path=spec/dummy

> cd has_prerequisite


> git add -p

> git commit -m "init the rails specific setup of the gem"

Checklist:

  • Edit the Gemspec with the gems info
  • Tests are running?
  • Online Git repository

Gemspec structure

Gem::Specification.new do |s|
  s.name        = 'hola'
  s.version     = '0.0.0'
  s.date        = '2010-04-28'
  s.summary     = "Hola!"
  s.description = "A simple hello world gem"
  s.authors     = ["Nick Quaranto"]
  s.email       = 'nick@quaran.to'
  s.files       = ["lib/hola.rb"]
  s.homepage    = 'http://rubygems.org/gems/hola'
  s.license     = 'MIT'
end

Step 4:

Move the code!

Move one test first

  • Make sure required dev and test dependencies are in the Gemspec (rspec-rails, shoulda-matchers, its, etc)
  • Some minor changes to the test setup are expected
  • Make sure they run
  • Make sure they fail properly

Move the implementation

  • If step 2 was skipped, you might have broken dependencies
  • Add them to the gemfile and require
  • Implement components you don't want to be dependent on
  • Make sure to not move any secrets

Decisions

I decided to keep the dependency on Active Support but not Devise.

 

So I have to implement similar methods

Failures:

  1) HasPrerequisite redirection it redirects to the path when the prerequisite is not met
     Failure/Error: store_location_for(:user, request.fullpath)
     
     NoMethodError:
       undefined method `store_location_for' for #<#<Class:0x007f9d98a359e8>:0x007f9d9b931238>
     # ./lib/has_prerequisite.rb:36:in `perform_checks'
     # ./spec/has_prerequisite_spec.rb:43:in `block (2 levels) in <top (required)>'
     # ./spec/has_prerequisite_spec.rb:55:in `block (3 levels) in <top (required)>'

Refactor

Add features

Refactor again

Railties

Hooks for initialisation

Railties

  • Extend core classes
  • Initialisers
  • Set generators

Railties

module HasPrerequisite
  class Railtie > ::Rails::Railtie
    initializer "has_prerequisite.configure_view_controller" do |app|
      ActiveSupport.on_load :action_controller do
        include HasPrerequisite
      end
    end
  end
end

Step 5:

Test locally

Test again?

  • Yes.
  • Integration test (I have a dummy app!)
  • Pack the gem locally
  • Reference it from your application

Method 1: Reference from a path

gem 'has_prerequisite', path: '~/dev/has_prerequisite'
> bundle install
=> It will use your gem 

> rspec

Method 2: Pack and install locally

gem 'has_prerequisite'
> gem build has_prerequisite.gemspec
Successfully built RubyGem
Name: has_prerequisite
Version: 0.0.0
File: has_prerequisite-0.0.0.gem

> gem install ./has_prerequisite-0.0.0.gem
Successfully installed has_prerequisite-0.0.0
1 gem installed

Step 6:

Publish!

Version number

I recommend following Semantic Versioning

MAJOR . MINOR . PATCH

Version number

  • Production ready?
  • Stable API?


When to publish 1.0.0 ?

 

Tip: you can always use the labels (1.0.0.beta, 1.0.0.rc.1, 1.0.0.pre, 1.0.0.racecar-1)

Licence

choosealicense.com

Code of conduct

contributor-covenant.org

Use Git/Github features

  • Readme for documentation
  • Push hooks to test
  • Issue tracker
  • Projects
  • Releases automatically tagged
  • Use branches to merge and deploy bugfixes across versions

Rubygems.org

  • Where gems magically come from
  • Create an account

Private servers

  • Run your own with `gem server`

  • Gemfurry

  • Note: configure .gemspec to avoid accidental pushes to Rubygems.org

Publish!

→ bundle exec rake release   
has_prerequisite 0.0.1 built to pkg/has_prerequisite-0.0.1.gem.
Tagged v0.0.1.
Pushed git commits and tags.
Pushed has_prerequisite 0.0.1 to rubygems.org.

Step 6:

Profit.

Step 6:

Maintenance

Bugs

New features

Pull requests

Never forget about the documentation

Open-Source Starter Kit

  • CI servers
  • Contributing guides
  • Set expectations on PR
  • Use automated review tools
  • Build a team you trust

 

  • Those pull-requests can wait
  • Those bugs can wait
  • Delegate! Delegate!
  • Your free time is YOURS
  • You don't owe anyone anything

Free Time management

Don't burn out

Enjoy it

Step 7:

Profit!

Thank you! ❤️💎

@sophiedeziel

Useful links

Copy of Extracting a Gem from your Rails app

By Sophie Déziel

Copy of Extracting a Gem from your Rails app

Talk I gave on Dec 6th for Confoo Vancouver 2017

  • 1,482