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