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 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-p125@active_record_uuid"
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 and simple gem that add uuid support to your `activerecord` models, `associations`, `schema.rb`, and `rake script`.
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
- ## Usage
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
- This gem doesn't try to overwrite anything, it just makes use of existing activerecord apis in order to work with `uuid`.
23
+ ## Usage
22
24
 
23
- In your migration files, you don't add any `:primary`, because it will make that column as auto-increment integer primary key. Do something like this:
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
- There are two ways you can use this gem: by `inherit` and `include`.
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
- class Post < ActiveRecord::UuidBase
38
- end
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
- Or
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
- include UuidBaseHelper
57
+ uuid_config do
58
+ primary_key true
59
+ store_as :binary
60
+ end
44
61
  end
45
62
 
46
- You include `UuidBaseHelper` to your model when your model is already inherited from other classes (STI, ...).
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
- @post.assign_uuid
89
+ post.assign_uuid
60
90
 
61
91
  # assign a uuid and save immediately
62
- @post.assign_uuid!
92
+ post.assign_uuid!
93
+
94
+ # check the uuid value is valid or not
95
+ post.uuid_valid?
63
96
 
64
- # generate to a uuid
97
+ # generate a new uuid
65
98
  Post.generate_uuid
66
99
 
67
- # It expects you have foreign_keys with `_uuid`
68
- # you don't have to pass `:foreign_key` option inside association methods
69
- class Author < ActiveRecord::UuidBase
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::UuidBase
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::UuidBase
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::UuidBase
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/)
@@ -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{A nice gem to add uuids to your models, associations, and schema.rb}
8
- gem.summary = %q{A gem for adding uuids to your active record models}
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 "sqlite3"
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
@@ -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, 'active_record_uuid/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