active_record_uuid 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|