declare_schema 0.10.1 → 0.12.0

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 (29) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -0
  3. data/Gemfile.lock +1 -1
  4. data/README.md +16 -6
  5. data/lib/declare_schema.rb +12 -1
  6. data/lib/declare_schema/extensions/active_record/fields_declaration.rb +4 -2
  7. data/lib/declare_schema/model.rb +59 -15
  8. data/lib/declare_schema/model/column.rb +2 -2
  9. data/lib/declare_schema/model/field_spec.rb +4 -4
  10. data/lib/declare_schema/model/foreign_key_definition.rb +2 -3
  11. data/lib/declare_schema/model/habtm_model_shim.rb +2 -2
  12. data/lib/declare_schema/model/index_definition.rb +8 -6
  13. data/lib/declare_schema/schema_change/column_add.rb +3 -3
  14. data/lib/declare_schema/version.rb +1 -1
  15. data/lib/generators/declare_schema/migration/USAGE +14 -24
  16. data/lib/generators/declare_schema/migration/migration_generator.rb +40 -38
  17. data/lib/generators/declare_schema/migration/migrator.rb +18 -14
  18. data/lib/generators/declare_schema/migration/templates/migration.rb.erb +3 -3
  19. data/spec/lib/declare_schema/api_spec.rb +2 -4
  20. data/spec/lib/declare_schema/field_spec_spec.rb +3 -3
  21. data/spec/lib/declare_schema/generator_spec.rb +2 -2
  22. data/spec/lib/declare_schema/interactive_primary_key_spec.rb +8 -11
  23. data/spec/lib/declare_schema/migration_generator_spec.rb +352 -130
  24. data/spec/lib/declare_schema/model/column_spec.rb +2 -6
  25. data/spec/lib/declare_schema/model/foreign_key_definition_spec.rb +32 -8
  26. data/spec/lib/declare_schema/model/habtm_model_shim_spec.rb +4 -6
  27. data/spec/lib/declare_schema/model/index_definition_spec.rb +4 -4
  28. data/spec/spec_helper.rb +1 -1
  29. metadata +2 -2
@@ -1,47 +1,37 @@
1
1
  Description:
2
2
 
3
3
  This generator compares your existing schema against the
4
- schema declared inside your fields declarations in your
5
- models.
4
+ schema declared inside your declare_schema do ... end
5
+ declarations in your models.
6
6
 
7
7
  If the generator finds differences, it will display the
8
- migration it has created, and ask you if you wish to
9
- [g]enerate migration, generate and [m]igrate now or [c]ancel?
10
- Enter "g" to just generate the migration but do not run it.
11
- Enter "m" to generate the migration and run it, or press "c"
12
- to do nothing.
13
-
14
- The generator will then prompt you for the migration name,
8
+ migration it has created, and prompt you for the migration name,
15
9
  supplying a numbered default name.
16
10
 
17
11
  The generator is conservative and will prompt you to resolve
18
12
  any ambiguities.
19
13
 
20
- Examples:
14
+ Example:
21
15
 
22
- $ rails generate declare_schema:migration
16
+ $ bundle exec rails generate declare_schema:migration
23
17
 
24
18
  ---------- Up Migration ----------
25
- create_table :foos do |t|
19
+ create_table :users do |t|
20
+ t.string :first_name, limit: 50
21
+ t.string :last_name, limit: 50
26
22
  t.datetime :created_at
27
23
  t.datetime :updated_at
28
24
  end
29
25
  ----------------------------------
30
26
 
31
27
  ---------- Down Migration --------
32
- drop_table :foos
28
+ drop_table :users
33
29
  ----------------------------------
34
- What now: [g]enerate migration, generate and [m]igrate now or [c]ancel? m
35
-
36
- Migration filename:
37
- (you can type spaces instead of '_' -- every little helps)
38
- Filename [declare_schema_migration_2]: create_foo
30
+ Migration filename: (spaces will be converted to _) [declare_schema_migration_2]: create users
39
31
  exists db/migrate
40
- create db/migrate/20091023183838_create_foo.rb
41
- (in /work/foo)
42
- == CreateFoo: migrating ======================================================
43
- -- create_table(:yos)
44
- -> 0.0856s
45
- == CreateFoo: migrated (0.0858s) =============================================
32
+ create db/migrate/20091023183838_create_users.rb
33
+
34
+ Not running migration since --migrate not given. When you are ready, run:
46
35
 
36
+ bundle exec rails db:migrate
47
37
 
@@ -38,20 +38,18 @@ module DeclareSchema
38
38
  type: :boolean,
39
39
  desc: "Don't prompt for a migration name - just pick one"
40
40
 
41
- class_option :generate,
42
- aliases: '-g',
43
- type: :boolean,
44
- desc: "Don't prompt for action - generate the migration"
45
-
46
41
  class_option :migrate,
47
42
  aliases: '-m',
48
43
  type: :boolean,
49
- desc: "Don't prompt for action - generate and migrate"
44
+ desc: "After generating migration, run it"
50
45
 
51
46
  def migrate
52
47
  return if migrations_pending?
53
48
 
54
- generator = Generators::DeclareSchema::Migration::Migrator.new(->(c, d, k, p) { extract_renames!(c, d, k, p) })
49
+ generator = Generators::DeclareSchema::Migration::Migrator.new do |to_create, to_drop, kind_str, name_prefix|
50
+ extract_renames!(to_create, to_drop, kind_str, name_prefix)
51
+ end
52
+
55
53
  up, down = generator.generate
56
54
 
57
55
  if up.blank?
@@ -67,34 +65,31 @@ module DeclareSchema
67
65
  say down
68
66
  say "----------------------------------"
69
67
 
70
- action = options[:generate] && 'g' ||
71
- options[:migrate] && 'm' ||
72
- choose("\nWhat now: [g]enerate migration, generate and [m]igrate now or [c]ancel?", /^(g|m|c)$/)
73
-
74
- if action != 'c'
75
- if name.blank? && !options[:default_name]
76
- final_migration_name = choose("\nMigration filename: [<enter>=#{migration_name}|<custom_name>]:", /^[a-z0-9_ ]*$/, migration_name).strip.gsub(' ', '_')
77
- end
78
- final_migration_name = migration_name if final_migration_name.blank?
79
-
80
- up.gsub!("\n", "\n ")
81
- up.gsub!(/ +\n/, "\n")
82
- down.gsub!("\n", "\n ")
83
- down.gsub!(/ +\n/, "\n")
84
-
85
- @up = up
86
- @down = down
87
- @migration_class_name = final_migration_name.camelize
88
-
89
- migration_template('migration.rb.erb', "db/migrate/#{final_migration_name.underscore}.rb")
90
- if action == 'm'
91
- case Rails::VERSION::MAJOR
92
- when 4
93
- rake('db:migrate')
94
- else
95
- rails_command('db:migrate')
96
- end
68
+ final_migration_name =
69
+ name.presence ||
70
+ if !options[:default_name]
71
+ choose("\nMigration filename (spaces will be converted to _) [#{default_migration_name}]:", /^[a-z0-9_ ]*$/,
72
+ default_migration_name).strip.gsub(' ', '_').presence
73
+ end ||
74
+ default_migration_name
75
+
76
+ @up = indent(up.strip, 4)
77
+ @down = indent(down.strip, 4)
78
+ @migration_class_name = final_migration_name.camelize
79
+
80
+ migration_template('migration.rb.erb', "db/migrate/#{final_migration_name.underscore}.rb")
81
+
82
+ db_migrate_command = ::DeclareSchema.db_migrate_command
83
+ if options[:migrate]
84
+ say db_migrate_command
85
+ bare_rails_command = db_migrate_command.sub(/\Abundle exec +/, '').sub(/\Arake +|rails +/, '')
86
+ if ActiveSupport::VERSION::MAJOR < 5
87
+ rake(bare_rails_command)
88
+ else
89
+ rails_command(bare_rails_command)
97
90
  end
91
+ else
92
+ say "\nNot running migration since --migrate not given. When you are ready, run:\n\n #{db_migrate_command}\n\n"
98
93
  end
99
94
  rescue ::DeclareSchema::UnknownTypeError => ex
100
95
  say "Invalid field type: #{ex}"
@@ -102,8 +97,15 @@ module DeclareSchema
102
97
 
103
98
  private
104
99
 
100
+ if ActiveSupport::VERSION::MAJOR < 5
101
+ def indent(string, columns)
102
+ whitespace = ' ' * columns
103
+ string.gsub("\n", "\n#{whitespace}").gsub(/ +\n/, "\n")
104
+ end
105
+ end
106
+
105
107
  def migrations_pending?
106
- migrations = case Rails::VERSION::MAJOR
108
+ migrations = case ActiveSupport::VERSION::MAJOR
107
109
  when 4
108
110
  ActiveRecord::Migrator.migrations('db/migrate')
109
111
  when 5
@@ -111,7 +113,7 @@ module DeclareSchema
111
113
  else
112
114
  ActiveRecord::MigrationContext.new(ActiveRecord::Migrator.migrations_paths, ActiveRecord::SchemaMigration).migrations
113
115
  end
114
- pending_migrations = case Rails::VERSION::MAJOR
116
+ pending_migrations = case ActiveSupport::VERSION::MAJOR
115
117
  when 4, 5
116
118
  ActiveRecord::Migrator.new(:up, migrations).pending_migrations
117
119
  else
@@ -176,8 +178,8 @@ module DeclareSchema
176
178
  to_rename
177
179
  end
178
180
 
179
- def migration_name
180
- name || Generators::DeclareSchema::Migration::Migrator.default_migration_name
181
+ def default_migration_name
182
+ Generators::DeclareSchema::Migration::Migrator.default_migration_name
181
183
  end
182
184
  end
183
185
  end
@@ -47,8 +47,8 @@ module Generators
47
47
  deprecate :default_charset=, :default_collation=, :default_charset, :default_collation, deprecator: ActiveSupport::Deprecation.new('1.0', 'declare_schema')
48
48
  end
49
49
 
50
- def initialize(ambiguity_resolver = {}, renames: nil)
51
- @ambiguity_resolver = ambiguity_resolver
50
+ def initialize(renames: nil, &block)
51
+ @ambiguity_resolver = block
52
52
  @drops = []
53
53
  @renames = renames
54
54
  end
@@ -187,6 +187,12 @@ module Generators
187
187
  models, db_tables = models_and_tables
188
188
  models_by_table_name = {}
189
189
  models.each do |m|
190
+ m.try(:field_specs)&.each do |_name, field_spec|
191
+ if (pre_migration = field_spec.options.delete(:pre_migration))
192
+ pre_migration.call(field_spec)
193
+ end
194
+ end
195
+
190
196
  if !models_by_table_name.has_key?(m.table_name)
191
197
  models_by_table_name[m.table_name] = m
192
198
  elsif m.superclass == models_by_table_name[m.table_name].superclass.superclass
@@ -203,8 +209,8 @@ module Generators
203
209
 
204
210
  to_create = model_table_names - db_tables
205
211
  to_drop = db_tables - model_table_names - self.class.always_ignore_tables
206
- to_change = model_table_names
207
212
  to_rename = extract_table_renames!(to_create, to_drop)
213
+ to_change = model_table_names
208
214
 
209
215
  renames = to_rename.map do |old_name, new_name|
210
216
  ::DeclareSchema::SchemaChange::TableRename.new(old_name, new_name)
@@ -297,14 +303,14 @@ module Generators
297
303
  end
298
304
 
299
305
  def create_table_options(model, disable_auto_increment)
300
- primary_key = model._defined_primary_key
306
+ primary_key = model._declared_primary_key
301
307
  if primary_key.blank? || disable_auto_increment
302
308
  { id: false }
303
309
  elsif primary_key == "id"
304
310
  { id: :bigint }
305
311
  else
306
312
  { primary_key: primary_key.to_sym }
307
- end
313
+ end.merge(model._table_options)
308
314
  end
309
315
 
310
316
  def table_options_for_model(model)
@@ -312,8 +318,8 @@ module Generators
312
318
  {}
313
319
  else
314
320
  {
315
- charset: model.table_options[:charset] || ::DeclareSchema.default_charset,
316
- collation: model.table_options[:collation] || ::DeclareSchema.default_collation
321
+ charset: model._table_options&.[](:charset) || ::DeclareSchema.default_charset,
322
+ collation: model._table_options&.[](:collation) || ::DeclareSchema.default_collation
317
323
  }
318
324
  end
319
325
  end
@@ -336,18 +342,16 @@ module Generators
336
342
  new_table_name = model.table_name
337
343
 
338
344
  db_columns = model.connection.columns(current_table_name).index_by(&:name)
339
- key_missing = db_columns[model._defined_primary_key].nil? && model._defined_primary_key.present?
340
- if model._defined_primary_key.present?
341
- db_columns.delete(model._defined_primary_key)
345
+ if (pk = model._declared_primary_key.presence)
346
+ pk_was_in_db_columns = db_columns.delete(pk)
342
347
  end
343
348
 
344
349
  model_column_names = model.field_specs.keys.map(&:to_s)
345
350
  db_column_names = db_columns.keys.map(&:to_s)
346
351
 
347
352
  to_add = model_column_names - db_column_names
348
- to_add += [model._defined_primary_key] if key_missing && model._defined_primary_key.present?
353
+ to_add << pk if pk && !pk_was_in_db_columns
349
354
  to_remove = db_column_names - model_column_names
350
- to_remove -= [model._defined_primary_key.to_sym] if model._defined_primary_key.present?
351
355
 
352
356
  to_rename = extract_column_renames!(to_add, to_remove, new_table_name)
353
357
 
@@ -483,7 +487,7 @@ module Generators
483
487
  parent_columns = connection.columns(parent_table) rescue []
484
488
  pk_limit =
485
489
  if (pk_column = parent_columns.find { |column| column.name.to_s == "id" }) # right now foreign keys assume id is the target
486
- if Rails::VERSION::MAJOR < 5
490
+ if ActiveSupport::VERSION::MAJOR < 5
487
491
  pk_column.cast_type.limit
488
492
  else
489
493
  pk_column.limit
@@ -549,7 +553,7 @@ module Generators
549
553
  end
550
554
  end
551
555
 
552
- SchemaDumper = case Rails::VERSION::MAJOR
556
+ SchemaDumper = case ActiveSupport::VERSION::MAJOR
553
557
  when 4
554
558
  ActiveRecord::SchemaDumper
555
559
  else
@@ -1,9 +1,9 @@
1
- class <%= @migration_class_name %> < (Rails::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration)
1
+ class <%= @migration_class_name %> < (ActiveSupport::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration)
2
2
  def self.up
3
- <%= @up %>
3
+ <%= @up.presence or raise "no @up given!" %>
4
4
  end
5
5
 
6
6
  def self.down
7
- <%= @down %>
7
+ <%= @down.presence or raise "no @down given!" %>
8
8
  end
9
9
  end
@@ -39,17 +39,15 @@ RSpec.describe 'DeclareSchema API' do
39
39
 
40
40
  load_models
41
41
 
42
- if Rails::VERSION::MAJOR == 5
43
- # TODO: get this to work on Travis for Rails 6
42
+ if ActiveSupport::VERSION::MAJOR == 5
44
43
  generate_migrations '-n', '-m'
45
44
  end
46
45
 
47
46
  require 'advert'
47
+ Advert.reset_primary_key
48
48
 
49
49
  ## The Basics
50
50
 
51
- # The main feature of DeclareSchema, aside from the migration generator, is the ability to declare rich types for your fields. For example, you can declare that a field is an email address, and the field will be automatically validated for correct email address syntax.
52
-
53
51
  ### Field Types
54
52
 
55
53
  # Field values are returned as the type you specify.
@@ -6,13 +6,13 @@ rescue LoadError
6
6
  end
7
7
 
8
8
  RSpec.describe DeclareSchema::Model::FieldSpec do
9
- let(:model) { double('model', table_options: {}, _defined_primary_key: 'id') }
9
+ let(:model) { double('model', _table_options: {}, _declared_primary_key: 'id') }
10
10
  let(:col_spec) { double('col_spec', type: :string) }
11
11
 
12
12
  before do
13
13
  load File.expand_path('prepare_testapp.rb', __dir__)
14
14
 
15
- if Rails::VERSION::MAJOR < 5
15
+ if ActiveSupport::VERSION::MAJOR < 5
16
16
  allow(col_spec).to receive(:type_cast_from_database, &:itself)
17
17
  end
18
18
  end
@@ -184,7 +184,7 @@ RSpec.describe DeclareSchema::Model::FieldSpec do
184
184
 
185
185
  describe '#schema_attributes' do
186
186
  let(:col_spec) do
187
- case Rails::VERSION::MAJOR
187
+ case ActiveSupport::VERSION::MAJOR
188
188
  when 4
189
189
  cast_type = ActiveRecord::Type::Integer.new(limit: 8)
190
190
  ActiveRecord::ConnectionAdapters::Column.new("price", nil, cast_type, "integer(8)", false)
@@ -27,7 +27,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
27
27
  end
28
28
  EOS
29
29
 
30
- case Rails::VERSION::MAJOR
30
+ case ActiveSupport::VERSION::MAJOR
31
31
  when 4, 5
32
32
  expect_test_definition_to_eq('alpha/beta', <<~EOS)
33
33
  require "test_helper"
@@ -50,7 +50,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
50
50
  EOS
51
51
  end
52
52
 
53
- case Rails::VERSION::MAJOR
53
+ case ActiveSupport::VERSION::MAJOR
54
54
  when 4
55
55
  expect_test_fixture_to_eq('alpha/beta', <<~EOS)
56
56
  # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rails'
4
3
  begin
5
4
  require 'mysql2'
6
5
  rescue LoadError
@@ -20,7 +19,7 @@ RSpec.describe 'DeclareSchema Migration Generator interactive primary key' do
20
19
  end
21
20
 
22
21
  generate_migrations '-n', '-m'
23
- expect(Foo._defined_primary_key).to eq('foo_id')
22
+ expect(Foo._declared_primary_key).to eq('foo_id')
24
23
 
25
24
  ### migrate from
26
25
  # rename from custom primary_key
@@ -32,7 +31,7 @@ RSpec.describe 'DeclareSchema Migration Generator interactive primary key' do
32
31
 
33
32
  allow_any_instance_of(DeclareSchema::Support::ThorShell).to receive(:ask).with(/one of the rename choices or press enter to keep/) { 'id' }
34
33
  generate_migrations '-n', '-m'
35
- expect(Foo._defined_primary_key).to eq('id')
34
+ expect(Foo._declared_primary_key).to eq('id')
36
35
 
37
36
  nuke_model_class(Foo)
38
37
 
@@ -63,7 +62,7 @@ RSpec.describe 'DeclareSchema Migration Generator interactive primary key' do
63
62
  ActiveRecord::Base.connection.execute("CREATE TABLE foos (id integer PRIMARY KEY AUTOINCREMENT NOT NULL)")
64
63
  end
65
64
 
66
- if Rails::VERSION::MAJOR >= 5 && !defined?(Mysql2) # TODO TECH-4814 Put this test back for Mysql2
65
+ if ActiveSupport::VERSION::MAJOR >= 5 && !defined?(Mysql2) # TODO TECH-4814 Put this test back for Mysql2
67
66
  # replace custom primary_key
68
67
  class Foo < ActiveRecord::Base
69
68
  fields do
@@ -73,7 +72,7 @@ RSpec.describe 'DeclareSchema Migration Generator interactive primary key' do
73
72
 
74
73
  allow_any_instance_of(DeclareSchema::Support::ThorShell).to receive(:ask).with(/one of the rename choices or press enter to keep/) { 'drop id' }
75
74
  generate_migrations '-n', '-m'
76
- expect(Foo._defined_primary_key).to eq('foo_id')
75
+ expect(Foo._declared_primary_key).to eq('foo_id')
77
76
  end
78
77
  end
79
78
  end
@@ -81,8 +80,7 @@ RSpec.describe 'DeclareSchema Migration Generator interactive primary key' do
81
80
  context 'Using declare_schema' do
82
81
  it "allows alternate primary keys" do
83
82
  class Foo < ActiveRecord::Base
84
- declare_schema do
85
- end
83
+ declare_schema { }
86
84
  self.primary_key = "foo_id"
87
85
  end
88
86
 
@@ -92,15 +90,14 @@ RSpec.describe 'DeclareSchema Migration Generator interactive primary key' do
92
90
  ### migrate from
93
91
  # rename from custom primary_key
94
92
  class Foo < ActiveRecord::Base
95
- declare_schema do
96
- end
93
+ declare_schema { }
97
94
  self.primary_key = "id"
98
95
  end
99
96
 
100
97
  puts "\n\e[45m Please enter 'id' (no quotes) at the next prompt \e[0m"
101
98
  allow_any_instance_of(DeclareSchema::Support::ThorShell).to receive(:ask).with(/one of the rename choices or press enter to keep/) { 'id' }
102
99
  generate_migrations '-n', '-m'
103
- expect(Foo.primary_key).to eq('id')
100
+ expect(Foo._declared_primary_key).to eq('id')
104
101
 
105
102
  nuke_model_class(Foo)
106
103
 
@@ -131,7 +128,7 @@ RSpec.describe 'DeclareSchema Migration Generator interactive primary key' do
131
128
  ActiveRecord::Base.connection.execute("CREATE TABLE foos (id integer PRIMARY KEY AUTOINCREMENT NOT NULL)")
132
129
  end
133
130
 
134
- if Rails::VERSION::MAJOR >= 5 && !defined?(Mysql2) # TODO TECH-4814 Put this test back for Mysql2
131
+ if ActiveSupport::VERSION::MAJOR >= 5 && !defined?(Mysql2) # TODO TECH-4814 Put this test back for Mysql2
135
132
  # replace custom primary_key
136
133
  class Foo < ActiveRecord::Base
137
134
  declare_schema do
@@ -1,11 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rails'
4
3
  begin
5
4
  require 'mysql2'
6
5
  rescue LoadError
7
6
  end
8
7
 
8
+ begin
9
+ require 'sqlite3'
10
+ rescue LoadError
11
+ end
12
+
9
13
  RSpec.describe 'DeclareSchema Migration Generator' do
10
14
  before do
11
15
  load File.expand_path('prepare_testapp.rb', __dir__)
@@ -26,20 +30,20 @@ RSpec.describe 'DeclareSchema Migration Generator' do
26
30
  end
27
31
  end
28
32
  let(:datetime_precision) do
29
- if defined?(Mysql2) && Rails::VERSION::MAJOR >= 5
33
+ if defined?(Mysql2) && ActiveSupport::VERSION::MAJOR >= 5
30
34
  ', precision: 0'
31
35
  end
32
36
  end
33
37
  let(:table_options) do
34
38
  if defined?(Mysql2)
35
- ", options: \"#{'ENGINE=InnoDB ' if Rails::VERSION::MAJOR == 5}DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin\"" +
36
- if Rails::VERSION::MAJOR >= 6
39
+ ", options: \"#{'ENGINE=InnoDB ' if ActiveSupport::VERSION::MAJOR == 5}DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin\"" +
40
+ if ActiveSupport::VERSION::MAJOR >= 6
37
41
  ', charset: "utf8mb4", collation: "utf8mb4_bin"'
38
42
  else
39
43
  ''
40
44
  end
41
45
  else
42
- ", id: :integer" unless Rails::VERSION::MAJOR < 5
46
+ ", id: :integer" unless ActiveSupport::VERSION::MAJOR < 5
43
47
  end
44
48
  end
45
49
  let(:lock_version_limit) do
@@ -87,7 +91,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
87
91
  ActiveRecord::Migration.class_eval(up)
88
92
  expect(Advert.columns.map(&:name)).to eq(["id", "name"])
89
93
 
90
- if Rails::VERSION::MAJOR < 5
94
+ if ActiveSupport::VERSION::MAJOR < 5
91
95
  # Rails 4 drivers don't always create PK properly. Fix that by dropping and recreating.
92
96
  ActiveRecord::Base.connection.execute("drop table adverts")
93
97
  if defined?(Mysql2)
@@ -366,10 +370,10 @@ RSpec.describe 'DeclareSchema Migration Generator' do
366
370
  migrate_up(<<~EOS.strip)
367
371
  add_column :adverts, :category_id, :integer, limit: 8, null: false
368
372
  add_index :adverts, [:category_id], name: :on_category_id
369
- #{"add_foreign_key :adverts, :categories, column: :category_id, name: :on_category_id" if defined?(Mysql2)}
373
+ #{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id" if defined?(Mysql2)}
370
374
  EOS
371
375
  .and migrate_down(<<~EOS.strip)
372
- #{"remove_foreign_key :adverts, name: :on_category_id" if defined?(Mysql2)}
376
+ #{"remove_foreign_key :adverts, name: :index_adverts_on_category_id" if defined?(Mysql2)}
373
377
  remove_index :adverts, name: :on_category_id
374
378
  remove_column :adverts, :category_id
375
379
  EOS
@@ -390,8 +394,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
390
394
  migrate_up(<<~EOS.strip)
391
395
  add_column :adverts, :c_id, :integer, limit: 8, null: false
392
396
  add_index :adverts, [:c_id], name: :on_c_id
393
- #{"add_foreign_key :adverts, :categories, column: :category_id, name: :on_category_id\n" +
394
- "add_foreign_key :adverts, :categories, column: :c_id, name: :on_c_id" if defined?(Mysql2)}
397
+ #{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
398
+ "add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
395
399
  EOS
396
400
  )
397
401
 
@@ -409,8 +413,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
409
413
  expect(Generators::DeclareSchema::Migration::Migrator.run).to(
410
414
  migrate_up(<<~EOS.strip)
411
415
  add_column :adverts, :category_id, :integer, limit: 8, null: false
412
- #{"add_foreign_key :adverts, :categories, column: :category_id, name: :on_category_id\n" +
413
- "add_foreign_key :adverts, :categories, column: :c_id, name: :on_c_id" if defined?(Mysql2)}
416
+ #{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
417
+ "add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
414
418
  EOS
415
419
  )
416
420
 
@@ -429,18 +433,18 @@ RSpec.describe 'DeclareSchema Migration Generator' do
429
433
  migrate_up(<<~EOS.strip)
430
434
  add_column :adverts, :category_id, :integer, limit: 8, null: false
431
435
  add_index :adverts, [:category_id], name: :my_index
432
- #{"add_foreign_key :adverts, :categories, column: :category_id, name: :on_category_id\n" +
433
- "add_foreign_key :adverts, :categories, column: :c_id, name: :on_c_id" if defined?(Mysql2)}
436
+ #{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
437
+ "add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
434
438
  EOS
435
439
  )
436
440
 
437
441
  Advert.field_specs.delete(:category_id)
438
442
  Advert.index_definitions.delete_if { |spec| spec.fields == ["category_id"] }
439
443
 
440
- ### Timestamps and Optimimistic Locking
444
+ ### Timestamps and Optimistic Locking
441
445
 
442
446
  # `updated_at` and `created_at` can be declared with the shorthand `timestamps`.
443
- # Similarly, `lock_version` can be declared with the "shorthand" `optimimistic_lock`.
447
+ # Similarly, `lock_version` can be declared with the "shorthand" `optimistic_lock`.
444
448
 
445
449
  class Advert < ActiveRecord::Base
446
450
  fields do
@@ -454,12 +458,12 @@ RSpec.describe 'DeclareSchema Migration Generator' do
454
458
  add_column :adverts, :created_at, :datetime, null: true
455
459
  add_column :adverts, :updated_at, :datetime, null: true
456
460
  add_column :adverts, :lock_version, :integer#{lock_version_limit}, null: false, default: 1
457
- #{"add_foreign_key :adverts, :categories, column: :category_id, name: :on_category_id\n" +
458
- "add_foreign_key :adverts, :categories, column: :c_id, name: :on_c_id" if defined?(Mysql2)}
461
+ #{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
462
+ "add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
459
463
  EOS
460
464
  .and migrate_down(<<~EOS.strip)
461
- #{"remove_foreign_key :adverts, name: :on_c_id\n" +
462
- "remove_foreign_key :adverts, name: :on_category_id" if defined?(Mysql2)}
465
+ #{"remove_foreign_key :adverts, name: :index_adverts_on_c_id\n" +
466
+ "remove_foreign_key :adverts, name: :index_adverts_on_category_id" if defined?(Mysql2)}
463
467
  remove_column :adverts, :lock_version
464
468
  remove_column :adverts, :updated_at
465
469
  remove_column :adverts, :created_at
@@ -484,8 +488,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
484
488
  migrate_up(<<~EOS.strip)
485
489
  add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
486
490
  add_index :adverts, [:title], name: :on_title
487
- #{"add_foreign_key :adverts, :categories, column: :category_id, name: :on_category_id\n" +
488
- "add_foreign_key :adverts, :categories, column: :c_id, name: :on_c_id" if defined?(Mysql2)}
491
+ #{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
492
+ "add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
489
493
  EOS
490
494
  )
491
495
 
@@ -503,8 +507,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
503
507
  migrate_up(<<~EOS.strip)
504
508
  add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
505
509
  add_index :adverts, [:title], name: :on_title, unique: true
506
- #{"add_foreign_key :adverts, :categories, column: :category_id, name: :on_category_id\n" +
507
- "add_foreign_key :adverts, :categories, column: :c_id, name: :on_c_id" if defined?(Mysql2)}
510
+ #{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
511
+ "add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
508
512
  EOS
509
513
  )
510
514
 
@@ -522,8 +526,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
522
526
  migrate_up(<<~EOS.strip)
523
527
  add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
524
528
  add_index :adverts, [:title], name: :my_index
525
- #{"add_foreign_key :adverts, :categories, column: :category_id, name: :on_category_id\n" +
526
- "add_foreign_key :adverts, :categories, column: :c_id, name: :on_c_id" if defined?(Mysql2)}
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)}
527
531
  EOS
528
532
  )
529
533
 
@@ -539,8 +543,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
539
543
  migrate_up(<<~EOS.strip)
540
544
  add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
541
545
  add_index :adverts, [:title], name: :on_title
542
- #{"add_foreign_key :adverts, :categories, column: :category_id, name: :on_category_id\n" +
543
- "add_foreign_key :adverts, :categories, column: :c_id, name: :on_c_id" if defined?(Mysql2)}
546
+ #{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
547
+ "add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
544
548
  EOS
545
549
  )
546
550
 
@@ -556,8 +560,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
556
560
  migrate_up(<<~EOS.strip)
557
561
  add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
558
562
  add_index :adverts, [:title], name: :my_index, unique: true
559
- #{"add_foreign_key :adverts, :categories, column: :category_id, name: :on_category_id\n" +
560
- "add_foreign_key :adverts, :categories, column: :c_id, name: :on_c_id" if defined?(Mysql2)}
563
+ #{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
564
+ "add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
561
565
  EOS
562
566
  )
563
567
 
@@ -573,8 +577,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
573
577
  migrate_up(<<~EOS.strip)
574
578
  add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
575
579
  add_index :adverts, [:title, :category_id], name: :on_title_and_category_id
576
- #{"add_foreign_key :adverts, :categories, column: :category_id, name: :on_category_id\n" +
577
- "add_foreign_key :adverts, :categories, column: :c_id, name: :on_c_id" if defined?(Mysql2)}
580
+ #{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
581
+ "add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
578
582
  EOS
579
583
  )
580
584
 
@@ -606,14 +610,14 @@ RSpec.describe 'DeclareSchema Migration Generator' do
606
610
  add_column :ads, :title, :string, limit: 250, null: true#{charset_and_collation}
607
611
  add_column :ads, :body, :text#{', limit: 4294967295' if defined?(Mysql2)}, null: true#{charset_and_collation}
608
612
  #{if defined?(Mysql2)
609
- "add_foreign_key :adverts, :categories, column: :category_id, name: :on_category_id\n" +
610
- "add_foreign_key :adverts, :categories, column: :c_id, name: :on_c_id"
613
+ "add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
614
+ "add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id"
611
615
  end}
612
616
  EOS
613
617
  .and migrate_down(<<~EOS.strip)
614
618
  #{if defined?(Mysql2)
615
- "remove_foreign_key :adverts, name: :on_c_id\n" +
616
- "remove_foreign_key :adverts, name: :on_category_id"
619
+ "remove_foreign_key :adverts, name: :index_adverts_on_c_id\n" +
620
+ "remove_foreign_key :adverts, name: :index_adverts_on_category_id"
617
621
  end}
618
622
  remove_column :ads, :body
619
623
  remove_column :ads, :title
@@ -836,6 +840,54 @@ RSpec.describe 'DeclareSchema Migration Generator' do
836
840
  expect(Ad.field_specs['company'].options[:validates].inspect).to eq("{:presence=>true, :uniqueness=>{:case_sensitive=>false}}")
837
841
  end
838
842
 
843
+ context 'models with the same parent foreign key relation' do
844
+ before do
845
+ class Category < ActiveRecord::Base
846
+ fields do
847
+ name :string, limit: 250, null: true
848
+ end
849
+ end
850
+ class Advertiser < ActiveRecord::Base
851
+ fields do
852
+ name :string, limit: 250, null: true
853
+ end
854
+ belongs_to :category, limit: 8
855
+ end
856
+ class Affiliate < ActiveRecord::Base
857
+ fields do
858
+ name :string, limit: 250, null: true
859
+ end
860
+ belongs_to :category, limit: 8
861
+ end
862
+ end
863
+
864
+ it 'will genereate unique constraint names' do
865
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
866
+ migrate_up(<<~EOS.strip)
867
+ create_table :categories, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
868
+ t.string :name, limit: 250, null: true, charset: "utf8mb4", collation: "utf8mb4_bin"
869
+ end
870
+ create_table :advertisers, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
871
+ t.string :name, limit: 250, null: true, charset: "utf8mb4", collation: "utf8mb4_bin"
872
+ t.integer :category_id, limit: 8, null: false
873
+ end
874
+ create_table :affiliates, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
875
+ t.string :name, limit: 250, null: true, charset: "utf8mb4", collation: "utf8mb4_bin"
876
+ t.integer :category_id, limit: 8, null: false
877
+ end
878
+ add_index :advertisers, [:category_id], name: :on_category_id
879
+ add_index :affiliates, [:category_id], name: :on_category_id
880
+ add_foreign_key :advertisers, :categories, column: :category_id, name: :index_advertisers_on_category_id
881
+ add_foreign_key :affiliates, :categories, column: :category_id, name: :index_affiliates_on_category_id
882
+ EOS
883
+ )
884
+ migrate
885
+
886
+ nuke_model_class(Advertiser)
887
+ nuke_model_class(Affiliate)
888
+ end
889
+ end if !defined?(SQLite3) && ActiveRecord::VERSION::MAJOR >= 5
890
+
839
891
  describe 'serialize' do
840
892
  before do
841
893
  class Ad < ActiveRecord::Base
@@ -1020,8 +1072,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
1020
1072
  end
1021
1073
  end
1022
1074
 
1023
- context "for Rails #{Rails::VERSION::MAJOR}" do
1024
- if Rails::VERSION::MAJOR >= 5
1075
+ context "for Rails #{ActiveSupport::VERSION::MAJOR}" do
1076
+ if ActiveSupport::VERSION::MAJOR >= 5
1025
1077
  let(:optional_true) { { optional: true } }
1026
1078
  let(:optional_false) { { optional: false } }
1027
1079
  else
@@ -1127,13 +1179,13 @@ RSpec.describe 'DeclareSchema Migration Generator' do
1127
1179
  migration_content = File.read(migrations.first)
1128
1180
  first_line = migration_content.split("\n").first
1129
1181
  base_class = first_line.split(' < ').last
1130
- expect(base_class).to eq("(Rails::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration)")
1182
+ expect(base_class).to eq("(ActiveSupport::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration)")
1131
1183
  end
1132
1184
  end
1133
1185
 
1134
1186
  context 'Does not generate migrations' do
1135
1187
  it 'for aliased fields bigint -> integer limit 8' do
1136
- if Rails::VERSION::MAJOR >= 5 || !ActiveRecord::Base.connection.class.name.match?(/SQLite3Adapter/)
1188
+ if ActiveSupport::VERSION::MAJOR >= 5 || !ActiveRecord::Base.connection.class.name.match?(/SQLite3Adapter/)
1137
1189
  class Advert < active_record_base_class.constantize
1138
1190
  fields do
1139
1191
  price :bigint
@@ -1145,7 +1197,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
1145
1197
  migrations = Dir.glob('db/migrate/*declare_schema_migration*.rb')
1146
1198
  expect(migrations.size).to eq(1), migrations.inspect
1147
1199
 
1148
- if defined?(Mysql2) && Rails::VERSION::MAJOR < 5
1200
+ if defined?(Mysql2) && ActiveSupport::VERSION::MAJOR < 5
1149
1201
  ActiveRecord::Base.connection.execute("ALTER TABLE adverts ADD PRIMARY KEY (id)")
1150
1202
  end
1151
1203
 
@@ -1198,7 +1250,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
1198
1250
  ActiveRecord::Migration.class_eval(up)
1199
1251
  expect(Advert.columns.map(&:name)).to eq(["id", "name"])
1200
1252
 
1201
- if Rails::VERSION::MAJOR < 5
1253
+ if ActiveSupport::VERSION::MAJOR < 5
1202
1254
  # Rails 4 drivers don't always create PK properly. Fix that by dropping and recreating.
1203
1255
  ActiveRecord::Base.connection.execute("drop table adverts")
1204
1256
  if defined?(Mysql2)
@@ -1477,10 +1529,10 @@ RSpec.describe 'DeclareSchema Migration Generator' do
1477
1529
  migrate_up(<<~EOS.strip)
1478
1530
  add_column :adverts, :category_id, :integer, limit: 8, null: false
1479
1531
  add_index :adverts, [:category_id], name: :on_category_id
1480
- #{"add_foreign_key :adverts, :categories, column: :category_id, name: :on_category_id\n" if defined?(Mysql2)}
1532
+ #{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" if defined?(Mysql2)}
1481
1533
  EOS
1482
1534
  .and migrate_down(<<~EOS.strip)
1483
- #{"remove_foreign_key :adverts, name: :on_category_id" if defined?(Mysql2)}
1535
+ #{"remove_foreign_key :adverts, name: :index_adverts_on_category_id" if defined?(Mysql2)}
1484
1536
  remove_index :adverts, name: :on_category_id
1485
1537
  remove_column :adverts, :category_id
1486
1538
  EOS
@@ -1501,8 +1553,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
1501
1553
  migrate_up(<<~EOS.strip)
1502
1554
  add_column :adverts, :c_id, :integer, limit: 8, null: false
1503
1555
  add_index :adverts, [:c_id], name: :on_c_id
1504
- #{"add_foreign_key :adverts, :categories, column: :category_id, name: :on_category_id\n" +
1505
- "add_foreign_key :adverts, :categories, column: :c_id, name: :on_c_id" if defined?(Mysql2)}
1556
+ #{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
1557
+ "add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
1506
1558
  EOS
1507
1559
  )
1508
1560
 
@@ -1520,8 +1572,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
1520
1572
  expect(Generators::DeclareSchema::Migration::Migrator.run).to(
1521
1573
  migrate_up(<<~EOS.strip)
1522
1574
  add_column :adverts, :category_id, :integer, limit: 8, null: false
1523
- #{"add_foreign_key :adverts, :categories, column: :category_id, name: :on_category_id\n" +
1524
- "add_foreign_key :adverts, :categories, column: :c_id, name: :on_c_id" if defined?(Mysql2)}
1575
+ #{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
1576
+ "add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
1525
1577
  EOS
1526
1578
  )
1527
1579
 
@@ -1540,8 +1592,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
1540
1592
  migrate_up(<<~EOS.strip)
1541
1593
  add_column :adverts, :category_id, :integer, limit: 8, null: false
1542
1594
  add_index :adverts, [:category_id], name: :my_index
1543
- #{"add_foreign_key :adverts, :categories, column: :category_id, name: :on_category_id\n" +
1544
- "add_foreign_key :adverts, :categories, column: :c_id, name: :on_c_id" if defined?(Mysql2)}
1595
+ #{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
1596
+ "add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
1545
1597
  EOS
1546
1598
  )
1547
1599
 
@@ -1565,12 +1617,12 @@ RSpec.describe 'DeclareSchema Migration Generator' do
1565
1617
  add_column :adverts, :created_at, :datetime, null: true
1566
1618
  add_column :adverts, :updated_at, :datetime, null: true
1567
1619
  add_column :adverts, :lock_version, :integer#{lock_version_limit}, null: false, default: 1
1568
- #{"add_foreign_key :adverts, :categories, column: :category_id, name: :on_category_id\n" +
1569
- "add_foreign_key :adverts, :categories, column: :c_id, name: :on_c_id" if defined?(Mysql2)}
1620
+ #{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
1621
+ "add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
1570
1622
  EOS
1571
1623
  .and migrate_down(<<~EOS.strip)
1572
- #{"remove_foreign_key :adverts, name: :on_c_id\n" +
1573
- "remove_foreign_key :adverts, name: :on_category_id" if defined?(Mysql2)}
1624
+ #{"remove_foreign_key :adverts, name: :index_adverts_on_c_id\n" +
1625
+ "remove_foreign_key :adverts, name: :index_adverts_on_category_id" if defined?(Mysql2)}
1574
1626
  remove_column :adverts, :lock_version
1575
1627
  remove_column :adverts, :updated_at
1576
1628
  remove_column :adverts, :created_at
@@ -1595,8 +1647,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
1595
1647
  migrate_up(<<~EOS.strip)
1596
1648
  add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
1597
1649
  add_index :adverts, [:title], name: :on_title
1598
- #{"add_foreign_key :adverts, :categories, column: :category_id, name: :on_category_id\n" +
1599
- "add_foreign_key :adverts, :categories, column: :c_id, name: :on_c_id" if defined?(Mysql2)}
1650
+ #{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
1651
+ "add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
1600
1652
  EOS
1601
1653
  )
1602
1654
 
@@ -1614,8 +1666,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
1614
1666
  migrate_up(<<~EOS.strip)
1615
1667
  add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
1616
1668
  add_index :adverts, [:title], name: :on_title, unique: true
1617
- #{"add_foreign_key :adverts, :categories, column: :category_id, name: :on_category_id\n" +
1618
- "add_foreign_key :adverts, :categories, column: :c_id, name: :on_c_id" if defined?(Mysql2)}
1669
+ #{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
1670
+ "add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
1619
1671
  EOS
1620
1672
  )
1621
1673
 
@@ -1633,8 +1685,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
1633
1685
  migrate_up(<<~EOS.strip)
1634
1686
  add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
1635
1687
  add_index :adverts, [:title], name: :my_index
1636
- #{"add_foreign_key :adverts, :categories, column: :category_id, name: :on_category_id\n" +
1637
- "add_foreign_key :adverts, :categories, column: :c_id, name: :on_c_id" if defined?(Mysql2)}
1688
+ #{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
1689
+ "add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
1638
1690
  EOS
1639
1691
  )
1640
1692
 
@@ -1650,8 +1702,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
1650
1702
  migrate_up(<<~EOS.strip)
1651
1703
  add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
1652
1704
  add_index :adverts, [:title], name: :on_title
1653
- #{"add_foreign_key :adverts, :categories, column: :category_id, name: :on_category_id\n" +
1654
- "add_foreign_key :adverts, :categories, column: :c_id, name: :on_c_id" if defined?(Mysql2)}
1705
+ #{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
1706
+ "add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
1655
1707
  EOS
1656
1708
  )
1657
1709
 
@@ -1667,8 +1719,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
1667
1719
  migrate_up(<<~EOS.strip)
1668
1720
  add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
1669
1721
  add_index :adverts, [:title], name: :my_index, unique: true
1670
- #{"add_foreign_key :adverts, :categories, column: :category_id, name: :on_category_id\n" +
1671
- "add_foreign_key :adverts, :categories, column: :c_id, name: :on_c_id" if defined?(Mysql2)}
1722
+ #{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
1723
+ "add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
1672
1724
  EOS
1673
1725
  )
1674
1726
 
@@ -1684,8 +1736,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
1684
1736
  migrate_up(<<~EOS.strip)
1685
1737
  add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
1686
1738
  add_index :adverts, [:title, :category_id], name: :on_title_and_category_id
1687
- #{"add_foreign_key :adverts, :categories, column: :category_id, name: :on_category_id\n" +
1688
- "add_foreign_key :adverts, :categories, column: :c_id, name: :on_c_id" if defined?(Mysql2)}
1739
+ #{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
1740
+ "add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
1689
1741
  EOS
1690
1742
  )
1691
1743
 
@@ -1716,14 +1768,14 @@ RSpec.describe 'DeclareSchema Migration Generator' do
1716
1768
  add_column :ads, :title, :string, limit: 250, null: true#{charset_and_collation}
1717
1769
  add_column :ads, :body, :text#{', limit: 4294967295' if defined?(Mysql2)}, null: true#{charset_and_collation}
1718
1770
  #{if defined?(Mysql2)
1719
- "add_foreign_key :adverts, :categories, column: :category_id, name: :on_category_id\n" +
1720
- "add_foreign_key :adverts, :categories, column: :c_id, name: :on_c_id"
1771
+ "add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
1772
+ "add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id"
1721
1773
  end}
1722
1774
  EOS
1723
1775
  .and migrate_down(<<~EOS.strip)
1724
1776
  #{if defined?(Mysql2)
1725
- "remove_foreign_key :adverts, name: :on_c_id\n" +
1726
- "remove_foreign_key :adverts, name: :on_category_id"
1777
+ "remove_foreign_key :adverts, name: :index_adverts_on_c_id\n" +
1778
+ "remove_foreign_key :adverts, name: :index_adverts_on_category_id"
1727
1779
  end}
1728
1780
  remove_column :ads, :body
1729
1781
  remove_column :ads, :title
@@ -1946,6 +1998,54 @@ RSpec.describe 'DeclareSchema Migration Generator' do
1946
1998
  expect(Ad.field_specs['company'].options[:validates].inspect).to eq("{:presence=>true, :uniqueness=>{:case_sensitive=>false}}")
1947
1999
  end
1948
2000
 
2001
+ context 'models with the same parent foreign key relation' do
2002
+ before do
2003
+ class Category < ActiveRecord::Base
2004
+ fields do
2005
+ name :string, limit: 250, null: true
2006
+ end
2007
+ end
2008
+ class Advertiser < ActiveRecord::Base
2009
+ fields do
2010
+ name :string, limit: 250, null: true
2011
+ end
2012
+ belongs_to :category, limit: 8
2013
+ end
2014
+ class Affiliate < ActiveRecord::Base
2015
+ fields do
2016
+ name :string, limit: 250, null: true
2017
+ end
2018
+ belongs_to :category, limit: 8
2019
+ end
2020
+ end
2021
+
2022
+ it 'will genereate unique constraint names' do
2023
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
2024
+ migrate_up(<<~EOS.strip)
2025
+ create_table :categories, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
2026
+ t.string :name, limit: 250, null: true, charset: "utf8mb4", collation: "utf8mb4_bin"
2027
+ end
2028
+ create_table :advertisers, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
2029
+ t.string :name, limit: 250, null: true, charset: "utf8mb4", collation: "utf8mb4_bin"
2030
+ t.integer :category_id, limit: 8, null: false
2031
+ end
2032
+ create_table :affiliates, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
2033
+ t.string :name, limit: 250, null: true, charset: "utf8mb4", collation: "utf8mb4_bin"
2034
+ t.integer :category_id, limit: 8, null: false
2035
+ end
2036
+ add_index :advertisers, [:category_id], name: :on_category_id
2037
+ add_index :affiliates, [:category_id], name: :on_category_id
2038
+ add_foreign_key :advertisers, :categories, column: :category_id, name: :index_advertisers_on_category_id
2039
+ add_foreign_key :affiliates, :categories, column: :category_id, name: :index_affiliates_on_category_id
2040
+ EOS
2041
+ )
2042
+ migrate
2043
+
2044
+ nuke_model_class(Advertiser)
2045
+ nuke_model_class(Affiliate)
2046
+ end
2047
+ end if !defined?(SQLite3) && ActiveRecord::VERSION::MAJOR >= 5
2048
+
1949
2049
  describe 'serialize' do
1950
2050
  before do
1951
2051
  class Ad < ActiveRecord::Base
@@ -2130,8 +2230,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
2130
2230
  end
2131
2231
  end
2132
2232
 
2133
- context "for Rails #{Rails::VERSION::MAJOR}" do
2134
- if Rails::VERSION::MAJOR >= 5
2233
+ context "for Rails #{ActiveSupport::VERSION::MAJOR}" do
2234
+ if ActiveSupport::VERSION::MAJOR >= 5
2135
2235
  let(:optional_true) { { optional: true } }
2136
2236
  let(:optional_false) { { optional: false } }
2137
2237
  else
@@ -2141,80 +2241,202 @@ RSpec.describe 'DeclareSchema Migration Generator' do
2141
2241
  let(:optional_flag) { { false => optional_false, true => optional_true } }
2142
2242
 
2143
2243
  describe 'belongs_to' do
2144
- before do
2145
- unless defined?(AdCategory)
2146
- class AdCategory < ActiveRecord::Base
2147
- declare_schema { }
2244
+ context 'with AdCategory and Advert in DB' do
2245
+ before do
2246
+ unless defined?(AdCategory)
2247
+ class AdCategory < ActiveRecord::Base
2248
+ declare_schema { }
2249
+ end
2148
2250
  end
2149
- end
2150
2251
 
2151
- class Advert < ActiveRecord::Base
2152
- declare_schema do
2153
- string :name, limit: 250, null: true
2154
- integer :category_id, limit: 8
2155
- integer :nullable_category_id, limit: 8, null: true
2252
+ class Advert < ActiveRecord::Base
2253
+ declare_schema do
2254
+ string :name, limit: 250, null: true
2255
+ integer :category_id, limit: 8
2256
+ integer :nullable_category_id, limit: 8, null: true
2257
+ end
2156
2258
  end
2259
+ up = Generators::DeclareSchema::Migration::Migrator.run.first
2260
+ ActiveRecord::Migration.class_eval(up)
2157
2261
  end
2158
- up = Generators::DeclareSchema::Migration::Migrator.run.first
2159
- ActiveRecord::Migration.class_eval(up)
2160
- end
2161
2262
 
2162
- it 'passes through optional: when given' do
2163
- class AdvertBelongsTo < ActiveRecord::Base
2164
- self.table_name = 'adverts'
2165
- declare_schema { }
2166
- reset_column_information
2167
- belongs_to :ad_category, optional: true
2168
- end
2169
- expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_true)
2170
- end
2171
-
2172
- describe 'contradictory settings' do # contradictory settings are ok--for example, during migration
2173
- it 'passes through optional: true, null: false' do
2263
+ it 'passes through optional: when given' do
2174
2264
  class AdvertBelongsTo < ActiveRecord::Base
2175
2265
  self.table_name = 'adverts'
2176
2266
  declare_schema { }
2177
2267
  reset_column_information
2178
- belongs_to :ad_category, optional: true, null: false
2268
+ belongs_to :ad_category, optional: true
2179
2269
  end
2180
2270
  expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_true)
2181
- expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(false)
2182
2271
  end
2183
2272
 
2184
- it 'passes through optional: false, null: true' do
2185
- class AdvertBelongsTo < ActiveRecord::Base
2186
- self.table_name = 'adverts'
2187
- declare_schema { }
2188
- reset_column_information
2189
- belongs_to :ad_category, optional: false, null: true
2273
+ describe 'contradictory settings' do # contradictory settings are ok--for example, during migration
2274
+ it 'passes through optional: true, null: false' do
2275
+ class AdvertBelongsTo < ActiveRecord::Base
2276
+ self.table_name = 'adverts'
2277
+ declare_schema { }
2278
+ reset_column_information
2279
+ belongs_to :ad_category, optional: true, null: false
2280
+ end
2281
+ expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_true)
2282
+ expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(false)
2283
+ end
2284
+
2285
+ it 'passes through optional: false, null: true' do
2286
+ class AdvertBelongsTo < ActiveRecord::Base
2287
+ self.table_name = 'adverts'
2288
+ declare_schema { }
2289
+ reset_column_information
2290
+ belongs_to :ad_category, optional: false, null: true
2291
+ end
2292
+ expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_false)
2293
+ expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(true)
2294
+ end
2295
+ end
2296
+
2297
+ [false, true].each do |nullable|
2298
+ context "nullable=#{nullable}" do
2299
+ it 'infers optional: from null:' do
2300
+ eval <<~EOS
2301
+ class AdvertBelongsTo < ActiveRecord::Base
2302
+ declare_schema { }
2303
+ belongs_to :ad_category, null: #{nullable}
2304
+ end
2305
+ EOS
2306
+ expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_flag[nullable])
2307
+ expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(nullable)
2308
+ end
2309
+
2310
+ it 'infers null: from optional:' do
2311
+ eval <<~EOS
2312
+ class AdvertBelongsTo < ActiveRecord::Base
2313
+ declare_schema { }
2314
+ belongs_to :ad_category, optional: #{nullable}
2315
+ end
2316
+ EOS
2317
+ expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_flag[nullable])
2318
+ expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(nullable)
2319
+ end
2190
2320
  end
2191
- expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_false)
2192
- expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(true)
2321
+ end
2322
+
2323
+ it 'deprecates limit:' do
2324
+ expect(ActiveSupport::Deprecation).to receive(:warn).with("belongs_to limit: is deprecated since it is now inferred")
2325
+ eval <<~EOS
2326
+ class UsingLimit < ActiveRecord::Base
2327
+ declare_schema { }
2328
+ belongs_to :ad_category, limit: 4
2329
+ end
2330
+ EOS
2193
2331
  end
2194
2332
  end
2195
2333
 
2196
- [false, true].each do |nullable|
2197
- context "nullable=#{nullable}" do
2198
- it 'infers optional: from null:' do
2199
- eval <<~EOS
2200
- class AdvertBelongsTo < ActiveRecord::Base
2201
- declare_schema { }
2202
- belongs_to :ad_category, null: #{nullable}
2203
- end
2204
- EOS
2205
- expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_flag[nullable])
2206
- expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(nullable)
2334
+ context 'when parent object PKs have different limits' do
2335
+ before do
2336
+ class IdDefault < ActiveRecord::Base
2337
+ declare_schema { }
2338
+ end
2339
+ class Id4 < ActiveRecord::Base
2340
+ declare_schema id: :integer do
2341
+ end
2207
2342
  end
2343
+ class Id8 < ActiveRecord::Base
2344
+ declare_schema id: :bigint do
2345
+ end
2346
+ end
2347
+ class Fk < ActiveRecord::Base
2348
+ declare_schema { }
2349
+ belongs_to :id_default, (ActiveSupport::VERSION::MAJOR < 5 ? { constraint: false } : {})
2350
+ belongs_to :id4, (ActiveSupport::VERSION::MAJOR < 5 ? { constraint: false } : {})
2351
+ belongs_to :id8, (ActiveSupport::VERSION::MAJOR < 5 ? { constraint: false } : {})
2352
+ end
2353
+ end
2208
2354
 
2209
- it 'infers null: from optional:' do
2210
- eval <<~EOS
2211
- class AdvertBelongsTo < ActiveRecord::Base
2212
- declare_schema { }
2213
- belongs_to :ad_category, optional: #{nullable}
2214
- end
2215
- EOS
2216
- expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_flag[nullable])
2217
- expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(nullable)
2355
+ it 'creates the proper PKs' do
2356
+ up = Generators::DeclareSchema::Migration::Migrator.run.first
2357
+
2358
+ create_id4_defaults = up.split("\n").grep(/create_table :id_defaults/).first
2359
+ expect(create_id4_defaults).to be, up
2360
+ expect(create_id4_defaults).to match(/, id: :bigint/)
2361
+
2362
+ create_id4s = up.split("\n").grep(/create_table :id4s/).first
2363
+ expect(create_id4s).to be, up
2364
+ expect(create_id4s).to match(/, id: :integer/)
2365
+
2366
+ create_id8s = up.split("\n").grep(/create_table :id8s/).first
2367
+ expect(create_id8s).to be, up
2368
+ expect(create_id8s).to match(/, id: :bigint/)
2369
+ end
2370
+
2371
+ it 'infers the correct FK type from the create_table id: type' do
2372
+ up = Generators::DeclareSchema::Migration::Migrator.run.first
2373
+
2374
+ create_fks = up.split("\n").grep(/t\.integer /).map { |command| command.gsub(', null: false', '').gsub(/^ +/, '') }
2375
+ if defined?(SQLite3)
2376
+ create_fks.map! { |command| command.gsub(/limit: [a-z0-9]+/, 'limit: X') }
2377
+ expect(create_fks).to eq([
2378
+ 't.integer :id_default_id, limit: X',
2379
+ 't.integer :id4_id, limit: X',
2380
+ 't.integer :id8_id, limit: X'
2381
+ ]), up
2382
+ else
2383
+ expect(create_fks).to eq([
2384
+ 't.integer :id_default_id, limit: 8',
2385
+ 't.integer :id4_id, limit: 4',
2386
+ 't.integer :id8_id, limit: 8'
2387
+ ]), up
2388
+ end
2389
+ end
2390
+
2391
+ context "when parent objects were migrated before and later definitions don't have explicit id:" do
2392
+ before do
2393
+ up = Generators::DeclareSchema::Migration::Migrator.run.first
2394
+ ActiveRecord::Migration.class_eval up
2395
+ nuke_model_class(IdDefault)
2396
+ nuke_model_class(Id4)
2397
+ nuke_model_class(Id8)
2398
+ nuke_model_class(Fk)
2399
+ ActiveRecord::Base.connection.schema_cache.clear!
2400
+
2401
+
2402
+ class NewIdDefault < ActiveRecord::Base
2403
+ self.table_name = 'id_defaults'
2404
+ declare_schema { }
2405
+ end
2406
+ class NewId4 < ActiveRecord::Base
2407
+ self.table_name = 'id4s'
2408
+ declare_schema { }
2409
+ end
2410
+ class NewId8 < ActiveRecord::Base
2411
+ self.table_name = 'id8s'
2412
+ declare_schema { }
2413
+ end
2414
+ class NewFk < ActiveRecord::Base
2415
+ declare_schema { }
2416
+ belongs_to :new_id_default
2417
+ belongs_to :new_id4
2418
+ belongs_to :new_id8
2419
+ end
2420
+ end
2421
+
2422
+ it 'infers the correct FK :integer limit: ' do
2423
+ up = Generators::DeclareSchema::Migration::Migrator.run.first
2424
+
2425
+ create_fks = up.split("\n").grep(/t\.integer /).map { |command| command.gsub(', null: false', '').gsub(/^ +/, '') }
2426
+ if defined?(SQLite3)
2427
+ create_fks.map! { |command| command.gsub(/limit: [a-z0-9]+/, 'limit: X') }
2428
+ expect(create_fks).to eq([
2429
+ 't.integer :new_id_default_id, limit: X',
2430
+ 't.integer :new_id4_id, limit: X',
2431
+ 't.integer :new_id8_id, limit: X'
2432
+ ]), up
2433
+ else
2434
+ expect(create_fks).to eq([
2435
+ 't.integer :new_id_default_id, limit: 8',
2436
+ 't.integer :new_id4_id, limit: 4',
2437
+ 't.integer :new_id8_id, limit: 8'
2438
+ ]), up
2439
+ end
2218
2440
  end
2219
2441
  end
2220
2442
  end
@@ -2237,13 +2459,13 @@ RSpec.describe 'DeclareSchema Migration Generator' do
2237
2459
  migration_content = File.read(migrations.first)
2238
2460
  first_line = migration_content.split("\n").first
2239
2461
  base_class = first_line.split(' < ').last
2240
- expect(base_class).to eq("(Rails::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration)")
2462
+ expect(base_class).to eq("(ActiveSupport::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration)")
2241
2463
  end
2242
2464
  end
2243
2465
 
2244
2466
  context 'Does not generate migrations' do
2245
2467
  it 'for aliased fields bigint -> integer limit 8' do
2246
- if Rails::VERSION::MAJOR >= 5 || !ActiveRecord::Base.connection.class.name.match?(/SQLite3Adapter/)
2468
+ if ActiveSupport::VERSION::MAJOR >= 5 || !ActiveRecord::Base.connection.class.name.match?(/SQLite3Adapter/)
2247
2469
  class Advert < active_record_base_class.constantize
2248
2470
  declare_schema do
2249
2471
  bigint :price
@@ -2255,7 +2477,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
2255
2477
  migrations = Dir.glob('db/migrate/*declare_schema_migration*.rb')
2256
2478
  expect(migrations.size).to eq(1), migrations.inspect
2257
2479
 
2258
- if defined?(Mysql2) && Rails::VERSION::MAJOR < 5
2480
+ if defined?(Mysql2) && ActiveSupport::VERSION::MAJOR < 5
2259
2481
  ActiveRecord::Base.connection.execute("ALTER TABLE adverts ADD PRIMARY KEY (id)")
2260
2482
  end
2261
2483