# has_uuid [![Build Status](https://travis-ci.org/madpilot/has_uuid.svg?branch=master)](https://travis-ci.org/madpilot/has_uuid) A gem to help you retrofit UUIDs to your existing Rails application. ## The scenario You have an existing Rails site that uses auto-incrementing ids, that you want to add, say, offline syncronization. The problem with auto-incrementing ids is that you will hit clashes if you create entries offline. One solution is to use UUIDs, which has a very, very low probability of clashing. Now the problem is how do you add that to Rails? One way is to replace all the auto-incrementing ids with uuids, using something like activeuuid. This can be problematic if you have a running site, as converting all the ids and relationships would be a pain. Enter: has_uuid To use has_uuid you mirror all of the primary key id, and foreign key ids with another uuid column, and it makes sure you can search the whole object graph using uuids! If that didn't make sense check this out. ## Example ###Migration ```ruby class SetupDatabase < ActiveRecord::Migration def up create_table :record_labels do |t| t.string :name t.uuid :uuid end create_table :albums do |t| t.uuid :uuid t.string :name t.integer :record_label_id t.uuid :record_label_uuid t.integer :artist_id t.uuid :artist_uuid end end end ``` has_uuid adds a uuid type to mirations. On SQLite and MySQL it's a binary(16), on PostgreSQL it uses their native uuid type. Notice how we have both a record_label_id and record_label_uuid column... ###Model ```ruby class RecordLabel < ActiveRecord::Base has_uuid has_many :albums end class Album < ActiveRecord::Base has_uuid belongs_to :record_label end ``` By calling the has_uuid class method, your model is primed. ### Finders ```ruby record_label = RecordLabel.create!(:name => 'Fat Wreck Chords') # id: 1, the autogenerated uuid is: cf1ba930-6946-4bd5-9265-d9043e5dbb93 record_label = RecordLabel.create!(:name => 'Misfit Records') # id: 2, the autogenerated uuid is: a957f2d6-371e-4275-9aec-b54a380688e0 RecordLabel.find(1, 2) RecordLabel.find('cf1ba930-6946-4bd5-9265-d9043e5dbb93', a957f2d6-371e-4275-9aec-b54a380688e0) RecordLabel.find(UUIDTools::UUID.parse('cf1ba930-6946-4bd5-9265-d9043e5dbb93'), UUIDTools::UUID.parse('a957f2d6-371e-4275-9aec-b54a380688e0')) ``` ...will return an array of objects that match those ids ### Relationships ```ruby record_label = RecordLabel.create!(:name => 'Fat Wreck Chords') artist_1 = Artist.create!(:name => 'NOFX') artist.record_label = record_label artist.record_label_uuid ``` Will return the uuid of the associated record label. The reverse is also true ```ruby record_label = RecordLabel.create!(:name => 'Fat Wreck Chords') artist_1 = Artist.create!(:name => 'NOFX') artist.record_label_uuid = record_label.uuid artist.record_label ``` Will return the record label object Finally, it'll find the uuid when you associate via id i ```ruby record_label = RecordLabel.create!(:name => 'Fat Wreck Chords') artist_1 = Artist.create!(:name => 'NOFX') artist.record_label_id = record_label.id artist.record_label_uuid ``` Will be the uuid of the record label Generally, a UUID will be a UUIDTools::UUID, but you can set uuids via a string, so these are equivalent: ```ruby uuid = UUIDTools::UUID.random_create => # record_label = RecordLabel.create!(:name => 'Fat Wreck Chords', :uuid => uuid) artist_1 = Artist.create!(:name => 'NOFX') artist.record_label_uuid = uuid.to # is the same as artist.record_label_uuid = '7c9748da-f9fe-467e-bdb3-34ce2dc67605' ``` ### Collections ```ruby record_label = RecordLabel.create!(:name => 'Fat Wreck Chords') artist_1 = Artist.create!(:name => 'NOFX') artist_2 = Artist.create!(:name => 'Strung Out') artist_3 = Artist.create!(:name => 'Screeching Weasel') record_label.artists = [ artist_1, artist_2, artist_3 ] record_label.save! record_label.artists_uuids ``` Returns an array of UUIDTools::UUID objects that correspond to artist_1, artist_2, artist_3 it also works the other way: ```ruby record_label = RecordLabel.create!(:name => 'Fat Wreck Chords') artist_1 = Artist.create!(:name => 'NOFX') artist_2 = Artist.create!(:name => 'Strung Out') artist_3 = Artist.create!(:name => 'Screeching Weasel') record_label.artist_uuids = [ artist_1.uuid, artist_2.uuid, artist_3.uuid ] record_label.save! record_label.artists ``` Returns artist_1, artist_2, artist_3 Finally, if you set a relationship id, it will automatically fetch the uuid for you ```ruby record_label = RecordLabel.create!(:name => 'Fat Wreck Chords') artist_1 = Artist.create!(:name => 'NOFX') artist_2 = Artist.create!(:name => 'Strung Out') artist_3 = Artist.create!(:name => 'Screeching Weasel') record_label.artist_ids = [ artist_1.uuid, artist_2.uuid, artist_3.uuid ] record_label.save! record_label.artists_uuids ``` Will also return an array of UUIDTools::UUID objects that correspond to artist_1, artist_2, artist_3 All of these will return the same record ```ruby record_label = RecordLabel.create!(:name => 'Fat Wreck Chords') # id: 1, the autogenerated uuid is: cf1ba930-6946-4bd5-9265-d9043e5dbb93 RecordLabel.find(1) RecordLabel.find('cf1ba930-6946-4bd5-9265-d9043e5dbb93') RecordLabel.find(UUIDTools::UUID.parse('cf1ba930-6946-4bd5-9265-d9043e5dbb93')) ``` ## What doesn't work Unfortunately, because of the way ARel works, you can only search via a UUIDTools::UUID ```ruby uuid = UUIDTools::UUID.random_create => # Artist.where('uuid = ?', uuid) # This works Artist.where('uuid = ?', '7cd2feb5-6929-4288-9ea4-c4e68927f289') # This won't (except on PostgreSQL) As a result, this also won't work Artist.find_by_uuid('7cd2feb5-6929-4288-9ea4-c4e68927f289') ``` ## TODO * Some more testing - I'm sure it will fail if you have a relationship between a has_uuidmodel and a regular one * Release as a gem - it's not tested well enough yet. * Probably other stuff I haven't thought of yet ## Contributing to has_uuid * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet. * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it. * Fork the project. * Start a feature/bugfix branch. * Commit and push until you are happy with your contribution. * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally. * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it. ## Setting up for development environment has_uuid uses the appraisal gem for testing against multiple versions of Rails. To get started run: ```appraisal install``` Then to run tests against rails 3.2: ```appraisal rails-3-2 rspec``` Against rails 4.0 ```appraisal rails-4-0 rspec``` Against rails 4.1 ```appraisal rails-4-1 rspec``` ## Copyright Copyright (c) 2012 [MadPilot Productions](http://www.madpilot.com.au/). See LICENSE.txt for further details.