active_record_uuid 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rvmrc +1 -1
- data/README.md +126 -22
- data/active_record_uuid.gemspec +4 -3
- data/lib/active_record_uuid.rb +10 -4
- data/lib/active_record_uuid/association_methods.rb +52 -0
- data/lib/active_record_uuid/config.rb +37 -0
- data/lib/active_record_uuid/extensions/quoting_extension.rb +31 -0
- data/lib/active_record_uuid/model.rb +66 -0
- data/lib/active_record_uuid/rails/db.rake +25 -0
- data/lib/active_record_uuid/rails/railtie.rb +19 -0
- data/lib/active_record_uuid/serializer.rb +50 -0
- data/lib/active_record_uuid/uuid_base.rb +12 -4
- data/lib/active_record_uuid/uuid_base_helper.rb +6 -83
- data/lib/active_record_uuid/version.rb +1 -1
- data/spec/lib/association_spec.rb +1 -1
- data/spec/lib/base64_uuid_spec.rb +57 -0
- data/spec/lib/binary_uuid_spec.rb +69 -0
- data/spec/lib/config_spec.rb +41 -0
- data/spec/lib/deprecation_spec.rb +17 -0
- data/spec/lib/hexdigest_uuid_spec.rb +57 -0
- data/spec/lib/serializer_spec.rb +11 -0
- data/spec/lib/string_uuid_spec.rb +57 -0
- data/spec/lib/uuid_config_spec.rb +100 -0
- data/spec/spec_helper.rb +24 -5
- data/spec/support/models.rb +60 -8
- data/spec/support/schema.rb +30 -0
- metadata +48 -12
- data/lib/active_record_uuid/railtie.rb +0 -7
- data/lib/active_record_uuid/tasks/db.rake +0 -26
- data/spec/lib/uuid_base_helper_spec.rb +0 -69
- data/spec/lib/uuid_base_spec.rb +0 -68
data/.rvmrc
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
# First we specify our desired <ruby>[@<gemset>], the @gemset name is optional,
|
7
7
|
# Only full ruby name is supported here, for short names use:
|
8
8
|
# echo "rvm use 1.9.3" > .rvmrc
|
9
|
-
environment_id="ruby-1.9.3-
|
9
|
+
environment_id="ruby-1.9.3-p194@active_record_uuid"
|
10
10
|
|
11
11
|
# Uncomment the following lines if you want to verify rvm version per project
|
12
12
|
# rvmrc_rvm_version="1.12.1 (stable)" # 1.10.1 seams as a safe start
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# ActiveRecordUuid
|
2
2
|
|
3
|
-
`active_record_uuid` is a nice
|
3
|
+
`active_record_uuid` is a nice gem that add uuid supports to your `activerecord` models (MySQL). It allows you to store uuid in various formats: binary (16 bytes), base64 (24 bytes), hexdigest (32 bytes), or string (36 bytes), and query back with uuid string.
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
@@ -15,36 +15,66 @@ And then execute:
|
|
15
15
|
Or install it yourself as:
|
16
16
|
|
17
17
|
$ gem install active_record_uuid
|
18
|
+
|
19
|
+
## Upgrade from version 0.0.1
|
18
20
|
|
19
|
-
|
21
|
+
`ActiveRecordBase::UuidBase` and `UuidBaseHelper` are depreciated. Right now, you can configure your model by using `uuid_config` and inherit from `ActiveRecord::Base`. Check out the usage below.
|
20
22
|
|
21
|
-
|
23
|
+
## Usage
|
22
24
|
|
23
|
-
|
25
|
+
If you need to use `uuid` column as primary key, don't add any `:primary_key` in migration file, because it will make that column as auto-increment integer primary key. Instead, do something like this:
|
24
26
|
|
25
27
|
create_table :posts, :force => true, :id => false do |t|
|
26
28
|
t.string :uuid, :limit => 36
|
27
29
|
t.string :text
|
28
30
|
t.timestamps
|
29
31
|
end
|
30
|
-
|
31
|
-
Add primary keys to all tables
|
32
32
|
|
33
|
-
$ rake db:add_primary_keys
|
33
|
+
$ rake db:add_primary_keys # Add primary keys to all models
|
34
|
+
$ rake db:add_primary_key[model_name] # Add primary key to a model
|
34
35
|
|
35
|
-
|
36
|
+
The performance issues arises when you store primary key in large data size. You have two options:
|
37
|
+
- use `auto-increment` primary key along with uuid column
|
38
|
+
- use `uuid` primary key, but store in binary format
|
36
39
|
|
37
|
-
|
38
|
-
|
40
|
+
You have various choices when storing uuid:
|
41
|
+
- binary format, 16 bytes
|
42
|
+
- base64 format, 24 bytes (encode uuid into base64)
|
43
|
+
- hexdigest, 32 bytes string (compact uuid format, without dash)
|
44
|
+
- string, 36 bytes string (full length uuid)
|
39
45
|
|
40
|
-
|
46
|
+
### Migration
|
41
47
|
|
48
|
+
In order for the gem to work well, you need to specify the column `type` and `limit` correctly according to your `uuid_config` (`store_as`).
|
49
|
+
|
50
|
+
create_table :posts, :force => true, :id => false do |t|
|
51
|
+
t.binary :uuid, :limit => 16 # must set to the correct value
|
52
|
+
t.string :text
|
53
|
+
t.timestamps
|
54
|
+
end
|
55
|
+
|
42
56
|
class Post < ActiveRecord::Base
|
43
|
-
|
57
|
+
uuid_config do
|
58
|
+
primary_key true
|
59
|
+
store_as :binary
|
60
|
+
end
|
44
61
|
end
|
45
62
|
|
46
|
-
|
63
|
+
### Configuring your model
|
64
|
+
|
65
|
+
To use uuid in your model, call `uuid_config` in your model.
|
47
66
|
|
67
|
+
class Post < ActiveRecord::Base
|
68
|
+
uuid_config do
|
69
|
+
column :uuid # :uuid is default
|
70
|
+
primary_key true # false is default
|
71
|
+
association false # false is default
|
72
|
+
generator :timestamp # :timestamp is default
|
73
|
+
store_as :string # :string is default
|
74
|
+
hook :before_create # :before_validation is default
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
48
78
|
# create a post with auto-generated uuid
|
49
79
|
post = Post.new(:text => "Manual uuid")
|
50
80
|
post.save
|
@@ -56,31 +86,105 @@ You include `UuidBaseHelper` to your model when your model is already inherited
|
|
56
86
|
post.save
|
57
87
|
|
58
88
|
# assign a uuid
|
59
|
-
|
89
|
+
post.assign_uuid
|
60
90
|
|
61
91
|
# assign a uuid and save immediately
|
62
|
-
|
92
|
+
post.assign_uuid!
|
93
|
+
|
94
|
+
# check the uuid value is valid or not
|
95
|
+
post.uuid_valid?
|
63
96
|
|
64
|
-
# generate
|
97
|
+
# generate a new uuid
|
65
98
|
Post.generate_uuid
|
66
99
|
|
67
|
-
|
68
|
-
|
69
|
-
class
|
100
|
+
### Binary uuid model (example)
|
101
|
+
|
102
|
+
class PostBinary < ActiveRecord::Base
|
103
|
+
uuid_config do
|
104
|
+
primary_key true
|
105
|
+
store_as :binary
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
post = PostBinary.create(:text => "Binary uuid1")
|
110
|
+
# INSERT INTO `post_binaries` (`created_at`, `text`, `updated_at`, `uuid`) VALUES ('2012-06-20 17:32:47', 'Binary uuid1', '2012-06-20 17:32:47', x'4748f690bac311e18e440026b90faf3c')
|
111
|
+
|
112
|
+
post.uuid # "4748f690-bac3-11e1-8e44-0026b90faf3c"
|
113
|
+
|
114
|
+
# it works as usual for finding records
|
115
|
+
PostBinary.find_by_uuid(post.uuid)
|
116
|
+
PostBinary.where(:uuid => post.uuid)
|
117
|
+
PostBinary.find(post)
|
118
|
+
PostBinary.find(post.uuid)
|
119
|
+
PostBinary.find([post.uuid])
|
120
|
+
post.comments.create(:text => "Comment 1")
|
121
|
+
|
122
|
+
# access the value that stored in db
|
123
|
+
post.reload
|
124
|
+
post.attributes_before_type_cast["uuid"]["value"]
|
125
|
+
|
126
|
+
### Avaliable options inside `uuid_config`
|
127
|
+
#### `column` option
|
128
|
+
|
129
|
+
Set the column name to store uuid value.
|
130
|
+
|
131
|
+
#### `primary_key` option
|
132
|
+
|
133
|
+
Specify whether the value of `column` is primary key or not.
|
134
|
+
|
135
|
+
#### `generator` option
|
136
|
+
|
137
|
+
Specify which generator amongs `[:random, :timestamp]` used to generate `uuid` value.
|
138
|
+
|
139
|
+
#### `store_as` option
|
140
|
+
|
141
|
+
Specify which format to store. Possible values are `[:binary, :base64, :hexdigest, :string]`.
|
142
|
+
|
143
|
+
#### `hook` option
|
144
|
+
|
145
|
+
Specify the activerecord hook `[:after_initialize, :before_validation, :before_create]` to generate uuid value. Assign it to `:after_intialize` when you want to associate this record to its children.
|
146
|
+
|
147
|
+
#### `assoication` option
|
148
|
+
|
149
|
+
When you set this option to `true`, it expects you have foreign_keys with `_uuid`. Therefore, you don't have to pass `:foreign_key` option inside association methods
|
150
|
+
|
151
|
+
class Author < ActiveRecord::Base
|
152
|
+
uuid_config do
|
153
|
+
primary_key true
|
154
|
+
association true
|
155
|
+
end
|
70
156
|
has_and_belongs_to_many :posts
|
71
157
|
end
|
72
158
|
|
73
|
-
class Post < ActiveRecord::
|
159
|
+
class Post < ActiveRecord::Base
|
160
|
+
uuid_config do
|
161
|
+
primary_key true
|
162
|
+
association true
|
163
|
+
end
|
74
164
|
has_many :comments
|
75
165
|
has_one :comment
|
76
166
|
has_and_belongs_to_many :authors
|
77
167
|
end
|
78
168
|
|
79
|
-
class Comment < ActiveRecord::
|
169
|
+
class Comment < ActiveRecord::Base
|
170
|
+
uuid_config do
|
171
|
+
primary_key true
|
172
|
+
association true
|
173
|
+
end
|
80
174
|
belongs_to :post
|
81
175
|
end
|
82
176
|
|
83
177
|
# overwrite :foreign_key option and add additional option
|
84
|
-
class Post < ActiveRecord::
|
178
|
+
class Post < ActiveRecord::Base
|
179
|
+
uuid_config do
|
180
|
+
primary_key true
|
181
|
+
association true
|
182
|
+
end
|
85
183
|
has_many :comments, :foreign_key => "comment_id", :inverse_of => :post
|
86
184
|
end
|
185
|
+
|
186
|
+
## Testing
|
187
|
+
This gem will set the format for dumping the database schema to `:sql`. This means that it is no longer dump the schema to ruby code but database-independent version, `db/structure.sql`. Therefore, you would not have any problems when running the tests.
|
188
|
+
|
189
|
+
## References
|
190
|
+
- [http://www.mysqlperformanceblog.com/2007/03/13/to-uuid-or-not-to-uuid/](http://www.mysqlperformanceblog.com/2007/03/13/to-uuid-or-not-to-uuid/)
|
data/active_record_uuid.gemspec
CHANGED
@@ -4,8 +4,8 @@ require File.expand_path('../lib/active_record_uuid/version', __FILE__)
|
|
4
4
|
Gem::Specification.new do |gem|
|
5
5
|
gem.authors = ["Chamnap"]
|
6
6
|
gem.email = ["chamnapchhorn@gmail.com"]
|
7
|
-
gem.description = %q{
|
8
|
-
gem.summary = %q{A gem for adding
|
7
|
+
gem.description = %q{active_record_uuid is a nice gem that add uuid supports to your activerecord models (MySQL). It allows you to store uuid in various formats: binary (16 bytes), base64 (24 bytes), hexdigest (32 bytes), or string (36 bytes), and query back with uuid string.}
|
8
|
+
gem.summary = %q{A full-featured gem for adding uuid support to your active record models}
|
9
9
|
gem.homepage = "https://github.com/chamnap/active_record_uuid"
|
10
10
|
|
11
11
|
gem.files = `git ls-files`.split($\)
|
@@ -17,7 +17,8 @@ Gem::Specification.new do |gem|
|
|
17
17
|
|
18
18
|
gem.add_development_dependency "bundler", ">= 1.1.3"
|
19
19
|
gem.add_development_dependency "rspec", "~> 2.8.0"
|
20
|
-
gem.add_development_dependency "
|
20
|
+
gem.add_development_dependency "mysql2"
|
21
21
|
gem.add_dependency "activerecord", "~> 3.0"
|
22
22
|
gem.add_dependency "uuidtools", "~> 2.1.2"
|
23
|
+
gem.add_dependency "mysql2"
|
23
24
|
end
|
data/lib/active_record_uuid.rb
CHANGED
@@ -1,9 +1,15 @@
|
|
1
1
|
require 'uuidtools'
|
2
|
+
require 'base64'
|
2
3
|
require 'active_record'
|
3
|
-
|
4
|
-
require 'active_record_uuid/uuid_base'
|
5
|
-
require 'active_record_uuid/railtie' if defined?(Rails)
|
4
|
+
require 'active_support/concern'
|
6
5
|
|
7
6
|
module ActiveRecordUuid
|
8
|
-
autoload :VERSION,
|
7
|
+
autoload :VERSION, 'active_record_uuid/version'
|
8
|
+
autoload :Config, 'active_record_uuid/config'
|
9
|
+
autoload :Serializer, 'active_record_uuid/serializer'
|
10
|
+
autoload :AssociationMethods, 'active_record_uuid/association_methods'
|
11
|
+
autoload :Model, 'active_record_uuid/model'
|
9
12
|
end
|
13
|
+
|
14
|
+
require 'active_record_uuid/uuid_base'
|
15
|
+
require 'active_record_uuid/rails/railtie' if defined?(Rails::Railtie)
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module ActiveRecordUuid
|
2
|
+
module AssociationMethods
|
3
|
+
def has_many(name, options = {}, &extension)
|
4
|
+
options = uuid_assoc_options(:has_many, name, options)
|
5
|
+
super
|
6
|
+
end
|
7
|
+
|
8
|
+
def has_one(name, options = {})
|
9
|
+
options = uuid_assoc_options(:has_one, name, options)
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def belongs_to(name, options = {})
|
14
|
+
options = uuid_assoc_options(:belongs_to, name, options)
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def has_and_belongs_to_many(name, options = {}, &extension)
|
19
|
+
options = uuid_assoc_options(:has_and_belongs_to_many, name, options)
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def uuid_assoc_options(macro, association_name, options)
|
25
|
+
opts = {}
|
26
|
+
|
27
|
+
# Set class_name only if not a has-through relation or poly relation
|
28
|
+
if options[:through].blank? and options[:as].blank? and options[:class_name].blank? and !self.name.match(/::/)
|
29
|
+
opts[:class_name] = "::#{association_name.to_s.singularize.camelize}"
|
30
|
+
end
|
31
|
+
|
32
|
+
# Set foreign_key only if not passed
|
33
|
+
if options[:foreign_key].blank?
|
34
|
+
case macro
|
35
|
+
when :has_many, :has_one
|
36
|
+
opts[:foreign_key] = uuid_foreign_key(self.name)
|
37
|
+
when :belongs_to
|
38
|
+
opts[:foreign_key] = uuid_foreign_key(association_name)
|
39
|
+
when :has_and_belongs_to_many
|
40
|
+
opts[:foreign_key] = uuid_foreign_key(self.name)
|
41
|
+
opts[:association_foreign_key] = uuid_foreign_key(association_name)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
options.merge(opts)
|
46
|
+
end
|
47
|
+
|
48
|
+
def uuid_foreign_key(name)
|
49
|
+
name.to_s.singularize.underscore.downcase + "_uuid"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module ActiveRecordUuid
|
2
|
+
class Config
|
3
|
+
def initialize
|
4
|
+
@column = :uuid
|
5
|
+
@primary_key = false
|
6
|
+
@association = false
|
7
|
+
@generator = :timestamp
|
8
|
+
@store_as = :string
|
9
|
+
@hook = :before_validation
|
10
|
+
end
|
11
|
+
|
12
|
+
METHODS = [:column, :primary_key, :association, :generator, :store_as, :hook]
|
13
|
+
METHODS.each do |meth|
|
14
|
+
define_method(meth.to_sym) do |value=nil|
|
15
|
+
instance_variable_set("@#{meth}".to_sym, value) if value
|
16
|
+
instance_variable_get("@#{meth}")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def validate_options!
|
21
|
+
default_generators = [:timestamp, :random]
|
22
|
+
raise ArgumentError,
|
23
|
+
"Expected :timestamp or :random, got #{@generator}." \
|
24
|
+
unless default_generators.include?(@generator)
|
25
|
+
|
26
|
+
default_stores = [:binary, :base64, :hexdigest, :string]
|
27
|
+
raise ArgumentError,
|
28
|
+
"Expected :binary, :base64, :hexdigest, or :string, got #{@store_as}." \
|
29
|
+
unless default_stores.include?(@store_as)
|
30
|
+
|
31
|
+
default_hooks = [:before_validation, :after_initialize, :before_create]
|
32
|
+
raise ArgumentError,
|
33
|
+
"Expected :before_validation, :after_initialize, or :before_create, got #{hook}." \
|
34
|
+
unless default_hooks.include?(@hook)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module ActiveRecordUuid
|
2
|
+
module QuotingExtension
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
def quote(value, column = nil)
|
7
|
+
return super if column.blank? or !value.instance_of?(String) or value.bytesize != 36
|
8
|
+
|
9
|
+
begin
|
10
|
+
uuid = UUIDTools::UUID.parse(value)
|
11
|
+
rescue ArgumentError, TypeError
|
12
|
+
return super
|
13
|
+
end
|
14
|
+
|
15
|
+
case column.type
|
16
|
+
when :binary
|
17
|
+
"BINARY x'#{uuid.hexdigest}'"
|
18
|
+
when :string
|
19
|
+
case column.limit
|
20
|
+
when 24
|
21
|
+
"'#{Base64.encode64(uuid.raw).strip}'"
|
22
|
+
when 32
|
23
|
+
"'#{uuid.hexdigest}'"
|
24
|
+
else
|
25
|
+
"'#{uuid.to_s}'"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module ActiveRecordUuid::Model
|
2
|
+
def self.included(base)
|
3
|
+
base.send(:extend, ClassMethods)
|
4
|
+
base.send(:include, InstanceMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
module InstanceMethods
|
8
|
+
def assign_uuid
|
9
|
+
send("#{uuid_column}=", self.class.generate_uuid)
|
10
|
+
end
|
11
|
+
|
12
|
+
def assign_uuid!
|
13
|
+
assign_uuid
|
14
|
+
save!
|
15
|
+
end
|
16
|
+
|
17
|
+
def uuid_valid?
|
18
|
+
begin
|
19
|
+
UUIDTools::UUID.parse(uuid_value).valid?
|
20
|
+
rescue ArgumentError, TypeError
|
21
|
+
false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def uuid_value
|
26
|
+
send(uuid_column)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
def assign_uuid_when_blank
|
31
|
+
assign_uuid if uuid_value.blank?
|
32
|
+
end
|
33
|
+
|
34
|
+
def uuid_column
|
35
|
+
self.class.uuid_config.column
|
36
|
+
end
|
37
|
+
|
38
|
+
def validates_uuid
|
39
|
+
errors.add(uuid_column, "is invalid format") unless uuid_valid?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module ClassMethods
|
44
|
+
def uuid_config(&block)
|
45
|
+
return @config if @config.present?
|
46
|
+
|
47
|
+
@config = ActiveRecordUuid::Config.new
|
48
|
+
@config.instance_eval(&block) if block_given?
|
49
|
+
@config.validate_options!
|
50
|
+
|
51
|
+
column_name = @config.column.to_sym
|
52
|
+
self.primary_key = column_name if @config.primary_key
|
53
|
+
self.serialize column_name, ActiveRecordUuid::Serializer.new(@config.store_as)
|
54
|
+
self.send(@config.hook, :assign_uuid_when_blank)
|
55
|
+
self.validates_uniqueness_of column_name, :if => Proc.new { |r| r.uuid_value.present? }
|
56
|
+
self.validate :validates_uuid , :if => Proc.new { |r| r.uuid_value.present? }
|
57
|
+
self.send(:extend, ActiveRecordUuid::AssociationMethods) if @config.association
|
58
|
+
|
59
|
+
@config
|
60
|
+
end
|
61
|
+
|
62
|
+
def generate_uuid
|
63
|
+
UUIDTools::UUID.send("#{uuid_config.generator}_create").to_s
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
namespace :db do
|
2
|
+
task :add_primary_keys => :environment do
|
3
|
+
Dir["#{Rails.root}/app/models/**/*.rb"].each { |path| require path }
|
4
|
+
ActiveRecord::Base.descendants.each do |model|
|
5
|
+
next if model.abstract_class
|
6
|
+
|
7
|
+
add_primary_key(model.table_name, model.primary_key)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
task :add_primary_key, [:model_name] => :environment do |t, args|
|
12
|
+
model = Object.const_get(args.model_name)
|
13
|
+
|
14
|
+
add_primary_key(model.table_name, model.primary_key)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
def add_primary_key(table_name, primary_key)
|
19
|
+
begin
|
20
|
+
ActiveRecord::Base.connection.execute "ALTER TABLE #{table_name} ADD PRIMARY KEY (#{primary_key});"
|
21
|
+
rescue => e
|
22
|
+
p e
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|