declare_schema 1.4.0.colin.7 → 1.4.0.colin.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/Gemfile.lock +1 -1
- data/README.md +4 -1
- data/lib/declare_schema/model/field_spec.rb +2 -0
- data/lib/declare_schema/model/foreign_key_definition.rb +39 -43
- data/lib/declare_schema/model/habtm_model_shim.rb +30 -30
- data/lib/declare_schema/model/index_definition.rb +51 -30
- data/lib/declare_schema/model/table_options_definition.rb +11 -1
- data/lib/declare_schema/model.rb +55 -42
- data/lib/declare_schema/version.rb +1 -1
- data/lib/declare_schema.rb +34 -2
- data/lib/generators/declare_schema/migration/migrator.rb +32 -21
- data/spec/lib/declare_schema/field_spec_spec.rb +22 -2
- data/spec/lib/declare_schema/migration_generator_spec.rb +63 -41
- data/spec/lib/declare_schema/model/foreign_key_definition_spec.rb +28 -27
- data/spec/lib/declare_schema/model/habtm_model_shim_spec.rb +84 -62
- data/spec/lib/declare_schema/model/index_definition_spec.rb +126 -55
- data/spec/lib/declare_schema/model/table_options_definition_spec.rb +26 -6
- data/spec/lib/declare_schema_spec.rb +62 -8
- data/spec/lib/generators/declare_schema/migration/migrator_spec.rb +3 -1
- data/spec/spec_helper.rb +4 -3
- metadata +2 -2
data/lib/declare_schema.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require 'active_support'
|
4
4
|
require 'active_support/all'
|
5
5
|
require_relative 'declare_schema/version'
|
6
|
+
require 'rubygems/version'
|
6
7
|
|
7
8
|
ActiveSupport::Dependencies.autoload_paths |= [__dir__]
|
8
9
|
|
@@ -21,6 +22,8 @@ module DeclareSchema
|
|
21
22
|
text: String
|
22
23
|
}.freeze
|
23
24
|
|
25
|
+
SEMVER_8 = Gem::Version.new('8.0.0').freeze
|
26
|
+
|
24
27
|
@default_charset = "utf8mb4"
|
25
28
|
@default_collation = "utf8mb4_bin"
|
26
29
|
@default_text_limit = 0xffff_ffff
|
@@ -32,6 +35,7 @@ module DeclareSchema
|
|
32
35
|
@max_index_and_constraint_name_length = 64 # limit for MySQL
|
33
36
|
|
34
37
|
class << self
|
38
|
+
attr_writer :mysql_version
|
35
39
|
attr_reader :default_charset, :default_collation, :default_text_limit, :default_string_limit, :default_null,
|
36
40
|
:default_generate_foreign_keys, :default_generate_indexing, :db_migrate_command,
|
37
41
|
:max_index_and_constraint_name_length
|
@@ -47,14 +51,42 @@ module DeclareSchema
|
|
47
51
|
end
|
48
52
|
end
|
49
53
|
|
54
|
+
def mysql_version
|
55
|
+
if defined?(@mysql_version)
|
56
|
+
@mysql_version
|
57
|
+
else
|
58
|
+
@mysql_version =
|
59
|
+
if ActiveRecord::Base.connection.class.name.match?(/mysql/i)
|
60
|
+
version_string = ActiveRecord::Base.connection.select_value('SELECT VERSION()')
|
61
|
+
Gem::Version.new(version_string)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def normalize_charset(charset)
|
67
|
+
if mysql_version && mysql_version >= SEMVER_8 && charset == 'utf8'
|
68
|
+
'utf8mb3'
|
69
|
+
else
|
70
|
+
charset
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def normalize_collation(collation)
|
75
|
+
if mysql_version && mysql_version >= SEMVER_8
|
76
|
+
collation.sub(/\Autf8_/, 'utf8mb3_')
|
77
|
+
else
|
78
|
+
collation
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
50
82
|
def default_charset=(charset)
|
51
83
|
charset.is_a?(String) or raise ArgumentError, "charset must be a string (got #{charset.inspect})"
|
52
|
-
@default_charset = charset
|
84
|
+
@default_charset = normalize_charset(charset)
|
53
85
|
end
|
54
86
|
|
55
87
|
def default_collation=(collation)
|
56
88
|
collation.is_a?(String) or raise ArgumentError, "collation must be a string (got #{collation.inspect})"
|
57
|
-
@default_collation = collation
|
89
|
+
@default_collation = normalize_collation(collation)
|
58
90
|
end
|
59
91
|
|
60
92
|
def default_text_limit=(text_limit)
|
@@ -3,6 +3,7 @@
|
|
3
3
|
require 'active_record'
|
4
4
|
require 'active_record/connection_adapters/abstract_adapter'
|
5
5
|
require 'declare_schema/schema_change/all'
|
6
|
+
require_relative '../../../declare_schema/model/habtm_model_shim'
|
6
7
|
|
7
8
|
module Generators
|
8
9
|
module DeclareSchema
|
@@ -203,8 +204,8 @@ module Generators
|
|
203
204
|
end
|
204
205
|
end
|
205
206
|
# generate shims for HABTM models
|
206
|
-
habtm_tables.each do |name,
|
207
|
-
models_by_table_name[name] = ::DeclareSchema::Model::HabtmModelShim.from_reflection(
|
207
|
+
habtm_tables.each do |name, reflections|
|
208
|
+
models_by_table_name[name] = ::DeclareSchema::Model::HabtmModelShim.from_reflection(reflections.first)
|
208
209
|
end
|
209
210
|
model_table_names = models_by_table_name.keys
|
210
211
|
|
@@ -221,8 +222,8 @@ module Generators
|
|
221
222
|
::DeclareSchema::SchemaChange::TableRemove.new(t, add_table_back(t))
|
222
223
|
end
|
223
224
|
|
224
|
-
creates = to_create.map do |
|
225
|
-
model = models_by_table_name[
|
225
|
+
creates = to_create.map do |table_name|
|
226
|
+
model = models_by_table_name[table_name]
|
226
227
|
disable_auto_increment = model.try(:disable_auto_increment)
|
227
228
|
|
228
229
|
primary_key_definition =
|
@@ -239,10 +240,13 @@ module Generators
|
|
239
240
|
table_options_definition = ::DeclareSchema::Model::TableOptionsDefinition.new(model.table_name, **table_options_for_model(model))
|
240
241
|
table_options = create_table_options(model, disable_auto_increment)
|
241
242
|
|
242
|
-
table_add = ::DeclareSchema::SchemaChange::TableAdd.new(
|
243
|
-
|
244
|
-
|
245
|
-
|
243
|
+
table_add = ::DeclareSchema::SchemaChange::TableAdd.new(
|
244
|
+
table_name,
|
245
|
+
primary_key_definition + field_definitions,
|
246
|
+
table_options,
|
247
|
+
sql_options: table_options_definition.settings
|
248
|
+
)
|
249
|
+
|
246
250
|
[
|
247
251
|
table_add,
|
248
252
|
*Array((create_indexes(model) if ::DeclareSchema.default_generate_indexing)),
|
@@ -255,9 +259,9 @@ module Generators
|
|
255
259
|
fk_changes = []
|
256
260
|
table_options_changes = []
|
257
261
|
|
258
|
-
to_change.each do |
|
259
|
-
model = models_by_table_name[
|
260
|
-
table = to_rename.key(
|
262
|
+
to_change.each do |table_name|
|
263
|
+
model = models_by_table_name[table_name]
|
264
|
+
table = to_rename.key(table_name) || model.table_name
|
261
265
|
if table.in?(db_tables)
|
262
266
|
change, index_change, fk_change, table_options_change = change_table(model, table)
|
263
267
|
changes << change
|
@@ -311,6 +315,8 @@ module Generators
|
|
311
315
|
{ id: false }
|
312
316
|
elsif primary_key == "id"
|
313
317
|
{ id: :bigint }
|
318
|
+
elsif primary_key.is_a?(Array)
|
319
|
+
{ primary_key: primary_key.map(&:to_sym) }
|
314
320
|
else
|
315
321
|
{ primary_key: primary_key.to_sym }
|
316
322
|
end.merge(model._table_options)
|
@@ -335,9 +341,9 @@ module Generators
|
|
335
341
|
end
|
336
342
|
|
337
343
|
def create_constraints(model)
|
338
|
-
model.
|
344
|
+
model.constraint_definitions.map do |fk|
|
339
345
|
::DeclareSchema::SchemaChange::ForeignKeyAdd.new(fk.child_table_name, fk.parent_table_name,
|
340
|
-
column_name: fk.
|
346
|
+
column_name: fk.foreign_key_column, name: fk.constraint_name)
|
341
347
|
end
|
342
348
|
end
|
343
349
|
|
@@ -425,7 +431,7 @@ module Generators
|
|
425
431
|
::DeclareSchema.default_generate_indexing or return []
|
426
432
|
|
427
433
|
new_table_name = model.table_name
|
428
|
-
existing_indexes = ::DeclareSchema::Model::IndexDefinition.
|
434
|
+
existing_indexes = ::DeclareSchema::Model::IndexDefinition.for_table(old_table_name || new_table_name, model.ignore_indexes, model.connection)
|
429
435
|
model_indexes_with_equivalents = model.index_definitions_with_primary_key.to_a
|
430
436
|
model_indexes = model_indexes_with_equivalents.map do |i|
|
431
437
|
if i.explicit_name.nil?
|
@@ -485,8 +491,12 @@ module Generators
|
|
485
491
|
ActiveRecord::Base.connection.class.name.match?(/SQLite3Adapter/) and raise ArgumentError, 'SQLite does not support foreign keys'
|
486
492
|
::DeclareSchema.default_generate_foreign_keys or return []
|
487
493
|
|
488
|
-
|
489
|
-
|
494
|
+
if model.is_a?(::DeclareSchema::Model::HabtmModelShim)
|
495
|
+
force_dependent_delete = :delete
|
496
|
+
end
|
497
|
+
|
498
|
+
existing_fks = ::DeclareSchema::Model::ForeignKeyDefinition.for_table(old_table_name || model.table_name, model.connection, dependent: force_dependent_delete)
|
499
|
+
model_fks = model.constraint_definitions.to_a
|
490
500
|
|
491
501
|
fks_to_drop = existing_fks - model_fks
|
492
502
|
fks_to_add = model_fks - existing_fks
|
@@ -495,13 +505,13 @@ module Generators
|
|
495
505
|
|
496
506
|
drop_fks = (fks_to_drop - renamed_fks_to_drop).map do |fk|
|
497
507
|
::DeclareSchema::SchemaChange::ForeignKeyRemove.new(fk.child_table_name, fk.parent_table_name,
|
498
|
-
column_name: fk.
|
508
|
+
column_name: fk.foreign_key_column, name: fk.constraint_name)
|
499
509
|
end
|
500
510
|
|
501
511
|
add_fks = (fks_to_add - renamed_fks_to_add).map do |fk|
|
502
512
|
# next if fk.parent.constantize.abstract_class || fk.parent == fk.model.class_name
|
503
513
|
::DeclareSchema::SchemaChange::ForeignKeyAdd.new(fk.child_table_name, fk.parent_table_name,
|
504
|
-
column_name: fk.
|
514
|
+
column_name: fk.foreign_key_column, name: fk.constraint_name)
|
505
515
|
end
|
506
516
|
|
507
517
|
[drop_fks + add_fks]
|
@@ -523,9 +533,8 @@ module Generators
|
|
523
533
|
end
|
524
534
|
|
525
535
|
def fk_field_options(model, field_name)
|
526
|
-
foreign_key = model.
|
527
|
-
|
528
|
-
parent_columns = connection.columns(parent_table) rescue []
|
536
|
+
if (foreign_key = model.constraint_definitions.find { |fk| field_name == fk.foreign_key_column })
|
537
|
+
parent_columns = connection.columns(foreign_key.parent_table_name) rescue []
|
529
538
|
pk_limit =
|
530
539
|
if (pk_column = parent_columns.find { |column| column.name.to_s == "id" }) # right now foreign keys assume id is the target
|
531
540
|
pk_column.limit
|
@@ -585,6 +594,8 @@ module Generators
|
|
585
594
|
case charset
|
586
595
|
when "utf8"
|
587
596
|
"utf8_general_ci"
|
597
|
+
when "utf8mb3"
|
598
|
+
"utf8mb3_general_ci"
|
588
599
|
when "utf8mb4"
|
589
600
|
"utf8mb4_general_ci"
|
590
601
|
end
|
@@ -5,6 +5,11 @@ begin
|
|
5
5
|
rescue LoadError
|
6
6
|
end
|
7
7
|
|
8
|
+
begin
|
9
|
+
require 'sqlite3'
|
10
|
+
rescue LoadError
|
11
|
+
end
|
12
|
+
|
8
13
|
RSpec.describe DeclareSchema::Model::FieldSpec do
|
9
14
|
let(:model) { double('model', _table_options: {}, _declared_primary_key: 'id') }
|
10
15
|
let(:col_spec) { double('col_spec', type: :string) }
|
@@ -58,8 +63,23 @@ RSpec.describe DeclareSchema::Model::FieldSpec do
|
|
58
63
|
end
|
59
64
|
end
|
60
65
|
|
61
|
-
|
62
|
-
|
66
|
+
if defined?(Mysql2)
|
67
|
+
context 'when running on MySQL 8.0' do
|
68
|
+
around do |spec|
|
69
|
+
DeclareSchema.mysql_version = Gem::Version.new('8.0.21')
|
70
|
+
spec.run
|
71
|
+
ensure
|
72
|
+
DeclareSchema.remove_instance_variable('@mysql_version') rescue nil
|
73
|
+
end
|
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)
|
77
|
+
|
78
|
+
expect(subject.schema_attributes(col_spec)).to eq(type: :string, limit: 100, null: true, charset: 'utf8mb3', collation: 'utf8mb3_general')
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'raises error when default_string_limit option is nil when not explicitly set in field spec' do
|
63
83
|
expect(::DeclareSchema).to receive(:default_string_limit) { nil }
|
64
84
|
expect do
|
65
85
|
described_class.new(model, :title, :string, null: true, charset: 'utf8mb4', position: 0)
|
@@ -391,6 +391,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
391
391
|
|
392
392
|
Advert.field_specs.delete(:c_id)
|
393
393
|
Advert.index_definitions.delete_if { |spec| spec.fields == ["c_id"] }
|
394
|
+
Advert.constraint_definitions.delete_if { |spec| spec.foreign_key_column == "c_id" }
|
394
395
|
|
395
396
|
# You can avoid generating the index by specifying `index: false`
|
396
397
|
|
@@ -403,17 +404,17 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
403
404
|
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
404
405
|
migrate_up(<<~EOS.strip)
|
405
406
|
add_column :adverts, :category_id, :integer, limit: 8, null: false
|
406
|
-
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id
|
407
|
-
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
407
|
+
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id" if defined?(Mysql2)}
|
408
408
|
EOS
|
409
409
|
)
|
410
410
|
|
411
411
|
Advert.field_specs.delete(:category_id)
|
412
412
|
Advert.index_definitions.delete_if { |spec| spec.fields == ["category_id"] }
|
413
|
+
Advert.constraint_definitions.delete_if { |spec| spec.foreign_key_column == "category_id" }
|
413
414
|
|
414
415
|
# You can specify the index name with index: 'name' [deprecated]
|
415
416
|
|
416
|
-
expect(
|
417
|
+
expect(ActiveSupport::Deprecation).to receive(:warn).with(/belongs_to :category, index: 'name' is deprecated; use index: \{ name: 'name' \} instead/i)
|
417
418
|
|
418
419
|
class Category < ActiveRecord::Base; end
|
419
420
|
class Advert < ActiveRecord::Base
|
@@ -425,13 +426,13 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
425
426
|
migrate_up(<<~EOS.strip)
|
426
427
|
add_column :adverts, :category_id, :integer, limit: 8, null: false
|
427
428
|
add_index :adverts, [:category_id], name: :my_index
|
428
|
-
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :
|
429
|
-
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
429
|
+
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :my_index" if defined?(Mysql2)}
|
430
430
|
EOS
|
431
431
|
)
|
432
432
|
|
433
433
|
Advert.field_specs.delete(:category_id)
|
434
434
|
Advert.index_definitions.delete_if { |spec| spec.fields == ["category_id"] }
|
435
|
+
Advert.constraint_definitions.delete_if { |spec| spec.foreign_key_column == "category_id" }
|
435
436
|
|
436
437
|
# You can specify the index name with index: { name: }'name', unique: true|false }
|
437
438
|
|
@@ -443,15 +444,15 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
443
444
|
|
444
445
|
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
445
446
|
migrate_up(<<~EOS.strip)
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
EOS
|
447
|
+
add_column :adverts, :category_id, :integer, limit: 8, null: false
|
448
|
+
add_index :adverts, [:category_id], name: :my_index
|
449
|
+
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :my_index" if defined?(Mysql2)}
|
450
|
+
EOS
|
451
451
|
)
|
452
452
|
|
453
453
|
Advert.field_specs.delete(:category_id)
|
454
454
|
Advert.index_definitions.delete_if { |spec| spec.fields == ["category_id"] }
|
455
|
+
Advert.constraint_definitions.delete_if { |spec| spec.foreign_key_column == "category_id" }
|
455
456
|
|
456
457
|
### Timestamps and Optimimistic Locking
|
457
458
|
|
@@ -470,12 +471,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
470
471
|
add_column :adverts, :created_at, :datetime, null: true
|
471
472
|
add_column :adverts, :updated_at, :datetime, null: true
|
472
473
|
add_column :adverts, :lock_version, :integer#{lock_version_limit}, null: false, default: 1
|
473
|
-
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
474
|
-
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
475
474
|
EOS
|
476
475
|
.and migrate_down(<<~EOS.strip)
|
477
|
-
#{"remove_foreign_key :adverts, name: :index_adverts_on_c_id\n" +
|
478
|
-
"remove_foreign_key :adverts, name: :index_adverts_on_category_id" if defined?(Mysql2)}
|
479
476
|
remove_column :adverts, :lock_version
|
480
477
|
remove_column :adverts, :updated_at
|
481
478
|
remove_column :adverts, :created_at
|
@@ -490,8 +487,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
490
487
|
|
491
488
|
# You can add an index to a field definition
|
492
489
|
|
493
|
-
expect(
|
494
|
-
expect(
|
490
|
+
expect(ActiveSupport::Deprecation).to receive(:warn).with(/belongs_to :category, index: 'name' is deprecated; use index: \{ name: 'name' \} instead/i)
|
491
|
+
expect(ActiveSupport::Deprecation).to receive(:warn).with(/belongs_to :category, unique: true\|false is deprecated; use index: \{ unique: true\|false \} instead/i)
|
495
492
|
|
496
493
|
class Advert < ActiveRecord::Base
|
497
494
|
declare_schema do
|
@@ -506,15 +503,15 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
506
503
|
add_column :adverts, :category_id, :integer, limit: 8, null: false
|
507
504
|
add_index :adverts, [:title], name: :index_adverts_on_title
|
508
505
|
add_index :adverts, [:category_id], name: :my_index
|
509
|
-
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :
|
510
|
-
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
506
|
+
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :my_index" if defined?(Mysql2)}
|
511
507
|
EOS
|
512
508
|
)
|
513
509
|
|
514
510
|
Advert.field_specs.delete(:category_id)
|
515
511
|
Advert.index_definitions.delete_if { |spec| spec.fields == ["title"] || spec.fields == ["category_id"] }
|
512
|
+
Advert.constraint_definitions.delete_if { |spec| spec.foreign_key_column == "category_id" }
|
516
513
|
|
517
|
-
# You can ask for a unique index
|
514
|
+
# You can ask for a unique index (deprecated syntax; use index: { unique: true } instead).
|
518
515
|
|
519
516
|
class Advert < ActiveRecord::Base
|
520
517
|
declare_schema do
|
@@ -526,8 +523,6 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
526
523
|
migrate_up(<<~EOS.strip)
|
527
524
|
add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
528
525
|
add_index :adverts, [:title], name: :index_adverts_on_title, unique: true
|
529
|
-
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
530
|
-
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
531
526
|
EOS
|
532
527
|
)
|
533
528
|
|
@@ -545,8 +540,6 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
545
540
|
migrate_up(<<~EOS.strip)
|
546
541
|
add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
547
542
|
add_index :adverts, [:title], name: :my_index
|
548
|
-
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
549
|
-
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
550
543
|
EOS
|
551
544
|
)
|
552
545
|
|
@@ -555,6 +548,9 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
555
548
|
# You can ask for an index outside of the fields block
|
556
549
|
|
557
550
|
class Advert < ActiveRecord::Base
|
551
|
+
declare_schema do
|
552
|
+
string :title, limit: 250, null: true
|
553
|
+
end
|
558
554
|
index :title
|
559
555
|
end
|
560
556
|
|
@@ -562,8 +558,6 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
562
558
|
migrate_up(<<~EOS.strip)
|
563
559
|
add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
564
560
|
add_index :adverts, [:title], name: :index_adverts_on_title
|
565
|
-
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
566
|
-
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
567
561
|
EOS
|
568
562
|
)
|
569
563
|
|
@@ -578,9 +572,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
578
572
|
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
579
573
|
migrate_up(<<~EOS.strip)
|
580
574
|
add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
581
|
-
add_index :adverts, [:title], name: :my_index, length: 10
|
582
|
-
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
583
|
-
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
575
|
+
add_index :adverts, [:title], name: :my_index, length: { title: 10 }
|
584
576
|
EOS
|
585
577
|
)
|
586
578
|
|
@@ -596,8 +588,6 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
596
588
|
migrate_up(<<~EOS.strip)
|
597
589
|
add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
598
590
|
add_index :adverts, [:title, :category_id], name: :index_adverts_on_title_and_category_id
|
599
|
-
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
600
|
-
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
601
591
|
EOS
|
602
592
|
)
|
603
593
|
|
@@ -627,16 +617,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
627
617
|
rename_table :adverts, :ads
|
628
618
|
add_column :ads, :title, :string, limit: 250, null: true#{charset_and_collation}
|
629
619
|
add_column :ads, :body, :text#{', limit: 4294967295' if defined?(Mysql2)}, null: true#{charset_and_collation}
|
630
|
-
|
631
|
-
"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
632
|
-
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id"
|
633
|
-
end}
|
634
|
-
EOS
|
620
|
+
EOS
|
635
621
|
.and migrate_down(<<~EOS.strip)
|
636
|
-
#{if defined?(Mysql2)
|
637
|
-
"remove_foreign_key :adverts, name: :index_adverts_on_c_id\n" +
|
638
|
-
"remove_foreign_key :adverts, name: :index_adverts_on_category_id"
|
639
|
-
end}
|
640
622
|
remove_column :ads, :body
|
641
623
|
remove_column :ads, :title
|
642
624
|
rename_table :ads, :adverts
|
@@ -838,6 +820,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
838
820
|
expect(User.field_specs.keys).to eq(['company'])
|
839
821
|
expect(User.field_specs['company'].options[:ruby_default]&.call).to eq("BigCorp")
|
840
822
|
|
823
|
+
nuke_model_class(User)
|
824
|
+
|
841
825
|
## validates
|
842
826
|
|
843
827
|
# DeclareSchema can accept a validates hash in the field options.
|
@@ -858,6 +842,44 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
858
842
|
up, _down = Generators::DeclareSchema::Migration::Migrator.run
|
859
843
|
ActiveRecord::Migration.class_eval(up)
|
860
844
|
expect(Ad.field_specs['company'].options[:validates].inspect).to eq("{:presence=>true, :uniqueness=>{:case_sensitive=>false}}")
|
845
|
+
|
846
|
+
# DeclareSchema supports has_and_belongs_to_many relationships and generates the intersection ("join") table
|
847
|
+
# with appropriate primary key, indexes, and foreign keys.
|
848
|
+
|
849
|
+
class Advertiser < ActiveRecord::Base
|
850
|
+
declare_schema do
|
851
|
+
string :name, limit: 250
|
852
|
+
end
|
853
|
+
has_and_belongs_to_many :creatives
|
854
|
+
end
|
855
|
+
class Creative < ActiveRecord::Base
|
856
|
+
declare_schema do
|
857
|
+
string :url, limit: 500
|
858
|
+
end
|
859
|
+
has_and_belongs_to_many :advertisers
|
860
|
+
end
|
861
|
+
|
862
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
863
|
+
migrate_up(<<~EOS.strip)
|
864
|
+
create_table :advertisers, id: :bigint#{create_table_charset_and_collation} do |t|
|
865
|
+
t.string :name, limit: 250, null: false#{charset_and_collation}
|
866
|
+
end
|
867
|
+
create_table :advertisers_creatives, primary_key: [:advertiser_id, :creative_id]#{create_table_charset_and_collation} do |t|
|
868
|
+
t.integer :advertiser_id, limit: 8, null: false
|
869
|
+
t.integer :creative_id, limit: 8, null: false
|
870
|
+
end
|
871
|
+
create_table :creatives, id: :bigint#{create_table_charset_and_collation} do |t|
|
872
|
+
t.string :url, limit: 500, null: false#{charset_and_collation}
|
873
|
+
end
|
874
|
+
add_index :advertisers_creatives, [:creative_id], name: :index_advertisers_creatives_on_creative_id
|
875
|
+
add_foreign_key :advertisers_creatives, :advertisers, column: :advertiser_id, name: :advertisers_creatives_FK1
|
876
|
+
add_foreign_key :advertisers_creatives, :creatives, column: :creative_id, name: :advertisers_creatives_FK2
|
877
|
+
EOS
|
878
|
+
)
|
879
|
+
|
880
|
+
nuke_model_class(Ad)
|
881
|
+
nuke_model_class(Advertiser)
|
882
|
+
nuke_model_class(Creative)
|
861
883
|
end
|
862
884
|
|
863
885
|
context 'models with the same parent foreign key relation' do
|
@@ -881,7 +903,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
881
903
|
end
|
882
904
|
end
|
883
905
|
|
884
|
-
it 'will
|
906
|
+
it 'will generate unique constraint names' do
|
885
907
|
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
886
908
|
migrate_up(<<~EOS.strip)
|
887
909
|
create_table :categories, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
|
@@ -1178,7 +1200,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
1178
1200
|
end
|
1179
1201
|
|
1180
1202
|
it 'deprecates limit:' do
|
1181
|
-
expect(ActiveSupport::Deprecation).to receive(:warn).with("belongs_to limit: is deprecated since it is now inferred")
|
1203
|
+
expect(ActiveSupport::Deprecation).to receive(:warn).with("belongs_to :ad_category, limit: is deprecated since it is now inferred")
|
1182
1204
|
eval <<~EOS
|
1183
1205
|
class UsingLimit < ActiveRecord::Base
|
1184
1206
|
declare_schema { }
|
@@ -21,9 +21,9 @@ RSpec.describe DeclareSchema::Model::ForeignKeyDefinition do
|
|
21
21
|
describe 'instance methods' do
|
22
22
|
let(:connection) { instance_double(ActiveRecord::Base.connection.class) }
|
23
23
|
let(:model) { instance_double('Model', table_name: 'models', connection: connection) }
|
24
|
-
let(:
|
25
|
-
let(:options) { {} }
|
26
|
-
subject { described_class.new(
|
24
|
+
let(:foreign_key_column) { :network_id }
|
25
|
+
let(:options) { { child_table_name: 'advertisers' } }
|
26
|
+
subject { described_class.new(foreign_key_column, **options)}
|
27
27
|
|
28
28
|
before do
|
29
29
|
allow(model.connection).to receive(:index_name).with(any_args) { 'index_on_network_id' }
|
@@ -31,63 +31,64 @@ RSpec.describe DeclareSchema::Model::ForeignKeyDefinition do
|
|
31
31
|
|
32
32
|
describe '#initialize' do
|
33
33
|
it 'normalizes symbols to strings' do
|
34
|
-
expect(subject.
|
34
|
+
expect(subject.foreign_key_column).to eq('network_id')
|
35
35
|
expect(subject.parent_table_name).to eq('networks')
|
36
36
|
end
|
37
37
|
|
38
38
|
context 'when most options passed' do
|
39
|
-
let(:options) { {
|
39
|
+
let(:options) { { child_table_name: 'advertisers', parent_class_name: 'Network' } }
|
40
40
|
|
41
41
|
it 'normalizes symbols to strings' do
|
42
|
-
expect(subject.
|
43
|
-
expect(subject.foreign_key_name).to eq('the_network_id')
|
42
|
+
expect(subject.foreign_key_column).to eq('network_id')
|
44
43
|
expect(subject.parent_table_name).to eq('networks')
|
45
|
-
expect(subject.
|
46
|
-
expect(subject.constraint_name).to eq('
|
47
|
-
expect(subject.
|
44
|
+
expect(subject.foreign_key_column).to eq('network_id')
|
45
|
+
expect(subject.constraint_name).to eq('index_advertisers_on_network_id')
|
46
|
+
expect(subject.dependent).to be_nil
|
48
47
|
end
|
49
48
|
end
|
50
49
|
|
51
50
|
context 'when all options passed' do
|
52
|
-
let(:options) { {
|
51
|
+
let(:options) { { child_table_name: 'advertisers', parent_class_name: 'Network', constraint_name: :constraint_1, dependent: :delete } }
|
53
52
|
|
54
53
|
it 'normalizes symbols to strings' do
|
55
|
-
expect(subject.
|
56
|
-
expect(subject.foreign_key_name).to eq('the_network_id')
|
54
|
+
expect(subject.foreign_key_column).to eq('network_id')
|
57
55
|
expect(subject.parent_table_name).to eq('networks')
|
58
56
|
expect(subject.constraint_name).to eq('constraint_1')
|
59
|
-
expect(subject.
|
57
|
+
expect(subject.dependent).to eq(:delete)
|
60
58
|
end
|
61
59
|
end
|
62
60
|
|
63
61
|
describe `#<=>` do
|
62
|
+
let(:foreign_key_column) { :the_network_id }
|
63
|
+
|
64
64
|
context 'when class name not passed' do
|
65
|
-
let(:options) { {
|
65
|
+
let(:options) { { child_table_name: 'advertisers', constraint_name: :constraint_1, dependent: :delete } }
|
66
66
|
|
67
|
-
it 'compares equal without
|
67
|
+
it 'compares equal without requiring the parent class' do
|
68
68
|
expect(subject <=> subject).to eq(0)
|
69
69
|
end
|
70
70
|
end
|
71
71
|
|
72
72
|
context 'when class name passed' do
|
73
|
-
let(:options) { {
|
73
|
+
let(:options) { { child_table_name: 'advertisers', parent_class_name: 'TheNetwork', constraint_name: :constraint_1 } }
|
74
74
|
|
75
|
-
it 'compares equal without
|
75
|
+
it 'compares equal without requiring the parent class' do
|
76
76
|
expect(subject <=> subject).to eq(0)
|
77
77
|
end
|
78
78
|
end
|
79
79
|
end
|
80
80
|
|
81
81
|
context 'when constraint name passed as empty string' do
|
82
|
-
let(:options) { { constraint_name: "" } }
|
82
|
+
let(:options) { { child_table_name: 'advertisers', constraint_name: "" } }
|
83
|
+
|
83
84
|
it 'defaults to rails constraint name' do
|
84
|
-
expect(subject.constraint_name).to eq("
|
85
|
+
expect(subject.constraint_name).to eq("index_advertisers_on_network_id")
|
85
86
|
end
|
86
87
|
end
|
87
88
|
|
88
89
|
context 'when no constraint name passed' do
|
89
90
|
it 'defaults to rails constraint name' do
|
90
|
-
expect(subject.constraint_name).to eq("
|
91
|
+
expect(subject.constraint_name).to eq("index_advertisers_on_network_id")
|
91
92
|
end
|
92
93
|
end
|
93
94
|
end
|
@@ -103,13 +104,13 @@ RSpec.describe DeclareSchema::Model::ForeignKeyDefinition do
|
|
103
104
|
allow(connection).to receive(:index_name).with('models', column: 'network_id') { }
|
104
105
|
end
|
105
106
|
|
106
|
-
describe '.
|
107
|
-
subject { described_class.
|
107
|
+
describe '.for_table' do
|
108
|
+
subject { described_class.for_table(old_table_name, model.connection) }
|
108
109
|
|
109
|
-
it 'returns
|
110
|
-
expect(subject.
|
111
|
-
|
112
|
-
|
110
|
+
it 'returns definitions' do
|
111
|
+
expect(subject.map(&:key)).to eq([
|
112
|
+
["networks", "network_id", nil]
|
113
|
+
])
|
113
114
|
end
|
114
115
|
end
|
115
116
|
end
|