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
Official guide:
guides.rubygems.org/publishing/
Moving code to lib folder:
justinweiss.com/articles/a-guide-to-extracting-your-first-ruby-gem/
PR for open-source:
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