#135
Nov 10, 2008

Making a Gem

Want to create a Ruby Gem instead of a Rails plugin? In this episode I will walk you through creating a gem to extend Rails.
Download (18.2 MB, 9:08)
alternative download for iPod & Apple TV (12.1 MB, 9:08)

Resources

sudo gem install echoe
mkdir -p uniquify/lib
touch README.rdoc Rakefile lib/uniquify.rb
rake -T
rake manifest
rake install
rake build_gemspec
git init
git add .
git commit -m "initial commit"
git remote add origin git@github.com:ryanb/uniquify.git
git push
touch CHANGELOG
touch init.rb
# Rakefile
require 'rubygems'
require 'rake'
require 'echoe'

Echoe.new('uniquify', '0.1.0') do |p|
  p.description    = "Generate a unique token with Active Record."
  p.url            = "http://github.com/ryanb/uniquify"
  p.author         = "Ryan Bates"
  p.email          = "ryan@railscasts.com"
  p.ignore_pattern = ["tmp/*", "script/*"]
  p.development_dependencies = []
end

Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }

# lib/uniquify.rb
module Uniquify
  def self.included(base)
    base.extend ClassMethods
  end
  
  def ensure_unique(name)
    begin
      self[name] = yield
    end while self.class.exists?(name => self[name])
  end
  
  module ClassMethods
    
    def uniquify(*args, &block)
      options = { :length => 8, :chars => ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a }
      options.merge!(args.pop) if args.last.kind_of? Hash
      args.each do |name|
        before_create do |record|
          if block
            record.ensure_unique(name, &block)
          else
            record.ensure_unique(name) do
              Array.new(options[:length]) { options[:chars].to_a[rand(options[:chars].to_a.size)] }.join
            end
          end
        end
      end
    end
    
  end
end

class ActiveRecord::Base
  include Uniquify
end

# init.rb
require 'uniquify'

RSS Feed for Episode Comments 23 comments

1. Josh Nov 10, 2008 at 00:18

Another great railscast, I've been wondering how to make a gem and this was really straight forward! Thanks!


2. Morten Nov 10, 2008 at 03:34

Full circle! Great as usual..


3. Dr Nic Nov 10, 2008 at 04:14

Watching you 'just paste in some code' made me remember why generators are cool :)

Protip: if you want to distribute command-line executables with your gem, like the "rake" or "rails" command, then put them in <code>bin/</code> folder. Hoe, and I think Echoe, automatically package them up as executables, which is sexy.


4. sintaxi Nov 10, 2008 at 13:05

there has never been enough talk about publishing gems. Thanks ryan for adding to a lacking topic. btw - I sure like how echoe is not required as a dependency.


5. Ryan Bates Nov 10, 2008 at 17:25

@drnic good point about generators, and thanks for the protip. I did paste a lot of code in this episode, but the majority of it is specific to the project. The only thing I feel needs generating is the Rakefile, and even that has a lot of project-specific settings in it.

A generator is very much a personal preference, but I encourage everyone to try the existing ones out there or make your own as needed. For me copy & pasting the Rakefile content from an existing one is easy enough.


6. Bryce Nov 11, 2008 at 02:35

Thankyou for this tutorial, I just made my first Ruby Gem - a gem for accessing the Google Translation API, currently hosted @ github: http://github.com/bdude/rosettastone/tree/master


7. David Trasbo Nov 11, 2008 at 22:54

Ryan, you should seriously consider some kind of spam protection that prevents bots etc. from posting comments here. It shouldn't be more complicated than just a form field: "What is the sum of 2 and 2?"


8. Ryan Bates Nov 12, 2008 at 10:41

@David, there already is spam protection in place behind the scenes which has been working really well (it's blocking an amazing amount of spam). As far as I can tell, the spam you were seeing (now deleted) was from an actual human and not a spam bot.


9. Laurent Farcy Nov 13, 2008 at 06:34

Ryan. Thx for your great screencasts.

Your plugin has a clear (hard) dependency on ActiveRecord. But you do not declare anything in your gem. Can you do that with echoe ? (so uniquify cannot be installed unless activerecord is already installed on the host). Do you recommend to explicit dependencies in gems ?


10. Cassiano Nov 13, 2008 at 08:53

@Ryan,

Thanks for this very useful screencast.

Should Rakefile really be uploaded to github, as it seems like it wont' be used by either github or the final user of the gem?

Shouldn't your gem be named "ryanb-uniquify" instead, as this will be the final folder in "ruby\lib\ruby\gems\1.8\gems" (plus version, of course)?


11. Cassiano Nov 14, 2008 at 03:04

Clarifying my previous post: after generating the gem, if you install it locally by running "gem install pkg\<gem-name>-<version>.gem", you end up with a distinct folder name in "...\ruby\lib\ruby\gems\1.8\gems" when compared to installing it from the git repository (as github always seems to add an owner prefix to the gem name provided in .gemspec file).

But of course adding the owner prefix yourself doesn't solve the issue, as github will still add the owner prefix to whatever gem name you supply to it.


12. Ryan Bates Nov 14, 2008 at 10:08

@Laurent, good question, I prefer to only use dependencies where I have a "require" statement which loads that gem. Here there is no "require 'activerecord'" line because I expect the developer to load this environment how he desires. He may have edge rails frozen, in that case ActiveRecord is not loaded from a gem and there's really no gem dependency.

If I had other gem dependencies outside of Rails then I would likely add those.

@Cassiano, it's important the Rakefile be in the git repository so anyone else who clones/forks the repo can regenerate the gem after making changes.

Also the gem name should not contain your username prefix as GitHub will add this when it generates the gem using your gemspec.


13. Cassiano Nov 14, 2008 at 11:20

@Ryan,

Thanks for the Rakefile explanation.

Regarding the naming problem, my intent is to have a local gem that installs exactly the same as the remote (gihub's) one, such that running:

gem install pkg\ryanb-uniquify-0.1.0.gem

or

gem install ryanb-uniquify --source http://gems.github.com

would yield the same installation.

But the only way I can do it is by manually adding the username prefix to my *local* copy of Rakefile and regenerating the gem via the "rake install" task.

The Rakefile would then be restored to its original state before running the "rake build_gemspec" task and committing it to git.


14. Tim Matheson Nov 17, 2008 at 21:28

What is the benefit of using gems as opposed to plugins?


15. Amos King Nov 25, 2008 at 19:57

I don't think you are supposed to ignore the Manifest file on github.


16. softprops Dec 05, 2008 at 16:31

Ryan B. You make the world a better place. I can always find great content here and for your main focus to be sharing info rather than promoting info, it shows you have lot of character. Your mom should be proud.


17. redvex Aug 11, 2009 at 06:21

load my gem in github but...

ERROR: could not find gem redvex-ar_cache locally or in a repository

can you help me?


18. redvex Aug 11, 2009 at 06:23

This is the url on github

http://github.com/redvex/ar_cache/tree/master


19. redvex Aug 11, 2009 at 06:43

Ok... github told me that my gem mill Manifest file, maybe you should correct the post, becouse in it you told us to add Manifest file in .gitignore


20. daria Jan 13, 2010 at 05:25

thanks


21. LouisVuitton bags Jan 22, 2010 at 19:30

f ef erfew fwefwefew order skelaxin from canadian pharmacy


22. lida daidaihua Jan 22, 2010 at 19:33

there any way to put the format string things in the file other than environment.rb?


23. cheap adidas shoes Jan 31, 2010 at 19:05

Adidas Shoes Online Shop-Hot Selling Adidas Shoes & Cheap Adidas Shoes,
ha

Add your comment:

(SKIP THIS ONE)

(required)

(not shown)


(use pastie or gist for code)

sponsored by:
if you want to help:
required:
Get Quicktime Player
Give Back to Open Source