declare_schema 0.8.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/declare_schema_build.yml +1 -1
  3. data/CHANGELOG.md +37 -0
  4. data/Gemfile.lock +1 -1
  5. data/README.md +102 -14
  6. data/lib/declare_schema.rb +57 -0
  7. data/lib/declare_schema/dsl.rb +39 -0
  8. data/lib/declare_schema/extensions/active_record/fields_declaration.rb +23 -4
  9. data/lib/declare_schema/model.rb +4 -6
  10. data/lib/declare_schema/model/column.rb +1 -1
  11. data/lib/declare_schema/model/field_spec.rb +9 -6
  12. data/lib/declare_schema/model/foreign_key_definition.rb +6 -11
  13. data/lib/declare_schema/model/index_definition.rb +1 -20
  14. data/lib/declare_schema/schema_change/all.rb +22 -0
  15. data/lib/declare_schema/schema_change/base.rb +45 -0
  16. data/lib/declare_schema/schema_change/column_add.rb +27 -0
  17. data/lib/declare_schema/schema_change/column_change.rb +32 -0
  18. data/lib/declare_schema/schema_change/column_remove.rb +20 -0
  19. data/lib/declare_schema/schema_change/column_rename.rb +23 -0
  20. data/lib/declare_schema/schema_change/foreign_key_add.rb +25 -0
  21. data/lib/declare_schema/schema_change/foreign_key_remove.rb +20 -0
  22. data/lib/declare_schema/schema_change/index_add.rb +33 -0
  23. data/lib/declare_schema/schema_change/index_remove.rb +20 -0
  24. data/lib/declare_schema/schema_change/primary_key_change.rb +33 -0
  25. data/lib/declare_schema/schema_change/table_add.rb +37 -0
  26. data/lib/declare_schema/schema_change/table_change.rb +36 -0
  27. data/lib/declare_schema/schema_change/table_remove.rb +22 -0
  28. data/lib/declare_schema/schema_change/table_rename.rb +22 -0
  29. data/lib/declare_schema/version.rb +1 -1
  30. data/lib/generators/declare_schema/migration/USAGE +14 -24
  31. data/lib/generators/declare_schema/migration/migration_generator.rb +40 -38
  32. data/lib/generators/declare_schema/migration/migrator.rb +184 -198
  33. data/lib/generators/declare_schema/migration/templates/migration.rb.erb +1 -1
  34. data/lib/generators/declare_schema/support/model.rb +4 -4
  35. data/spec/lib/declare_schema/api_spec.rb +8 -8
  36. data/spec/lib/declare_schema/field_declaration_dsl_spec.rb +41 -15
  37. data/spec/lib/declare_schema/field_spec_spec.rb +44 -2
  38. data/spec/lib/declare_schema/generator_spec.rb +5 -5
  39. data/spec/lib/declare_schema/interactive_primary_key_spec.rb +117 -28
  40. data/spec/lib/declare_schema/migration_generator_spec.rb +1990 -843
  41. data/spec/lib/declare_schema/model/column_spec.rb +49 -23
  42. data/spec/lib/declare_schema/model/foreign_key_definition_spec.rb +158 -57
  43. data/spec/lib/declare_schema/model/habtm_model_shim_spec.rb +0 -2
  44. data/spec/lib/declare_schema/model/index_definition_spec.rb +189 -78
  45. data/spec/lib/declare_schema/model/table_options_definition_spec.rb +75 -11
  46. data/spec/lib/declare_schema/schema_change/base_spec.rb +75 -0
  47. data/spec/lib/declare_schema/schema_change/column_add_spec.rb +30 -0
  48. data/spec/lib/declare_schema/schema_change/column_change_spec.rb +33 -0
  49. data/spec/lib/declare_schema/schema_change/column_remove_spec.rb +30 -0
  50. data/spec/lib/declare_schema/schema_change/column_rename_spec.rb +28 -0
  51. data/spec/lib/declare_schema/schema_change/foreign_key_add_spec.rb +29 -0
  52. data/spec/lib/declare_schema/schema_change/foreign_key_remove_spec.rb +29 -0
  53. data/spec/lib/declare_schema/schema_change/index_add_spec.rb +56 -0
  54. data/spec/lib/declare_schema/schema_change/index_remove_spec.rb +29 -0
  55. data/spec/lib/declare_schema/schema_change/primary_key_change_spec.rb +69 -0
  56. data/spec/lib/declare_schema/schema_change/table_add_spec.rb +50 -0
  57. data/spec/lib/declare_schema/schema_change/table_change_spec.rb +30 -0
  58. data/spec/lib/declare_schema/schema_change/table_remove_spec.rb +27 -0
  59. data/spec/lib/declare_schema/schema_change/table_rename_spec.rb +27 -0
  60. data/spec/lib/declare_schema_spec.rb +101 -0
  61. data/spec/lib/generators/declare_schema/migration/migrator_spec.rb +71 -13
  62. data/spec/spec_helper.rb +1 -1
  63. data/spec/support/acceptance_spec_helpers.rb +2 -2
  64. metadata +36 -6
  65. data/test_responses.txt +0 -2
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../../lib/declare_schema/schema_change/primary_key_change'
4
+
5
+ RSpec.describe DeclareSchema::SchemaChange::PrimaryKeyChange do
6
+ before do
7
+ load File.expand_path('../prepare_testapp.rb', __dir__)
8
+ end
9
+
10
+ let(:table_name) { 'users' }
11
+ let(:old_column_names) { ['id'] }
12
+ let(:new_column_names) { [:last_name, 'first_name'] }
13
+ let(:name) { 'PRIMARY' }
14
+ subject { described_class.new(table_name, old_column_names, new_column_names) }
15
+
16
+ describe '#up/down' do
17
+ context 'when PRIMARY KEY set -> set' do
18
+ describe '#up' do
19
+ it 'responds with command' do
20
+ command = "ALTER TABLE #{ActiveRecord::Base.connection.quote_table_name(table_name)} DROP PRIMARY KEY, ADD PRIMARY KEY (#{new_column_names.join(', ')})"
21
+ expect(subject.up).to eq("execute #{command.inspect}\n")
22
+ end
23
+ end
24
+
25
+ describe '#down' do
26
+ it 'responds with command' do
27
+ command = "ALTER TABLE #{ActiveRecord::Base.connection.quote_table_name(table_name)} DROP PRIMARY KEY, ADD PRIMARY KEY (#{old_column_names.join(', ')})"
28
+ expect(subject.down).to eq("execute #{command.inspect}\n")
29
+ end
30
+ end
31
+ end
32
+
33
+ context 'when PRIMARY KEY unset -> set' do
34
+ let(:old_column_names) { nil }
35
+
36
+ describe '#up' do
37
+ it 'responds with command' do
38
+ command = "ALTER TABLE #{ActiveRecord::Base.connection.quote_table_name(table_name)} ADD PRIMARY KEY (#{new_column_names.join(', ')})"
39
+ expect(subject.up).to eq("execute #{command.inspect}\n")
40
+ end
41
+ end
42
+
43
+ describe '#down' do
44
+ it 'responds with command' do
45
+ command = "ALTER TABLE #{ActiveRecord::Base.connection.quote_table_name(table_name)} DROP PRIMARY KEY"
46
+ expect(subject.down).to eq("execute #{command.inspect}\n")
47
+ end
48
+ end
49
+ end
50
+
51
+ context 'when PRIMARY KEY set -> unset' do
52
+ let(:new_column_names) { nil }
53
+
54
+ describe '#up' do
55
+ it 'responds with command' do
56
+ command = "ALTER TABLE #{ActiveRecord::Base.connection.quote_table_name(table_name)} DROP PRIMARY KEY"
57
+ expect(subject.up).to eq("execute #{command.inspect}\n")
58
+ end
59
+ end
60
+
61
+ describe '#down' do
62
+ it 'responds with command' do
63
+ command = "ALTER TABLE #{ActiveRecord::Base.connection.quote_table_name(table_name)} ADD PRIMARY KEY (#{old_column_names.join(', ')})"
64
+ expect(subject.down).to eq("execute #{command.inspect}\n")
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../../lib/declare_schema/schema_change/table_add'
4
+
5
+ RSpec.describe DeclareSchema::SchemaChange::TableAdd do
6
+ before do
7
+ load File.expand_path('../prepare_testapp.rb', __dir__)
8
+ end
9
+
10
+ let(:table_name) { 'networks' }
11
+ let(:fields) { [[:string, :title, limit: 255, null: false ], [:boolean, :admin, null: false]] }
12
+ let(:create_table_options) { { id: :primary_key } }
13
+ let(:sql_options) { '' }
14
+
15
+ subject { described_class.new(table_name, fields, create_table_options, sql_options: sql_options) }
16
+
17
+ describe '#up/down' do
18
+ describe '#up' do
19
+ it 'responds with command' do
20
+ expect(subject.up).to eq(<<~EOS)
21
+ create_table :networks, id: :primary_key do |t|
22
+ t.string :title, limit: 255, null: false
23
+ t.boolean :admin, null: false
24
+ end
25
+
26
+ EOS
27
+ end
28
+
29
+ context 'with sql_options' do
30
+ let(:sql_options) { 'CHARACTER SET utf8mb4' }
31
+
32
+ it 'responds with command' do
33
+ expect(subject.up).to eq(<<~EOS)
34
+ create_table :networks, id: :primary_key, options: "CHARACTER SET utf8mb4" do |t|
35
+ t.string :title, limit: 255, null: false
36
+ t.boolean :admin, null: false
37
+ end
38
+
39
+ EOS
40
+ end
41
+ end
42
+ end
43
+
44
+ describe '#down' do
45
+ it 'responds with command' do
46
+ expect(subject.down).to eq("drop_table :#{table_name}\n")
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../../lib/declare_schema/schema_change/table_change'
4
+
5
+ RSpec.describe DeclareSchema::SchemaChange::TableChange do
6
+ before do
7
+ load File.expand_path('../prepare_testapp.rb', __dir__)
8
+ end
9
+
10
+ let(:table_name) { 'networks' }
11
+ let(:old_options) { { charset: 'utf8', collation: 'utf8_ci' } }
12
+ let(:new_options) { { charset: 'utf8mb4', collation: 'utf8mb4_bin' } }
13
+ subject { described_class.new(table_name, old_options, new_options) }
14
+
15
+ describe '#up/down' do
16
+ describe '#up' do
17
+ it 'responds with command' do
18
+ statement = "ALTER TABLE #{ActiveRecord::Base.connection.quote_table_name(table_name)} CHARACTER SET utf8mb4 COLLATE utf8mb4_bin"
19
+ expect(subject.up).to eq("execute #{statement.inspect}\n")
20
+ end
21
+ end
22
+
23
+ describe '#down' do
24
+ it 'responds with command' do
25
+ statement = "ALTER TABLE #{ActiveRecord::Base.connection.quote_table_name(table_name)} CHARACTER SET utf8 COLLATE utf8_ci"
26
+ expect(subject.down).to eq("execute #{statement.inspect}\n")
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../../lib/declare_schema/schema_change/table_remove'
4
+
5
+ RSpec.describe DeclareSchema::SchemaChange::TableRemove do
6
+ before do
7
+ load File.expand_path('../prepare_testapp.rb', __dir__)
8
+ end
9
+
10
+ let(:table_name) { 'networks' }
11
+ let(:add_table_back) { "create table networks(\n)" }
12
+ subject { described_class.new(table_name, add_table_back) }
13
+
14
+ describe '#up/down' do
15
+ describe '#up' do
16
+ it 'responds with command' do
17
+ expect(subject.up).to eq("drop_table :#{table_name}\n")
18
+ end
19
+ end
20
+
21
+ describe '#down' do
22
+ it 'responds with command' do
23
+ expect(subject.down).to eq("#{add_table_back}\n\n")
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../../lib/declare_schema/schema_change/table_rename'
4
+
5
+ RSpec.describe DeclareSchema::SchemaChange::TableRename do
6
+ before do
7
+ load File.expand_path('../prepare_testapp.rb', __dir__)
8
+ end
9
+
10
+ let(:old_name) { 'networks' }
11
+ let(:new_name) { 'customers' }
12
+ subject { described_class.new(old_name, new_name) }
13
+
14
+ describe '#up/down' do
15
+ describe '#up' do
16
+ it 'responds with command' do
17
+ expect(subject.up).to eq("rename_table :#{old_name}, :#{new_name}\n")
18
+ end
19
+ end
20
+
21
+ describe '#down' do
22
+ it 'responds with command' do
23
+ expect(subject.down).to eq("rename_table :#{new_name}, :#{old_name}\n")
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe DeclareSchema do
4
+ describe '#default_charset' do
5
+ subject { described_class.default_charset }
6
+
7
+ context 'when not explicitly set' do
8
+ it { should eq("utf8mb4") }
9
+ end
10
+
11
+ context 'when explicitly set' do
12
+ before { described_class.default_charset = "utf8" }
13
+ after { described_class.default_charset = "utf8mb4" }
14
+ it { should eq("utf8") }
15
+ end
16
+ end
17
+
18
+ describe '#default_collation' do
19
+ subject { described_class.default_collation }
20
+
21
+ context 'when not explicitly set' do
22
+ it { should eq("utf8mb4_bin") }
23
+ end
24
+
25
+ context 'when explicitly set' do
26
+ before { described_class.default_collation = "utf8mb4_general_ci" }
27
+ after { described_class.default_collation = "utf8mb4_bin" }
28
+ it { should eq("utf8mb4_general_ci") }
29
+ end
30
+ end
31
+
32
+ describe '#default_text_limit' do
33
+ subject { described_class.default_text_limit }
34
+
35
+ context 'when not explicitly set' do
36
+ it { should eq(0xffff_ffff) }
37
+ end
38
+
39
+ context 'when explicitly set' do
40
+ before { described_class.default_text_limit = 0xffff }
41
+ after { described_class.default_text_limit = 0xffff_ffff }
42
+ it { should eq(0xffff) }
43
+ end
44
+ end
45
+
46
+ describe '#default_string_limit' do
47
+ subject { described_class.default_string_limit }
48
+
49
+ context 'when not explicitly set' do
50
+ it { should eq(nil) }
51
+ end
52
+
53
+ context 'when explicitly set' do
54
+ before { described_class.default_string_limit = 225 }
55
+ after { described_class.default_string_limit = nil }
56
+ it { should eq(225) }
57
+ end
58
+ end
59
+
60
+ describe '#default_null' do
61
+ subject { described_class.default_null }
62
+
63
+ context 'when not explicitly set' do
64
+ it { should eq(false) }
65
+ end
66
+
67
+ context 'when explicitly set' do
68
+ before { described_class.default_null = true }
69
+ after { described_class.default_null = false }
70
+ it { should eq(true) }
71
+ end
72
+ end
73
+
74
+ describe '#default_generate_foreign_keys' do
75
+ subject { described_class.default_generate_foreign_keys }
76
+
77
+ context 'when not explicitly set' do
78
+ it { should eq(true) }
79
+ end
80
+
81
+ context 'when explicitly set' do
82
+ before { described_class.default_generate_foreign_keys = false }
83
+ after { described_class.default_generate_foreign_keys = true }
84
+ it { should eq(false) }
85
+ end
86
+ end
87
+
88
+ describe '#default_generate_indexing' do
89
+ subject { described_class.default_generate_indexing }
90
+
91
+ context 'when not explicitly set' do
92
+ it { should eq(true) }
93
+ end
94
+
95
+ context 'when explicitly set' do
96
+ before { described_class.default_generate_indexing = false }
97
+ after { described_class.default_generate_indexing = true }
98
+ it { should eq(false) }
99
+ end
100
+ end
101
+ end
@@ -11,18 +11,8 @@ module Generators
11
11
  module DeclareSchema
12
12
  module Migration
13
13
  RSpec.describe Migrator do
14
- before do
15
- ActiveRecord::Base.connection.tables
16
- end
17
-
18
14
  subject { described_class.new }
19
15
 
20
- describe 'format_options' do
21
- it 'returns an array of option .inspect strings, with symbols using the modern : hash notation' do
22
- expect(subject.format_options({ limit: 4, 'key' => 'value "quoted"' })).to eq(["limit: 4", '"key" => "value \"quoted\""'])
23
- end
24
- end
25
-
26
16
  describe '#before_generating_migration' do
27
17
  it 'requires a block be passed' do
28
18
  expect { described_class.before_generating_migration }.to raise_error(ArgumentError, 'A block is required when setting the before_generating_migration callback')
@@ -38,9 +28,14 @@ module Generators
38
28
 
39
29
  context 'when explicitly set' do
40
30
  before { described_class.default_charset = "utf8" }
41
- after { described_class.default_charset = described_class::DEFAULT_CHARSET }
31
+ after { described_class.default_charset = "utf8mb4" }
42
32
  it { should eq("utf8") }
43
33
  end
34
+
35
+ it 'should output deprecation warning' do
36
+ expect { described_class.default_charset = "utf8mb4" }.to output(/DEPRECATION WARNING: default_charset= is deprecated/).to_stderr
37
+ expect { subject }.to output(/DEPRECATION WARNING: default_charset is deprecated/).to_stderr
38
+ end
44
39
  end
45
40
 
46
41
  describe '#default_collation' do
@@ -52,12 +47,17 @@ module Generators
52
47
 
53
48
  context 'when explicitly set' do
54
49
  before { described_class.default_collation = "utf8mb4_general_ci" }
55
- after { described_class.default_collation = described_class::DEFAULT_COLLATION }
50
+ after { described_class.default_collation = "utf8mb4_bin" }
56
51
  it { should eq("utf8mb4_general_ci") }
57
52
  end
53
+
54
+ it 'should output deprecation warning' do
55
+ expect { described_class.default_collation = "utf8mb4_bin" }.to output(/DEPRECATION WARNING: default_collation= is deprecated/).to_stderr
56
+ expect { subject }.to output(/DEPRECATION WARNING: default_collation is deprecated/).to_stderr
57
+ end
58
58
  end
59
59
 
60
- describe 'load_rails_models' do
60
+ describe '#load_rails_models' do
61
61
  before do
62
62
  expect(Rails.application).to receive(:eager_load!)
63
63
  expect(Rails::Engine).to receive(:subclasses).and_return([])
@@ -80,6 +80,64 @@ module Generators
80
80
  it { should be_nil }
81
81
  end
82
82
  end
83
+
84
+ describe '#order_migrations' do
85
+ let(:class_name_order) do
86
+ %w[ TableRename
87
+ TableAdd
88
+ TableChange
89
+ ColumnAdd
90
+ ColumnRename
91
+ ColumnChange
92
+ PrimaryKeyChange
93
+ IndexAdd
94
+ ForeignKeyAdd
95
+ ForeignKeyRemove
96
+ IndexRemove
97
+ ColumnRemove
98
+ TableRemove ]
99
+ end
100
+ let(:one_of_each) do
101
+ class_name_order.map do |class_name|
102
+ klass = klass_from_class_name(class_name)
103
+ instance_double(klass).tap do |double|
104
+ allow(double).to receive(:class).and_return(klass)
105
+ end
106
+ end
107
+ end
108
+ let(:one_of_each_shuffled) { one_of_each.shuffle }
109
+
110
+ it 'orders properly' do
111
+ ordered = subject.order_migrations(one_of_each_shuffled)
112
+ expect(ordered.map { |c| c.class.name.sub(/.*::/, '') }).to eq(class_name_order)
113
+ end
114
+
115
+ context 'when there are dups' do
116
+ let(:one_of_each_with_dups) do
117
+ (class_name_order * 2).map do |class_name|
118
+ klass = klass_from_class_name(class_name)
119
+ instance_double(klass).tap do |double|
120
+ allow(double).to receive(:class).and_return(klass)
121
+ end
122
+ end
123
+ end
124
+ let(:one_of_each_with_dups_shuffled) { one_of_each_with_dups.shuffle }
125
+ let(:one_of_each_with_dups_shuffled_grouped) { one_of_each_with_dups_shuffled.group_by { |c| c.class.name } }
126
+
127
+ it 'sorts stably' do
128
+ ordered = subject.order_migrations(one_of_each_with_dups_shuffled)
129
+ ordered_grouped = ordered.group_by { |c| c.class.name }
130
+ ordered_grouped.each do |class_name, schema_changes|
131
+ shuffled_for_class = one_of_each_with_dups_shuffled_grouped[class_name]
132
+ expect(schema_changes.map(&:object_id)).to eq(shuffled_for_class.map(&:object_id))
133
+ end
134
+ end
135
+ end
136
+ end
137
+
138
+ def klass_from_class_name(class_name)
139
+ "::DeclareSchema::SchemaChange::#{class_name}".constantize
140
+ end
83
141
  end
84
142
  end
85
143
  end
data/spec/spec_helper.rb CHANGED
@@ -27,7 +27,7 @@ RSpec.configure do |config|
27
27
  RSpec::Support::ObjectFormatter.default_instance.max_formatted_output_length = 2_000
28
28
 
29
29
  def active_record_base_class
30
- if Rails::VERSION::MAJOR == 4
30
+ if ActiveSupport::VERSION::MAJOR == 4
31
31
  'ActiveRecord::Base'
32
32
  else
33
33
  'ApplicationRecord'
@@ -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].gsub(/, +([a-z_]+:)/i, ', \1')) # normalize multiple spaces to one
48
+ super(subject[0].strip.gsub(/, +([a-z_]+:)/i, ', \1').gsub(/\n+/, "\n")) # normalize multiple spaces and newlines 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].gsub(/, +([a-z_]+:)/i, ', \1')) # normalize multiple spaces to one
54
+ super(subject[1].strip.gsub(/, +([a-z_]+:)/i, ', \1').gsub(/\n+/, "\n")) # normalize multiple spaces and newlines to one
55
55
  end
56
56
  end
57
57
  end