activeid 0.6.1

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