Want to see the full-length video right now for free?
Sign In with GitHub for Free AccessWhen you find that you have code in your app that is not necessarily specific to your application, but instead is more general, you can consider extracting the logic to a gem. The act of extracting the code will help establish and maintain a clean line and prevent coupling of that logic to more application-specific concerns.
Note: this does not inherently require you to reuse this gem in more than one codebase or publish it to any gem server, public or private.
A more common use case for building or extracting a gem is to clean up cases where you have similar logic implemented across multiple applications. By extracting the code into a gem, and then using that across the multiple applications, you can ensure that the same code is used in all apps.
lib
Often the best way to build a gem is to start with the code in your app. When you are looking to extract logic from an app into a gem, this may already be the case. But even new code can start in an app with the plan to extract it later.
The key aspect of working with gem code in an app is to keep it as isolated
as possible. The code should collected in lib (with a matching spec/lib
or
test/lib
directory) and should be as decoupled as possible. Try to avoid
using application helpers or Rails helpers in your soon-to-be-gemified code.
As an example of extracting a gem, check out this commit from the Upcase
Exercises repo where Joe extracts the code for the Payload dependency
injection framework, replacing it with the gemified version. The extraction in
the commit is very clean as Joe had already done a great job of isolating and
collecting the code in lib
.
Note You'll need to have access to the Upcase Exercises repo, which all Professional subscribers can get by visiting the repositories page on Upcase.
When you're ready to actually create the gem, you can use bundler to build out the skeleton and boiler plate files needed.
$ bundle gem foo
create foo/Gemfile
create foo/Rakefile
create foo/LICENSE.txt
create foo/README.md
create foo/.gitignore
create foo/foo.gemspec
create foo/lib/foo.rb
create foo/lib/foo/version.rb
Initializing git repo in ~/code/foo
From there, you can go to work in the lib/foo.rb
(or equivalent for your
gem name) file, building out your code.
Stephen Ball has a great two part series on writing a gem with tons of detail on the full process.
You'll often want to poke around with your new gem code in the console while
working, and luckily bundler makes this very easy. Run bundle console
and
you'll be dropped into an irb
session with your gem's code loaded and ready
for testing.
You can also use our old favorite pry in place of irb
by running
$ bundle config console pry
and adding pry
as a development dependency in your gemspec.
# foo.gemspec file
Gem::Specification.new do |spec|
# additional gem config here...
spec.add_development_dependency "pry"
end
The typical method for working with gems is to host them on Rubygems.org, but in some cases you may need to keep the gem code private and Rubygems does not support this.
One option for keeping your gem private is to use a service like Gemfury
which acts as an additional source
in your Gemfile and allows you to host
and install your private gems via their infrastructure. This is likely the
most direct route, but is a paid service.
There are other solutions including using a private Github repo as your gem's source and including an oauth token in the URL or vendoring your private gem code. These are a bit more work and are less clean in their implementation, but are free. Check out this gist which contains a summary and discussion of some of these approaches and the pros and cons of each.
When working on the initial version or extraction of your gem, you'll often be in a period of high churn and iteration. For these times it can be useful to work against your local checkout of the gem, rather than against a published version of the gem.
Once again, bundler has our back and allows us to use the path
option in
our Gemfile
to point at a local path:
# in Gemfile
gem "payload", path: "~/code/work/upcase/payload"
Note - While this saves you from having to download and build your gem
for each change, you will still have to bundle
and restart the server in
the test app each time you make changes in the gem.
A good test suite is critical to building a quality gem, but as more people use it, the configurations and environments you must support can grow quickly. The following tips can help you stay on top of this and keep your gem working across all supported configurations.
Appraisal is a gem we built here at thoughtbot to make it easier to test our gems in a variety of contexts. From it's Readme:
Appraisal integrates with bundler and rake to test your library against different versions of dependencies in repeatable scenarios called "appraisals."
As an example, check out FactoryGirl Rails' Appraisals file.
In order to get full test coverage of dependencies / gems as well as Ruby versions, we use Travis CI's build matrix along with Appraisal to test across the full matrix of gem & ruby versions.
Check out Clearance's travis.yml config file for an example of how we configure this.
While you can use the Travis build matrix to fully cover the gem and ruby version configuration, we prefer to use Travis just to change ruby versions, and rely on Appraisal to manage the dependency sets as this allows us to reproduce these builds locally.
If you find yourself writing a gem for use within Rails applications, there are a few special features and configuration points that Rails exposes to allow your gem the access it needs.
Railties allow you to hook into Rails' internals in order to add configuration or provide generators and rake tasks. Each of the Rails gems like ActiveSupport, ActionView, and ActiveJob are implemented as Railties.
In addition, there are many community gems that act as Railties to provide more functionality. Some examples:
Rails Engines are a subclass of Railties, so they inherit all of the configuration power of Railties, but they go further allowing you to include arbitrary app code. Engines are like including another entire Rails app with routes, controllers, models, and views.
Some examples of Engines being used to great effect:
A subset of the functionality provided by acting as an Rails Engine is often
used to provide assets for the asset pipeline. Like with your app, the assets
paths {app,lib,vendor}/assets/{javascripts,stylesheets,images}
will all be
added to the asset pipeline load path, allowing you to reference a gem-provided
asset as if it were in your application.
Some examples of asset gem usage:
Derek also has a post on how to Gemify your assets that covers the process.
While it is great to know that we can use asset gems to provide asset files, the advent of Bower, a front end package manager, has to a certain degree superseded the asset gem technique.
Specifically Rails-assets.org is a free service that acts as an alternate gem source and provides an asset gem version of all packages on Bower allowing us to leverage those packages instead of duplicating them in the Rails world.
Now that you know all the tips and tools you can use to build and extract gems, why not try it out today and build your first gem!