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.
- checksums.yaml +7 -0
- data/.editorconfig +21 -0
- data/.github/workflows/macos.yml +45 -0
- data/.github/workflows/ubuntu.yml +47 -0
- data/.github/workflows/windows.yml +40 -0
- data/.gitignore +195 -0
- data/.hound.yml +3 -0
- data/.rubocop.yml +18 -0
- data/Gemfile +7 -0
- data/LICENSE.md +19 -0
- data/README.adoc +411 -0
- data/Rakefile +27 -0
- data/activeid.gemspec +42 -0
- data/examples/name_based_uuids.rb +92 -0
- data/examples/registering_active_record_type.rb +74 -0
- data/examples/storing_uuids_as_binaries.rb +88 -0
- data/examples/storing_uuids_as_strings.rb +81 -0
- data/examples/storing_uuids_natively.rb +93 -0
- data/examples/time_based_uuids.rb +58 -0
- data/examples/using_migrations.rb +50 -0
- data/gemfiles/Rails-5_0.gemfile +8 -0
- data/gemfiles/Rails-5_1.gemfile +8 -0
- data/gemfiles/Rails-5_2.gemfile +8 -0
- data/gemfiles/Rails-head.gemfile +8 -0
- data/lib/active_id.rb +12 -0
- data/lib/active_id/all.rb +2 -0
- data/lib/active_id/connection_patches.rb +65 -0
- data/lib/active_id/model.rb +55 -0
- data/lib/active_id/railtie.rb +12 -0
- data/lib/active_id/type.rb +100 -0
- data/lib/active_id/utils.rb +77 -0
- data/lib/active_id/version.rb +3 -0
- data/spec/integration/examples_for_uuid_models.rb +92 -0
- data/spec/integration/examples_for_uuid_models_having_namespaces.rb +12 -0
- data/spec/integration/examples_for_uuid_models_having_natural_keys.rb +11 -0
- data/spec/integration/migrations_spec.rb +92 -0
- data/spec/integration/model_without_uuids_spec.rb +44 -0
- data/spec/integration/no_patches_spec.rb +26 -0
- data/spec/integration/storing_uuids_as_binaries_spec.rb +34 -0
- data/spec/integration/storing_uuids_as_strings_spec.rb +22 -0
- data/spec/spec_helper.rb +64 -0
- data/spec/support/0_logger.rb +2 -0
- data/spec/support/1_db_connection.rb +3 -0
- data/spec/support/2_db_cleaner.rb +14 -0
- data/spec/support/database.yml +12 -0
- data/spec/support/fabricators.rb +15 -0
- data/spec/support/models.rb +41 -0
- data/spec/support/schema.rb +38 -0
- data/spec/unit/attribute_type_spec.rb +70 -0
- data/spec/unit/utils_spec.rb +97 -0
- metadata +313 -0
@@ -0,0 +1,92 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.shared_examples "model with UUIDs" do
|
4
|
+
let!(:article) { Fabricate model.name.underscore }
|
5
|
+
let!(:id) { article.id }
|
6
|
+
subject { model }
|
7
|
+
|
8
|
+
context "model" do
|
9
|
+
its(:primary_key) { is_expected.to eq("id") }
|
10
|
+
its(:all) { is_expected.to eq([article]) }
|
11
|
+
its(:first) { is_expected.to eq(article) }
|
12
|
+
end
|
13
|
+
|
14
|
+
context "existance" do
|
15
|
+
subject { article }
|
16
|
+
its(:id) { is_expected.to be_a UUIDTools::UUID }
|
17
|
+
end
|
18
|
+
|
19
|
+
context "interpolation" do
|
20
|
+
specify { model.where("id = :id", id: quoted_article_id) }
|
21
|
+
end
|
22
|
+
|
23
|
+
context "batch interpolation" do
|
24
|
+
before { model.update_all(["title = CASE WHEN id = :id THEN 'Passed' ELSE 'Nothing' END", id: quoted_article_id]) }
|
25
|
+
specify { expect(article.reload.title).to eq("Passed") }
|
26
|
+
end
|
27
|
+
|
28
|
+
context ".find" do
|
29
|
+
specify { expect(model.find(article.id)).to eq(article) }
|
30
|
+
specify { expect(model.find(id)).to eq(article) }
|
31
|
+
specify { expect(model.find(id.to_s)).to eq(article) }
|
32
|
+
specify { expect(model.find(id.raw)).to eq(article) }
|
33
|
+
end
|
34
|
+
|
35
|
+
context ".where" do
|
36
|
+
specify { expect(model.where(id: article).first).to eq(article) }
|
37
|
+
specify { expect(model.where(id: id).first).to eq(article) }
|
38
|
+
specify { expect(model.where(id: id.to_s).first).to eq(article) }
|
39
|
+
specify { expect(model.where(id: id.raw).first).to eq(article) }
|
40
|
+
end
|
41
|
+
|
42
|
+
context "#destroy" do
|
43
|
+
subject { article }
|
44
|
+
its(:delete) { is_expected.to be_truthy }
|
45
|
+
its(:destroy) { is_expected.to be_truthy }
|
46
|
+
end
|
47
|
+
|
48
|
+
context "#reload" do
|
49
|
+
subject { article }
|
50
|
+
its(:'reload.id') { is_expected.to eq(id) }
|
51
|
+
specify { expect(subject.reload(select: :another_uuid).id).to eq(id) }
|
52
|
+
end
|
53
|
+
|
54
|
+
shared_examples "for UUID attribute" do
|
55
|
+
let(:attr_type) { model.attribute_types[attr_name.to_s] }
|
56
|
+
let(:attr_getter) { article.method(attr_name) }
|
57
|
+
let(:attr_setter) { article.method("#{attr_name}=") }
|
58
|
+
|
59
|
+
let(:uuid) { UUIDTools::UUID.random_create }
|
60
|
+
|
61
|
+
it "has proper ActiveRecord type" do
|
62
|
+
expect(attr_type).to be_kind_of(ActiveID::Type::Base)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "allows to assign UUID instance" do
|
66
|
+
attr_setter.(uuid)
|
67
|
+
expect(attr_getter.()).to be_an(UUIDTools::UUID) & eq(uuid)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "allows to assign UUID string" do
|
71
|
+
attr_setter.(uuid.to_s)
|
72
|
+
expect(attr_getter.()).to be_an(UUIDTools::UUID) & eq(uuid)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "persists UUID in database" do
|
76
|
+
attr_setter.(uuid)
|
77
|
+
article.save
|
78
|
+
article.reload
|
79
|
+
expect(attr_getter.()).to be_an(UUIDTools::UUID) & eq(uuid)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "UUID attribute which is a model's primary key" do
|
84
|
+
let(:attr_name) { :id }
|
85
|
+
include_examples "for UUID attribute"
|
86
|
+
end
|
87
|
+
|
88
|
+
describe "UUID attribute which isn't a model's primary key" do
|
89
|
+
let(:attr_name) { :another_uuid }
|
90
|
+
include_examples "for UUID attribute"
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.shared_examples "model with UUIDs and a namespace" do
|
4
|
+
let!(:article) { Fabricate model.name.underscore }
|
5
|
+
let!(:id) { article.id }
|
6
|
+
let!(:namespace) { model._uuid_namespace }
|
7
|
+
let!(:uuid) { UUIDTools::UUID.sha1_create(namespace, article.title) }
|
8
|
+
subject { article }
|
9
|
+
context "natural_key_with_namespace" do
|
10
|
+
its(:id) { is_expected.to eq(uuid) }
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.shared_examples "model with UUIDs and a natural key" do
|
4
|
+
let!(:article) { Fabricate model.name.underscore }
|
5
|
+
let!(:id) { article.id }
|
6
|
+
let!(:uuid) { UUIDTools::UUID.sha1_create(UUIDTools::UUID_OID_NAMESPACE, article.title) }
|
7
|
+
subject { article }
|
8
|
+
context "natural_key" do
|
9
|
+
its(:id) { is_expected.to eq(uuid) }
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe "migration methods" do
|
4
|
+
before do
|
5
|
+
if ENV.fetch("NO_PATCHES", false)
|
6
|
+
skip "Migrations are unavailable without monkey patching"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
shared_examples "active record examples" do |can_change_column_to_uuid: true|
|
11
|
+
let(:connection) { ActiveRecord::Base.connection }
|
12
|
+
let(:table_name) { :test_uuid_field_creation }
|
13
|
+
let(:table_columns) { connection.columns(table_name) }
|
14
|
+
let(:column) { table_columns.detect { |c| c.name.to_sym == column_name } }
|
15
|
+
|
16
|
+
def table_exists?(table_name)
|
17
|
+
connection.respond_to?(:data_source_exists?) ?
|
18
|
+
connection.data_source_exists?(table_name) :
|
19
|
+
connection.table_exists?(table_name)
|
20
|
+
end
|
21
|
+
|
22
|
+
around do |example|
|
23
|
+
connection.drop_table(table_name) if table_exists?(table_name)
|
24
|
+
connection.create_table(table_name)
|
25
|
+
expect(table_exists?(table_name)).to be(true)
|
26
|
+
example.run
|
27
|
+
connection.drop_table(table_name)
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#add_column" do
|
31
|
+
let(:column_name) { :uuid_column }
|
32
|
+
|
33
|
+
def perform
|
34
|
+
connection.add_column(table_name, column_name, :uuid)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "adds a column of correct SQL type to the table" do
|
38
|
+
perform
|
39
|
+
expect(connection.column_exists?(table_name, column_name)).to be(true)
|
40
|
+
expect(column.sql_type).to eq(sql_type_for_uuid)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "#change_column" do
|
45
|
+
let(:column_name) { :string_col }
|
46
|
+
|
47
|
+
before do
|
48
|
+
connection.add_column(table_name, column_name, :string)
|
49
|
+
end
|
50
|
+
|
51
|
+
def perform
|
52
|
+
connection.change_column(table_name, column_name, :uuid)
|
53
|
+
end
|
54
|
+
|
55
|
+
if can_change_column_to_uuid
|
56
|
+
it "changes column type to a proper one for UUID storage" do
|
57
|
+
perform
|
58
|
+
expect(column.sql_type).to eq(sql_type_for_uuid)
|
59
|
+
end
|
60
|
+
else
|
61
|
+
it "raises exception when attempting to change the column type " +
|
62
|
+
"to one proper for UUID storage" do
|
63
|
+
expect { perform }.to raise_exception(ActiveRecord::StatementInvalid)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context "with SQLite3 backend" do
|
70
|
+
before { skip "backend unavailable" unless ENV["DB"] == "sqlite3" }
|
71
|
+
|
72
|
+
let(:sql_type_for_uuid) { "binary(16)" }
|
73
|
+
|
74
|
+
include_examples "active record examples"
|
75
|
+
end
|
76
|
+
|
77
|
+
context "with MySQL backend" do
|
78
|
+
before { skip "backend unavailable" unless ENV["DB"] == "mysql" }
|
79
|
+
|
80
|
+
let(:sql_type_for_uuid) { "binary(16)" }
|
81
|
+
|
82
|
+
include_examples "active record examples"
|
83
|
+
end
|
84
|
+
|
85
|
+
context "with PostgreSQL backend" do
|
86
|
+
before { skip "backend unavailable" unless ENV["DB"] == "postgresql" }
|
87
|
+
|
88
|
+
let(:sql_type_for_uuid) { "uuid" }
|
89
|
+
|
90
|
+
include_examples "active record examples", can_change_column_to_uuid: false
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe Article do
|
4
|
+
let!(:article) { Fabricate :article }
|
5
|
+
let(:id) { article.id }
|
6
|
+
let(:model) { Article }
|
7
|
+
subject { model }
|
8
|
+
|
9
|
+
context "model" do
|
10
|
+
its(:all) { is_expected.to eq([article]) }
|
11
|
+
its(:first) { is_expected.to eq(article) }
|
12
|
+
end
|
13
|
+
|
14
|
+
context "existance" do
|
15
|
+
subject { article }
|
16
|
+
its(:id) { is_expected.to be_a Integer }
|
17
|
+
end
|
18
|
+
|
19
|
+
context ".find" do
|
20
|
+
specify { expect(model.find(id)).to eq(article) }
|
21
|
+
end
|
22
|
+
|
23
|
+
context ".where" do
|
24
|
+
specify { expect(model.where(id: id).first).to eq(article) }
|
25
|
+
end
|
26
|
+
|
27
|
+
context "#destroy" do
|
28
|
+
subject { article }
|
29
|
+
its(:delete) { is_expected.to be_truthy }
|
30
|
+
its(:destroy) { is_expected.to be_truthy }
|
31
|
+
end
|
32
|
+
|
33
|
+
context "#save" do
|
34
|
+
subject { article }
|
35
|
+
let(:array) { [1, 2, 3] }
|
36
|
+
|
37
|
+
its(:save) { is_expected.to be_truthy }
|
38
|
+
|
39
|
+
context "when change array field" do
|
40
|
+
before { article.some_array = array }
|
41
|
+
its(:save) { is_expected.to be_truthy }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe "without monkey patches" do
|
4
|
+
before do
|
5
|
+
unless ENV.fetch("NO_PATCHES", false)
|
6
|
+
skip "Monkey patching is enabled in this build"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
it "does not patch ActiveRecord connection" do
|
11
|
+
db_conn = ActiveRecord::Base.connection
|
12
|
+
db_conn_modules = db_conn.singleton_class.ancestors
|
13
|
+
|
14
|
+
expect(db_conn_modules).to all(satisfy { |m| /ActiveID/ !~ m.name })
|
15
|
+
end
|
16
|
+
|
17
|
+
it "does not patch Table class" do
|
18
|
+
table_modules = ActiveRecord::ConnectionAdapters::Table.ancestors
|
19
|
+
expect(table_modules).to all(satisfy { |m| /ActiveID/ !~ m.name })
|
20
|
+
end
|
21
|
+
|
22
|
+
it "does not patch TableDefinition class" do
|
23
|
+
table_modules = ActiveRecord::ConnectionAdapters::TableDefinition.ancestors
|
24
|
+
expect(table_modules).to all(satisfy { |m| /ActiveID/ !~ m.name })
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
Dir["#{__dir__}/examples_*.rb"].each { |f| require f }
|
4
|
+
|
5
|
+
RSpec.describe "storing simple UUIDs as binaries" do
|
6
|
+
if ENV["DB"] == "postgresql"
|
7
|
+
before { skip "Binary UUID storage does not work in PostgreSQL" }
|
8
|
+
end
|
9
|
+
|
10
|
+
include_examples "model with UUIDs" do
|
11
|
+
let(:model) { BinaryUuidArticle }
|
12
|
+
let(:quoted_article_id) { ActiveID.quote_as_binary(article.id) }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
RSpec.describe "storing UUIDs with a natural key as binaries" do
|
17
|
+
if ENV["DB"] == "postgresql"
|
18
|
+
before { skip "Binary UUID storage does not work in PostgreSQL" }
|
19
|
+
end
|
20
|
+
|
21
|
+
include_examples "model with UUIDs and a natural key" do
|
22
|
+
let(:model) { BinaryUuidArticleWithNaturalKey }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
RSpec.describe "storing UUIDs with a namespace as binaries" do
|
27
|
+
if ENV["DB"] == "postgresql"
|
28
|
+
before { skip "Binary UUID storage does not work in PostgreSQL" }
|
29
|
+
end
|
30
|
+
|
31
|
+
include_examples "model with UUIDs and a namespace" do
|
32
|
+
let(:model) { BinaryUuidArticleWithNamespace }
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
Dir["#{__dir__}/examples_*.rb"].each { |f| require f }
|
4
|
+
|
5
|
+
RSpec.describe "storing simple UUIDs as strings" do
|
6
|
+
include_examples "model with UUIDs" do
|
7
|
+
let(:model) { StringUuidArticle }
|
8
|
+
let(:quoted_article_id) { article.id.to_s }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
RSpec.describe "storing UUIDs with a natural key as strings" do
|
13
|
+
include_examples "model with UUIDs and a natural key" do
|
14
|
+
let(:model) { StringUuidArticleWithNaturalKey }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
RSpec.describe "storing UUIDs with a namespace as strings" do
|
19
|
+
include_examples "model with UUIDs and a namespace" do
|
20
|
+
let(:model) { StringUuidArticleWithNamespace }
|
21
|
+
end
|
22
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require "simplecov"
|
2
|
+
SimpleCov.start
|
3
|
+
|
4
|
+
if ENV.key?("CI")
|
5
|
+
require "codecov"
|
6
|
+
SimpleCov.formatter = SimpleCov::Formatter::Codecov
|
7
|
+
end
|
8
|
+
|
9
|
+
ENV["DB"] ||= "sqlite3"
|
10
|
+
|
11
|
+
require "bundler/setup"
|
12
|
+
Bundler.require :development
|
13
|
+
|
14
|
+
if ENV.fetch("NO_PATCHES", false)
|
15
|
+
require "active_id"
|
16
|
+
else
|
17
|
+
require "active_id/all"
|
18
|
+
end
|
19
|
+
|
20
|
+
Dir[File.expand_path("support/**/*.rb", __dir__)].sort.each { |f| require f }
|
21
|
+
|
22
|
+
RSpec.configure do |config|
|
23
|
+
config.expect_with :rspec do |expectations|
|
24
|
+
# This option will default to `true` in RSpec 4. It makes the `description`
|
25
|
+
# and `failure_message` of custom matchers include text for helper methods
|
26
|
+
# defined using `chain`, e.g.:
|
27
|
+
# be_bigger_than(2).and_smaller_than(4).description
|
28
|
+
# # => "be bigger than 2 and smaller than 4"
|
29
|
+
# ...rather than:
|
30
|
+
# # => "be bigger than 2"
|
31
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
32
|
+
end
|
33
|
+
|
34
|
+
config.mock_with :rspec do |mocks|
|
35
|
+
# Prevents you from mocking or stubbing a method that does not exist on
|
36
|
+
# a real object. This is generally recommended, and will default to
|
37
|
+
# `true` in RSpec 4.
|
38
|
+
mocks.verify_partial_doubles = true
|
39
|
+
end
|
40
|
+
|
41
|
+
# Limits the available syntax to the non-monkey patched syntax that is
|
42
|
+
# recommended. For more details, see:
|
43
|
+
# - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
|
44
|
+
# - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
45
|
+
# - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
|
46
|
+
config.disable_monkey_patching!
|
47
|
+
|
48
|
+
# This option will default to `:apply_to_host_groups` in RSpec 4 (and will
|
49
|
+
# have no way to turn it off -- the option exists only for backwards
|
50
|
+
# compatibility in RSpec 3). It causes shared context metadata to be
|
51
|
+
# inherited by the metadata hash of host groups and examples, rather than
|
52
|
+
# triggering implicit auto-inclusion in groups with matching metadata.
|
53
|
+
config.shared_context_metadata_behavior = :apply_to_host_groups
|
54
|
+
|
55
|
+
# This setting enables warnings. It's recommended, but in some cases may
|
56
|
+
# be too noisy due to issues in dependencies.
|
57
|
+
config.warnings = true
|
58
|
+
|
59
|
+
# Seed global randomization in this process using the `--seed` CLI option.
|
60
|
+
# Setting this allows you to use `--seed` to deterministically reproduce
|
61
|
+
# test failures related to randomization by passing the same `--seed` value
|
62
|
+
# as the one that triggered the failure.
|
63
|
+
Kernel.srand config.seed
|
64
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
RSpec.configure do |config|
|
2
|
+
config.before(:suite) do
|
3
|
+
DatabaseCleaner.clean_with(:truncation)
|
4
|
+
DatabaseCleaner.strategy = :transaction
|
5
|
+
end
|
6
|
+
|
7
|
+
config.before(:each) do
|
8
|
+
DatabaseCleaner.start
|
9
|
+
end
|
10
|
+
|
11
|
+
config.after(:each) do
|
12
|
+
DatabaseCleaner.clean
|
13
|
+
end
|
14
|
+
end
|