declare_schema 0.5.0 → 0.6.4

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