UUIDs

<img src=“http://img.shields.io/gem/v/uuids.svg?style=flat” alt=“Gem Version” /> <img src=“http://img.shields.io/travis/nepalez/uuids.svg?style=flat” alt=“Bild Status” /> <img src=“http://img.shields.io/codeclimate/github/nepalez/uuids.svg?style=flat” alt=“Code Metrics” /> <img src=“http://img.shields.io/gemnasium/nepalez/uuids.svg?style=flat” alt=“Dependency Status” /> <img src=“http://img.shields.io/coveralls/nepalez/uuids.svg?style=flat” alt=“Coverage Status” /> <img src=“http://img.shields.io/badge/license-MIT-blue.svg?style=flat” alt=“License” />

About

The gem allows addressing ActiveRecord objects by UUID[http://en.wikipedia.org/wiki/Universally_Unique_Identifier]s following the RFC4122 standard.

The gem requires Ruby 2.1+ and ActiveRecord 3.1+. It doesn’t depend on full stack Rails and can be used in domain apps based on ActiveRecord.

Pattern

The module allows adressing records by UUID(s) instead of ID. A record can be identified by many UUIDs.

This makes it possible to merge records without touching external to the records.

For merging records it’s sufficient to:

  • reassign all their uuids to the united record.

  • destroy old records, which no uuid refers to.

Whatever external models are referred by uuid to the deleted records, that references remains valid and will lead to the united record.

Example

Suppose you have models referred to cities. One day you discover a duplication among two cities: the “New York” and the “NEW YORK”.

You should:

  • reassign both UUIDs to “New York”;

  • safely remove the “NEW YORK” record.

You needn’t track all records that refer to “NEW YORK”. All those references will authomatically lead to merged record via old UUID.

Now the model of cities should **know nothing about outer models** that use it.

Installation

Add this line to your application’s Gemfile:

gem "uuids"

And then execute:

$ bundle

Or install it yourself as:

$ gem install uuids

Initialization

After installation you should copy and run uuid’s db migration into your module.

When you install the module to a final application, the migration should be installed to ‘db/migrate` path directly.

$ uuids install

When you install the module to another gem as a part of its environment, the migration isn’t a part of your project. It only needed for testing your gem in a proper environment.

In this case the migration should be installed to a dummy app in a ‘spec/dummy/db/migrate` folder. Do it with the `-d` key:

$ uuids install -d

Usage

Adding UUIDs to models

Add the assotiation to your AR model with a ‘has_uuids` helper:

class City < ActiveRecord::Base
  include Uuids::Base
  has_uuids
end

This will add methods:

#uuids

List of Uuids::Models::Uuid objects referred to the record.

#uuid=(value)

assigns the UUID (an alias for #uuids.new value: value).

#uuid

main UUID for the record - a value of the first uuids (ordered by value).

.by_uuid(*values)

A scope for selecting unique records by UUID.

The first uuid is added by default. It can also be set manually:

# UUID generated by default:
city = City.create!
city.uuid.nil?
# => false

# UUID(s) assigned manually:
city = City.create! uuids: "6d9456a9-8f54-4ff7-ba0d-9854f1954417"
city.uuid
# => "6d9456a9-8f54-4ff7-ba0d-9854f1954417"

Destruction of object is forbidden while it has a uuid. You should reassign all object’s UUIDs to another record in advance.

Referring model by UUID

Instead of ActiveRecord::Associations belongs_to, has_one and has_many, you should define custom methods explicitly.

# db/migrate/*_create_streets.rb
class CreateStreetsTable < ActiveRecord::Migration
  def change
    create_table :streets do |t|
      t.string :city_uuid, limit: 36
    end
    add_index :streets_table, :city_uuid
  end
end

# app/models/street.rb
class Street < ActiveRecord::Base

  scope :by_city, ->(city) { City.by_uuid(city.uuid) }

  def city
    @city ||= City.by_uuid(city_uuid)
  end

  def city=(city)
    write_attribute :city_uuid, city.uuid
  end
end

Adding uuid

The module also contains the service object Add:

service = Uuids::Services::Add.new(
  value: "43523547-8230-5723-0457-234057254725",
  record: #<ActiveRecord::Base ... >
)
service.subscribe listener
service.run

Depending on the result of creation, the listener will receive either the :created, uuid, messages or :error, messages notification.

The service doesn’t know what the record is. In the success message it will be referred as the record “with id: %id”. To provide more concise messaging, the service should be reloaded with a new name method.

module Users
  module Services
    class AddUuid < Uuids::Services::Add
      private
      def name
        # It is expected the record is a user, that responds to full_name.
        # Not the success message will be something near:
        # "The uuid ... has been added to the record Иван Иванов."
        record.full_name
      end
    end
  end
end

Contributing

  1. Fork it ( github.com/nepalez/uuids/fork )

  2. Create your feature branch (git checkout -b my-new-feature)

  3. Commit your changes (git commit -am 'Add some feature')

  4. Push to the branch (git push origin my-new-feature)

  5. Create a new Pull Request

License

The plugin is distributed under MIT license