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