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,27 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require "rspec/core"
4
+ require "rspec/core/rake_task"
5
+
6
+ module TempFixForRakeLastComment
7
+ def last_comment
8
+ last_description
9
+ end
10
+ end
11
+ Rake::Application.send :include, TempFixForRakeLastComment
12
+
13
+ RSpec::Core::RakeTask.new(:spec)
14
+
15
+ task default: :test
16
+
17
+ task test: [:spec, :examples]
18
+
19
+ task :examples do
20
+ Dir.glob("examples/**.rb").sort.each do |example|
21
+ example_name = File.basename(example, ".rb").tr("_", " ")
22
+ puts "-" * 40
23
+ puts "Testing example: #{example_name}"
24
+ system "ruby", example
25
+ puts "-" * 40
26
+ end
27
+ end
@@ -0,0 +1,42 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ $:.push File.expand_path("../lib", __FILE__)
4
+ require "active_id/version"
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "activeid"
8
+ s.version = ActiveID::VERSION
9
+ s.authors = ["Ribose Inc."]
10
+ s.email = ["open.source@ribose.com"]
11
+ s.homepage = "https://github.com/riboseinc/activeid"
12
+ s.summary = "Support for binary UUIDs in ActiveRecord"
13
+ s.description = "Support for binary UUIDs in ActiveRecord"
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ s.add_development_dependency "activesupport"
21
+ s.add_development_dependency "database_cleaner"
22
+ s.add_development_dependency "fabrication"
23
+ s.add_development_dependency "forgery"
24
+ s.add_development_dependency "pry"
25
+ s.add_development_dependency "rake"
26
+ s.add_development_dependency "rspec", "~> 3.5"
27
+ s.add_development_dependency "rspec-its"
28
+ s.add_development_dependency "solid_assert", "~> 1.0"
29
+
30
+ if RUBY_ENGINE == "jruby"
31
+ s.add_development_dependency "activerecord-jdbcmysql-adapter"
32
+ s.add_development_dependency "activerecord-jdbcpostgresql-adapter"
33
+ s.add_development_dependency "activerecord-jdbcsqlite3-adapter"
34
+ else
35
+ s.add_development_dependency "mysql2"
36
+ s.add_development_dependency "pg"
37
+ s.add_development_dependency "sqlite3", "~> 1.3.6"
38
+ end
39
+
40
+ s.add_runtime_dependency "activerecord", ">= 5.0", "< 6.0"
41
+ s.add_runtime_dependency "uuidtools"
42
+ end
@@ -0,0 +1,92 @@
1
+ # Name-based UUIDs (version 5) are generated deterministically basing
2
+ # on attribute values and namespace.
3
+
4
+ ENV["DB"] ||= "sqlite3"
5
+
6
+ require "bundler/setup"
7
+ Bundler.require :development
8
+
9
+ require "active_id"
10
+ require_relative "../spec/support/0_logger"
11
+ require_relative "../spec/support/1_db_connection"
12
+
13
+ #### SCHEMA ####
14
+
15
+ ActiveRecord::Schema.define do
16
+ create_table :works, id: false, force: true do |t|
17
+ t.string :id, limit: 36, primary_key: true
18
+ t.string :author_id, limit: 36, index: true
19
+ t.string :title
20
+ t.timestamps
21
+ end
22
+
23
+ create_table :authors, id: false, force: true do |t|
24
+ t.string :id, limit: 36, primary_key: true
25
+ t.string :name
26
+ t.timestamps
27
+ end
28
+ end
29
+
30
+ #### MODELS ####
31
+
32
+ class Work < ActiveRecord::Base
33
+ include ActiveID::Model
34
+ attribute :id, ActiveID::Type::StringUUID.new
35
+ attribute :author_id, ActiveID::Type::StringUUID.new
36
+ belongs_to :author
37
+ natural_key :author_id, :title
38
+ uuid_namespace "a6908e1e-5493-4c55-a11d-cd8445654de6"
39
+ end
40
+
41
+ class Author < ActiveRecord::Base
42
+ include ActiveID::Model
43
+ attribute :id, ActiveID::Type::StringUUID.new
44
+ has_many :works
45
+ natural_key :name
46
+ end
47
+
48
+ #### PROOF ####
49
+
50
+ SolidAssert.enable_assertions
51
+
52
+ poe = Author.create! name: "Edgar Alan Poe"
53
+ thu = Author.create! name: "Thucydides"
54
+
55
+ Work.create! title: "The Raven", author: poe
56
+ Work.create! title: "The Black Cat", author: poe
57
+ Work.create! title: "History of the Peloponnesian War", author: thu
58
+
59
+ assert Author.count == 2
60
+ assert Work.count == 3
61
+
62
+ assert Author.find_by(name: "Edgar Alan Poe").works.size == 2
63
+ assert Author.find_by(name: "Thucydides").works.size == 1
64
+
65
+ assert UUIDTools::UUID === Author.first.id
66
+ assert UUIDTools::UUID === Work.first.id
67
+ assert UUIDTools::UUID === Work.first.author_id
68
+
69
+ # Natural keys (UUIDs version 5) are generated deterministically. Hence,
70
+ # following will succeed despite that id is hardcoded:
71
+ assert Author.find_by(id: "cb23040c-7635-58f2-a703-434c962821c1") == poe
72
+
73
+ # Above UUID has been generated basing on author's name:
74
+ uuid_namespace = UUIDTools::UUID_OID_NAMESPACE
75
+ poe_id = UUIDTools::UUID.sha1_create(uuid_namespace, "Edgar Alan Poe")
76
+ assert Author.find_by(id: poe_id).name == "Edgar Alan Poe"
77
+
78
+ # Natural keys can be generated from more than just one field. Also,
79
+ # a namespace can be set for given model:
80
+ uuid_namespace = UUIDTools::UUID.parse("a6908e1e-5493-4c55-a11d-cd8445654de6")
81
+ raven_id = UUIDTools::UUID.sha1_create(uuid_namespace, "#{poe_id}-The Raven")
82
+ assert Work.find_by(id: raven_id).title == "The Raven"
83
+
84
+ #### PROVE THAT ASSERTIONS WERE WORKING ####
85
+
86
+ begin
87
+ assert 1 == 2
88
+ rescue SolidAssert::AssertionFailedError
89
+ puts "All OK."
90
+ else
91
+ raise "Assertions do not work!"
92
+ end
@@ -0,0 +1,74 @@
1
+ # Active UUID types can be added to Active Record's type registry. This is
2
+ # convenient as you can reference them in your models with a symbol.
3
+ #
4
+ # See Rails API docs for more information about +ActiveRecord::Type.register+:
5
+ # https://api.rubyonrails.org/classes/ActiveRecord/Type.html#method-c-register
6
+
7
+ ENV["DB"] ||= "sqlite3"
8
+
9
+ require "bundler/setup"
10
+ Bundler.require :development
11
+
12
+ require "active_id"
13
+ require_relative "../spec/support/0_logger"
14
+ require_relative "../spec/support/1_db_connection"
15
+
16
+ #### SCHEMA ####
17
+
18
+ ActiveRecord::Schema.define do
19
+ create_table :authors, id: false, force: true do |t|
20
+ if ENV["DB"] == "postgresql"
21
+ t.uuid :id, primary_key: true
22
+ else
23
+ t.binary :id, limit: 16, primary_key: true
24
+ end
25
+ t.string :name
26
+ t.timestamps
27
+ end
28
+ end
29
+
30
+ #### TYPE REGISTRATION ####
31
+
32
+ ActiveRecord::Type.register(
33
+ :uuid,
34
+ ActiveID::Type::BinaryUUID,
35
+ )
36
+
37
+ # In PostgreSQL adapter, +:uuid+ is already registered, but it can be overriden.
38
+ ActiveRecord::Type.register(
39
+ :uuid,
40
+ ActiveID::Type::StringUUID,
41
+ adapter: :postgresql,
42
+ override: true,
43
+ )
44
+
45
+ #### MODELS ####
46
+
47
+ class Author < ActiveRecord::Base
48
+ include ActiveID::Model
49
+ attribute :id, :uuid
50
+ end
51
+
52
+ #### PROOF ####
53
+
54
+ SolidAssert.enable_assertions
55
+
56
+ Author.create! name: "Edgar Alan Poe"
57
+
58
+ assert Author.count == 1
59
+
60
+ if ENV["DB"] == "postgresql"
61
+ assert ActiveID::Type::StringUUID === Author.first.type_for_attribute("id")
62
+ else
63
+ assert ActiveID::Type::BinaryUUID === Author.first.type_for_attribute("id")
64
+ end
65
+
66
+ #### PROVE THAT ASSERTIONS WERE WORKING ####
67
+
68
+ begin
69
+ assert 1 == 2
70
+ rescue SolidAssert::AssertionFailedError
71
+ puts "All OK."
72
+ else
73
+ raise "Assertions do not work!"
74
+ end
@@ -0,0 +1,88 @@
1
+ # See README for comparison between string and binary storage.
2
+
3
+ ENV["DB"] ||= "sqlite3"
4
+
5
+ if ENV["DB"] == "postgresql"
6
+ puts <<~MESSAGE
7
+ Example irrelevant for selected database (#{ENV['DB']}).
8
+ Storing UUIDs as binaries is not compatible with PostgreSQL adapter.
9
+ MESSAGE
10
+ exit(0)
11
+ end
12
+
13
+ require "bundler/setup"
14
+ Bundler.require :development
15
+
16
+ require "active_id"
17
+ require_relative "../spec/support/0_logger"
18
+ require_relative "../spec/support/1_db_connection"
19
+
20
+ #### SCHEMA ####
21
+
22
+ ActiveRecord::Schema.define do
23
+ create_table :works, id: false, force: true do |t|
24
+ t.binary :id, limit: 16, primary_key: true
25
+ t.binary :author_id, limit: 16, index: true
26
+ t.string :title
27
+ t.timestamps
28
+ end
29
+
30
+ create_table :authors, id: false, force: true do |t|
31
+ t.binary :id, limit: 16, primary_key: true
32
+ t.string :name
33
+ t.timestamps
34
+ end
35
+ end
36
+
37
+ #### MODELS ####
38
+
39
+ class Work < ActiveRecord::Base
40
+ include ActiveID::Model
41
+ attribute :id, ActiveID::Type::BinaryUUID.new
42
+ attribute :author_id, ActiveID::Type::BinaryUUID.new
43
+ belongs_to :author
44
+ end
45
+
46
+ class Author < ActiveRecord::Base
47
+ include ActiveID::Model
48
+ attribute :id, ActiveID::Type::BinaryUUID.new
49
+ has_many :works
50
+ end
51
+
52
+ #### PROOF ####
53
+
54
+ SolidAssert.enable_assertions
55
+
56
+ poe = Author.create! name: "Edgar Alan Poe"
57
+ thu = Author.create! name: "Thucydides"
58
+
59
+ Work.create! title: "The Raven", author: poe
60
+ Work.create! title: "The Black Cat", author: poe
61
+ Work.create! title: "History of the Peloponnesian War", author: thu
62
+
63
+ assert Author.count == 2
64
+ assert Work.count == 3
65
+
66
+ assert Author.find_by(name: "Edgar Alan Poe").works.size == 2
67
+ assert Author.find_by(name: "Thucydides").works.size == 1
68
+
69
+ assert UUIDTools::UUID === Author.first.id
70
+ assert UUIDTools::UUID === Work.first.id
71
+ assert UUIDTools::UUID === Work.first.author_id
72
+
73
+ # Version 4 means randomly generated UUID
74
+ assert Author.first.id.version == 4
75
+
76
+ # UUIDs are stored in database as 16 bytes long binaries
77
+ raw_id = Author.first.attributes_before_type_cast["id"]
78
+ assert raw_id.bytes.size == 16
79
+
80
+ #### PROVE THAT ASSERTIONS WERE WORKING ####
81
+
82
+ begin
83
+ assert 1 == 2
84
+ rescue SolidAssert::AssertionFailedError
85
+ puts "All OK."
86
+ else
87
+ raise "Assertions do not work!"
88
+ end
@@ -0,0 +1,81 @@
1
+ # See README for comparison between string and binary storage.
2
+
3
+ ENV["DB"] ||= "sqlite3"
4
+
5
+ require "bundler/setup"
6
+ Bundler.require :development
7
+
8
+ require "active_id"
9
+ require_relative "../spec/support/0_logger"
10
+ require_relative "../spec/support/1_db_connection"
11
+
12
+ #### SCHEMA ####
13
+
14
+ ActiveRecord::Schema.define do
15
+ create_table :works, id: false, force: true do |t|
16
+ t.string :id, limit: 36, primary_key: true
17
+ t.string :author_id, limit: 36, index: true
18
+ t.string :title
19
+ t.timestamps
20
+ end
21
+
22
+ create_table :authors, id: false, force: true do |t|
23
+ t.string :id, limit: 36, primary_key: true
24
+ t.string :name
25
+ t.timestamps
26
+ end
27
+ end
28
+
29
+ #### MODELS ####
30
+
31
+ class Work < ActiveRecord::Base
32
+ include ActiveID::Model
33
+ attribute :id, ActiveID::Type::StringUUID.new
34
+ attribute :author_id, ActiveID::Type::StringUUID.new
35
+ belongs_to :author
36
+ end
37
+
38
+ class Author < ActiveRecord::Base
39
+ include ActiveID::Model
40
+ attribute :id, ActiveID::Type::StringUUID.new
41
+ has_many :works
42
+ end
43
+
44
+ #### PROOF ####
45
+
46
+ SolidAssert.enable_assertions
47
+
48
+ poe = Author.create! name: "Edgar Alan Poe"
49
+ thu = Author.create! name: "Thucydides"
50
+
51
+ Work.create! title: "The Raven", author: poe
52
+ Work.create! title: "The Black Cat", author: poe
53
+ Work.create! title: "History of the Peloponnesian War", author: thu
54
+
55
+ assert Author.count == 2
56
+ assert Work.count == 3
57
+
58
+ assert Author.find_by(name: "Edgar Alan Poe").works.size == 2
59
+ assert Author.find_by(name: "Thucydides").works.size == 1
60
+
61
+ assert UUIDTools::UUID === Author.first.id
62
+ assert UUIDTools::UUID === Work.first.id
63
+ assert UUIDTools::UUID === Work.first.author_id
64
+
65
+ # Version 4 means randomly generated UUID
66
+ assert Author.first.id.version == 4
67
+
68
+ # UUIDs are stored in database as 36 characters long strings
69
+ raw_id = Author.first.attributes_before_type_cast["id"]
70
+ assert raw_id.chars.size == 36
71
+ assert /^[-a-f0-9]*$/ =~ raw_id
72
+
73
+ #### PROVE THAT ASSERTIONS WERE WORKING ####
74
+
75
+ begin
76
+ assert 1 == 2
77
+ rescue SolidAssert::AssertionFailedError
78
+ puts "All OK."
79
+ else
80
+ raise "Assertions do not work!"
81
+ end
@@ -0,0 +1,93 @@
1
+ # PostgreSQL natively features a UUID data type, which offerst great performance
2
+ # without sacrificing human-readability.
3
+
4
+ ENV["DB"] ||= "sqlite3"
5
+
6
+ unless ENV["DB"] == "postgresql"
7
+ puts <<~MESSAGE
8
+ Example irrelevant for selected database (#{ENV['DB']}).
9
+ From supported databases, only PostgreSQL features
10
+ a UUID data type natively.
11
+ MESSAGE
12
+ exit(0)
13
+ end
14
+
15
+ require "bundler/setup"
16
+ Bundler.require :development
17
+
18
+ require "active_id"
19
+ require_relative "../spec/support/0_logger"
20
+ require_relative "../spec/support/1_db_connection"
21
+
22
+ #### SCHEMA ####
23
+
24
+ # PostgreSQL adapter adds #uuid column method to table definitions
25
+
26
+ ActiveRecord::Schema.define do
27
+ create_table :works, id: false, force: true do |t|
28
+ t.uuid :id, primary_key: true
29
+ t.uuid :author_id, index: true
30
+ t.string :title
31
+ t.timestamps
32
+ end
33
+
34
+ create_table :authors, id: false, force: true do |t|
35
+ t.uuid :id, primary_key: true
36
+ t.string :name
37
+ t.timestamps
38
+ end
39
+ end
40
+
41
+ #### MODELS ####
42
+
43
+ class Work < ActiveRecord::Base
44
+ include ActiveID::Model
45
+ attribute :id, ActiveID::Type::StringUUID.new
46
+ attribute :author_id, ActiveID::Type::StringUUID.new
47
+ belongs_to :author
48
+ end
49
+
50
+ class Author < ActiveRecord::Base
51
+ include ActiveID::Model
52
+ attribute :id, ActiveID::Type::StringUUID.new
53
+ has_many :works
54
+ end
55
+
56
+ #### PROOF ####
57
+
58
+ SolidAssert.enable_assertions
59
+
60
+ poe = Author.create! name: "Edgar Alan Poe"
61
+ thu = Author.create! name: "Thucydides"
62
+
63
+ Work.create! title: "The Raven", author: poe
64
+ Work.create! title: "The Black Cat", author: poe
65
+ Work.create! title: "History of the Peloponnesian War", author: thu
66
+
67
+ assert Author.count == 2
68
+ assert Work.count == 3
69
+
70
+ assert Author.find_by(name: "Edgar Alan Poe").works.size == 2
71
+ assert Author.find_by(name: "Thucydides").works.size == 1
72
+
73
+ assert UUIDTools::UUID === Author.first.id
74
+ assert UUIDTools::UUID === Work.first.id
75
+ assert UUIDTools::UUID === Work.first.author_id
76
+
77
+ # Version 4 means randomly generated UUID
78
+ assert Author.first.id.version == 4
79
+
80
+ # UUIDs are stored in database as 36 characters long strings
81
+ raw_id = Author.first.attributes_before_type_cast["id"]
82
+ assert raw_id.chars.size == 36
83
+ assert /^[-a-f0-9]*$/ =~ raw_id
84
+
85
+ #### PROVE THAT ASSERTIONS WERE WORKING ####
86
+
87
+ begin
88
+ assert 1 == 2
89
+ rescue SolidAssert::AssertionFailedError
90
+ puts "All OK."
91
+ else
92
+ raise "Assertions do not work!"
93
+ end