declare_schema 0.5.0 → 0.6.4

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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/declare_schema_build.yml +60 -0
  3. data/.gitignore +1 -0
  4. data/Appraisals +21 -4
  5. data/CHANGELOG.md +46 -0
  6. data/Gemfile +1 -2
  7. data/Gemfile.lock +4 -6
  8. data/README.md +3 -3
  9. data/Rakefile +17 -4
  10. data/bin/declare_schema +1 -1
  11. data/declare_schema.gemspec +1 -1
  12. data/gemfiles/rails_4_mysql.gemfile +22 -0
  13. data/gemfiles/{rails_4.gemfile → rails_4_sqlite.gemfile} +1 -2
  14. data/gemfiles/rails_5_mysql.gemfile +22 -0
  15. data/gemfiles/{rails_5.gemfile → rails_5_sqlite.gemfile} +1 -2
  16. data/gemfiles/rails_6_mysql.gemfile +22 -0
  17. data/gemfiles/{rails_6.gemfile → rails_6_sqlite.gemfile} +2 -3
  18. data/lib/declare_schema/command.rb +10 -3
  19. data/lib/declare_schema/model.rb +1 -1
  20. data/lib/declare_schema/model/field_spec.rb +18 -14
  21. data/lib/declare_schema/model/foreign_key_definition.rb +36 -25
  22. data/lib/declare_schema/model/table_options_definition.rb +8 -6
  23. data/lib/declare_schema/version.rb +1 -1
  24. data/lib/generators/declare_schema/migration/migrator.rb +80 -34
  25. data/spec/lib/declare_schema/field_spec_spec.rb +69 -0
  26. data/spec/lib/declare_schema/generator_spec.rb +4 -2
  27. data/spec/lib/declare_schema/interactive_primary_key_spec.rb +8 -2
  28. data/spec/lib/declare_schema/migration_generator_spec.rb +286 -158
  29. data/spec/lib/declare_schema/model/foreign_key_definition_spec.rb +93 -0
  30. data/spec/lib/declare_schema/model/index_definition_spec.rb +4 -5
  31. data/spec/lib/declare_schema/model/table_options_definition_spec.rb +19 -29
  32. data/spec/lib/generators/declare_schema/migration/migrator_spec.rb +17 -22
  33. data/spec/support/acceptance_spec_helpers.rb +3 -3
  34. metadata +15 -10
  35. data/.travis.yml +0 -37
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../../lib/declare_schema/model/foreign_key_definition'
4
+
5
+ RSpec.describe DeclareSchema::Model::ForeignKeyDefinition do
6
+ before do
7
+ load File.expand_path('../prepare_testapp.rb', __dir__)
8
+
9
+ class Network < ActiveRecord::Base
10
+ fields do
11
+ name :string, limit: 127, index: true
12
+
13
+ timestamps
14
+ end
15
+ end
16
+ end
17
+
18
+ let(:model_class) { Network }
19
+
20
+ describe 'instance methods' do
21
+ let(:connection) { instance_double(ActiveRecord::Base.connection.class) }
22
+ let(:model) { instance_double('Model', table_name: 'models', connection: connection) }
23
+ let(:foreign_key) { :network_id }
24
+ let(:options) { {} }
25
+ subject { described_class.new(model, foreign_key, options)}
26
+
27
+ before do
28
+ allow(connection).to receive(:index_name).with('models', column: 'network_id') { 'on_network_id' }
29
+ end
30
+
31
+ describe '#initialize' do
32
+ it 'normalizes symbols to strings' do
33
+ expect(subject.foreign_key).to eq('network_id')
34
+ expect(subject.parent_table_name).to eq('networks')
35
+ end
36
+
37
+ context 'when most options passed' do
38
+ let(:options) { { parent_table: :networks, foreign_key: :the_network_id, index_name: :index_on_network_id } }
39
+
40
+ it 'normalizes symbols to strings' do
41
+ expect(subject.foreign_key).to eq('network_id')
42
+ expect(subject.foreign_key_name).to eq('the_network_id')
43
+ expect(subject.parent_table_name).to eq('networks')
44
+ expect(subject.foreign_key).to eq('network_id')
45
+ expect(subject.constraint_name).to eq('index_on_network_id')
46
+ expect(subject.on_delete_cascade).to be_falsey
47
+ end
48
+ end
49
+
50
+ context 'when all options passed' do
51
+ let(:foreign_key) { nil }
52
+ let(:options) { { parent_table: :networks, foreign_key: :the_network_id, index_name: :index_on_network_id,
53
+ constraint_name: :constraint_1, dependent: :delete } }
54
+
55
+ it 'normalizes symbols to strings' do
56
+ expect(subject.foreign_key).to be_nil
57
+ expect(subject.foreign_key_name).to eq('the_network_id')
58
+ expect(subject.parent_table_name).to eq('networks')
59
+ expect(subject.constraint_name).to eq('constraint_1')
60
+ expect(subject.on_delete_cascade).to be_truthy
61
+ end
62
+ end
63
+ end
64
+
65
+ describe '#to_add_statement' do
66
+ it 'returns add_foreign_key command' do
67
+ expect(subject.to_add_statement).to eq('add_foreign_key("models", "networks", column: "network_id", name: "on_network_id")')
68
+ end
69
+ end
70
+ end
71
+
72
+ describe 'class << self' do
73
+ let(:connection) { instance_double(ActiveRecord::Base.connection.class) }
74
+ let(:model) { instance_double('Model', table_name: 'models', connection: connection) }
75
+ let(:old_table_name) { 'networks' }
76
+ before do
77
+ allow(connection).to receive(:quote_table_name).with('networks') { 'networks' }
78
+ allow(connection).to receive(:select_rows) { [['CONSTRAINT `constraint` FOREIGN KEY (`network_id`) REFERENCES `networks` (`id`)']] }
79
+ allow(connection).to receive(:index_name).with('models', column: 'network_id') { }
80
+ end
81
+
82
+ describe '.for_model' do
83
+ subject { described_class.for_model(model, old_table_name) }
84
+
85
+ it 'returns new object' do
86
+ expect(subject.size).to eq(1), subject.inspect
87
+ expect(subject.first).to be_kind_of(described_class)
88
+ expect(subject.first.foreign_key).to eq('network_id')
89
+ end
90
+ end
91
+ end
92
+ # TODO: fill out remaining tests
93
+ end
@@ -64,7 +64,7 @@ RSpec.describe DeclareSchema::Model::IndexDefinition do
64
64
  ActiveRecord::Base.connection.execute <<~EOS
65
65
  CREATE TABLE index_definition_test_models (
66
66
  id INTEGER NOT NULL PRIMARY KEY,
67
- name TEXT NOT NULL
67
+ name #{if defined?(Sqlite3) then 'TEXT' else 'VARCHAR(255)' end} NOT NULL
68
68
  )
69
69
  EOS
70
70
  ActiveRecord::Base.connection.execute <<~EOS
@@ -72,8 +72,8 @@ RSpec.describe DeclareSchema::Model::IndexDefinition do
72
72
  EOS
73
73
  ActiveRecord::Base.connection.execute <<~EOS
74
74
  CREATE TABLE index_definition_compound_index_models (
75
- fk1_id INTEGER NULL,
76
- fk2_id INTEGER NULL,
75
+ fk1_id INTEGER NOT NULL,
76
+ fk2_id INTEGER NOT NULL,
77
77
  PRIMARY KEY (fk1_id, fk2_id)
78
78
  )
79
79
  EOS
@@ -99,10 +99,9 @@ RSpec.describe DeclareSchema::Model::IndexDefinition do
99
99
  let(:model_class) { IndexDefinitionCompoundIndexModel }
100
100
 
101
101
  it 'returns the indexes for the model' do
102
- # Simulate MySQL for Rails 4 work-around
103
102
  if Rails::VERSION::MAJOR < 5
104
103
  expect(model_class.connection).to receive(:primary_key).with('index_definition_compound_index_models').and_return(nil)
105
- connection_stub = instance_double(ActiveRecord::ConnectionAdapters::SQLite3Adapter, "connection")
104
+ connection_stub = instance_double(ActiveRecord::Base.connection.class, "connection")
106
105
  expect(connection_stub).to receive(:indexes).
107
106
  with('index_definition_compound_index_models').
108
107
  and_return([DeclareSchema::Model::IndexDefinition.new(model_class, ['fk1_id', 'fk2_id'], name: 'PRIMARY')])
@@ -1,6 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_record/connection_adapters/mysql2_adapter'
3
+ begin
4
+ require 'mysql2'
5
+ require 'active_record/connection_adapters/mysql2_adapter'
6
+ rescue LoadError
7
+ end
4
8
  require_relative '../../../../lib/declare_schema/model/table_options_definition'
5
9
 
6
10
  RSpec.describe DeclareSchema::Model::TableOptionsDefinition do
@@ -22,7 +26,7 @@ RSpec.describe DeclareSchema::Model::TableOptionsDefinition do
22
26
 
23
27
  describe '#to_key' do
24
28
  subject { model.to_key }
25
- it { should eq(["table_options_definition_test_models", "{:charset=>\"utf8\", :collation=>\"utf8_general\"}"]) }
29
+ it { should eq(['table_options_definition_test_models', '{:charset=>"utf8", :collation=>"utf8_general"}']) }
26
30
  end
27
31
 
28
32
  describe '#settings' do
@@ -32,7 +36,7 @@ RSpec.describe DeclareSchema::Model::TableOptionsDefinition do
32
36
 
33
37
  describe '#hash' do
34
38
  subject { model.hash }
35
- it { should eq(["table_options_definition_test_models", "{:charset=>\"utf8\", :collation=>\"utf8_general\"}"].hash) }
39
+ it { should eq(['table_options_definition_test_models', '{:charset=>"utf8", :collation=>"utf8_general"}'].hash) }
36
40
  end
37
41
 
38
42
  describe '#to_s' do
@@ -42,42 +46,28 @@ RSpec.describe DeclareSchema::Model::TableOptionsDefinition do
42
46
 
43
47
  describe '#alter_table_statement' do
44
48
  subject { model.alter_table_statement }
45
- it { should eq('execute "ALTER TABLE \"table_options_definition_test_models\" CHARACTER SET utf8 COLLATE utf8_general;"') }
49
+ it { should match(/execute "ALTER TABLE .*table_options_definition_test_models.* CHARACTER SET utf8 COLLATE utf8_general"/) }
46
50
  end
47
51
  end
48
52
 
49
53
 
50
54
  context 'class << self' do
51
55
  describe '#for_model' do
52
- context 'when using a SQLite connection' do
56
+ context 'when database migrated' do
57
+ let(:options) do
58
+ if defined?(Mysql2)
59
+ { charset: "utf8mb4", collation: "utf8mb4_bin" }
60
+ else
61
+ { }
62
+ end
63
+ end
53
64
  subject { described_class.for_model(model_class) }
54
- it { should eq(described_class.new(model_class.table_name, {})) }
55
- end
56
- # TODO: Convert these tests to run against a MySQL database so that we can
57
- # perform them without mocking out so much
58
- context 'when using a MySQL connection' do
65
+
59
66
  before do
60
- double(ActiveRecord::ConnectionAdapters::Mysql2Adapter).tap do |stub_connection|
61
- expect(stub_connection).to receive(:class).and_return(ActiveRecord::ConnectionAdapters::Mysql2Adapter)
62
- expect(stub_connection).to receive(:current_database).and_return('test_database')
63
- expect(stub_connection).to receive(:quote_string).with('test_database').and_return('test_database')
64
- expect(stub_connection).to receive(:quote_string).with(model_class.table_name).and_return(model_class.table_name)
65
- expect(stub_connection).to(
66
- receive(:select_one).with(<<~EOS)
67
- SELECT CCSA.character_set_name, CCSA.collation_name
68
- FROM information_schema.`TABLES` T, information_schema.`COLLATION_CHARACTER_SET_APPLICABILITY` CCSA
69
- WHERE CCSA.collation_name = T.table_collation AND
70
- T.table_schema = 'test_database' AND
71
- T.table_name = '#{model_class.table_name}';
72
- EOS
73
- .and_return({ "character_set_name" => "utf8", "collation_name" => "utf8_general" })
74
- )
75
- allow(model_class).to receive(:connection).and_return(stub_connection)
76
- end
67
+ generate_migrations '-n', '-m'
77
68
  end
78
69
 
79
- subject { described_class.for_model(model_class) }
80
- it { should eq(described_class.new(model_class.table_name, { charset: "utf8", collation: "utf8_general" })) }
70
+ it { should eq(described_class.new(model_class.table_name, options)) }
81
71
  end
82
72
  end
83
73
  end
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ begin
4
+ require 'mysql2'
5
+ rescue LoadError
6
+ end
3
7
  require 'rails'
4
8
  require 'rails/generators'
5
9
 
@@ -15,25 +19,16 @@ module Generators
15
19
 
16
20
  describe 'format_options' do
17
21
  let(:mysql_longtext_limit) { 0xffff_ffff }
18
-
19
- context 'MySQL' do
20
- before do
21
- expect(::DeclareSchema::Model::FieldSpec).to receive(:mysql_text_limits?).and_return(true)
22
- end
23
-
24
- it 'returns text limits' do
25
- expect(subject.format_options({ limit: mysql_longtext_limit }, :text)).to eq(["limit: #{mysql_longtext_limit}"])
22
+ let(:limit_option) do
23
+ if defined?(Mysql2)
24
+ ["limit: #{mysql_longtext_limit}"]
25
+ else
26
+ []
26
27
  end
27
28
  end
28
29
 
29
- context 'non-MySQL' do
30
- before do
31
- expect(::DeclareSchema::Model::FieldSpec).to receive(:mysql_text_limits?).and_return(false)
32
- end
33
-
34
- it 'returns text limits' do
35
- expect(subject.format_options({ limit: mysql_longtext_limit }, :text)).to eq([])
36
- end
30
+ it 'returns text limits if supported' do
31
+ expect(subject.format_options({ limit: mysql_longtext_limit }, :text)).to eq(limit_option)
37
32
  end
38
33
  end
39
34
 
@@ -47,13 +42,13 @@ module Generators
47
42
  subject { described_class.default_charset }
48
43
 
49
44
  context 'when not explicitly set' do
50
- it { should eq(:utf8mb4) }
45
+ it { should eq("utf8mb4") }
51
46
  end
52
47
 
53
48
  context 'when explicitly set' do
54
- before { described_class.default_charset = :utf8 }
49
+ before { described_class.default_charset = "utf8" }
55
50
  after { described_class.default_charset = described_class::DEFAULT_CHARSET }
56
- it { should eq(:utf8) }
51
+ it { should eq("utf8") }
57
52
  end
58
53
  end
59
54
 
@@ -61,13 +56,13 @@ module Generators
61
56
  subject { described_class.default_collation }
62
57
 
63
58
  context 'when not explicitly set' do
64
- it { should eq(:utf8mb4_general) }
59
+ it { should eq("utf8mb4_bin") }
65
60
  end
66
61
 
67
62
  context 'when explicitly set' do
68
- before { described_class.default_collation = :utf8mb4_general_ci }
63
+ before { described_class.default_collation = "utf8mb4_general_ci" }
69
64
  after { described_class.default_collation = described_class::DEFAULT_COLLATION }
70
- it { should eq(:utf8mb4_general_ci) }
65
+ it { should eq("utf8mb4_general_ci") }
71
66
  end
72
67
  end
73
68
 
@@ -23,7 +23,7 @@ module AcceptanceSpecHelpers
23
23
 
24
24
  def expect_file_to_eq(file_path, expectation)
25
25
  expect(File.exist?(file_path)).to be_truthy
26
- expect(File.read(file_path)).to eq(expectation)
26
+ expect(File.read(file_path).gsub(/require '([^']*)'/, 'require "\1"')).to eq(expectation)
27
27
  end
28
28
 
29
29
  def clean_up_model(model)
@@ -45,13 +45,13 @@ module AcceptanceSpecHelpers
45
45
 
46
46
  class MigrationUpEquals < RSpec::Matchers::BuiltIn::Eq
47
47
  def matches?(subject)
48
- super(subject[0])
48
+ super(subject[0].gsub(/, +([a-z_]+:)/i, ', \1')) # normalize multiple spaces to one
49
49
  end
50
50
  end
51
51
 
52
52
  class MigrationDownEquals < RSpec::Matchers::BuiltIn::Eq
53
53
  def matches?(subject)
54
- super(subject[1])
54
+ super(subject[1].gsub(/, +([a-z_]+:)/i, ', \1')) # normalize multiple spaces to one
55
55
  end
56
56
  end
57
57
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: declare_schema
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Invoca Development adapted from hobo_fields by Tom Locke
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-21 00:00:00.000000000 Z
11
+ date: 2021-02-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -33,12 +33,12 @@ extensions: []
33
33
  extra_rdoc_files: []
34
34
  files:
35
35
  - ".github/dependabot.yml"
36
+ - ".github/workflows/declare_schema_build.yml"
36
37
  - ".github/workflows/gem_release.yml"
37
38
  - ".gitignore"
38
39
  - ".rspec"
39
40
  - ".rubocop.yml"
40
41
  - ".ruby-version"
41
- - ".travis.yml"
42
42
  - Appraisals
43
43
  - CHANGELOG.md
44
44
  - Gemfile
@@ -49,9 +49,12 @@ files:
49
49
  - bin/declare_schema
50
50
  - declare_schema.gemspec
51
51
  - gemfiles/.bundle/config
52
- - gemfiles/rails_4.gemfile
53
- - gemfiles/rails_5.gemfile
54
- - gemfiles/rails_6.gemfile
52
+ - gemfiles/rails_4_mysql.gemfile
53
+ - gemfiles/rails_4_sqlite.gemfile
54
+ - gemfiles/rails_5_mysql.gemfile
55
+ - gemfiles/rails_5_sqlite.gemfile
56
+ - gemfiles/rails_6_mysql.gemfile
57
+ - gemfiles/rails_6_sqlite.gemfile
55
58
  - lib/declare_schema.rb
56
59
  - lib/declare_schema/command.rb
57
60
  - lib/declare_schema/extensions/active_record/fields_declaration.rb
@@ -75,9 +78,11 @@ files:
75
78
  - lib/generators/declare_schema/support/thor_shell.rb
76
79
  - spec/lib/declare_schema/api_spec.rb
77
80
  - spec/lib/declare_schema/field_declaration_dsl_spec.rb
81
+ - spec/lib/declare_schema/field_spec_spec.rb
78
82
  - spec/lib/declare_schema/generator_spec.rb
79
83
  - spec/lib/declare_schema/interactive_primary_key_spec.rb
80
84
  - spec/lib/declare_schema/migration_generator_spec.rb
85
+ - spec/lib/declare_schema/model/foreign_key_definition_spec.rb
81
86
  - spec/lib/declare_schema/model/index_definition_spec.rb
82
87
  - spec/lib/declare_schema/model/table_options_definition_spec.rb
83
88
  - spec/lib/declare_schema/prepare_testapp.rb
@@ -89,7 +94,7 @@ homepage: https://github.com/Invoca/declare_schema
89
94
  licenses: []
90
95
  metadata:
91
96
  allowed_push_host: https://rubygems.org
92
- post_install_message:
97
+ post_install_message:
93
98
  rdoc_options: []
94
99
  require_paths:
95
100
  - lib
@@ -105,7 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
105
110
  version: 1.3.6
106
111
  requirements: []
107
112
  rubygems_version: 3.0.3
108
- signing_key:
113
+ signing_key:
109
114
  specification_version: 4
110
- summary: Database migration generator for Rails
115
+ summary: Database schema declaration and migration generator for Rails
111
116
  test_files: []
data/.travis.yml DELETED
@@ -1,37 +0,0 @@
1
- ---
2
- dist: trusty
3
- os: linux
4
- language: ruby
5
- cache: bundler
6
- rvm:
7
- - 2.4.5
8
- - 2.5.8
9
- - 2.6.5
10
- - 2.7.1
11
- - ruby-head
12
- gemfile:
13
- - gemfiles/rails_4.gemfile
14
- - gemfiles/rails_5.gemfile
15
- - gemfiles/rails_6.gemfile
16
- jobs:
17
- fast_finish: false
18
- exclude:
19
- - gemfile: gemfiles/rails_4.gemfile
20
- rvm: 2.7.1
21
- - gemfile: gemfiles/rails_5.gemfile
22
- rvm: 2.4.5
23
- - gemfile: gemfiles/rails_6.gemfile
24
- rvm: 2.4.5
25
- allow_failures:
26
- - rvm: ruby-head
27
- before_install:
28
- - rm -f /home/travis/.rvm/rubies/ruby-2.*/lib/ruby/gems/2.*/specifications/default/bundler-2.*.gemspec
29
- - echo y | rvm @global do gem install bundler -v 1.17.3 --force --default
30
- - gem install bundler -v 1.17.3 --force --default
31
- - gem install bundler -v 1.17.3
32
- install:
33
- - bundle --version
34
- - bundle install --jobs=3 --retry=3 --path=${BUNDLE_PATH:-vendor/bundle}
35
- script:
36
- - bundle exec rake test:prepare_testapp[force]
37
- - bundle exec rake test:all < test_responses.txt