activeuuid-ps 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/README.mkd +134 -0
- data/Rakefile +1 -0
- data/activeuuid.gemspec +21 -0
- data/lib/activeuuid.rb +6 -0
- data/lib/activeuuid/railtie.rb +21 -0
- data/lib/activeuuid/uuid.rb +107 -0
- data/lib/activeuuid/version.rb +3 -0
- metadata +65 -0
data/Gemfile
ADDED
data/README.mkd
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
# activeuuid
|
2
|
+
|
3
|
+
Add `binary(16)` UUIDs to ActiveRecord.
|
4
|
+
|
5
|
+
## Example
|
6
|
+
|
7
|
+
### Create a Migration
|
8
|
+
|
9
|
+
`activeuuid` adds the `uuid` type to your migrations. Example:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
class CreateEmails < ActiveRecord::Migration
|
13
|
+
def self.up
|
14
|
+
create_table :emails, :id => false do |t|
|
15
|
+
t.uuid :id, :unique => true
|
16
|
+
t.uuid :sender_id # belongs_to :sender
|
17
|
+
|
18
|
+
t.string :subject
|
19
|
+
t.text :body
|
20
|
+
|
21
|
+
t.timestamp :sent_at
|
22
|
+
t.timestamps
|
23
|
+
end
|
24
|
+
add_index :emails, :id
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.down
|
28
|
+
drop_table :emails
|
29
|
+
end
|
30
|
+
end
|
31
|
+
```
|
32
|
+
|
33
|
+
### include ActiveUUID::UUID in your model
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
class Email < ActiveRecord::Base
|
37
|
+
include ActiveUUID::UUID
|
38
|
+
belongs_to :sender
|
39
|
+
end
|
40
|
+
```
|
41
|
+
|
42
|
+
### use it:
|
43
|
+
Here are some example specs:
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
require 'spec_helper'
|
47
|
+
|
48
|
+
describe Email do
|
49
|
+
|
50
|
+
context "when using uuid's as keys" do
|
51
|
+
before(:each) do
|
52
|
+
Email.delete_all
|
53
|
+
@guid = "1dd74dd0-d116-11e0-99c7-5ac5d975667e"
|
54
|
+
@e = Email.new(:subject => "hello", :body => "world") {|e| e.id = UUIDTools::UUID.parse(@guid) }
|
55
|
+
@e.save
|
56
|
+
end
|
57
|
+
|
58
|
+
it "the id guid should be equal to the uuid" do
|
59
|
+
@e.id.to_s.should eql(@guid)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should be able to find an email by the uuid" do
|
63
|
+
f = Email.find(UUIDTools::UUID.parse(@guid))
|
64
|
+
f.id.to_s.should eql(@guid)
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
```
|
70
|
+
|
71
|
+
## Motivation
|
72
|
+
|
73
|
+
From [2]:
|
74
|
+
|
75
|
+
> [Here is a] UUID: 1e8ef774-581c-102c-bcfe-f1ab81872213
|
76
|
+
>
|
77
|
+
> A UUID like the one above is 36 characters long, including dashes. If you store this VARCHAR(36), you're going to decrease compare performance dramatically. This is your primary key, you don't want it to be slow.
|
78
|
+
>
|
79
|
+
> At its bit level, a UUID is 128 bits, which means it will fit into
|
80
|
+
> 16 bytes, note this is not very human readable, but it will keep
|
81
|
+
> storage low, and is only 4 times larger than a 32-bit int, or 2
|
82
|
+
> times larger than a 64-bit int.
|
83
|
+
|
84
|
+
Many of the existing examples of how to use UUIDs as primary keys
|
85
|
+
in Rails use strings rather than bytes (e.g. [3]).
|
86
|
+
|
87
|
+
However, this plugin stores the primary keys as bytes. To the
|
88
|
+
application the keys are represented by a UUIDTools::UUID object.
|
89
|
+
|
90
|
+
## Benefits of UUIDs as primary key
|
91
|
+
|
92
|
+
* no id conflict during multi-master write
|
93
|
+
* no locking due to auto-increment
|
94
|
+
* with time-based UUIDs you can store a timestamp within your UUID
|
95
|
+
* you can create natural keys (based on the SHA of model attributes)
|
96
|
+
|
97
|
+
## Future work
|
98
|
+
* more transparent support for natural and composite keys
|
99
|
+
* support for MySQLs `INSERT ... ON DUPLICATE KEY UPDATE` syntax
|
100
|
+
* support a primary column name other than `id`
|
101
|
+
* work on other databases (Postgres, etc)
|
102
|
+
* tests
|
103
|
+
|
104
|
+
## Inspiration
|
105
|
+
James Golick's `friendly` is a great gem for NoSQL on MySQL. It's
|
106
|
+
a great gateway drug to systems like Cassandra for teams that are
|
107
|
+
already familiar with the ins-and-outs of MySQL.
|
108
|
+
|
109
|
+
## Installation
|
110
|
+
|
111
|
+
Add this to your `Gemfile`
|
112
|
+
|
113
|
+
gem "activeuuid"
|
114
|
+
|
115
|
+
Or get the code here: https://github.com/jashmenn/activeuuid
|
116
|
+
|
117
|
+
## References
|
118
|
+
* [1] http://bret.appspot.com/entry/how-friendfeed-uses-mysql
|
119
|
+
* [2] http://kekoav.com/blog/36-computers/58-uuids-as-primary-keys-in-mysql.html
|
120
|
+
* [3] https://gist.github.com/937739
|
121
|
+
* [4] http://www.codinghorror.com/blog/2007/03/primary-keys-ids-versus-guids.html
|
122
|
+
* [5] http://krow.livejournal.com/497839.html
|
123
|
+
* [6] https://github.com/jamesgolick/friendly
|
124
|
+
|
125
|
+
## Dependencies
|
126
|
+
Rails ~> 3.1.0 - It uses the custom column serialization Aaron
|
127
|
+
Patterson introduced in Rails 3.1.
|
128
|
+
|
129
|
+
I'm using JRuby 1.9 (1.6.3) but this should work under MRI (YMMV).
|
130
|
+
|
131
|
+
## Author
|
132
|
+
|
133
|
+
Nate Murray <nate@xcombinator.com>
|
134
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/activeuuid.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "activeuuid/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "activeuuid-ps"
|
7
|
+
s.version = Activeuuid::VERSION
|
8
|
+
s.authors = ["Nate Murray, Ralf S. Bongiolo"]
|
9
|
+
s.email = ["nate@natemurray.com, ralfsb2008@gmail.com"]
|
10
|
+
s.homepage = "http://www.xcombinator.com"
|
11
|
+
s.summary = %q{Add binary UUIDs to ActiveRecord in PostgreSQL}
|
12
|
+
s.description = %q{Add binary (not string) UUIDs to ActiveRecord in PostgreSQL}
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
|
19
|
+
# s.add_development_dependency "rspec"
|
20
|
+
s.add_runtime_dependency "uuidtools"
|
21
|
+
end
|
data/lib/activeuuid.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'activeuuid'
|
2
|
+
require 'rails'
|
3
|
+
|
4
|
+
module ActiveUUID
|
5
|
+
class Railtie < Rails::Railtie
|
6
|
+
railtie_name :activeuuid
|
7
|
+
initializer "activeuuid.configure_rails_initialization" do
|
8
|
+
|
9
|
+
module ActiveRecord::ConnectionAdapters
|
10
|
+
class TableDefinition
|
11
|
+
def uuid (*args)
|
12
|
+
options = args.extract_options!
|
13
|
+
column_names = args
|
14
|
+
column_names.each { |name| column(name, 'binary(16)', options) }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module UUIDTools
|
2
|
+
class UUID
|
3
|
+
# monkey-patch Friendly::UUID to serialize UUIDs to MySQL
|
4
|
+
def quoted_id
|
5
|
+
s = raw.unpack("H*")[0]
|
6
|
+
"x'#{s}'"
|
7
|
+
end
|
8
|
+
|
9
|
+
# monkey-patch Friendly::UUID to serialize UUIDs to MySQL
|
10
|
+
def quoted_id_ps
|
11
|
+
s = raw.unpack("H*")[0]
|
12
|
+
"\x'#{s}'"
|
13
|
+
end
|
14
|
+
|
15
|
+
def as_json(options = nil)
|
16
|
+
hexdigest.upcase
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_param
|
20
|
+
hexdigest.upcase
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module Arel
|
26
|
+
module Visitors
|
27
|
+
class MySQL < Arel::Visitors::ToSql
|
28
|
+
def visit_UUIDTools_UUID(o)
|
29
|
+
o.quoted_id
|
30
|
+
end
|
31
|
+
end
|
32
|
+
class PostgreSQL < Arel::Visitors::ToSql
|
33
|
+
def visit_UUIDTools_UUID(o)
|
34
|
+
o.quoted_id_ps
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
module ActiveUUID
|
41
|
+
class UUIDSerializer
|
42
|
+
def load(binary)
|
43
|
+
case binary
|
44
|
+
when UUIDTools::UUID then binary
|
45
|
+
when nil then nil
|
46
|
+
else UUIDTools::UUID.parse_raw(binary)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
def dump(uuid)
|
50
|
+
uuid ? uuid.raw : nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
module UUID
|
55
|
+
extend ActiveSupport::Concern
|
56
|
+
|
57
|
+
included do
|
58
|
+
before_create :generate_uuid_if_needed
|
59
|
+
|
60
|
+
set_primary_key "id"
|
61
|
+
serialize :id, ActiveUUID::UUIDSerializer.new
|
62
|
+
|
63
|
+
def generate_uuid_if_needed
|
64
|
+
generate_uuid unless self.id
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_param
|
68
|
+
id.to_param
|
69
|
+
end
|
70
|
+
|
71
|
+
def generate_uuid
|
72
|
+
if nka = self.class.natural_key_attributes
|
73
|
+
# TODO if all the attributes return nil you might want to warn about this
|
74
|
+
chained = nka.collect{|a| self.send(a).to_s}.join("-")
|
75
|
+
self.id = UUIDTools::UUID.sha1_create(UUIDTools::UUID_OID_NAMESPACE, chained)
|
76
|
+
else
|
77
|
+
self.id = UUIDTools::UUID.timestamp_create
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
module ClassMethods
|
83
|
+
def natural_key_attributes
|
84
|
+
@_activeuuid_natural_key_attributes
|
85
|
+
end
|
86
|
+
|
87
|
+
def natural_key(*attributes)
|
88
|
+
@_activeuuid_natural_key_attributes = attributes
|
89
|
+
end
|
90
|
+
|
91
|
+
def uuids(*attributes)
|
92
|
+
attributes.each do |attribute|
|
93
|
+
serialize attribute.intern, ActiveUUID::UUIDSerializer.new
|
94
|
+
#class_eval <<-eos
|
95
|
+
# # def #{@association_name}
|
96
|
+
# # @_#{@association_name} ||= self.class.associations[:#{@association_name}].new_proxy(self)
|
97
|
+
# # end
|
98
|
+
#eos
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
module InstanceMethods
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: activeuuid-ps
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Nate Murray, Ralf S. Bongiolo
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-09-28 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: uuidtools
|
16
|
+
requirement: &23679000 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *23679000
|
25
|
+
description: Add binary (not string) UUIDs to ActiveRecord in PostgreSQL
|
26
|
+
email:
|
27
|
+
- nate@natemurray.com, ralfsb2008@gmail.com
|
28
|
+
executables: []
|
29
|
+
extensions: []
|
30
|
+
extra_rdoc_files: []
|
31
|
+
files:
|
32
|
+
- .gitignore
|
33
|
+
- Gemfile
|
34
|
+
- README.mkd
|
35
|
+
- Rakefile
|
36
|
+
- activeuuid.gemspec
|
37
|
+
- lib/activeuuid.rb
|
38
|
+
- lib/activeuuid/railtie.rb
|
39
|
+
- lib/activeuuid/uuid.rb
|
40
|
+
- lib/activeuuid/version.rb
|
41
|
+
homepage: http://www.xcombinator.com
|
42
|
+
licenses: []
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
none: false
|
49
|
+
requirements:
|
50
|
+
- - ! '>='
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0'
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ! '>='
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
requirements: []
|
60
|
+
rubyforge_project:
|
61
|
+
rubygems_version: 1.7.2
|
62
|
+
signing_key:
|
63
|
+
specification_version: 3
|
64
|
+
summary: Add binary UUIDs to ActiveRecord in PostgreSQL
|
65
|
+
test_files: []
|