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,3 @@
1
+ module ActiveID
2
+ VERSION = "0.6.1".freeze
3
+ end
@@ -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
@@ -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,2 @@
1
+ log_path = File.expand_path("../../log/test.log", __dir__)
2
+ ActiveRecord::Base.logger = Logger.new(log_path)
@@ -0,0 +1,3 @@
1
+ db_config_path = File.expand_path("database.yml", __dir__)
2
+ ActiveRecord::Base.configurations = YAML::safe_load(File.read(db_config_path))
3
+ ActiveRecord::Base.establish_connection(ENV["DB"].to_sym)
@@ -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
@@ -0,0 +1,12 @@
1
+ sqlite3:
2
+ adapter: sqlite3
3
+ database: ":memory:"
4
+ postgresql:
5
+ adapter: postgresql
6
+ host: localhost
7
+ database: activeid_test
8
+ mysql:
9
+ adapter: mysql2
10
+ username: root
11
+ database: activeid_test
12
+ encoding: utf8