Skip to content

Instantly share code, notes, and snippets.

@ntamvl
Last active November 12, 2024 19:35
Show Gist options
  • Save ntamvl/7a6658b4cd82d6fbd15434f0a9953411 to your computer and use it in GitHub Desktop.
Save ntamvl/7a6658b4cd82d6fbd15434f0a9953411 to your computer and use it in GitHub Desktop.
How to create a Ruby gem that adds Rake tasks

How to create a Ruby gem that adds Rake tasks

Create a gem

One way to do this is to use bundler to scaffold our gem:

bundler gem my_gem

Add rake tasks to our gem

I prefer to put tasks meant to manage the gem itself in lib/tasks, and tasks the gem is meant to provide to gem users in lib/my_gem/tasks.

I also follow the following rules when writing Rake tasks:

  1. One task per file
  2. File name should match the task name exactly and have a .rake extension
  3. Tasks should be namespaced
  4. Files should be in a directory whose name matches the task namespace

So, say our gem is called my_gem and adds the tasks foo:a, foo:b, and bar:c. Our directory structure would look something like this:

my_gem
 └── bin
 └── ...
 └── lib
      └── my_gem
           └── tasks
                └── foo
                     ├── a.rake
                     ├── b.rake
                └── bar
                     ├── c.rake
           ├── Rakefile
           ├── railtie.rb
           ├── helper.rb
           ├── ...
      ├── my_gem.rb
  └── ...

Integrate our gem with Rails apps

Use Rails::Railtie to have all rake tasks automatically loaded when this gem is required in a Rails app.

railtie.rb (seen in directory structure above), at a minimum, would look something like this:

# lib/railtie.rb
require 'my_gem'
require 'rails'

module MyGem
  class Railtie < Rails::Railtie
    railtie_name :my_gem

    rake_tasks do
      path = File.expand_path(__dir__)
      Dir.glob("#{path}/tasks/**/*.rake").each { |f| load f }
    end
  end
end

Then, in my_gem.rb aka the file loaded when our gem is required, we’d want something like:

# lib/my_gem.rb
module MyGem
  require 'my_gem/railtie' if defined?(Rails)
end

Integrate our gem with plain old Ruby projects Provide a Rakefile that gem users can load into their Rakefiles:

# lib/Rakefile
require 'my_gem'

path = File.expand_path(__dir__)
Dir.glob("#{path}/tasks/**/*.rake").each { |f| import f }

Use our gem’s rake tasks in another project

Rails In the project Rakefile, at some point before Rails.application.load_tasks is run, be sure to require my_gem, whether that be through Bundler.require(*Rails.groups), require 'my_gem', or whatever.

Make sure that the new code is actually being run e.g. restart spring if that’s been running.

Plain Old Ruby

In the project Rakefile, load my_gem‘s Rakefile:

require 'my_gem'

spec = Gem::Specification.find_by_name 'my_gem'
rakefile = "#{spec.gem_dir}/lib/my_gem/Rakefile"
load rakefile

Extra: View Rake tasks

From command line

rake -T -A

Programmatically inside Ruby script

Rake.application.tasks.map(&:to_s)

===========

source: http://mdzhang.com/blog/code/2016/09/10/create-ruby-gem-that-adds-rake-tasks/
@pboling
Copy link

pboling commented May 18, 2023

Can Rake.add_rakelib simplify this at all? It seems to not work from within the rake_tasks hook of the Railtie, but not sure why.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment