activeid 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|