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 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