declare_schema 0.3.0.pre.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeclareSchema
4
- VERSION = "0.3.0.pre.1"
4
+ VERSION = "0.4.1"
5
5
  end
@@ -45,14 +45,14 @@ module Generators
45
45
  false # no single-column primary key
46
46
  end
47
47
 
48
- def index_specs_with_primary_key
48
+ def index_definitions_with_primary_key
49
49
  [
50
- ::DeclareSchema::Model::IndexSpec.new(self, foreign_keys, unique: true, name: "PRIMARY_KEY"),
51
- ::DeclareSchema::Model::IndexSpec.new(self, foreign_keys.last) # not unique by itself; combines with primary key to be unique
50
+ ::DeclareSchema::Model::IndexDefinition.new(self, foreign_keys, unique: true, name: ::DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME),
51
+ ::DeclareSchema::Model::IndexDefinition.new(self, foreign_keys.last) # not unique by itself; combines with primary key to be unique
52
52
  ]
53
53
  end
54
54
 
55
- alias_method :index_specs, :index_specs_with_primary_key
55
+ alias_method :index_definitions, :index_definitions_with_primary_key
56
56
 
57
57
  def ignore_indexes
58
58
  []
@@ -60,8 +60,8 @@ module Generators
60
60
 
61
61
  def constraint_specs
62
62
  [
63
- ::DeclareSchema::Model::ForeignKeySpec.new(self, foreign_keys.first, parent_table: foreign_key_classes.first.table_name, constraint_name: "#{join_table}_FK1", dependent: :delete),
64
- ::DeclareSchema::Model::ForeignKeySpec.new(self, foreign_keys.last, parent_table: foreign_key_classes.last.table_name, constraint_name: "#{join_table}_FK2", dependent: :delete)
63
+ ::DeclareSchema::Model::ForeignKeyDefinition.new(self, foreign_keys.first, parent_table: foreign_key_classes.first.table_name, constraint_name: "#{join_table}_FK1", dependent: :delete),
64
+ ::DeclareSchema::Model::ForeignKeyDefinition.new(self, foreign_keys.last, parent_table: foreign_key_classes.last.table_name, constraint_name: "#{join_table}_FK2", dependent: :delete)
65
65
  ]
66
66
  end
67
67
  end
@@ -69,12 +69,14 @@ module Generators
69
69
  class Migrator
70
70
  class Error < RuntimeError; end
71
71
 
72
- @ignore_models = []
73
- @ignore_tables = []
74
- @active_record_class = ActiveRecord::Base
72
+ @ignore_models = []
73
+ @ignore_tables = []
74
+ @before_generating_migration_callback = nil
75
+ @active_record_class = ActiveRecord::Base
75
76
 
76
77
  class << self
77
78
  attr_accessor :ignore_models, :ignore_tables, :disable_indexing, :disable_constraints, :active_record_class
79
+ attr_reader :before_generating_migration_callback
78
80
 
79
81
  def active_record_class
80
82
  @active_record_class.is_a?(Class) or @active_record_class = @active_record_class.to_s.constantize
@@ -110,6 +112,11 @@ module Generators
110
112
  def native_types
111
113
  @native_types ||= fix_native_types(connection.native_database_types)
112
114
  end
115
+
116
+ def before_generating_migration(&block)
117
+ block or raise ArgumentError, 'A block is required when setting the before_generating_migration callback'
118
+ @before_generating_migration_callback = block
119
+ end
113
120
  end
114
121
 
115
122
  def initialize(ambiguity_resolver = {})
@@ -120,14 +127,12 @@ module Generators
120
127
 
121
128
  attr_accessor :renames
122
129
 
123
- # TODO: Add an application callback (maybe an initializer in a special group?) that
124
- # the application can use to load other models that live in the database, to support DeclareSchema migrations
125
- # for them.
126
130
  def load_rails_models
127
131
  ActiveRecord::Migration.verbose = false
128
132
 
129
133
  Rails.application.eager_load!
130
134
  Rails::Engine.subclasses.each(&:eager_load!)
135
+ self.class.before_generating_migration_callback&.call
131
136
  end
132
137
 
133
138
  # Returns an array of model classes that *directly* extend
@@ -336,7 +341,7 @@ module Generators
336
341
  end
337
342
 
338
343
  def create_indexes(model)
339
- model.index_specs.map { |i| i.to_add_statement(model.table_name) }
344
+ model.index_definitions.map { |i| i.to_add_statement(model.table_name) }
340
345
  end
341
346
 
342
347
  def create_constraints(model)
@@ -408,7 +413,7 @@ module Generators
408
413
  col_name = old_names[c] || c
409
414
  col = db_columns[col_name]
410
415
  spec = model.field_specs[c]
411
- if spec.different_to?(col) # TODO: DRY this up to a diff function that returns the differences. It's different if it has differences. -Colin
416
+ if spec.different_to?(col) # TODO: TECH-4814 DRY this up to a diff function that returns the differences. It's different if it has differences. -Colin
412
417
  change_spec = fk_field_options(model, c)
413
418
  change_spec[:limit] ||= spec.limit if (spec.sql_type != :text ||
414
419
  ::DeclareSchema::Model::FieldSpec.mysql_text_limits?) &&
@@ -444,8 +449,8 @@ module Generators
444
449
  return [[], []] if Migrator.disable_constraints
445
450
 
446
451
  new_table_name = model.table_name
447
- existing_indexes = ::DeclareSchema::Model::IndexSpec.for_model(model, old_table_name)
448
- model_indexes_with_equivalents = model.index_specs_with_primary_key
452
+ existing_indexes = ::DeclareSchema::Model::IndexDefinition.for_model(model, old_table_name)
453
+ model_indexes_with_equivalents = model.index_definitions_with_primary_key
449
454
  model_indexes = model_indexes_with_equivalents.map do |i|
450
455
  if i.explicit_name.nil?
451
456
  if ex = existing_indexes.find { |e| i != e && e.equivalent?(i) }
@@ -453,20 +458,20 @@ module Generators
453
458
  end
454
459
  end || i
455
460
  end
456
- existing_has_primary_key = existing_indexes.any? { |i| i.name == 'PRIMARY_KEY' }
457
- model_has_primary_key = model_indexes.any? { |i| i.name == 'PRIMARY_KEY' }
461
+ existing_has_primary_key = existing_indexes.any? { |i| i.name == ::DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME }
462
+ model_has_primary_key = model_indexes.any? { |i| i.name == ::DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME }
458
463
 
459
464
  add_indexes_init = model_indexes - existing_indexes
460
465
  drop_indexes_init = existing_indexes - model_indexes
461
466
  undo_add_indexes = []
462
467
  undo_drop_indexes = []
463
468
  add_indexes = add_indexes_init.map do |i|
464
- undo_add_indexes << drop_index(old_table_name, i.name) unless i.name == "PRIMARY_KEY"
469
+ undo_add_indexes << drop_index(old_table_name, i.name) unless i.name == ::DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME
465
470
  i.to_add_statement(new_table_name, existing_has_primary_key)
466
471
  end
467
472
  drop_indexes = drop_indexes_init.map do |i|
468
473
  undo_drop_indexes << i.to_add_statement(old_table_name, model_has_primary_key)
469
- drop_index(new_table_name, i.name) unless i.name == "PRIMARY_KEY"
474
+ drop_index(new_table_name, i.name) unless i.name == ::DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME
470
475
  end.compact
471
476
 
472
477
  # the order is important here - adding a :unique, for instance needs to remove then add
@@ -484,7 +489,7 @@ module Generators
484
489
  return [[], []] if Migrator.disable_indexing
485
490
 
486
491
  new_table_name = model.table_name
487
- existing_fks = ::DeclareSchema::Model::ForeignKeySpec.for_model(model, old_table_name)
492
+ existing_fks = ::DeclareSchema::Model::ForeignKeyDefinition.for_model(model, old_table_name)
488
493
  model_fks = model.constraint_specs
489
494
  add_fks = model_fks - existing_fks
490
495
  drop_fks = existing_fks - model_fks
@@ -1,4 +1,4 @@
1
- class <%= @migration_class_name %> < ActiveRecord::Migration<%= ('[4.2]' if Rails::VERSION::MAJOR >= 5) %>
1
+ class <%= @migration_class_name %> < (Rails::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration)
2
2
  def self.up
3
3
  <%= @up %>
4
4
  end
@@ -10,14 +10,14 @@ RSpec.describe 'DeclareSchema API' do
10
10
 
11
11
  describe 'example models' do
12
12
  it 'generates a model' do
13
- expect(system("bundle exec rails generate declare_schema:model advert title:string body:text")).to be_truthy
13
+ generate_model 'advert', 'title:string', 'body:text'
14
14
 
15
15
  # The above will generate the test, fixture and a model file like this:
16
16
  # model_declaration = Rails::Generators.invoke('declare_schema:model', ['advert2', 'title:string', 'body:text'])
17
17
  # expect(model_declaration.first).to eq([["Advert"], nil, "app/models/advert.rb", nil,
18
18
  # [["AdvertTest"], "test/models/advert_test.rb", nil, "test/fixtures/adverts.yml"]])
19
19
 
20
- expect(File.read("#{TESTAPP_PATH}/app/models/advert.rb")).to eq(<<~EOS)
20
+ expect_model_definition_to_eq('advert', <<~EOS)
21
21
  class Advert < #{active_record_base_class}
22
22
 
23
23
  fields do
@@ -27,7 +27,8 @@ RSpec.describe 'DeclareSchema API' do
27
27
 
28
28
  end
29
29
  EOS
30
- system("rm -rf #{TESTAPP_PATH}/app/models/advert2.rb #{TESTAPP_PATH}/test/models/advert2.rb #{TESTAPP_PATH}/test/fixtures/advert2.rb")
30
+
31
+ clean_up_model('advert2')
31
32
 
32
33
  # The migration generator uses this information to create a migration.
33
34
  # The following creates and runs the migration:
@@ -36,13 +37,11 @@ RSpec.describe 'DeclareSchema API' do
36
37
 
37
38
  # We're now ready to start demonstrating the API
38
39
 
39
- Rails.application.config.autoload_paths += ["#{TESTAPP_PATH}/app/models"]
40
-
41
- $LOAD_PATH << "#{TESTAPP_PATH}/app/models"
40
+ load_models
42
41
 
43
- unless Rails::VERSION::MAJOR >= 6
42
+ if Rails::VERSION::MAJOR == 5
44
43
  # TODO: get this to work on Travis for Rails 6
45
- Rails::Generators.invoke('declare_schema:migration', %w[-n -m])
44
+ generate_migrations '-n', '-m'
46
45
  end
47
46
 
48
47
  require 'advert'
@@ -6,22 +6,20 @@ RSpec.describe 'DeclareSchema Migration Generator' do
6
6
  end
7
7
 
8
8
  it "generates nested models" do
9
- Rails::Generators.invoke('declare_schema:model', %w[alpha/beta one:string two:integer])
9
+ generate_model 'alpha/beta', 'one:string', 'two:integer'
10
10
 
11
- expect(File.exist?('app/models/alpha/beta.rb')).to be_truthy
12
-
13
- expect(File.read('app/models/alpha/beta.rb')).to eq(<<~EOS)
11
+ expect_model_definition_to_eq('alpha/beta', <<~EOS)
14
12
  class Alpha::Beta < #{active_record_base_class}
15
-
13
+
16
14
  fields do
17
15
  one :string, limit: 255
18
16
  two :integer
19
17
  end
20
-
18
+
21
19
  end
22
20
  EOS
23
21
 
24
- expect(File.read('app/models/alpha.rb')).to eq(<<~EOS)
22
+ expect_model_definition_to_eq('alpha', <<~EOS)
25
23
  module Alpha
26
24
  def self.table_name_prefix
27
25
  'alpha_'
@@ -29,9 +27,9 @@ RSpec.describe 'DeclareSchema Migration Generator' do
29
27
  end
30
28
  EOS
31
29
 
32
- expect(File.read('test/models/alpha/beta_test.rb')).to eq(<<~EOS)
30
+ expect_test_definition_to_eq('alpha/beta', <<~EOS)
33
31
  require 'test_helper'
34
-
32
+
35
33
  class Alpha::BetaTest < ActiveSupport::TestCase
36
34
  # test "the truth" do
37
35
  # assert true
@@ -39,7 +37,50 @@ RSpec.describe 'DeclareSchema Migration Generator' do
39
37
  end
40
38
  EOS
41
39
 
42
- expect(File.exist?('test/fixtures/alpha/beta.yml')).to be_truthy
40
+ case Rails::VERSION::MAJOR
41
+ when 4
42
+ expect_test_fixture_to_eq('alpha/beta', <<~EOS)
43
+ # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
44
+
45
+ # This model initially had no columns defined. If you add columns to the
46
+ # model remove the '{}' from the fixture names and add the columns immediately
47
+ # below each fixture, per the syntax in the comments below
48
+ #
49
+ one: {}
50
+ # column: value
51
+ #
52
+ two: {}
53
+ # column: value
54
+ EOS
55
+ when 5
56
+ expect_test_fixture_to_eq('alpha/beta', <<~EOS)
57
+ # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
58
+
59
+ # This model initially had no columns defined. If you add columns to the
60
+ # model remove the '{}' from the fixture names and add the columns immediately
61
+ # below each fixture, per the syntax in the comments below
62
+ #
63
+ one: {}
64
+ # column: value
65
+ #
66
+ two: {}
67
+ # column: value
68
+ EOS
69
+ when 6
70
+ expect_test_fixture_to_eq('alpha/beta', <<~EOS)
71
+ # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
72
+
73
+ # This model initially had no columns defined. If you add columns to the
74
+ # model remove the '{}' from the fixture names and add the columns immediately
75
+ # below each fixture, per the syntax in the comments below
76
+ #
77
+ one: {}
78
+ # column: value
79
+ #
80
+ two: {}
81
+ # column: value
82
+ EOS
83
+ end
43
84
 
44
85
  $LOAD_PATH << "#{TESTAPP_PATH}/app/models"
45
86
 
@@ -12,7 +12,7 @@ RSpec.describe 'DeclareSchema Migration Generator interactive primary key' do
12
12
  self.primary_key = "foo_id"
13
13
  end
14
14
 
15
- Rails::Generators.invoke('declare_schema:migration', %w[-n -m])
15
+ generate_migrations '-n', '-m'
16
16
  expect(Foo.primary_key).to eq('foo_id')
17
17
 
18
18
  ### migrate from
@@ -24,28 +24,30 @@ RSpec.describe 'DeclareSchema Migration Generator interactive primary key' do
24
24
  end
25
25
 
26
26
  puts "\n\e[45m Please enter 'id' (no quotes) at the next prompt \e[0m"
27
- Rails::Generators.invoke('declare_schema:migration', %w[-n -m])
27
+ generate_migrations '-n', '-m'
28
28
  expect(Foo.primary_key).to eq('id')
29
29
 
30
30
  nuke_model_class(Foo)
31
31
 
32
32
  ### migrate to
33
33
 
34
- # rename to custom primary_key
35
- class Foo < ActiveRecord::Base
36
- fields do
34
+ if Rails::VERSION::MAJOR >= 5
35
+ # rename to custom primary_key
36
+ class Foo < ActiveRecord::Base
37
+ fields do
38
+ end
39
+ self.primary_key = "foo_id"
37
40
  end
38
- self.primary_key = "foo_id"
39
- end
40
41
 
41
- puts "\n\e[45m Please enter 'drop id' (no quotes) at the next prompt \e[0m"
42
- Rails::Generators.invoke('declare_schema:migration', %w[-n -m])
43
- expect(Foo.primary_key).to eq('foo_id')
42
+ puts "\n\e[45m Please enter 'drop id' (no quotes) at the next prompt \e[0m"
43
+ generate_migrations '-n', '-m'
44
+ expect(Foo.primary_key).to eq('foo_id')
44
45
 
45
- ### ensure it doesn't cause further migrations
46
+ ### ensure it doesn't cause further migrations
46
47
 
47
- # check no further migrations
48
- up, down = Generators::DeclareSchema::Migration::Migrator.run
49
- expect(up).to eq("")
48
+ # check no further migrations
49
+ up = Generators::DeclareSchema::Migration::Migrator.run.first
50
+ expect(up).to eq("")
51
+ end
50
52
  end
51
53
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'rails'
4
+
3
5
  RSpec.describe 'DeclareSchema Migration Generator' do
4
6
  before do
5
7
  load File.expand_path('prepare_testapp.rb', __dir__)
@@ -9,14 +11,12 @@ RSpec.describe 'DeclareSchema Migration Generator' do
9
11
  it 'generates migrations' do
10
12
  ## The migration generator -- introduction
11
13
 
12
- up_down = Generators::DeclareSchema::Migration::Migrator.run
13
- expect(up_down).to eq(["", ""])
14
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to migrate_up("").and migrate_down("")
14
15
 
15
16
  class Advert < ActiveRecord::Base
16
17
  end
17
18
 
18
- up_down = Generators::DeclareSchema::Migration::Migrator.run
19
- expect(up_down).to eq(["", ""])
19
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to migrate_up("").and migrate_down("")
20
20
 
21
21
  Generators::DeclareSchema::Migration::Migrator.ignore_tables = ["green_fishes"]
22
22
 
@@ -28,17 +28,27 @@ RSpec.describe 'DeclareSchema Migration Generator' do
28
28
  name :string, limit: 255, null: true
29
29
  end
30
30
  end
31
- up, down = Generators::DeclareSchema::Migration::Migrator.run
32
- expect(up).to eq(<<~EOS.strip)
33
- create_table :adverts, id: :bigint do |t|
34
- t.string :name, limit: 255
35
- end
36
- EOS
37
- expect(down).to eq("drop_table :adverts")
31
+
32
+ up, _ = Generators::DeclareSchema::Migration::Migrator.run.tap do |migrations|
33
+ expect(migrations).to(
34
+ migrate_up(<<~EOS.strip)
35
+ create_table :adverts, id: :bigint do |t|
36
+ t.string :name, limit: 255
37
+ end
38
+ EOS
39
+ .and migrate_down("drop_table :adverts")
40
+ )
41
+ end
38
42
 
39
43
  ActiveRecord::Migration.class_eval(up)
40
44
  expect(Advert.columns.map(&:name)).to eq(["id", "name"])
41
45
 
46
+ if Rails::VERSION::MAJOR < 5
47
+ # Rails 4 sqlite driver doesn't create PK properly. Fix that by dropping and recreating.
48
+ ActiveRecord::Base.connection.execute("drop table adverts")
49
+ ActiveRecord::Base.connection.execute('CREATE TABLE "adverts" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255))')
50
+ end
51
+
42
52
  class Advert < ActiveRecord::Base
43
53
  fields do
44
54
  name :string, limit: 255, null: true
@@ -46,19 +56,20 @@ RSpec.describe 'DeclareSchema Migration Generator' do
46
56
  published_at :datetime, null: true
47
57
  end
48
58
  end
49
- up, down = migrate
50
- expect(up).to eq(<<~EOS.strip)
51
- add_column :adverts, :body, :text
52
- add_column :adverts, :published_at, :datetime
53
59
 
54
- add_index :adverts, [:id], unique: true, name: 'PRIMARY_KEY'
55
- EOS
56
- # TODO: ^ add_index should not be there?
60
+ Advert.connection.schema_cache.clear!
61
+ Advert.reset_column_information
57
62
 
58
- expect(down).to eq(<<~EOS.strip)
59
- remove_column :adverts, :body
60
- remove_column :adverts, :published_at
61
- EOS
63
+ expect(migrate).to(
64
+ migrate_up(<<~EOS.strip)
65
+ add_column :adverts, :body, :text
66
+ add_column :adverts, :published_at, :datetime
67
+ EOS
68
+ .and migrate_down(<<~EOS.strip)
69
+ remove_column :adverts, :body
70
+ remove_column :adverts, :published_at
71
+ EOS
72
+ )
62
73
 
63
74
  Advert.field_specs.clear # not normally needed
64
75
  class Advert < ActiveRecord::Base
@@ -68,9 +79,11 @@ RSpec.describe 'DeclareSchema Migration Generator' do
68
79
  end
69
80
  end
70
81
 
71
- up, down = migrate
72
- expect(up).to eq("remove_column :adverts, :published_at")
73
- expect(down).to eq("add_column :adverts, :published_at, :datetime")
82
+ expect(migrate).to(
83
+ migrate_up("remove_column :adverts, :published_at").and(
84
+ migrate_down("add_column :adverts, :published_at, :datetime")
85
+ )
86
+ )
74
87
 
75
88
  nuke_model_class(Advert)
76
89
  class Advert < ActiveRecord::Base
@@ -80,20 +93,22 @@ RSpec.describe 'DeclareSchema Migration Generator' do
80
93
  end
81
94
  end
82
95
 
83
- up, down = Generators::DeclareSchema::Migration::Migrator.run
84
- expect(up).to eq(<<~EOS.strip)
85
- add_column :adverts, :title, :string, limit: 255
86
- remove_column :adverts, :name
87
- EOS
88
-
89
- expect(down).to eq(<<~EOS.strip)
90
- remove_column :adverts, :title
91
- add_column :adverts, :name, :string, limit: 255
92
- EOS
93
-
94
- up, down = Generators::DeclareSchema::Migration::Migrator.run(adverts: { name: :title })
95
- expect(up).to eq("rename_column :adverts, :name, :title")
96
- expect(down).to eq("rename_column :adverts, :title, :name")
96
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
97
+ migrate_up(<<~EOS.strip)
98
+ add_column :adverts, :title, :string, limit: 255
99
+ remove_column :adverts, :name
100
+ EOS
101
+ .and migrate_down(<<~EOS.strip)
102
+ remove_column :adverts, :title
103
+ add_column :adverts, :name, :string, limit: 255
104
+ EOS
105
+ )
106
+
107
+ expect(Generators::DeclareSchema::Migration::Migrator.run(adverts: { name: :title })).to(
108
+ migrate_up("rename_column :adverts, :name, :title").and(
109
+ migrate_down("rename_column :adverts, :title, :name")
110
+ )
111
+ )
97
112
 
98
113
  migrate
99
114
 
@@ -104,9 +119,11 @@ RSpec.describe 'DeclareSchema Migration Generator' do
104
119
  end
105
120
  end
106
121
 
107
- up_down = Generators::DeclareSchema::Migration::Migrator.run
108
- expect(up_down).to eq(["change_column :adverts, :title, :text",
109
- "change_column :adverts, :title, :string, limit: 255"])
122
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
123
+ migrate_up("change_column :adverts, :title, :text").and(
124
+ migrate_down("change_column :adverts, :title, :string, limit: 255")
125
+ )
126
+ )
110
127
 
111
128
  class Advert < ActiveRecord::Base
112
129
  fields do
@@ -115,11 +132,14 @@ RSpec.describe 'DeclareSchema Migration Generator' do
115
132
  end
116
133
  end
117
134
 
118
- up, down = migrate
119
- expect(up.split(',').slice(0,3).join(',')).to eq('change_column :adverts, :title, :string')
120
- expect(up.split(',').slice(3,2).sort.join(',')).to eq(" default: \"Untitled\", limit: 255")
121
- expect(down).to eq("change_column :adverts, :title, :string, limit: 255")
122
-
135
+ expect(migrate).to(
136
+ migrate_up(<<~EOS.strip)
137
+ change_column :adverts, :title, :string, limit: 255, default: "Untitled"
138
+ EOS
139
+ .and migrate_down(<<~EOS.strip)
140
+ change_column :adverts, :title, :string, limit: 255
141
+ EOS
142
+ )
123
143
 
124
144
  ### Limits
125
145
 
@@ -129,8 +149,9 @@ RSpec.describe 'DeclareSchema Migration Generator' do
129
149
  end
130
150
  end
131
151
 
132
- up = Generators::DeclareSchema::Migration::Migrator.run.first
133
- expect(up).to eq("add_column :adverts, :price, :integer, limit: 2")
152
+ up, _ = Generators::DeclareSchema::Migration::Migrator.run.tap do |migrations|
153
+ expect(migrations).to migrate_up("add_column :adverts, :price, :integer, limit: 2")
154
+ end
134
155
 
135
156
  # Now run the migration, then change the limit:
136
157
 
@@ -141,9 +162,14 @@ RSpec.describe 'DeclareSchema Migration Generator' do
141
162
  end
142
163
  end
143
164
 
144
- up, down = Generators::DeclareSchema::Migration::Migrator.run
145
- expect(up).to eq("change_column :adverts, :price, :integer, limit: 3")
146
- expect(down).to eq("change_column :adverts, :price, :integer, limit: 2")
165
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
166
+ migrate_up(<<~EOS.strip)
167
+ change_column :adverts, :price, :integer, limit: 3
168
+ EOS
169
+ .and migrate_down(<<~EOS.strip)
170
+ change_column :adverts, :price, :integer, limit: 2
171
+ EOS
172
+ )
147
173
 
148
174
  # Note that limit on a decimal column is ignored (use :scale and :precision)
149
175
 
@@ -154,8 +180,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
154
180
  end
155
181
  end
156
182
 
157
- up = Generators::DeclareSchema::Migration::Migrator.run.first
158
- expect(up).to eq("add_column :adverts, :price, :decimal")
183
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to migrate_up("add_column :adverts, :price, :decimal")
159
184
 
160
185
  # Limits are generally not needed for `text` fields, because by default, `text` fields will use the maximum size
161
186
  # allowed for that database type (0xffffffff for LONGTEXT in MySQL unlimited in Postgres, 1 billion in Sqlite).
@@ -170,12 +195,13 @@ RSpec.describe 'DeclareSchema Migration Generator' do
170
195
  end
171
196
  end
172
197
 
173
- up = Generators::DeclareSchema::Migration::Migrator.run.first
174
- expect(up).to eq(<<~EOS.strip)
175
- add_column :adverts, :price, :decimal
176
- add_column :adverts, :notes, :text, null: false
177
- add_column :adverts, :description, :text, null: false
178
- EOS
198
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
199
+ migrate_up(<<~EOS.strip)
200
+ add_column :adverts, :price, :decimal
201
+ add_column :adverts, :notes, :text, null: false
202
+ add_column :adverts, :description, :text, null: false
203
+ EOS
204
+ )
179
205
 
180
206
  # (There is no limit on `add_column ... :description` above since these tests are run against SQLite.)
181
207
 
@@ -194,11 +220,12 @@ RSpec.describe 'DeclareSchema Migration Generator' do
194
220
  end
195
221
  end
196
222
 
197
- up = Generators::DeclareSchema::Migration::Migrator.run.first
198
- expect(up).to eq(<<~EOS.strip)
199
- add_column :adverts, :notes, :text, null: false, limit: 4294967295
200
- add_column :adverts, :description, :text, null: false, limit: 255
201
- EOS
223
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
224
+ migrate_up(<<~EOS.strip)
225
+ add_column :adverts, :notes, :text, null: false, limit: 4294967295
226
+ add_column :adverts, :description, :text, null: false, limit: 255
227
+ EOS
228
+ )
202
229
 
203
230
  Advert.field_specs.delete :notes
204
231
 
@@ -237,9 +264,14 @@ RSpec.describe 'DeclareSchema Migration Generator' do
237
264
  end
238
265
  end
239
266
 
240
- up, down = Generators::DeclareSchema::Migration::Migrator.run
241
- expect(up).to eq("change_column :adverts, :description, :text, limit: 4294967295, null: false")
242
- expect(down).to eq("change_column :adverts, :description, :text")
267
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
268
+ migrate_up(<<~EOS.strip)
269
+ change_column :adverts, :description, :text, limit: 4294967295, null: false
270
+ EOS
271
+ .and migrate_down(<<~EOS.strip)
272
+ change_column :adverts, :description, :text
273
+ EOS
274
+ )
243
275
 
244
276
  # TODO TECH-4814: The above test should have this output:
245
277
  # TODO => "change_column :adverts, :description, :text, limit: 255
@@ -252,9 +284,14 @@ RSpec.describe 'DeclareSchema Migration Generator' do
252
284
  end
253
285
  end
254
286
 
255
- up, down = Generators::DeclareSchema::Migration::Migrator.run
256
- expect(up).to eq("change_column :adverts, :description, :text, limit: 4294967295, null: false")
257
- expect(down).to eq("change_column :adverts, :description, :text")
287
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
288
+ migrate_up(<<~EOS.strip)
289
+ change_column :adverts, :description, :text, limit: 4294967295, null: false
290
+ EOS
291
+ .and migrate_down(<<~EOS.strip)
292
+ change_column :adverts, :description, :text
293
+ EOS
294
+ )
258
295
  ::DeclareSchema::Model::FieldSpec::instance_variable_set(:@mysql_text_limits, false)
259
296
 
260
297
  Advert.field_specs.clear
@@ -284,18 +321,21 @@ RSpec.describe 'DeclareSchema Migration Generator' do
284
321
  belongs_to :category
285
322
  end
286
323
 
287
- up, down = Generators::DeclareSchema::Migration::Migrator.run
288
- expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
289
- add_column :adverts, :category_id, :integer, limit: 8, null: false
290
- add_index :adverts, [:category_id], name: 'on_category_id'
291
- EOS
292
- expect(down.sub(/\n+/, "\n")).to eq(<<~EOS.strip)
293
- remove_column :adverts, :category_id
294
- remove_index :adverts, name: :on_category_id rescue ActiveRecord::StatementInvalid
295
- EOS
324
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
325
+ migrate_up(<<~EOS.strip)
326
+ add_column :adverts, :category_id, :integer, limit: 8, null: false
327
+
328
+ add_index :adverts, [:category_id], name: 'on_category_id'
329
+ EOS
330
+ .and migrate_down(<<~EOS.strip)
331
+ remove_column :adverts, :category_id
332
+
333
+ remove_index :adverts, name: :on_category_id rescue ActiveRecord::StatementInvalid
334
+ EOS
335
+ )
296
336
 
297
337
  Advert.field_specs.delete(:category_id)
298
- Advert.index_specs.delete_if {|spec| spec.fields==["category_id"]}
338
+ Advert.index_definitions.delete_if { |spec| spec.fields==["category_id"] }
299
339
 
300
340
  # If you specify a custom foreign key, the migration generator observes that:
301
341
 
@@ -304,14 +344,17 @@ RSpec.describe 'DeclareSchema Migration Generator' do
304
344
  fields { }
305
345
  belongs_to :category, foreign_key: "c_id", class_name: 'Category'
306
346
  end
307
- up = Generators::DeclareSchema::Migration::Migrator.run.first
308
- expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
309
- add_column :adverts, :c_id, :integer, limit: 8, null: false
310
- add_index :adverts, [:c_id], name: 'on_c_id'
311
- EOS
347
+
348
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
349
+ migrate_up(<<~EOS.strip)
350
+ add_column :adverts, :c_id, :integer, limit: 8, null: false
351
+
352
+ add_index :adverts, [:c_id], name: 'on_c_id'
353
+ EOS
354
+ )
312
355
 
313
356
  Advert.field_specs.delete(:c_id)
314
- Advert.index_specs.delete_if { |spec| spec.fields == ["c_id"] }
357
+ Advert.index_definitions.delete_if { |spec| spec.fields == ["c_id"] }
315
358
 
316
359
  # You can avoid generating the index by specifying `index: false`
317
360
 
@@ -320,11 +363,15 @@ RSpec.describe 'DeclareSchema Migration Generator' do
320
363
  fields { }
321
364
  belongs_to :category, index: false
322
365
  end
323
- up = Generators::DeclareSchema::Migration::Migrator.run.first
324
- expect(up.gsub(/\n+/, "\n")).to eq("add_column :adverts, :category_id, :integer, limit: 8, null: false")
366
+
367
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
368
+ migrate_up(<<~EOS.strip)
369
+ add_column :adverts, :category_id, :integer, limit: 8, null: false
370
+ EOS
371
+ )
325
372
 
326
373
  Advert.field_specs.delete(:category_id)
327
- Advert.index_specs.delete_if { |spec| spec.fields == ["category_id"] }
374
+ Advert.index_definitions.delete_if { |spec| spec.fields == ["category_id"] }
328
375
 
329
376
  # You can specify the index name with :index
330
377
 
@@ -333,14 +380,17 @@ RSpec.describe 'DeclareSchema Migration Generator' do
333
380
  fields { }
334
381
  belongs_to :category, index: 'my_index'
335
382
  end
336
- up = Generators::DeclareSchema::Migration::Migrator.run.first
337
- expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
338
- add_column :adverts, :category_id, :integer, limit: 8, null: false
339
- add_index :adverts, [:category_id], name: 'my_index'
340
- EOS
383
+
384
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
385
+ migrate_up(<<~EOS.strip)
386
+ add_column :adverts, :category_id, :integer, limit: 8, null: false
387
+
388
+ add_index :adverts, [:category_id], name: 'my_index'
389
+ EOS
390
+ )
341
391
 
342
392
  Advert.field_specs.delete(:category_id)
343
- Advert.index_specs.delete_if { |spec| spec.fields == ["category_id"] }
393
+ Advert.index_definitions.delete_if { |spec| spec.fields == ["category_id"] }
344
394
 
345
395
  ### Timestamps and Optimimistic Locking
346
396
 
@@ -353,17 +403,19 @@ RSpec.describe 'DeclareSchema Migration Generator' do
353
403
  optimistic_lock
354
404
  end
355
405
  end
356
- up, down = Generators::DeclareSchema::Migration::Migrator.run
357
- expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
358
- add_column :adverts, :created_at, :datetime
359
- add_column :adverts, :updated_at, :datetime
360
- add_column :adverts, :lock_version, :integer, null: false, default: 1
361
- EOS
362
- expect(down.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
363
- remove_column :adverts, :created_at
364
- remove_column :adverts, :updated_at
365
- remove_column :adverts, :lock_version
366
- EOS
406
+
407
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
408
+ migrate_up(<<~EOS.strip)
409
+ add_column :adverts, :created_at, :datetime
410
+ add_column :adverts, :updated_at, :datetime
411
+ add_column :adverts, :lock_version, :integer, null: false, default: 1
412
+ EOS
413
+ .and migrate_down(<<~EOS.strip)
414
+ remove_column :adverts, :created_at
415
+ remove_column :adverts, :updated_at
416
+ remove_column :adverts, :lock_version
417
+ EOS
418
+ )
367
419
 
368
420
  Advert.field_specs.delete(:updated_at)
369
421
  Advert.field_specs.delete(:created_at)
@@ -378,13 +430,16 @@ RSpec.describe 'DeclareSchema Migration Generator' do
378
430
  title :string, index: true, limit: 255, null: true
379
431
  end
380
432
  end
381
- up = Generators::DeclareSchema::Migration::Migrator.run.first
382
- expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
383
- add_column :adverts, :title, :string, limit: 255
384
- add_index :adverts, [:title], name: 'on_title'
385
- EOS
386
433
 
387
- Advert.index_specs.delete_if { |spec| spec.fields==["title"] }
434
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
435
+ migrate_up(<<~EOS.strip)
436
+ add_column :adverts, :title, :string, limit: 255
437
+
438
+ add_index :adverts, [:title], name: 'on_title'
439
+ EOS
440
+ )
441
+
442
+ Advert.index_definitions.delete_if { |spec| spec.fields==["title"] }
388
443
 
389
444
  # You can ask for a unique index
390
445
 
@@ -393,13 +448,16 @@ RSpec.describe 'DeclareSchema Migration Generator' do
393
448
  title :string, index: true, unique: true, null: true, limit: 255
394
449
  end
395
450
  end
396
- up = Generators::DeclareSchema::Migration::Migrator.run.first
397
- expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
398
- add_column :adverts, :title, :string, limit: 255
399
- add_index :adverts, [:title], unique: true, name: 'on_title'
400
- EOS
401
451
 
402
- Advert.index_specs.delete_if { |spec| spec.fields == ["title"] }
452
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
453
+ migrate_up(<<~EOS.strip)
454
+ add_column :adverts, :title, :string, limit: 255
455
+
456
+ add_index :adverts, [:title], unique: true, name: 'on_title'
457
+ EOS
458
+ )
459
+
460
+ Advert.index_definitions.delete_if { |spec| spec.fields == ["title"] }
403
461
 
404
462
  # You can specify the name for the index
405
463
 
@@ -408,52 +466,64 @@ RSpec.describe 'DeclareSchema Migration Generator' do
408
466
  title :string, index: 'my_index', limit: 255, null: true
409
467
  end
410
468
  end
411
- up = Generators::DeclareSchema::Migration::Migrator.run.first
412
- expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
413
- add_column :adverts, :title, :string, limit: 255
414
- add_index :adverts, [:title], name: 'my_index'
415
- EOS
416
469
 
417
- Advert.index_specs.delete_if { |spec| spec.fields==["title"] }
470
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
471
+ migrate_up(<<~EOS.strip)
472
+ add_column :adverts, :title, :string, limit: 255
473
+
474
+ add_index :adverts, [:title], name: 'my_index'
475
+ EOS
476
+ )
477
+
478
+ Advert.index_definitions.delete_if { |spec| spec.fields==["title"] }
418
479
 
419
480
  # You can ask for an index outside of the fields block
420
481
 
421
482
  class Advert < ActiveRecord::Base
422
483
  index :title
423
484
  end
424
- up = Generators::DeclareSchema::Migration::Migrator.run.first
425
- expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
426
- add_column :adverts, :title, :string, limit: 255
427
- add_index :adverts, [:title], name: 'on_title'
428
- EOS
429
485
 
430
- Advert.index_specs.delete_if { |spec| spec.fields == ["title"] }
486
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
487
+ migrate_up(<<~EOS.strip)
488
+ add_column :adverts, :title, :string, limit: 255
489
+
490
+ add_index :adverts, [:title], name: 'on_title'
491
+ EOS
492
+ )
493
+
494
+ Advert.index_definitions.delete_if { |spec| spec.fields == ["title"] }
431
495
 
432
496
  # The available options for the index function are `:unique` and `:name`
433
497
 
434
498
  class Advert < ActiveRecord::Base
435
499
  index :title, unique: true, name: 'my_index'
436
500
  end
437
- up = Generators::DeclareSchema::Migration::Migrator.run.first
438
- expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
439
- add_column :adverts, :title, :string, limit: 255
440
- add_index :adverts, [:title], unique: true, name: 'my_index'
441
- EOS
442
501
 
443
- Advert.index_specs.delete_if { |spec| spec.fields == ["title"] }
502
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
503
+ migrate_up(<<~EOS.strip)
504
+ add_column :adverts, :title, :string, limit: 255
505
+
506
+ add_index :adverts, [:title], unique: true, name: 'my_index'
507
+ EOS
508
+ )
509
+
510
+ Advert.index_definitions.delete_if { |spec| spec.fields == ["title"] }
444
511
 
445
512
  # You can create an index on more than one field
446
513
 
447
514
  class Advert < ActiveRecord::Base
448
515
  index [:title, :category_id]
449
516
  end
450
- up = Generators::DeclareSchema::Migration::Migrator.run.first
451
- expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
452
- add_column :adverts, :title, :string, limit: 255
453
- add_index :adverts, [:title, :category_id], name: 'on_title_and_category_id'
454
- EOS
455
517
 
456
- Advert.index_specs.delete_if { |spec| spec.fields==["title", "category_id"] }
518
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
519
+ migrate_up(<<~EOS.strip)
520
+ add_column :adverts, :title, :string, limit: 255
521
+
522
+ add_index :adverts, [:title, :category_id], name: 'on_title_and_category_id'
523
+ EOS
524
+ )
525
+
526
+ Advert.index_definitions.delete_if { |spec| spec.fields==["title", "category_id"] }
457
527
 
458
528
  # Finally, you can specify that the migration generator should completely ignore an
459
529
  # index by passing its name to ignore_index in the model.
@@ -474,19 +544,24 @@ RSpec.describe 'DeclareSchema Migration Generator' do
474
544
  Advert.connection.schema_cache.clear!
475
545
  Advert.reset_column_information
476
546
 
477
- up, down = Generators::DeclareSchema::Migration::Migrator.run("adverts" => "ads")
478
- expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
479
- rename_table :adverts, :ads
480
- add_column :ads, :title, :string, limit: 255
481
- add_column :ads, :body, :text
482
- add_index :ads, [:id], unique: true, name: 'PRIMARY_KEY'
483
- EOS
484
- expect(down.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
485
- remove_column :ads, :title
486
- remove_column :ads, :body
487
- rename_table :ads, :adverts
488
- add_index :adverts, [:id], unique: true, name: 'PRIMARY_KEY'
489
- EOS
547
+ expect(Generators::DeclareSchema::Migration::Migrator.run("adverts" => "ads")).to(
548
+ migrate_up(<<~EOS.strip)
549
+ rename_table :adverts, :ads
550
+
551
+ add_column :ads, :title, :string, limit: 255
552
+ add_column :ads, :body, :text
553
+
554
+ add_index :ads, [:id], unique: true, name: 'PRIMARY'
555
+ EOS
556
+ .and migrate_down(<<~EOS.strip)
557
+ remove_column :ads, :title
558
+ remove_column :ads, :body
559
+
560
+ rename_table :ads, :adverts
561
+
562
+ add_index :adverts, [:id], unique: true, name: 'PRIMARY'
563
+ EOS
564
+ )
490
565
 
491
566
  # Set the table name back to what it should be and confirm we're in sync:
492
567
 
@@ -510,21 +585,27 @@ RSpec.describe 'DeclareSchema Migration Generator' do
510
585
  body :text, null: true
511
586
  end
512
587
  end
513
- up, down = Generators::DeclareSchema::Migration::Migrator.run("adverts" => "advertisements")
514
- expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
515
- rename_table :adverts, :advertisements
516
- add_column :advertisements, :title, :string, limit: 255
517
- add_column :advertisements, :body, :text
518
- remove_column :advertisements, :name
519
- add_index :advertisements, [:id], unique: true, name: 'PRIMARY_KEY'
520
- EOS
521
- expect(down.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
522
- remove_column :advertisements, :title
523
- remove_column :advertisements, :body
524
- add_column :adverts, :name, :string, limit: 255
525
- rename_table :advertisements, :adverts
526
- add_index :adverts, [:id], unique: true, name: 'PRIMARY_KEY'
527
- EOS
588
+
589
+ expect(Generators::DeclareSchema::Migration::Migrator.run("adverts" => "advertisements")).to(
590
+ migrate_up(<<~EOS.strip)
591
+ rename_table :adverts, :advertisements
592
+
593
+ add_column :advertisements, :title, :string, limit: 255
594
+ add_column :advertisements, :body, :text
595
+ remove_column :advertisements, :name
596
+
597
+ add_index :advertisements, [:id], unique: true, name: 'PRIMARY'
598
+ EOS
599
+ .and migrate_down(<<~EOS.strip)
600
+ remove_column :advertisements, :title
601
+ remove_column :advertisements, :body
602
+ add_column :adverts, :name, :string, limit: 255
603
+
604
+ rename_table :advertisements, :adverts
605
+
606
+ add_index :adverts, [:id], unique: true, name: 'PRIMARY'
607
+ EOS
608
+ )
528
609
 
529
610
  ### Drop a table
530
611
 
@@ -534,9 +615,24 @@ RSpec.describe 'DeclareSchema Migration Generator' do
534
615
 
535
616
  # Dropping tables is where the automatic down-migration really comes in handy:
536
617
 
537
- up, down = Generators::DeclareSchema::Migration::Migrator.run
538
- expect(up).to eq("drop_table :adverts")
539
- expect(down.gsub(/,.*/m, '')).to eq("create_table \"adverts\"")
618
+ rails4_table_create = <<~EOS.strip
619
+ create_table "adverts", force: :cascade do |t|
620
+ t.string "name", limit: 255
621
+ end
622
+ EOS
623
+
624
+ rails5_table_create = <<~EOS.strip
625
+ create_table "adverts", id: :integer, force: :cascade do |t|
626
+ t.string "name", limit: 255
627
+ end
628
+ EOS
629
+
630
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
631
+ migrate_up(<<~EOS.strip)
632
+ drop_table :adverts
633
+ EOS
634
+ .and migrate_down(Rails::VERSION::MAJOR >= 5 ? rails5_table_create : rails4_table_create)
635
+ )
540
636
 
541
637
  ## STI
542
638
 
@@ -557,20 +653,26 @@ RSpec.describe 'DeclareSchema Migration Generator' do
557
653
  end
558
654
  class SuperFancyAdvert < FancyAdvert
559
655
  end
560
- up, down = Generators::DeclareSchema::Migration::Migrator.run
561
- expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
562
- add_column :adverts, :type, :string, limit: 255
563
- add_index :adverts, [:type], name: 'on_type'
564
- EOS
565
- expect(down.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
566
- remove_column :adverts, :type
567
- remove_index :adverts, name: :on_type rescue ActiveRecord::StatementInvalid
568
- EOS
656
+
657
+ up, _ = Generators::DeclareSchema::Migration::Migrator.run do |migrations|
658
+ expect(migrations).to(
659
+ migrate_up(<<~EOS.strip)
660
+ add_column :adverts, :type, :string, limit: 255
661
+
662
+ add_index :adverts, [:type], name: 'on_type'
663
+ EOS
664
+ .and migrate_down(<<~EOS.strip)
665
+ remove_column :adverts, :type
666
+
667
+ remove_index :adverts, name: :on_type rescue ActiveRecord::StatementInvalid
668
+ EOS
669
+ )
670
+ end
569
671
 
570
672
  Advert.field_specs.delete(:type)
571
673
  nuke_model_class(SuperFancyAdvert)
572
674
  nuke_model_class(FancyAdvert)
573
- Advert.index_specs.delete_if { |spec| spec.fields==["type"] }
675
+ Advert.index_definitions.delete_if { |spec| spec.fields==["type"] }
574
676
 
575
677
  ## Coping with multiple changes
576
678
 
@@ -598,16 +700,17 @@ RSpec.describe 'DeclareSchema Migration Generator' do
598
700
  body :text, null: true
599
701
  end
600
702
  end
601
- up, down = Generators::DeclareSchema::Migration::Migrator.run(adverts: { title: :name })
602
- expect(up).to eq(<<~EOS.strip)
603
- rename_column :adverts, :title, :name
604
- change_column :adverts, :name, :string, limit: 255, default: \"No Name\"
605
- EOS
606
703
 
607
- expect(down).to eq(<<~EOS.strip)
608
- rename_column :adverts, :name, :title
609
- change_column :adverts, :title, :string, limit: 255, default: \"Untitled\"
610
- EOS
704
+ expect(Generators::DeclareSchema::Migration::Migrator.run(adverts: { title: :name })).to(
705
+ migrate_up(<<~EOS.strip)
706
+ rename_column :adverts, :title, :name
707
+ change_column :adverts, :name, :string, limit: 255, default: "No Name"
708
+ EOS
709
+ .and migrate_down(<<~EOS.strip)
710
+ rename_column :adverts, :name, :title
711
+ change_column :adverts, :title, :string, limit: 255, default: "Untitled"
712
+ EOS
713
+ )
611
714
 
612
715
  ### Rename a table and add a column
613
716
 
@@ -619,13 +722,17 @@ RSpec.describe 'DeclareSchema Migration Generator' do
619
722
  created_at :datetime
620
723
  end
621
724
  end
622
- up = Generators::DeclareSchema::Migration::Migrator.run(adverts: :ads).first
623
- expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
624
- rename_table :adverts, :ads
625
- add_column :ads, :created_at, :datetime, null: false
626
- change_column :ads, :title, :string, limit: 255, null: false, default: \"Untitled\"
627
- add_index :ads, [:id], unique: true, name: 'PRIMARY_KEY'
628
- EOS
725
+
726
+ expect(Generators::DeclareSchema::Migration::Migrator.run(adverts: :ads)).to(
727
+ migrate_up(<<~EOS.strip)
728
+ rename_table :adverts, :ads
729
+
730
+ add_column :ads, :created_at, :datetime, null: false
731
+ change_column :ads, :title, :string, limit: 255, null: false, default: \"Untitled\"
732
+
733
+ add_index :ads, [:id], unique: true, name: 'PRIMARY'
734
+ EOS
735
+ )
629
736
 
630
737
  class Advert < ActiveRecord::Base
631
738
  fields do
@@ -646,11 +753,14 @@ RSpec.describe 'DeclareSchema Migration Generator' do
646
753
  end
647
754
  self.primary_key = "advert_id"
648
755
  end
649
- up, _down = Generators::DeclareSchema::Migration::Migrator.run(adverts: { id: :advert_id })
650
- expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
651
- rename_column :adverts, :id, :advert_id
652
- add_index :adverts, [:advert_id], unique: true, name: 'PRIMARY_KEY'
653
- EOS
756
+
757
+ expect(Generators::DeclareSchema::Migration::Migrator.run(adverts: { id: :advert_id })).to(
758
+ migrate_up(<<~EOS.strip)
759
+ rename_column :adverts, :id, :advert_id
760
+
761
+ add_index :adverts, [:advert_id], unique: true, name: 'PRIMARY'
762
+ EOS
763
+ )
654
764
 
655
765
  nuke_model_class(Advert)
656
766
  ActiveRecord::Base.connection.execute("drop table `adverts`;")
@@ -689,47 +799,298 @@ RSpec.describe 'DeclareSchema Migration Generator' do
689
799
  expect(Ad.field_specs['company'].options[:validates].inspect).to eq("{:presence=>true, :uniqueness=>{:case_sensitive=>false}}")
690
800
  end
691
801
 
692
- describe 'belongs_to' do
802
+ describe 'serialize' do
693
803
  before do
694
- unless defined?(AdCategory)
695
- class AdCategory < ActiveRecord::Base
696
- fields { }
804
+ class Ad < ActiveRecord::Base
805
+ @serialize_args = []
806
+
807
+ class << self
808
+ attr_reader :serialize_args
809
+
810
+ def serialize(*args)
811
+ @serialize_args << args
812
+ end
697
813
  end
698
814
  end
815
+ end
699
816
 
700
- class Advert < ActiveRecord::Base
701
- fields do
702
- name :string, limit: 255, null: true
703
- category_id :integer, limit: 8
704
- nullable_category_id :integer, limit: 8, null: true
817
+ describe 'untyped' do
818
+ it 'allows serialize: true' do
819
+ class Ad < ActiveRecord::Base
820
+ fields do
821
+ allow_list :text, limit: 0xFFFF, serialize: true
822
+ end
823
+ end
824
+
825
+ expect(Ad.serialize_args).to eq([[:allow_list]])
826
+ end
827
+
828
+ it 'converts defaults with .to_yaml' do
829
+ class Ad < ActiveRecord::Base
830
+ fields do
831
+ allow_list :string, limit: 255, serialize: true, null: true, default: []
832
+ allow_hash :string, limit: 255, serialize: true, null: true, default: {}
833
+ allow_string :string, limit: 255, serialize: true, null: true, default: ['abc']
834
+ allow_null :string, limit: 255, serialize: true, null: true, default: nil
835
+ end
836
+ end
837
+
838
+ expect(Ad.field_specs['allow_list'].default).to eq("--- []\n")
839
+ expect(Ad.field_specs['allow_hash'].default).to eq("--- {}\n")
840
+ expect(Ad.field_specs['allow_string'].default).to eq("---\n- abc\n")
841
+ expect(Ad.field_specs['allow_null'].default).to eq(nil)
842
+ end
843
+ end
844
+
845
+ describe 'Array' do
846
+ it 'allows serialize: Array' do
847
+ class Ad < ActiveRecord::Base
848
+ fields do
849
+ allow_list :string, limit: 255, serialize: Array, null: true
850
+ end
851
+ end
852
+
853
+ expect(Ad.serialize_args).to eq([[:allow_list, Array]])
854
+ end
855
+
856
+ it 'allows Array defaults' do
857
+ class Ad < ActiveRecord::Base
858
+ fields do
859
+ allow_list :string, limit: 255, serialize: Array, null: true, default: [2]
860
+ allow_string :string, limit: 255, serialize: Array, null: true, default: ['abc']
861
+ allow_empty :string, limit: 255, serialize: Array, null: true, default: []
862
+ allow_null :string, limit: 255, serialize: Array, null: true, default: nil
863
+ end
864
+ end
865
+
866
+ expect(Ad.field_specs['allow_list'].default).to eq("---\n- 2\n")
867
+ expect(Ad.field_specs['allow_string'].default).to eq("---\n- abc\n")
868
+ expect(Ad.field_specs['allow_empty'].default).to eq(nil)
869
+ expect(Ad.field_specs['allow_null'].default).to eq(nil)
870
+ end
871
+ end
872
+
873
+ describe 'Hash' do
874
+ it 'allows serialize: Hash' do
875
+ class Ad < ActiveRecord::Base
876
+ fields do
877
+ allow_list :string, limit: 255, serialize: Hash, null: true
878
+ end
879
+ end
880
+
881
+ expect(Ad.serialize_args).to eq([[:allow_list, Hash]])
882
+ end
883
+
884
+ it 'allows Hash defaults' do
885
+ class Ad < ActiveRecord::Base
886
+ fields do
887
+ allow_loc :string, limit: 255, serialize: Hash, null: true, default: { 'state' => 'CA' }
888
+ allow_hash :string, limit: 255, serialize: Hash, null: true, default: {}
889
+ allow_null :string, limit: 255, serialize: Hash, null: true, default: nil
890
+ end
891
+ end
892
+
893
+ expect(Ad.field_specs['allow_loc'].default).to eq("---\nstate: CA\n")
894
+ expect(Ad.field_specs['allow_hash'].default).to eq(nil)
895
+ expect(Ad.field_specs['allow_null'].default).to eq(nil)
896
+ end
897
+ end
898
+
899
+ describe 'JSON' do
900
+ it 'allows serialize: JSON' do
901
+ class Ad < ActiveRecord::Base
902
+ fields do
903
+ allow_list :string, limit: 255, serialize: JSON
904
+ end
905
+ end
906
+
907
+ expect(Ad.serialize_args).to eq([[:allow_list, JSON]])
908
+ end
909
+
910
+ it 'allows JSON defaults' do
911
+ class Ad < ActiveRecord::Base
912
+ fields do
913
+ allow_hash :string, limit: 255, serialize: JSON, null: true, default: { 'state' => 'CA' }
914
+ allow_empty_array :string, limit: 255, serialize: JSON, null: true, default: []
915
+ allow_empty_hash :string, limit: 255, serialize: JSON, null: true, default: {}
916
+ allow_null :string, limit: 255, serialize: JSON, null: true, default: nil
917
+ end
705
918
  end
919
+
920
+ expect(Ad.field_specs['allow_hash'].default).to eq("{\"state\":\"CA\"}")
921
+ expect(Ad.field_specs['allow_empty_array'].default).to eq("[]")
922
+ expect(Ad.field_specs['allow_empty_hash'].default).to eq("{}")
923
+ expect(Ad.field_specs['allow_null'].default).to eq(nil)
706
924
  end
707
- up = Generators::DeclareSchema::Migration::Migrator.run.first
708
- ActiveRecord::Migration.class_eval(up)
709
925
  end
710
926
 
711
- it 'passes through optional:, if given' do
712
- class AdvertBelongsTo < ActiveRecord::Base
713
- self.table_name = 'adverts'
714
- fields { }
715
- reset_column_information
716
- belongs_to :ad_category, optional: true
927
+ class ValueClass
928
+ delegate :present?, :inspect, to: :@value
929
+
930
+ def initialize(value)
931
+ @value = value
932
+ end
933
+
934
+ class << self
935
+ def dump(object)
936
+ if object&.present?
937
+ object.inspect
938
+ end
939
+ end
940
+
941
+ def load(serialized)
942
+ if serialized
943
+ raise 'not used ???'
944
+ end
945
+ end
717
946
  end
718
- expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional: true)
719
947
  end
720
948
 
721
- [false, true].each do |nullable|
722
- context "nullable=#{nullable}" do
723
- it 'infers the optional: argument from null: option' do
724
- eval <<~EOS
725
- class AdvertBelongsTo < ActiveRecord::Base
726
- fields { }
727
- belongs_to :ad_category, null: #{nullable}
728
- end
729
- EOS
730
- expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional: nullable)
949
+ describe 'custom coder' do
950
+ it 'allows serialize: ValueClass' do
951
+ class Ad < ActiveRecord::Base
952
+ fields do
953
+ allow_list :string, limit: 255, serialize: ValueClass
954
+ end
731
955
  end
956
+
957
+ expect(Ad.serialize_args).to eq([[:allow_list, ValueClass]])
732
958
  end
959
+
960
+ it 'allows ValueClass defaults' do
961
+ class Ad < ActiveRecord::Base
962
+ fields do
963
+ allow_hash :string, limit: 255, serialize: ValueClass, null: true, default: ValueClass.new([2])
964
+ allow_empty_array :string, limit: 255, serialize: ValueClass, null: true, default: ValueClass.new([])
965
+ allow_null :string, limit: 255, serialize: ValueClass, null: true, default: nil
966
+ end
967
+ end
968
+
969
+ expect(Ad.field_specs['allow_hash'].default).to eq("[2]")
970
+ expect(Ad.field_specs['allow_empty_array'].default).to eq(nil)
971
+ expect(Ad.field_specs['allow_null'].default).to eq(nil)
972
+ end
973
+ end
974
+
975
+ it 'disallows serialize: with a non-string column type' do
976
+ expect do
977
+ class Ad < ActiveRecord::Base
978
+ fields do
979
+ allow_list :integer, limit: 8, serialize: true
980
+ end
981
+ end
982
+ end.to raise_exception(ArgumentError, /must be :string or :text/)
983
+ end
984
+ end
985
+
986
+ context "for Rails #{Rails::VERSION::MAJOR}" do
987
+ if Rails::VERSION::MAJOR >= 5
988
+ let(:optional_true) { { optional: true } }
989
+ let(:optional_false) { { optional: false } }
990
+ else
991
+ let(:optional_true) { {} }
992
+ let(:optional_false) { {} }
993
+ end
994
+ let(:optional_flag) { { false => optional_false, true => optional_true } }
995
+
996
+ describe 'belongs_to' do
997
+ before do
998
+ unless defined?(AdCategory)
999
+ class AdCategory < ActiveRecord::Base
1000
+ fields { }
1001
+ end
1002
+ end
1003
+
1004
+ class Advert < ActiveRecord::Base
1005
+ fields do
1006
+ name :string, limit: 255, null: true
1007
+ category_id :integer, limit: 8
1008
+ nullable_category_id :integer, limit: 8, null: true
1009
+ end
1010
+ end
1011
+ up = Generators::DeclareSchema::Migration::Migrator.run.first
1012
+ ActiveRecord::Migration.class_eval(up)
1013
+ end
1014
+
1015
+ it 'passes through optional: when given' do
1016
+ class AdvertBelongsTo < ActiveRecord::Base
1017
+ self.table_name = 'adverts'
1018
+ fields { }
1019
+ reset_column_information
1020
+ belongs_to :ad_category, optional: true
1021
+ end
1022
+ expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_true)
1023
+ end
1024
+
1025
+ describe 'contradictory settings' do # contradictory settings are ok--for example, during migration
1026
+ it 'passes through optional: true, null: false' do
1027
+ class AdvertBelongsTo < ActiveRecord::Base
1028
+ self.table_name = 'adverts'
1029
+ fields { }
1030
+ reset_column_information
1031
+ belongs_to :ad_category, optional: true, null: false
1032
+ end
1033
+ expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_true)
1034
+ expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(false)
1035
+ end
1036
+
1037
+ it 'passes through optional: false, null: true' do
1038
+ class AdvertBelongsTo < ActiveRecord::Base
1039
+ self.table_name = 'adverts'
1040
+ fields { }
1041
+ reset_column_information
1042
+ belongs_to :ad_category, optional: false, null: true
1043
+ end
1044
+ expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_false)
1045
+ expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(true)
1046
+ end
1047
+ end
1048
+
1049
+ [false, true].each do |nullable|
1050
+ context "nullable=#{nullable}" do
1051
+ it 'infers optional: from null:' do
1052
+ eval <<~EOS
1053
+ class AdvertBelongsTo < ActiveRecord::Base
1054
+ fields { }
1055
+ belongs_to :ad_category, null: #{nullable}
1056
+ end
1057
+ EOS
1058
+ expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_flag[nullable])
1059
+ expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(nullable)
1060
+ end
1061
+
1062
+ it 'infers null: from optional:' do
1063
+ eval <<~EOS
1064
+ class AdvertBelongsTo < ActiveRecord::Base
1065
+ fields { }
1066
+ belongs_to :ad_category, optional: #{nullable}
1067
+ end
1068
+ EOS
1069
+ expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_flag[nullable])
1070
+ expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(nullable)
1071
+ end
1072
+ end
1073
+ end
1074
+ end
1075
+ end
1076
+
1077
+ describe 'migration base class' do
1078
+ it 'adapts to Rails 4' do
1079
+ class Advert < active_record_base_class.constantize
1080
+ fields do
1081
+ title :string, limit: 100
1082
+ end
1083
+ end
1084
+
1085
+ generate_migrations '-n', '-m'
1086
+
1087
+ migrations = Dir.glob('db/migrate/*declare_schema_migration*.rb')
1088
+ expect(migrations.size).to eq(1), migrations.inspect
1089
+
1090
+ migration_content = File.read(migrations.first)
1091
+ first_line = migration_content.split("\n").first
1092
+ base_class = first_line.split(' < ').last
1093
+ expect(base_class).to eq("(Rails::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration)")
733
1094
  end
734
1095
  end
735
1096
  end