declare_schema 2.0.0 → 2.1.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer/Dockerfile +19 -0
  3. data/.devcontainer/boot.sh +1 -0
  4. data/.devcontainer/devcontainer.json +55 -0
  5. data/.devcontainer/docker-compose.yml +40 -0
  6. data/.github/workflows/declare_schema_build.yml +35 -11
  7. data/Appraisals +2 -10
  8. data/CHANGELOG.md +5 -0
  9. data/CODE-OF-CONDUCT.md +16 -0
  10. data/CONTRIBUTING.md +46 -0
  11. data/Gemfile +6 -1
  12. data/Gemfile.lock +87 -77
  13. data/README.md +38 -14
  14. data/Rakefile +5 -15
  15. data/catalog-info.yaml +35 -0
  16. data/config/brakeman.ignore +2 -2
  17. data/gemfiles/{rails_6_1_sqlite3.gemfile → rails_6_1.gemfile} +3 -0
  18. data/gemfiles/{rails_7_0_sqlite3.gemfile → rails_7_0.gemfile} +3 -0
  19. data/gemfiles/{rails_7_1_sqlite3.gemfile → rails_7_1.gemfile} +3 -0
  20. data/gemfiles/{rails_7_0_mysql2.gemfile → rails_7_2.gemfile} +4 -1
  21. data/lib/declare_schema/command.rb +2 -8
  22. data/lib/declare_schema/model/column.rb +1 -1
  23. data/lib/declare_schema/model/foreign_key_definition.rb +9 -12
  24. data/lib/declare_schema/model/index_definition.rb +16 -8
  25. data/lib/declare_schema/model.rb +10 -14
  26. data/lib/declare_schema/schema_change/base.rb +4 -0
  27. data/lib/declare_schema/schema_change/primary_key_change.rb +19 -6
  28. data/lib/declare_schema/version.rb +1 -1
  29. data/lib/declare_schema.rb +20 -8
  30. data/lib/generators/declare_schema/migration/migration_generator.rb +15 -2
  31. data/lib/generators/declare_schema/migration/migrator.rb +16 -9
  32. data/spec/fixtures/migrations/mysql2/will_generate_unique_constraint_names_rails_6.txt +15 -0
  33. data/spec/fixtures/migrations/mysql2/will_generate_unique_constraint_names_rails_7.txt +15 -0
  34. data/spec/fixtures/migrations/postgresql/will_generate_unique_constraint_names_rails_6.txt +15 -0
  35. data/spec/fixtures/migrations/postgresql/will_generate_unique_constraint_names_rails_7.txt +15 -0
  36. data/spec/fixtures/migrations/sqlite3/will_generate_unique_constraint_names_rails_6.txt +15 -0
  37. data/spec/fixtures/migrations/sqlite3/will_generate_unique_constraint_names_rails_7.txt +15 -0
  38. data/spec/lib/declare_schema/api_spec.rb +1 -3
  39. data/spec/lib/declare_schema/field_declaration_dsl_spec.rb +3 -3
  40. data/spec/lib/declare_schema/field_spec_spec.rb +68 -45
  41. data/spec/lib/declare_schema/generator_spec.rb +2 -4
  42. data/spec/lib/declare_schema/interactive_primary_key_spec.rb +3 -10
  43. data/spec/lib/declare_schema/migration_generator_spec.rb +248 -249
  44. data/spec/lib/declare_schema/model/column_spec.rb +89 -41
  45. data/spec/lib/declare_schema/model/foreign_key_definition_spec.rb +14 -8
  46. data/spec/lib/declare_schema/model/habtm_model_shim_spec.rb +4 -9
  47. data/spec/lib/declare_schema/model/index_definition_spec.rb +18 -10
  48. data/spec/lib/declare_schema/model/table_options_definition_spec.rb +11 -11
  49. data/spec/lib/declare_schema/schema_change/base_spec.rb +5 -7
  50. data/spec/lib/declare_schema/schema_change/column_add_spec.rb +1 -3
  51. data/spec/lib/declare_schema/schema_change/column_change_spec.rb +1 -3
  52. data/spec/lib/declare_schema/schema_change/column_remove_spec.rb +1 -3
  53. data/spec/lib/declare_schema/schema_change/column_rename_spec.rb +1 -3
  54. data/spec/lib/declare_schema/schema_change/foreign_key_add_spec.rb +1 -3
  55. data/spec/lib/declare_schema/schema_change/foreign_key_remove_spec.rb +1 -3
  56. data/spec/lib/declare_schema/schema_change/index_add_spec.rb +1 -3
  57. data/spec/lib/declare_schema/schema_change/index_remove_spec.rb +1 -3
  58. data/spec/lib/declare_schema/schema_change/primary_key_change_spec.rb +39 -15
  59. data/spec/lib/declare_schema/schema_change/table_add_spec.rb +1 -3
  60. data/spec/lib/declare_schema/schema_change/table_change_spec.rb +1 -3
  61. data/spec/lib/declare_schema/schema_change/table_remove_spec.rb +1 -3
  62. data/spec/lib/declare_schema/schema_change/table_rename_spec.rb +1 -3
  63. data/spec/lib/generators/declare_schema/migration/migrator_spec.rb +0 -4
  64. data/spec/spec_helper.rb +3 -0
  65. data/spec/support/adapter_specific_test_helpers.rb +25 -0
  66. data/spec/{lib/declare_schema → support}/prepare_testapp.rb +3 -1
  67. data/spec/support/test_app_spec_helpers.rb +7 -0
  68. metadata +22 -9
  69. data/gemfiles/rails_6_1_mysql2.gemfile +0 -23
  70. data/gemfiles/rails_7_1_mysql2.gemfile +0 -23
@@ -0,0 +1,15 @@
1
+ create_table :affiliates, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
2
+ t.string :name, limit: 250, null: true, charset: "utf8mb4", collation: "utf8mb4_bin"
3
+ t.integer :category_id, limit: 8, null: false
4
+ end
5
+ create_table :advertisers, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
6
+ t.string :name, limit: 250, null: true, charset: "utf8mb4", collation: "utf8mb4_bin"
7
+ t.integer :category_id, limit: 8, null: false
8
+ end
9
+ create_table :categories, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
10
+ t.string :name, limit: 250, null: true, charset: "utf8mb4", collation: "utf8mb4_bin"
11
+ end
12
+ add_index :affiliates, [:category_id], name: :index_affiliates_on_category_id
13
+ add_index :advertisers, [:category_id], name: :index_advertisers_on_category_id
14
+ add_foreign_key :affiliates, :categories, column: :category_id, name: :index_affiliates_on_category_id
15
+ add_foreign_key :advertisers, :categories, column: :category_id, name: :index_advertisers_on_category_id
@@ -4,9 +4,7 @@ require 'rails'
4
4
  require 'rails/generators'
5
5
 
6
6
  RSpec.describe 'DeclareSchema API' do
7
- before do
8
- load File.expand_path('prepare_testapp.rb', __dir__)
9
- end
7
+ include_context 'prepare test app'
10
8
 
11
9
  describe 'example models' do
12
10
  it 'generates a model' do
@@ -3,14 +3,14 @@
3
3
  require_relative '../../../lib/declare_schema/field_declaration_dsl'
4
4
 
5
5
  RSpec.describe DeclareSchema::FieldDeclarationDsl do
6
+ include_context 'prepare test app'
7
+
6
8
  let(:model) { TestModel.new }
7
9
  subject { declared_class.new(model) }
8
10
 
9
11
  context 'Using declare_schema' do
10
12
  before do
11
- load File.expand_path('prepare_testapp.rb', __dir__)
12
-
13
- class TestModel < ActiveRecord::Base
13
+ class TestModel < ActiveRecord::Base # rubocop:disable Lint/ConstantDefinitionInBlock
14
14
  declare_schema do
15
15
  string :name, limit: 127
16
16
 
@@ -1,26 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- begin
4
- require 'mysql2'
5
- rescue LoadError
6
- end
7
-
8
- begin
9
- require 'sqlite3'
10
- rescue LoadError
11
- end
12
-
13
3
  RSpec.describe DeclareSchema::Model::FieldSpec do
14
- let(:model) { double('model', _table_options: {}, _declared_primary_key: 'id') }
15
- let(:col_spec) { double('col_spec', type: :string) }
16
-
17
- before do
18
- load File.expand_path('prepare_testapp.rb', __dir__)
4
+ include_context 'prepare test app'
5
+
6
+ let(:model) { double('model', _table_options: {}, _declared_primary_key: 'id') }
7
+ let(:col_spec) do
8
+ if current_adapter == 'postgresql'
9
+ instance_double(ActiveRecord::ConnectionAdapters::PostgreSQL::Column, type: :string, sql_type: 'character varying(100)', oid: 1043, fmod: 2052)
10
+ else
11
+ instance_double(ActiveRecord::ConnectionAdapters::Column, type: :string, sql_type: :string)
12
+ end
19
13
  end
20
14
 
21
15
  describe '#initialize' do
16
+ subject { described_class.new(model, :price, :integer, anonymize_using: 'x', null: false, position: 0, limit: 4) }
22
17
  it 'normalizes option order' do
23
- subject = described_class.new(model, :price, :integer, anonymize_using: 'x', null: false, position: 0, limit: 4)
24
18
  expect(subject.options.keys).to eq([:limit, :null, :anonymize_using])
25
19
  end
26
20
 
@@ -33,48 +27,54 @@ RSpec.describe DeclareSchema::Model::FieldSpec do
33
27
 
34
28
  describe '#schema_attributes' do
35
29
  describe 'integer 4' do
30
+ subject { described_class.new(model, :price, :integer, limit: 4, null: false, position: 0) }
36
31
  it 'returns schema attributes' do
37
- subject = described_class.new(model, :price, :integer, limit: 4, null: false, position: 0)
38
32
  expect(subject.schema_attributes(col_spec)).to eq(type: :integer, limit: 4, null: false)
39
33
  end
40
34
  end
41
35
 
42
36
  describe 'integer 8' do
37
+ subject { described_class.new(model, :price, :integer, limit: 8, null: true, position: 2) }
43
38
  it 'returns schema attributes' do
44
- subject = described_class.new(model, :price, :integer, limit: 8, null: true, position: 2)
45
39
  expect(subject.schema_attributes(col_spec)).to eq(type: :integer, limit: 8, null: true)
46
40
  end
47
41
  end
48
42
 
49
43
  describe 'bigint' do
44
+ subject { described_class.new(model, :price, :bigint, null: false, position: 2) }
50
45
  it 'returns schema attributes' do
51
- subject = described_class.new(model, :price, :bigint, null: false, position: 2)
52
46
  expect(subject.schema_attributes(col_spec)).to eq(type: :integer, limit: 8, null: false)
53
47
  end
54
48
  end
55
49
 
56
50
  describe 'string' do
51
+ subject { described_class.new(model, :title, :string, limit: 100, null: true, charset: 'utf8mb4', position: 0) }
52
+
57
53
  it 'returns schema attributes (including charset/collation iff mysql)' do
58
- subject = described_class.new(model, :title, :string, limit: 100, null: true, charset: 'utf8mb4', position: 0)
59
- if defined?(Mysql2)
54
+ case current_adapter
55
+ when 'mysql2'
60
56
  expect(subject.schema_attributes(col_spec)).to eq(type: :string, limit: 100, null: true, charset: 'utf8mb4', collation: 'utf8mb4_bin')
61
57
  else
62
58
  expect(subject.schema_attributes(col_spec)).to eq(type: :string, limit: 100, null: true)
63
59
  end
64
60
  end
65
61
 
66
- if defined?(Mysql2)
62
+ context 'MySQL only' do
63
+ include_context 'skip unless' do
64
+ let(:adapter) { 'mysql2' }
65
+ end
66
+
67
67
  context 'when running on MySQL 8.0' do
68
68
  around do |spec|
69
69
  DeclareSchema.mysql_version = Gem::Version.new('8.0.21')
70
70
  spec.run
71
71
  ensure
72
- DeclareSchema.remove_instance_variable('@mysql_version') rescue nil
72
+ DeclareSchema.remove_instance_variable('@mysql_version') rescue nil # rubocop:disable Style/RescueModifier
73
73
  end
74
74
 
75
- it 'normalizes charset and collation' do
76
- subject = described_class.new(model, :title, :string, limit: 100, null: true, charset: 'utf8', collation: 'utf8_general', position: 0)
75
+ subject { described_class.new(model, :title, :string, limit: 100, null: true, charset: 'utf8', collation: 'utf8_general', position: 0) }
77
76
 
77
+ it 'normalizes charset and collation' do
78
78
  expect(subject.schema_attributes(col_spec)).to eq(type: :string, limit: 100, null: true, charset: 'utf8mb3', collation: 'utf8mb3_general')
79
79
  end
80
80
  end
@@ -89,9 +89,9 @@ RSpec.describe DeclareSchema::Model::FieldSpec do
89
89
  end
90
90
 
91
91
  describe 'text' do
92
+ subject { described_class.new(model, :title, :text, limit: 200, null: true, charset: 'utf8mb4', position: 2) }
92
93
  it 'returns schema attributes (including charset/collation iff mysql)' do
93
- subject = described_class.new(model, :title, :text, limit: 200, null: true, charset: 'utf8mb4', position: 2)
94
- if defined?(Mysql2)
94
+ if current_adapter == 'mysql2'
95
95
  expect(subject.schema_attributes(col_spec)).to eq(type: :text, limit: 255, null: true, charset: 'utf8mb4', collation: 'utf8mb4_bin')
96
96
  else
97
97
  expect(subject.schema_attributes(col_spec)).to eq(type: :text, null: true)
@@ -99,7 +99,7 @@ RSpec.describe DeclareSchema::Model::FieldSpec do
99
99
  end
100
100
 
101
101
  it 'allows a default to be set unless mysql' do
102
- if defined?(Mysql2)
102
+ if current_adapter == 'mysql2'
103
103
  expect do
104
104
  described_class.new(model, :title, :text, limit: 200, null: true, default: 'none', charset: 'utf8mb4', position: 2)
105
105
  end.to raise_exception(DeclareSchema::MysqlTextMayNotHaveDefault)
@@ -110,10 +110,10 @@ RSpec.describe DeclareSchema::Model::FieldSpec do
110
110
  end
111
111
 
112
112
  describe 'limit' do
113
+ subject { described_class.new(model, :title, :text, null: true, charset: 'utf8mb4', position: 2) }
113
114
  it 'uses default_text_limit option when not explicitly set in field spec' do
114
115
  allow(::DeclareSchema).to receive(:default_text_limit) { 100 }
115
- subject = described_class.new(model, :title, :text, null: true, charset: 'utf8mb4', position: 2)
116
- if defined?(Mysql2)
116
+ if current_adapter == 'mysql2'
117
117
  expect(subject.schema_attributes(col_spec)).to eq(type: :text, limit: 255, null: true, charset: 'utf8mb4', collation: 'utf8mb4_bin')
118
118
  else
119
119
  expect(subject.schema_attributes(col_spec)).to eq(type: :text, null: true)
@@ -121,11 +121,9 @@ RSpec.describe DeclareSchema::Model::FieldSpec do
121
121
  end
122
122
 
123
123
  it 'raises error when default_text_limit option is nil when not explicitly set in field spec' do
124
- if defined?(Mysql2)
124
+ if current_adapter == 'mysql2'
125
125
  expect(::DeclareSchema).to receive(:default_text_limit) { nil }
126
- expect do
127
- described_class.new(model, :title, :text, null: true, charset: 'utf8mb4', position: 2)
128
- end.to raise_error(/limit: must be provided for :text field/)
126
+ expect { subject }.to raise_error(/limit: must be provided for :text field/)
129
127
  end
130
128
  end
131
129
  end
@@ -173,18 +171,22 @@ RSpec.describe DeclareSchema::Model::FieldSpec do
173
171
  end
174
172
  end
175
173
 
176
- if defined?(Mysql2)
174
+ context 'MySQL only' do
175
+ include_context 'skip unless' do
176
+ let(:adapter) { 'mysql2' }
177
+ end
178
+
177
179
  describe 'varbinary' do # TODO: :varbinary is an Invoca addition to Rails; make it a configurable option
180
+ subject { described_class.new(model, :binary_dump, :varbinary, limit: 200, null: false, position: 2) }
178
181
  it 'is supported' do
179
- subject = described_class.new(model, :binary_dump, :varbinary, limit: 200, null: false, position: 2)
180
182
  expect(subject.schema_attributes(col_spec)).to eq(type: :varbinary, limit: 200, null: false)
181
183
  end
182
184
  end
183
185
  end
184
186
 
185
187
  describe 'decimal' do
188
+ subject { described_class.new(model, :quantity, :decimal, precision: 8, scale: 10, null: true, position: 3) }
186
189
  it 'allows precision: and scale:' do
187
- subject = described_class.new(model, :quantity, :decimal, precision: 8, scale: 10, null: true, position: 3)
188
190
  expect(subject.schema_attributes(col_spec)).to eq(type: :decimal, precision: 8, scale: 10, null: true)
189
191
  end
190
192
 
@@ -199,10 +201,18 @@ RSpec.describe DeclareSchema::Model::FieldSpec do
199
201
  end
200
202
  end
201
203
 
202
- [:integer, :bigint, :string, :text, :binary, :datetime, :date, :time, (:varbinary if defined?(Mysql2))].compact.each do |t|
204
+ [:integer, :bigint, :string, :text, :binary, :datetime, :date, :time, :varbinary].compact.each do |t|
203
205
  describe t.to_s do
204
206
  let(:extra) { t == :string ? { limit: 100 } : {} }
205
207
 
208
+ around do |spec|
209
+ if t == :varbinary && current_adapter != 'mysql2'
210
+ spec.skip
211
+ else
212
+ spec.run
213
+ end
214
+ end
215
+
206
216
  it 'does not allow precision:' do
207
217
  expect_any_instance_of(described_class).to receive(:warn).with(/precision: only allowed for :decimal type/)
208
218
  described_class.new(model, :quantity, t, **{ precision: 8, null: true, position: 3 }.merge(extra))
@@ -216,25 +226,31 @@ RSpec.describe DeclareSchema::Model::FieldSpec do
216
226
  end
217
227
 
218
228
  describe 'datetime' do
229
+ subject { described_class.new(model, :created_at, :datetime, null: false, position: 1) }
219
230
  it 'keeps type as "datetime"' do
220
- subject = described_class.new(model, :created_at, :datetime, null: false, position: 1)
221
231
  expect(subject.schema_attributes(col_spec)).to eq(type: :datetime, null: false)
222
232
  end
223
233
  end
224
234
 
225
235
  describe 'timestamp' do
236
+ subject { described_class.new(model, :created_at, :timestamp, null: true, position: 2) }
226
237
  it 'normalizes type to "datetime"' do
227
- subject = described_class.new(model, :created_at, :timestamp, null: true, position: 2)
228
238
  expect(subject.schema_attributes(col_spec)).to eq(type: :datetime, null: true)
229
239
  end
230
240
  end
231
241
 
232
242
  describe 'default:' do
233
- let(:col_spec) { double('col_spec', type: :integer) }
243
+ subject { described_class.new(model, :price, :integer, limit: 4, default: '42', null: true, position: 2) }
244
+
245
+ let(:col_spec) do
246
+ if current_adapter == 'postgresql'
247
+ instance_double(ActiveRecord::ConnectionAdapters::PostgreSQL::Column, type: :integer, sql_type: "integer", limit: 4, oid: 23, fmod: -1)
248
+ else
249
+ instance_double(ActiveRecord::ConnectionAdapters::Column, type: :integer, sql_type: "integer", limit: 4)
250
+ end
251
+ end
234
252
 
235
253
  it 'typecasts default value' do
236
- allow(col_spec).to receive(:type_cast_from_database) { |default| Integer(default) }
237
- subject = described_class.new(model, :price, :integer, limit: 4, default: '42', null: true, position: 2)
238
254
  expect(subject.schema_attributes(col_spec)).to eq(type: :integer, limit: 4, default: 42, null: true)
239
255
  end
240
256
  end
@@ -242,8 +258,15 @@ RSpec.describe DeclareSchema::Model::FieldSpec do
242
258
 
243
259
  describe '#schema_attributes' do
244
260
  let(:col_spec) do
245
- sql_type_metadata = ActiveRecord::ConnectionAdapters::SqlTypeMetadata.new(sql_type: "integer(8)", type: :integer, limit: 8)
246
- ActiveRecord::ConnectionAdapters::Column.new("price", nil, sql_type_metadata, false, "adverts")
261
+ if current_adapter == 'postgresql'
262
+ instance_double(ActiveRecord::ConnectionAdapters::PostgreSQL::Column,
263
+ name: "price", type: :integer, sql_type: "integer", limit: 4,
264
+ null: false, default: nil, default_function: "adverts", oid: 23, fmod: -1)
265
+ else
266
+ instance_double(ActiveRecord::ConnectionAdapters::Column,
267
+ name: "price", type: :integer, sql_type: "integer(8)", limit: 8,
268
+ null: false, default: nil, default_function: "adverts")
269
+ end
247
270
  end
248
271
 
249
272
  it 'returns the attributes except name, position, and non-SQL options' do
@@ -1,9 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  RSpec.describe 'DeclareSchema Migration Generator' do
4
- before do
5
- load File.expand_path('prepare_testapp.rb', __dir__)
6
- end
4
+ include_context 'prepare test app'
7
5
 
8
6
  it "generates nested models" do
9
7
  generate_model 'alpha/beta', 'one:string', 'two:integer'
@@ -57,7 +55,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
57
55
 
58
56
  expect(File.exist?('db/schema.rb')).to be_truthy
59
57
 
60
- if defined?(SQLite3)
58
+ if current_adapter == 'sqlite3'
61
59
  if ActiveSupport.version >= Gem::Version.new('7.1.0')
62
60
  expect(File.exist?("storage/development.sqlite3") || File.exist?("storage/test.sqlite3")).to be_truthy
63
61
  else
@@ -1,14 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- begin
4
- require 'mysql2'
5
- rescue LoadError
6
- end
7
-
8
3
  RSpec.describe 'DeclareSchema Migration Generator interactive primary key' do
9
- before do
10
- load File.expand_path('prepare_testapp.rb', __dir__)
11
- end
4
+ include_context 'prepare test app'
12
5
 
13
6
  context 'Using declare_schema' do
14
7
  it "allows alternate primary keys" do
@@ -56,12 +49,12 @@ RSpec.describe 'DeclareSchema Migration Generator interactive primary key' do
56
49
  # (0.1ms) DROP TABLE "afoos"
57
50
  # (pry):17
58
51
  # (0.9ms) commit transaction
59
- if defined?(SQLite3)
52
+ if current_adapter == 'sqlite3'
60
53
  ActiveRecord::Base.connection.execute("drop table foos")
61
54
  ActiveRecord::Base.connection.execute("CREATE TABLE foos (id integer PRIMARY KEY AUTOINCREMENT NOT NULL)")
62
55
  end
63
56
 
64
- if !defined?(Mysql2) # TODO TECH-4814 Put this test back for Mysql2
57
+ unless current_adapter == 'mysql2' # TODO TECH-4814 Put this test back for Mysql2
65
58
  # replace custom primary_key
66
59
  class Foo < ActiveRecord::Base
67
60
  declare_schema do