declare_schema 2.0.0 → 2.1.0.pre.1

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