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.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeclareSchema
4
- VERSION = "1.4.0.colin.7"
4
+ VERSION = "1.4.0.colin.9"
5
5
  end
@@ -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, refls|
207
- models_by_table_name[name] = ::DeclareSchema::Model::HabtmModelShim.from_reflection(refls.first)
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 |t|
225
- model = models_by_table_name[t]
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(t,
243
- primary_key_definition + field_definitions,
244
- table_options,
245
- sql_options: table_options_definition.settings)
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 |t|
259
- model = models_by_table_name[t]
260
- table = to_rename.key(t) || model.table_name
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.constraint_specs.map do |fk|
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.foreign_key_name, name: fk.constraint_name)
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.for_model(model, old_table_name)
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
- existing_fks = ::DeclareSchema::Model::ForeignKeyDefinition.for_model(model, old_table_name)
489
- model_fks = model.constraint_specs.to_a
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.foreign_key_name, name: fk.constraint_name)
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.foreign_key_name, name: fk.constraint_name)
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.constraint_specs.find { |fk| field_name == fk.foreign_key.to_s }
527
- if foreign_key && (parent_table = foreign_key.parent_table_name)
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
- it 'raises error when default_string_limit option is nil when not explicitly set in field spec' do
62
- if defined?(Mysql2)
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\n" +
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(Kernel).to receive(:warn).with(/belongs_to index: 'name' is deprecated; use index: \{ name: 'name' \} instead/i)
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: :index_adverts_on_category_id\n" +
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
- add_column :adverts, :category_id, :integer, limit: 8, null: false
447
- add_index :adverts, [:category_id], name: :my_index
448
- #{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
449
- "add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
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(Kernel).to receive(:warn).with(/belongs_to index: 'name' is deprecated; use index: \{ name: 'name' \} instead/i)
494
- expect(Kernel).to receive(:warn).with(/belongs_to unique: true\|false is deprecated; use index: \{ unique: true\|false \} instead/i)
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: :index_adverts_on_category_id\n" +
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
- #{if defined?(Mysql2)
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 genereate unique constraint names' do
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(:foreign_key) { :network_id }
25
- let(:options) { {} }
26
- subject { described_class.new(model, foreign_key, **options)}
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.foreign_key).to eq('network_id')
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) { { parent_table: :networks, foreign_key: :the_network_id } }
39
+ let(:options) { { child_table_name: 'advertisers', parent_class_name: 'Network' } }
40
40
 
41
41
  it 'normalizes symbols to strings' do
42
- expect(subject.foreign_key).to eq('network_id')
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.foreign_key).to eq('network_id')
46
- expect(subject.constraint_name).to eq('index_on_network_id')
47
- expect(subject.on_delete_cascade).to be_falsey
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) { { parent_table: :networks, foreign_key: :the_network_id, constraint_name: :constraint_1, dependent: :delete } }
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.foreign_key).to eq('network_id')
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.on_delete_cascade).to be_truthy
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) { { foreign_key: :the_network_id, constraint_name: :constraint_1, dependent: :delete } }
65
+ let(:options) { { child_table_name: 'advertisers', constraint_name: :constraint_1, dependent: :delete } }
66
66
 
67
- it 'compares equal without requring the parent class' do
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) { { foreign_key: :the_network_id, class_name: 'TheNetwork', constraint_name: :constraint_1 } }
73
+ let(:options) { { child_table_name: 'advertisers', parent_class_name: 'TheNetwork', constraint_name: :constraint_1 } }
74
74
 
75
- it 'compares equal without requring the parent class' do
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("index_on_network_id")
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("index_on_network_id")
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 '.for_model' do
107
- subject { described_class.for_model(model, old_table_name) }
107
+ describe '.for_table' do
108
+ subject { described_class.for_table(old_table_name, model.connection) }
108
109
 
109
- it 'returns new object' do
110
- expect(subject.size).to eq(1), subject.inspect
111
- expect(subject.first).to be_kind_of(described_class)
112
- expect(subject.first.foreign_key).to eq('network_id')
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