gibbler icon indicating copy to clipboard operation
gibbler copied to clipboard

Implementation-agnostic git-like hashes and history for Ruby objects

= Gibbler - v0.9

Git-like hashes and history for Ruby objects for Ruby 1.8, 1.9 and JRuby.

Check out the screencast[http://www.rubypulse.com/episode-0.3-gibbler.html] created by Alex Peuchert.

== Some things to keep in mind

  • Digest calculation may change between minor releases (as it did between 0.6 and 0.7)
  • Gibbler history is not suitable for very large objects since it keeps complete copies of the object in memory. This is a very early implementation of this feature so don't rely on it for production code.
  • Don't forget to enjoy your life!

== Example 1 -- Standalone Usage

require 'gibbler'

g = Gibbler.new 'id', 1001   # => f4fb3796ababa3788d1bded8fdc589ab1ccb1c3d
g.base(36)                   # => sm71s7eam4hm5jlsuzlqkbuktwpe5h9

g == 'f4fb3796ababa3788d1bded8fdc589ab1ccb1c3d'   # => true
g === 'f4fb379'              # => true

== Example 2 -- Mixins Usage

require 'gibbler/mixins'

"kimmy".gibbler              # => c8027100ecc54945ab15ddac529230e38b1ba6a1
:kimmy.gibbler               # => 52be7494a602d85ff5d8a8ab4ffe7f1b171587df

config = {}
config.gibbler               # => 4fdcadc66a38feb9c57faf3c5a18d5e76a6d29bf
config.gibbled?              # => false

config[:server] = {          
  :users => [:dave, :ali],   
  :ports => [22, 80, 443]    
}                            
config.gibbled?              # => true
config.gibbler               # => ef23d605f8c4fc80a8e580f9a0e8dab8426454a8 

config[:server][:users] << :yanni

config.gibbler               # => 4c558a56bc2abf5f8a845a69e47ceb5e0003683f

config.gibbler.short         # => 4c558a56

config.gibbler.base36        # => 8x00l83jov4j80i9vfzpaxr9jag23wf

config.gibbler.base36.short  # => 8x00l83j

== Example 3 -- Object History

Gibbler can also keep track of the history of changes to an object. By default Gibbler supports history for Hash, Array, and String objects. The gibbler_commit method creates a clone of the current object and stores in an instance variable using the current hash digest as the key.

require 'gibbler/mixins'
require 'gibbler/history'

a = { :magic => :original }     
a.gibbler_commit             # => d7049916ddb25e6cc438b1028fb957e5139f9910

a[:magic] = :updated           
a.gibbler_commit             # => b668098e16d08898532bf3aa33ce2253a3a4150e

a[:magic] = :changed 
a.gibbler_commit             # => 0b11c377fccd44554a601e5d2b135c46dc1c4cb1

a.gibbler_history            # => d7049916, b668098e, 0b11c377

a.gibbler_revert! 'd7049916' # Return to a specific commit
a.gibbler                    # => d7049916ddb25e6cc438b1028fb957e5139f9910
a                            # => { :magic => :original } 

a.delete :magic

a.gibbler_revert!            # Return to the previous commit  
a.gibbler                    # => 0b11c377fccd44554a601e5d2b135c46dc1c4cb1
a                            # => { :magic => :changed }
    

a.gibbler_object 'b668098e'  # => { :magic => :updated }
a.gibbler_stamp              # => 2009-07-01 18:56:52 -0400

http://delano.github.com/gibbler/img/whoababy.gif

== Example 4 -- Method Aliases

If you have control over the namespaces of your objects, you can use the method aliases to tighten up your code a bit. The "gibbler" and "gibbled?" methods can be accessed via "digest" and "changed?", respectively. (The reason they're not enabled by default is to avoid conflicts.)

require 'gibbler/aliases'

"kimmy".digest               # => c8027100ecc54945ab15ddac529230e38b1ba6a1
:kimmy.digest                # => 52be7494a602d85ff5d8a8ab4ffe7f1b171587df

a = [:a, :b, :c]
a.digest                     # => e554061823b8f06367555d1ee4c25b4ffee61944
a << :d
a.changed?                   # => true

The history methods also have aliases which remove the "gibbler_" prefix.

require 'gibbler/aliases'
require 'gibbler/history'

a = { :magic => :original }     
a.commit                     
a.history                    
a.revert!                    
# etc...

== Example 5 -- Different Digest types

By default Gibbler creates SHA1 hashes. You can change this globally or per instance.

require 'gibbler/mixins'

Gibbler.digest_type = Digest::MD5

:kimmy.gibbler               # => 0c61ff17f46223f355759934154d5dcb

:kimmy.gibbler(Digest::SHA1) # => 52be7494a602d85ff5d8a8ab4ffe7f1b171587df

In Jruby, you can grab the digest types from the openssl library.

require 'openssl'

Gibbler.digest_type = OpenSSL::Digest::SHA256

:kimmy.gibbler               # => 1069428e6273cf329436c3dce9b680d4d4e229d7b7...

== Example 6 -- All your base

require 'gibbler/mixins'

:kimmy.gibbler               # => 52be7494a602d85ff5d8a8ab4ffe7f1b171587df
:kimmy.gibbler.base(16)      # => 52be7494a602d85ff5d8a8ab4ffe7f1b171587df
:kimmy.gibbler.base(36)      # => 9nydr6mpv6w4k8ngo3jtx0jz1n97h7j

:kimmy.gibbler.base(10)      # => 472384540402900668368761869477227308873774630879
:kimmy.gibbler.to_i          # => 472384540402900668368761869477227308873774630879

== Example 7 -- Global secret

Gibbler can prepend all digest inputs with a global secret. You can set this once per project to ensure your project's digests are unique.

require 'gibbler/mixins'

:kimmy.gibbler               # => 52be7494a602d85ff5d8a8ab4ffe7f1b171587df

Gibbler.secret = "sUp0r5ekRu7"

:kimmy.gibbler               # => 6c5f5aff4d809cec7e7da091214a35a2698489f8

== Supported Classes

Gibbler methods are available only to the classes which explicitly include them (see RDocs[http://delano.github.com/gibbler] for details on which classes are supported by default). You can also extend custom objects:

class FullHouse
  include Gibbler::Complex
  attr_accessor :roles
end

a = FullHouse.new
a.gibbler                    # => 4192d4cb59975813f117a51dcd4454ac16df6703

a.roles = [:jesse, :joey, :danny, :kimmy, :michelle, :dj, :stephanie]
a.gibbler                    # => 6ea546919dc4caa2bab69799b71d48810a1b48fa

Gibbler::Complex creates a digest based on the name of the class and the names and values of the instance variables. See the RDocs[http://delano.github.com/gibbler] for other Gibbler::* types.

If you want to support all Ruby objects, add the following to your application:

class Object
  include Gibbler::String
end

Gibbler::String creates a digest based on the name of the class and the output of the to_s method. This is a reasonable default for most objects however any object that includes the object address in to_s (e.g. "Object:0x0x4ac9f0...") will produce unreliable digests (because the address can change).

As of 0.7 all Proc objects have the same digest: 12075835e94be34438376cd7a54c8db7e746f15d.

== Known Issues

  • gibbler or gibbled? must be called at least once before gibbled? will be able to return a useful value (otherwise there is no previous digest value to compare to)
  • Digests for Bignum objects are different between Ruby and JRuby. Why?
  • Digests for Proc objects are different between Ruby 1.8 and 1.9 because Proc.arity returns different values and 1.8 has no lambda? method.

== Installation

Via Rubygems:

$ gem install gibbler

or via download:

  • gibbler-latest.tar.gz[http://github.com/delano/gibbler/tarball/latest]
  • gibbler-latest.zip[http://github.com/delano/gibbler/zipball/latest]

== What People Are Saying

  • "nice approach - everything is an object, every object is 'gittish'" -- @olgen_morten[http://twitter.com/olgen_morten/statuses/2629909133]
  • "gibbler is just awesome" -- @TomK32[http://twitter.com/TomK32/statuses/2618542872]
  • "wie cool ist Gibbler eigentlich?" -- @we5[http://twitter.com/we5/statuses/2615274279]
  • "it's nice idea and implementation!" -- HristoHristov[http://www.rubyinside.com/gibbler-git-like-hashes-and-history-for-ruby-objects-1980.html#comment-39092]

== More Info

  • Codes[http://github.com/delano/gibbler]
  • RDocs[http://delano.github.com/gibbler]
  • Sponsor[http://solutious.com/]
  • Inspiration[http://www.youtube.com/watch?v=fipD4DdV48g]

== Thanks

  • Kalin Harvey (krrh[http://github.com/krrh]) for the early feedback and artistic direction.
  • Alex Peuchert (aaalex[http://github.com/aaalex]) for creating the screencast.

== Credits

  • Delano Mandelbaum (delano[http://delanotes.com/])

== License

See: LICENSE.txt