activeid 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +21 -0
  3. data/.github/workflows/macos.yml +45 -0
  4. data/.github/workflows/ubuntu.yml +47 -0
  5. data/.github/workflows/windows.yml +40 -0
  6. data/.gitignore +195 -0
  7. data/.hound.yml +3 -0
  8. data/.rubocop.yml +18 -0
  9. data/Gemfile +7 -0
  10. data/LICENSE.md +19 -0
  11. data/README.adoc +411 -0
  12. data/Rakefile +27 -0
  13. data/activeid.gemspec +42 -0
  14. data/examples/name_based_uuids.rb +92 -0
  15. data/examples/registering_active_record_type.rb +74 -0
  16. data/examples/storing_uuids_as_binaries.rb +88 -0
  17. data/examples/storing_uuids_as_strings.rb +81 -0
  18. data/examples/storing_uuids_natively.rb +93 -0
  19. data/examples/time_based_uuids.rb +58 -0
  20. data/examples/using_migrations.rb +50 -0
  21. data/gemfiles/Rails-5_0.gemfile +8 -0
  22. data/gemfiles/Rails-5_1.gemfile +8 -0
  23. data/gemfiles/Rails-5_2.gemfile +8 -0
  24. data/gemfiles/Rails-head.gemfile +8 -0
  25. data/lib/active_id.rb +12 -0
  26. data/lib/active_id/all.rb +2 -0
  27. data/lib/active_id/connection_patches.rb +65 -0
  28. data/lib/active_id/model.rb +55 -0
  29. data/lib/active_id/railtie.rb +12 -0
  30. data/lib/active_id/type.rb +100 -0
  31. data/lib/active_id/utils.rb +77 -0
  32. data/lib/active_id/version.rb +3 -0
  33. data/spec/integration/examples_for_uuid_models.rb +92 -0
  34. data/spec/integration/examples_for_uuid_models_having_namespaces.rb +12 -0
  35. data/spec/integration/examples_for_uuid_models_having_natural_keys.rb +11 -0
  36. data/spec/integration/migrations_spec.rb +92 -0
  37. data/spec/integration/model_without_uuids_spec.rb +44 -0
  38. data/spec/integration/no_patches_spec.rb +26 -0
  39. data/spec/integration/storing_uuids_as_binaries_spec.rb +34 -0
  40. data/spec/integration/storing_uuids_as_strings_spec.rb +22 -0
  41. data/spec/spec_helper.rb +64 -0
  42. data/spec/support/0_logger.rb +2 -0
  43. data/spec/support/1_db_connection.rb +3 -0
  44. data/spec/support/2_db_cleaner.rb +14 -0
  45. data/spec/support/database.yml +12 -0
  46. data/spec/support/fabricators.rb +15 -0
  47. data/spec/support/models.rb +41 -0
  48. data/spec/support/schema.rb +38 -0
  49. data/spec/unit/attribute_type_spec.rb +70 -0
  50. data/spec/unit/utils_spec.rb +97 -0
  51. metadata +313 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: dc713ea54d39df97a832528211f697b13e45e3c744d4780fda842d39b213d48a
4
+ data.tar.gz: 3cf3366bbbfc5adb0cc5d16ce728145e91792dfbf62430be5a488764248772e9
5
+ SHA512:
6
+ metadata.gz: c85cce975a703dd95546d8c7efb42747b4e8bdd7fe762e2e0f0d0cc80334293fd1d823210f00007073334e0acec39b8e45d5eafad5293caa377575cc784d69f0
7
+ data.tar.gz: 2df3ba3814f9c64d75222afccbe230e4eb754caaefb0d27ae0c607a26ca7339657f3be699df59524ca1d1fad838072bae7602c8fe8c262618d2e6eb019eab50f
@@ -0,0 +1,21 @@
1
+ # EditorConfig is awesome: http://EditorConfig.org
2
+
3
+ # top-most EditorConfig file
4
+ root = true
5
+
6
+ # Unix-style newlines with a newline ending every file
7
+ [*]
8
+ charset = utf-8
9
+ end_of_line = lf
10
+
11
+ [{*.{rb,ru,yml,rake,feature,gemfile},Rakefile,Gemfile}]
12
+ indent_style = space
13
+ indent_size = 2
14
+ insert_final_newline = true
15
+ trim_trailing_whitespace = true
16
+
17
+ [*.sh]
18
+ indent_style = tab
19
+ indent_size = 4
20
+ insert_final_newline = true
21
+ trim_trailing_whitespace = true
@@ -0,0 +1,45 @@
1
+ # Auto-generated by Cimas: Do not edit it manually!
2
+ # See https://github.com/metanorma/cimas
3
+ name: macos
4
+
5
+ on:
6
+ push:
7
+ branches: [ master ]
8
+ pull_request:
9
+ paths-ignore:
10
+ - .github/workflows/ubuntu.yml
11
+ - .github/workflows/windows.yml
12
+ jobs:
13
+ test-macos:
14
+ name: Test on Ruby ${{ matrix.ruby }} macOS
15
+ runs-on: macos-latest
16
+ continue-on-error: ${{ matrix.experimental }}
17
+ strategy:
18
+ fail-fast: false
19
+ matrix:
20
+ ruby: [ '2.6', '2.5', '2.4' ]
21
+ experimental: [false]
22
+ include:
23
+ - ruby: '2.7'
24
+ experimental: true
25
+ steps:
26
+ - uses: actions/checkout@master
27
+ - name: Use Ruby
28
+ uses: actions/setup-ruby@v1
29
+ with:
30
+ ruby-version: ${{ matrix.ruby }}
31
+ architecture: 'x64'
32
+ - name: Install mysql and pg
33
+ run: |
34
+ brew install mysql
35
+ mysql -e 'create database activeuuid_test;'
36
+ # psql -c 'create database activeuuid_test;' -U postgres
37
+
38
+ - name: Update gems
39
+ run: |
40
+ sudo gem install bundler --force
41
+ gem install mysql2 -- --with-ldflags=-L/usr/local/opt/openssl/lib --with-cppflags=-I/usr/local/opt/openssl/include
42
+ bundle install --jobs 4 --retry 3
43
+ - name: Run specs
44
+ run: |
45
+ bundle exec rake
@@ -0,0 +1,47 @@
1
+ # Auto-generated by Cimas: Do not edit it manually!
2
+ # See https://github.com/metanorma/cimas
3
+ name: ubuntu
4
+
5
+ on:
6
+ push:
7
+ branches: [ master ]
8
+ tags:
9
+ - '*'
10
+ pull_request:
11
+ paths-ignore:
12
+ - .github/workflows/macos.yml
13
+ - .github/workflows/windows.yml
14
+ jobs:
15
+ test-linux:
16
+ name: Test on Ruby ${{ matrix.ruby }} Ubuntu
17
+ runs-on: ubuntu-latest
18
+ continue-on-error: ${{ matrix.experimental }}
19
+ strategy:
20
+ fail-fast: false
21
+ matrix:
22
+ ruby: [ '2.6', '2.5', '2.4' ]
23
+ experimental: [false]
24
+ include:
25
+ - ruby: '2.7'
26
+ experimental: true
27
+ steps:
28
+ - uses: actions/checkout@master
29
+ - name: Use Ruby
30
+ uses: actions/setup-ruby@v1
31
+ with:
32
+ ruby-version: ${{ matrix.ruby }}
33
+ architecture: 'x64'
34
+ - name: Install mysql and pg
35
+ run: |
36
+ # sudo apt-get update
37
+ # sudo apt install -y mariadb-server
38
+ sudo apt-get install -y postgresql postgresql-contrib
39
+ mysql -e 'create database activeuuid_test;'
40
+ psql -c 'create database activeuuid_test;' -U postgres
41
+ - name: Update gems
42
+ run: |
43
+ gem install bundler
44
+ bundle install --jobs 4 --retry 3
45
+ - name: Run specs
46
+ run: |
47
+ bundle exec rake
@@ -0,0 +1,40 @@
1
+ # Auto-generated by Cimas: Do not edit it manually!
2
+ # See https://github.com/metanorma/cimas
3
+ name: windows
4
+
5
+ on:
6
+ push:
7
+ branches: [ master ]
8
+ pull_request:
9
+ paths-ignore:
10
+ - .github/workflows/macos.yml
11
+ - .github/workflows/ubuntu.yml
12
+ jobs:
13
+ test-windows:
14
+ name: Test on Ruby ${{ matrix.ruby }} Windows
15
+ runs-on: windows-latest
16
+ continue-on-error: ${{ matrix.experimental }}
17
+ strategy:
18
+ fail-fast: false
19
+ matrix:
20
+ ruby: [ '2.6', '2.5', '2.4' ]
21
+ experimental: [false]
22
+ include:
23
+ - ruby: '2.7'
24
+ experimental: true
25
+ steps:
26
+ - uses: actions/checkout@master
27
+ - name: Use Ruby
28
+ uses: actions/setup-ruby@v1
29
+ with:
30
+ ruby-version: ${{ matrix.ruby }}
31
+ architecture: 'x64'
32
+ - name: Update gems
33
+ shell: pwsh
34
+ run: |
35
+ gem install bundler
36
+ bundle config --local path vendor/bundle
37
+ bundle install --jobs 4 --retry 3
38
+ - name: Run specs
39
+ run: |
40
+ bundle exec rake
@@ -0,0 +1,195 @@
1
+ # Rubocop caches
2
+ /.rubocop-http--*
3
+ /.rubocop-https--*
4
+
5
+ # Combustion gem
6
+ *.log
7
+ *.sqlite
8
+
9
+ # rspec failure tracking
10
+ /.rspec_status
11
+
12
+ *.gem
13
+ *.orig
14
+ *.rbc
15
+ .rspec
16
+ /.config
17
+ /InstalledFiles
18
+ /coverage/
19
+ /db/*.sqlite3
20
+ /db/*.sqlite3-journal
21
+ /log
22
+ /pkg/
23
+ /public/system
24
+ /spec/examples.txt
25
+ /spec/reports/
26
+ /spec/tmp
27
+ /test/tmp/
28
+ /test/version_tmp/
29
+ /tmp
30
+ capybara-*.html
31
+ pickle-email-*.html
32
+ rerun.txt
33
+
34
+
35
+ ## Specific to RubyMotion:
36
+ .dat*
37
+ .repl_history
38
+ build/
39
+ *.bridgesupport
40
+ build-iPhoneOS/
41
+ build-iPhoneSimulator/
42
+
43
+ ## Specific to RubyMotion (use of CocoaPods):
44
+ #
45
+ # We recommend against adding the Pods directory to your .gitignore. However
46
+ # you should judge for yourself, the pros and cons are mentioned at:
47
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
48
+ #
49
+ # vendor/Pods/
50
+
51
+ ## Documentation cache and generated files:
52
+ /.yardoc/
53
+ /_yardoc/
54
+ /doc/
55
+ /rdoc/
56
+
57
+ ## Environment normalization:
58
+ /.bundle/
59
+ /vendor/bundle
60
+ /lib/bundler/man/
61
+
62
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
63
+ .rvmrc
64
+
65
+ # TODO Comment out this rule if you are OK with secrets being uploaded to the
66
+ # repo
67
+ config/initializers/secret_token.rb
68
+
69
+ # Only include if you have production secrets in this file, which is no longer a Rails default
70
+ # config/secrets.yml
71
+
72
+ # dotenv
73
+ # Used by dotenv library to load environment variables.
74
+ # TODO Comment out this rule if environment variables can be committed
75
+ # .env
76
+
77
+ # for a library or gem, you might want to ignore these files since the code is
78
+ # intended to run in multiple environments; otherwise, check them in:
79
+ Gemfile.lock
80
+ .ruby-version
81
+ .ruby-gemset
82
+
83
+ # if using bower-rails ignore default bower_components path bower.json files
84
+ /vendor/assets/bower_components
85
+ *.bowerrc
86
+ bower.json
87
+
88
+ # Ignore pow environment settings
89
+ .powenv
90
+
91
+ # Ignore Byebug command history file.
92
+ .byebug_history
93
+
94
+
95
+ # Created by https://www.gitignore.io/api/vim
96
+
97
+ ### Vim ###
98
+ # swap
99
+ [._]*.s[a-v][a-z]
100
+ [._]*.sw[a-p]
101
+ [._]s[a-v][a-z]
102
+ [._]sw[a-p]
103
+ # session
104
+ Session.vim
105
+ # temporary
106
+ .netrwhist
107
+ *~
108
+ # auto-generated tag files
109
+ tags
110
+
111
+ # End of https://www.gitignore.io/api/vim
112
+
113
+
114
+ # Created by https://www.gitignore.io/api/emacs
115
+
116
+ ### Emacs ###
117
+ # -*- mode: gitignore; -*-
118
+ *~
119
+ \#*\#
120
+ /.emacs.desktop
121
+ /.emacs.desktop.lock
122
+ *.elc
123
+ auto-save-list
124
+ tramp
125
+ .\#*
126
+
127
+ # Org-mode
128
+ .org-id-locations
129
+ *_archive
130
+
131
+ # flymake-mode
132
+ *_flymake.*
133
+
134
+ # eshell files
135
+ /eshell/history
136
+ /eshell/lastdir
137
+
138
+ # elpa packages
139
+ /elpa/
140
+
141
+ # reftex files
142
+ *.rel
143
+
144
+ # AUCTeX auto folder
145
+ /auto/
146
+
147
+ # cask packages
148
+ .cask/
149
+ dist/
150
+
151
+ # Flycheck
152
+ flycheck_*.el
153
+
154
+ # server auth directory
155
+ /server/
156
+
157
+ # projectiles files
158
+ .projectile
159
+
160
+ # directory configuration
161
+ .dir-locals.el
162
+
163
+ # End of https://www.gitignore.io/api/emacs
164
+
165
+ # Created by https://www.gitignore.io/api/macos
166
+
167
+ ### macOS ###
168
+ *.DS_Store
169
+ .AppleDouble
170
+ .LSOverride
171
+
172
+ # Icon must end with two \r
173
+ Icon
174
+
175
+ # Thumbnails
176
+ ._*
177
+
178
+ # Files that might appear in the root of a volume
179
+ .DocumentRevisions-V100
180
+ .fseventsd
181
+ .Spotlight-V100
182
+ .TemporaryItems
183
+ .Trashes
184
+ .VolumeIcon.icns
185
+ .com.apple.timemachine.donotpresent
186
+
187
+ # Directories potentially created on remote AFP share
188
+ .AppleDB
189
+ .AppleDesktop
190
+ Network Trash Folder
191
+ Temporary Items
192
+ .apdisk
193
+
194
+
195
+ # End of https://www.gitignore.io/api/macos
@@ -0,0 +1,3 @@
1
+ ruby:
2
+ Enabled: true
3
+ config_file: .rubocop.yml
@@ -0,0 +1,18 @@
1
+ # All project-specific additions and overrides should be specified in this
2
+ # file.
3
+
4
+ inherit_from:
5
+ - https://raw.githubusercontent.com/riboseinc/oss-guides/master/ci/rubocop.yml
6
+
7
+ AllCops:
8
+ DisplayCopNames: false
9
+ StyleGuideCopsOnly: false
10
+ TargetRubyVersion: 2.4
11
+
12
+ Rails:
13
+ Enabled: true
14
+
15
+ # It's not a typical Rails application. There is no value in introducing
16
+ # the ApplicationRecord class.
17
+ Rails/ApplicationRecord:
18
+ Enabled: false
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in activeid.gemspec
4
+ gemspec
5
+
6
+ gem "codecov", require: false, group: :test
7
+ gem "simplecov", require: false, group: :test
@@ -0,0 +1,19 @@
1
+ The MIT License (MIT)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,411 @@
1
+ = ActiveID: binary UUIDs for ActiveRecord
2
+
3
+ // Document setup
4
+ :toc:
5
+ :toc-placement!:
6
+ :source-language: ruby
7
+ :source-highlighter: pygments
8
+ :pygments-style: native
9
+ :pygments-linenums-mode: inline
10
+
11
+ // Admonition captions in GitHub (here Emoji)
12
+ // See: https://github.com/ikatyang/emoji-cheat-sheet/blob/master/README.md
13
+ ifdef::env-github[]
14
+ :tip-caption: :bulb:
15
+ :note-caption: :information_source:
16
+ :important-caption: :heavy_exclamation_mark:
17
+ :caution-caption: :fire:
18
+ :warning-caption: :warning:
19
+ endif::[]
20
+
21
+ // Links
22
+ :dce_uuids: https://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01
23
+ :gem_original: https://rubygems.org/gems/activeid
24
+ :gem_uuidtools: https://github.com/sporkmonger/uuidtools
25
+ :maria_jira_uuid_func: https://jira.mariadb.org/browse/MDEV-15854
26
+ :mit_lic: https://opensource.org/licenses/MIT
27
+ :mysql_uuid: https://mysqlserverteam.com/mysql-8-0-uuid-support/
28
+ :examples: https://github.com/riboseinc/activeid/tree/master/examples
29
+ :percona_blog: https://www.percona.com/blog/2014/12/19/store-uuid-optimized-way/
30
+ :rails_api_type_register: https://api.rubyonrails.org/classes/ActiveRecord/Type.html#method-c-register
31
+ :rfc_uuids: https://tools.ietf.org/html/rfc4122
32
+ :ribose: https://www.ribose.com
33
+ :xkcd_comic: https://xkcd.com/927/
34
+
35
+ // Badges
36
+ ifdef::env-github[]
37
+ image:https://img.shields.io/travis/riboseinc/activeid/master.svg[
38
+ Build Status, link="https://travis-ci.org/riboseinc/activeid/branches"]
39
+ image:https://img.shields.io/codecov/c/github/riboseinc/activeid/master.svg[
40
+ Test Coverage, link="https://codecov.io/gh/riboseinc/activeid"]
41
+ image:https://img.shields.io/codeclimate/maintainability/riboseinc/activeid.svg[
42
+ "Code Climate", link="https://codeclimate.com/github/riboseinc/activeid"]
43
+ endif::[]
44
+
45
+ A modern, performant and database-agnostic solution for storing UUIDs
46
+ in ActiveRecord 5.0+, without any obligatory monkey patches.
47
+
48
+ toc::[]
49
+
50
+ == Rationale
51
+
52
+ If you search for "`uuid`" in RubyGems, you'll get
53
+ https://rubygems.org/search?utf8=%E2%9C%93&query=uuid[142 results] (as of
54
+ January 2019)… Most are outdated, all are far from our needs. Yes,
55
+ {xkcd_comic}[we think we need another one].
56
+
57
+ NOTE: ActiveID has evolved from a popular, but no longer maintained
58
+ {gem_original}[ActiveID] gem, and its forks.
59
+ From 2018 the gem was entirely rewritten to support newer Rails releases, and was finally detached as a fork in 2020 to prevent confusion from users. We thank Nate for bringing ActiveID to life!
60
+
61
+ === Storing UUIDs as binaries in MySQL and SQLite3
62
+
63
+ UUIDs are 16 bytes long, however their human-readable string representation
64
+ takes 36 characters. As a consequence, storing UUIDs in a human-readable
65
+ format is space inefficient. What is worse, whereas table row size is seldom
66
+ a big concern, size of table indices is significant -- the bigger part of given
67
+ index fits in RAM, the faster it works. And UUID columns are commonly indexed…
68
+
69
+ [NOTE]
70
+ ================================================================================
71
+ Another performance boost for UUIDs version 1 can be achieved by bits
72
+ rearrangement. This is not implemented yet, see
73
+ https://github.com/riboseinc/activeid/issues/43[issue #43].
74
+ ================================================================================
75
+
76
+ This gem brings an easy-to-use ability to efficiently store UUIDs in databases
77
+ which do not provide a dedicated UUID data type (i.e. MySQL, MariaDB, SQLite3,
78
+ etc.).
79
+
80
+ === Database-agnosticism
81
+
82
+ This gem provides a uniform API for storing UUIDs in database, be it MariaDB,
83
+ MySQL, SQLite3 (in binary or string format), or PostgreSQL (native data type).
84
+ This is especially important when using it as a dependency of another gem.
85
+
86
+ === Monkey patching is optional
87
+
88
+ No core feature relies on Rails monkey patching. Monkey patches can interfere
89
+ with other gems, and lead to issues. Nevertheless, some convenient features
90
+ (currently, migration methods only) are provided via monkey patching. Enabling
91
+ them is entirely optional, and their absence can be workarounded easily.
92
+
93
+ === Strings are not perfect for UUIDs
94
+
95
+ Although UUIDs are commonly represented as strings, it is beneficial to
96
+ introduce a dedicated class for following reasons:
97
+
98
+ - not every sequence of 16 bytes (or 32 hexadecimal digits) makes a valid UUID
99
+ - UUIDs are not opaque, they have their inner structure which can be accessed
100
+ (reading timestamp from time-based UUIDs is especially useful)
101
+ - using string equality operator for UUID comparison may give wrong results
102
+ (up-cased or lowercased strings, with dashes or without)
103
+
104
+ It is somewhat similar case to URIs, which also can be represented as plain
105
+ strings, but having a dedicated `URI` class is quite convenient.
106
+
107
+ ActiveID uses `UUID` class from {gem_uuidtools}[UUIDTools] gem to represent
108
+ UUIDs.
109
+
110
+ == Usage
111
+
112
+ [TIP]
113
+ ================================================================================
114
+ You may want to explore {examples}[examples] directory, in which typical
115
+ use cases are covered in bit more detail.
116
+ ================================================================================
117
+
118
+ === Installation
119
+
120
+ Depending on you want to apply monkey patches or not, require either
121
+ `activeid/all` (with monkey patches) or `activeid` (without them).
122
+
123
+ For example, if you are using `Gemfile`:
124
+
125
+ [source]
126
+ --------------------------------------------------------------------------------
127
+ gem "activeid" # without monkey patches
128
+ # or
129
+ gem "activeid", require: "activeid/all" # with monkey patches
130
+ --------------------------------------------------------------------------------
131
+
132
+ Depending on your needs, you can also pick monkey patches selectively -- just
133
+ take a look at the contents of `lib/activeid/all.rb`. However, currently
134
+ it is not very useful, as there is very little to choose from.
135
+
136
+ === Adding UUIDs to models
137
+
138
+ ActiveID relies on ActiveRecord's attributes API. Two attribute types are
139
+ defined: `StringUUID` and `BinaryUUID`.
140
+
141
+ StringUUID serializes UUIDs as 36 characters long strings. It is compatible
142
+ with textual SQL types, e.g. `VARCHAR(36)`, and more importantly, with
143
+ PostgreSQL-specific `UUID` type.
144
+
145
+ BinaryUUID serializes UUIDs as 16 bytes long binaries, which can be stored
146
+ in binary columns, e.g. `BLOB(16)` in SQLite3 or `VARBINARY(16)` in MySQL.
147
+ However, it is not compatible with PostgreSQL at all due to syntax differences.
148
+ See "<<Choosing between string and binary serialization>>" section for a brief
149
+ explanation of pros and cons of both approaches.
150
+
151
+ Whichever attribute type you prefer to use, an `ActiveID::Model` module must
152
+ be included in model.
153
+
154
+ For example, following example model stores two UUID attributes: `id`,
155
+ and `thread_id` as binaries.
156
+
157
+ [source]
158
+ --------------------------------------------------------------------------------
159
+ class Work < ActiveRecord::Base
160
+ include ActiveID::Model
161
+ attribute :id, ActiveID::Type::BinaryUUID.new
162
+ attribute :author_id, ActiveID::Type::BinaryUUID.new
163
+ belongs_to :author
164
+ end
165
+ --------------------------------------------------------------------------------
166
+
167
+ === Database migrations
168
+
169
+ A convenience `#uuid` method is added via monkey patching to Active Record's
170
+ `Table` and `TableDefinition` classes.
171
+
172
+ - In MySQL adapter, it stands for a `VARBINARY(16)` column.
173
+ - In SQLite3 adapter, it stands for a `BLOB(16)` column.
174
+ - In PostgreSQL adapter, it is shadowed by a stock Rails method
175
+ `::ActiveRecord::ConnectionAdapters::PostgreSQL::ColumnMethods:uuid`, which
176
+ stands for a `UUID` column.
177
+
178
+ If you want to use UUID column in your primary key, pass `:id => false` option
179
+ to `create_table` method and `:primary_key => true` to column definition.
180
+
181
+ For example:
182
+
183
+ [source]
184
+ --------------------------------------------------------------------------------
185
+ class CreateWorks < ActiveRecord::Migration
186
+ def change
187
+ create_table :works, id: false, force: true do |t|
188
+ t.uuid :id, primary_key: true
189
+ t.uuid :author_id, index: true
190
+ t.string :title
191
+ t.timestamps
192
+ end
193
+ end
194
+ end
195
+ --------------------------------------------------------------------------------
196
+
197
+ Alternatively, if monkey patches are disabled, `#uuid` method can be substituted
198
+ with `#binary` in MySQL and SQLite3 adapters. Following snippet is equivalent
199
+ to the above one in these two adapters. Please note `:limit => 16`, which is
200
+ passed as an option.
201
+
202
+ [source]
203
+ --------------------------------------------------------------------------------
204
+ class CreateWorks < ActiveRecord::Migration
205
+ def change
206
+ create_table :works, id: false, force: true do |t|
207
+ t.binary :id, limit: 16, primary_key: true
208
+ t.binary :author_id, limit: 16, index: true
209
+ t.string :title
210
+ t.timestamps
211
+ end
212
+ end
213
+ end
214
+ --------------------------------------------------------------------------------
215
+
216
+ === Registering UUID types in Active Record's type registry
217
+
218
+ For convenience, Active UUID types can be added to Active Record's type
219
+ registry. Then you can reference them in your models with a symbol.
220
+ See {rails_api_type_register}[Rails API docs] for detailed information.
221
+
222
+ For example, following will register `ActiveID::Type::BinaryUUID` at `:uuid`
223
+ symbol for all adapters except for PostgreSQL, in which this symbol is already
224
+ taken:
225
+
226
+ [source]
227
+ --------------------------------------------------------------------------------
228
+ ActiveRecord::Type.register(
229
+ :uuid,
230
+ ActiveID::Type::BinaryUUID,
231
+ )
232
+ --------------------------------------------------------------------------------
233
+
234
+ With above set, only symbol needs to be specified in attribute declaration,
235
+ as in following example:
236
+
237
+ [source]
238
+ --------------------------------------------------------------------------------
239
+ class Author < ActiveRecord::Base
240
+ include ActiveID::Model
241
+ attribute :id, :uuid
242
+ end
243
+ --------------------------------------------------------------------------------
244
+
245
+ It is also possible to override `:uuid` in PostgreSQL adapter:
246
+
247
+ [source]
248
+ --------------------------------------------------------------------------------
249
+ ActiveRecord::Type.register(
250
+ :uuid,
251
+ ActiveID::Type::StringUUID,
252
+ adapter: :postgresql,
253
+ override: true,
254
+ )
255
+ --------------------------------------------------------------------------------
256
+
257
+ [CAUTION]
258
+ ================================================================================
259
+ Overriding standard attribute types may cause other gems to behave abnormally.
260
+ ================================================================================
261
+
262
+ === Using UUIDs as primary keys
263
+
264
+ When model's primary key is a UUID, Active UUID automatically generates its
265
+ value as a version 1, 4, or 5 UUID:
266
+
267
+ - Version 1 UUIDs store timestamp of their creation, and are monotonically
268
+ increasing in time. This is very advantageous in some use cases.
269
+ - Version 4 UUIDs are pseudo-randomly generated.
270
+ - Version 5 UUIDs are generated deterministically via SHA-1 hashing from values
271
+ of specified attributes, and UUID namespace. They are well-suited for natural
272
+ keys.
273
+
274
+ UUIDs of all versions can be explicitly assigned to attributes.
275
+
276
+ ==== Random primary keys (version 4 UUIDs)
277
+
278
+ If model's primary key is a UUID, a version 4 UUID is generated by default.
279
+ For example:
280
+
281
+ [source]
282
+ --------------------------------------------------------------------------------
283
+ class Author < ActiveRecord::Base
284
+ include ActiveID::Model
285
+ attribute :id, ActiveID::Type::StringUUID.new
286
+ end
287
+ --------------------------------------------------------------------------------
288
+
289
+ === Time-based primary keys (version 1 UUIDs)
290
+
291
+ They are enabled for model's primary key with `#uuid_generator` method.
292
+ For example:
293
+
294
+ [source]
295
+ --------------------------------------------------------------------------------
296
+ class Author < ActiveRecord::Base
297
+ include ActiveID::Model
298
+ attribute :id, ActiveID::Type::StringUUID.new
299
+ uuid_generator :time
300
+ end
301
+ --------------------------------------------------------------------------------
302
+
303
+ === Name-based primary keys a.k.a. natural keys (version 5 UUIDs)
304
+
305
+ They are enabled for model's primary key by passing attribute names to
306
+ `#natural_key` method, and namespace to `#uuid_namespace` method. The latter
307
+ method accepts only UUIDs, either in string format, or a `UUIDTools::UUID`
308
+ object. If `#uuid_namespace` method is omitted, then ISO OID namespace is used.
309
+
310
+ In following example, a natural key in `a6908e1e-5493-4c55-a11d-cd8445654de6`
311
+ namespace will be build of values of `author_id`, and `title` attributes.
312
+
313
+ [source]
314
+ --------------------------------------------------------------------------------
315
+ class Work < ActiveRecord::Base
316
+ include ActiveID::Model
317
+ attribute :id, ActiveID::Type::BinaryUUID.new
318
+ attribute :author_id, ActiveID::Type::BinaryUUID.new
319
+ belongs_to :author
320
+ natural_key :author_id, :title
321
+ uuid_namespace "a6908e1e-5493-4c55-a11d-cd8445654de6"
322
+ end
323
+ --------------------------------------------------------------------------------
324
+
325
+ == Choosing between string and binary serialization
326
+
327
+ ActiveID allows you to choose between two ways of UUID serialization:
328
+ as 36 characters long string, or as 16 bytes long binary.
329
+
330
+ In PostgreSQL, the answer is easy: you should always choose string
331
+ serialization. It perfectly works with native `UUID` data type, which is
332
+ a non-standard feature of PostgreSQL. It also works with textual data types
333
+ (i.e. `VARCHAR`, `TEXT`, etc.), but a `UUID` type seems to be a better choice
334
+ for performance reasons. Because of special syntax requirements in PostgreSQL,
335
+ it does not work with binary types (i.e. `BYTEA`), however it seems to be
336
+ a neglect-able issue, as `UUID` type is more suitable. Please open an issue
337
+ if you disagree.
338
+
339
+ In other RDBSs, either human-readability, or performance must be sacrificed.
340
+
341
+ With binary serialization, UUIDs are stored in a space-efficient way as 16 bytes
342
+ long binaries. This is especially beneficial when column is indexed, which is
343
+ a very common case. Smaller value size means that a bigger piece of index can
344
+ be kept in RAM, which often leads to a significant performance boost.
345
+ The downside is that this representation is difficult to read for humans, who
346
+ access serialized values outside Rails (e.g. in a database console, or in
347
+ database logs). See also an excellent article "link:{percona_blog}[Store UUID
348
+ in an optimized way]" in Percona blog for more information about storing UUIDs
349
+ as binaries.
350
+
351
+ With string serialization, UUIDs are stored as 36 characters long strings, which
352
+ consist only of lowercase hexadecimal digits, and dashes
353
+ (`xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`). They are easy to read for humans, but
354
+ may hamper performance of indices, especially in case of large tables.
355
+
356
+ === Reading binary UUIDs in a database console
357
+
358
+ MySQL features a {mysql_uuid}[`BIN_TO_UUID()`] function, which converts binary
359
+ UUIDs to their human-readable string representation. There is
360
+ {maria_jira_uuid_func}[a feature request] to add a similar feature to MariaDB.
361
+
362
+ == Contributing
363
+
364
+ First, thank you for contributing! We love pull requests from everyone.
365
+ By participating in this project, you hereby grant
366
+ https://www.ribose.com[Ribose Inc.] the right to grant or transfer an
367
+ unlimited number of non exclusive licenses or sub-licenses to third
368
+ parties, under the copyright covering the contribution to use the
369
+ contribution by all means.
370
+
371
+ Here are a few technical guidelines to follow:
372
+
373
+ 1. Open an https://github.com/riboseinc/enmail/issues[issue] to discuss
374
+ a new feature.
375
+ 2. Write tests to support your new feature.
376
+ 3. Make sure the entire test suite passes locally and on CI.
377
+ 4. Open a Pull Request.
378
+ 5. After receiving feedback, perform
379
+ https://help.github.com/articles/about-git-rebase/[an interactive rebase]
380
+ on your branch, in order to create a series of cohesive commits with
381
+ descriptive messages.
382
+ 6. Party!
383
+
384
+ == Credits
385
+
386
+ This gem is developed, maintained and funded by {ribose}[Ribose Inc.]
387
+
388
+ The {gem_original}[ActiveID] gem which ActiveID was based on has been developed by Nate Murray
389
+ with notable help of:
390
+
391
+ * pyromaniac
392
+ * Andrew Kane
393
+ * Devin Foley
394
+ * Arkadiy Zabazhanov
395
+ * Jean-Denis Koeck
396
+ * Florian Staudacher
397
+ * Schuyler Erle
398
+ * Florian Schwab
399
+ * Thomas Guillory
400
+ * Daniel Blanco Rojas
401
+ * Olivier Amblet
402
+
403
+ == License
404
+
405
+ The gem is available as open source under the terms of the {mit_lic}[MIT
406
+ License].
407
+
408
+ == See also
409
+
410
+ * {rfc_uuids}[RFC 4122] "A Universally Unique IDentifier (UUID) URN Namespace"
411
+ * {gem_original}[ActiveID] gem (supports Rails < 5)