declare_schema 0.2.0 → 0.4.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.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeclareSchema
4
- VERSION = "0.2.0"
4
+ VERSION = "0.4.0"
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
42
  unless Rails::VERSION::MAJOR >= 6
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,13 +28,17 @@ 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"])
@@ -46,19 +50,20 @@ RSpec.describe 'DeclareSchema Migration Generator' do
46
50
  published_at :datetime, null: true
47
51
  end
48
52
  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
53
 
54
- add_index :adverts, [:id], unique: true, name: 'PRIMARY_KEY'
55
- EOS
56
- # TODO: ^ add_index should not be there?
54
+ Advert.connection.schema_cache.clear!
55
+ Advert.reset_column_information
57
56
 
58
- expect(down).to eq(<<~EOS.strip)
59
- remove_column :adverts, :body
60
- remove_column :adverts, :published_at
61
- EOS
57
+ expect(migrate).to(
58
+ migrate_up(<<~EOS.strip)
59
+ add_column :adverts, :body, :text
60
+ add_column :adverts, :published_at, :datetime
61
+ EOS
62
+ .and migrate_down(<<~EOS.strip)
63
+ remove_column :adverts, :body
64
+ remove_column :adverts, :published_at
65
+ EOS
66
+ )
62
67
 
63
68
  Advert.field_specs.clear # not normally needed
64
69
  class Advert < ActiveRecord::Base
@@ -68,9 +73,11 @@ RSpec.describe 'DeclareSchema Migration Generator' do
68
73
  end
69
74
  end
70
75
 
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")
76
+ expect(migrate).to(
77
+ migrate_up("remove_column :adverts, :published_at").and(
78
+ migrate_down("add_column :adverts, :published_at, :datetime")
79
+ )
80
+ )
74
81
 
75
82
  nuke_model_class(Advert)
76
83
  class Advert < ActiveRecord::Base
@@ -80,20 +87,22 @@ RSpec.describe 'DeclareSchema Migration Generator' do
80
87
  end
81
88
  end
82
89
 
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")
90
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
91
+ migrate_up(<<~EOS.strip)
92
+ add_column :adverts, :title, :string, limit: 255
93
+ remove_column :adverts, :name
94
+ EOS
95
+ .and migrate_down(<<~EOS.strip)
96
+ remove_column :adverts, :title
97
+ add_column :adverts, :name, :string, limit: 255
98
+ EOS
99
+ )
100
+
101
+ expect(Generators::DeclareSchema::Migration::Migrator.run(adverts: { name: :title })).to(
102
+ migrate_up("rename_column :adverts, :name, :title").and(
103
+ migrate_down("rename_column :adverts, :title, :name")
104
+ )
105
+ )
97
106
 
98
107
  migrate
99
108
 
@@ -104,9 +113,11 @@ RSpec.describe 'DeclareSchema Migration Generator' do
104
113
  end
105
114
  end
106
115
 
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"])
116
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
117
+ migrate_up("change_column :adverts, :title, :text").and(
118
+ migrate_down("change_column :adverts, :title, :string, limit: 255")
119
+ )
120
+ )
110
121
 
111
122
  class Advert < ActiveRecord::Base
112
123
  fields do
@@ -115,11 +126,14 @@ RSpec.describe 'DeclareSchema Migration Generator' do
115
126
  end
116
127
  end
117
128
 
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
-
129
+ expect(migrate).to(
130
+ migrate_up(<<~EOS.strip)
131
+ change_column :adverts, :title, :string, limit: 255, default: "Untitled"
132
+ EOS
133
+ .and migrate_down(<<~EOS.strip)
134
+ change_column :adverts, :title, :string, limit: 255
135
+ EOS
136
+ )
123
137
 
124
138
  ### Limits
125
139
 
@@ -129,8 +143,9 @@ RSpec.describe 'DeclareSchema Migration Generator' do
129
143
  end
130
144
  end
131
145
 
132
- up, down = Generators::DeclareSchema::Migration::Migrator.run
133
- expect(up).to eq("add_column :adverts, :price, :integer, limit: 2")
146
+ up, _ = Generators::DeclareSchema::Migration::Migrator.run.tap do |migrations|
147
+ expect(migrations).to migrate_up("add_column :adverts, :price, :integer, limit: 2")
148
+ end
134
149
 
135
150
  # Now run the migration, then change the limit:
136
151
 
@@ -141,9 +156,14 @@ RSpec.describe 'DeclareSchema Migration Generator' do
141
156
  end
142
157
  end
143
158
 
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")
159
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
160
+ migrate_up(<<~EOS.strip)
161
+ change_column :adverts, :price, :integer, limit: 3
162
+ EOS
163
+ .and migrate_down(<<~EOS.strip)
164
+ change_column :adverts, :price, :integer, limit: 2
165
+ EOS
166
+ )
147
167
 
148
168
  # Note that limit on a decimal column is ignored (use :scale and :precision)
149
169
 
@@ -154,8 +174,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
154
174
  end
155
175
  end
156
176
 
157
- up, down = Generators::DeclareSchema::Migration::Migrator.run
158
- expect(up).to eq("add_column :adverts, :price, :decimal")
177
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to migrate_up("add_column :adverts, :price, :decimal")
159
178
 
160
179
  # Limits are generally not needed for `text` fields, because by default, `text` fields will use the maximum size
161
180
  # allowed for that database type (0xffffffff for LONGTEXT in MySQL unlimited in Postgres, 1 billion in Sqlite).
@@ -170,12 +189,13 @@ RSpec.describe 'DeclareSchema Migration Generator' do
170
189
  end
171
190
  end
172
191
 
173
- up, down = Generators::DeclareSchema::Migration::Migrator.run
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
192
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
193
+ migrate_up(<<~EOS.strip)
194
+ add_column :adverts, :price, :decimal
195
+ add_column :adverts, :notes, :text, null: false
196
+ add_column :adverts, :description, :text, null: false
197
+ EOS
198
+ )
179
199
 
180
200
  # (There is no limit on `add_column ... :description` above since these tests are run against SQLite.)
181
201
 
@@ -194,11 +214,12 @@ RSpec.describe 'DeclareSchema Migration Generator' do
194
214
  end
195
215
  end
196
216
 
197
- up, down = Generators::DeclareSchema::Migration::Migrator.run
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
217
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
218
+ migrate_up(<<~EOS.strip)
219
+ add_column :adverts, :notes, :text, null: false, limit: 4294967295
220
+ add_column :adverts, :description, :text, null: false, limit: 255
221
+ EOS
222
+ )
202
223
 
203
224
  Advert.field_specs.delete :notes
204
225
 
@@ -237,9 +258,14 @@ RSpec.describe 'DeclareSchema Migration Generator' do
237
258
  end
238
259
  end
239
260
 
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")
261
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
262
+ migrate_up(<<~EOS.strip)
263
+ change_column :adverts, :description, :text, limit: 4294967295, null: false
264
+ EOS
265
+ .and migrate_down(<<~EOS.strip)
266
+ change_column :adverts, :description, :text
267
+ EOS
268
+ )
243
269
 
244
270
  # TODO TECH-4814: The above test should have this output:
245
271
  # TODO => "change_column :adverts, :description, :text, limit: 255
@@ -252,9 +278,14 @@ RSpec.describe 'DeclareSchema Migration Generator' do
252
278
  end
253
279
  end
254
280
 
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")
281
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
282
+ migrate_up(<<~EOS.strip)
283
+ change_column :adverts, :description, :text, limit: 4294967295, null: false
284
+ EOS
285
+ .and migrate_down(<<~EOS.strip)
286
+ change_column :adverts, :description, :text
287
+ EOS
288
+ )
258
289
  ::DeclareSchema::Model::FieldSpec::instance_variable_set(:@mysql_text_limits, false)
259
290
 
260
291
  Advert.field_specs.clear
@@ -266,7 +297,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
266
297
  end
267
298
  end
268
299
 
269
- up, down = Generators::DeclareSchema::Migration::Migrator.run
300
+ up = Generators::DeclareSchema::Migration::Migrator.run.first
270
301
  ActiveRecord::Migration.class_eval up
271
302
  Advert.connection.schema_cache.clear!
272
303
  Advert.reset_column_information
@@ -278,63 +309,82 @@ RSpec.describe 'DeclareSchema Migration Generator' do
278
309
 
279
310
  class Category < ActiveRecord::Base; end
280
311
  class Advert < ActiveRecord::Base
312
+ fields do
313
+ name :string, limit: 255, null: true
314
+ end
281
315
  belongs_to :category
282
316
  end
283
317
 
284
- up, down = Generators::DeclareSchema::Migration::Migrator.run
285
- expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
286
- add_column :adverts, :category_id, :integer, limit: 8, null: false
287
- add_index :adverts, [:category_id], name: 'on_category_id'
288
- EOS
289
- expect(down.sub(/\n+/, "\n")).to eq(<<~EOS.strip)
290
- remove_column :adverts, :category_id
291
- remove_index :adverts, name: :on_category_id rescue ActiveRecord::StatementInvalid
292
- EOS
318
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
319
+ migrate_up(<<~EOS.strip)
320
+ add_column :adverts, :category_id, :integer, limit: 8, null: false
321
+
322
+ add_index :adverts, [:category_id], name: 'on_category_id'
323
+ EOS
324
+ .and migrate_down(<<~EOS.strip)
325
+ remove_column :adverts, :category_id
326
+
327
+ remove_index :adverts, name: :on_category_id rescue ActiveRecord::StatementInvalid
328
+ EOS
329
+ )
293
330
 
294
331
  Advert.field_specs.delete(:category_id)
295
- Advert.index_specs.delete_if {|spec| spec.fields==["category_id"]}
332
+ Advert.index_definitions.delete_if { |spec| spec.fields==["category_id"] }
296
333
 
297
334
  # If you specify a custom foreign key, the migration generator observes that:
298
335
 
299
336
  class Category < ActiveRecord::Base; end
300
337
  class Advert < ActiveRecord::Base
338
+ fields { }
301
339
  belongs_to :category, foreign_key: "c_id", class_name: 'Category'
302
340
  end
303
- up, down = Generators::DeclareSchema::Migration::Migrator.run
304
- expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
305
- add_column :adverts, :c_id, :integer, limit: 8, null: false
306
- add_index :adverts, [:c_id], name: 'on_c_id'
307
- EOS
341
+
342
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
343
+ migrate_up(<<~EOS.strip)
344
+ add_column :adverts, :c_id, :integer, limit: 8, null: false
345
+
346
+ add_index :adverts, [:c_id], name: 'on_c_id'
347
+ EOS
348
+ )
308
349
 
309
350
  Advert.field_specs.delete(:c_id)
310
- Advert.index_specs.delete_if { |spec| spec.fields == ["c_id"] }
351
+ Advert.index_definitions.delete_if { |spec| spec.fields == ["c_id"] }
311
352
 
312
353
  # You can avoid generating the index by specifying `index: false`
313
354
 
314
355
  class Category < ActiveRecord::Base; end
315
356
  class Advert < ActiveRecord::Base
357
+ fields { }
316
358
  belongs_to :category, index: false
317
359
  end
318
- up, down = Generators::DeclareSchema::Migration::Migrator.run
319
- expect(up.gsub(/\n+/, "\n")).to eq("add_column :adverts, :category_id, :integer, limit: 8, null: false")
360
+
361
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
362
+ migrate_up(<<~EOS.strip)
363
+ add_column :adverts, :category_id, :integer, limit: 8, null: false
364
+ EOS
365
+ )
320
366
 
321
367
  Advert.field_specs.delete(:category_id)
322
- Advert.index_specs.delete_if { |spec| spec.fields == ["category_id"] }
368
+ Advert.index_definitions.delete_if { |spec| spec.fields == ["category_id"] }
323
369
 
324
370
  # You can specify the index name with :index
325
371
 
326
372
  class Category < ActiveRecord::Base; end
327
373
  class Advert < ActiveRecord::Base
374
+ fields { }
328
375
  belongs_to :category, index: 'my_index'
329
376
  end
330
- up, down = Generators::DeclareSchema::Migration::Migrator.run
331
- expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
332
- add_column :adverts, :category_id, :integer, limit: 8, null: false
333
- add_index :adverts, [:category_id], name: 'my_index'
334
- EOS
377
+
378
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
379
+ migrate_up(<<~EOS.strip)
380
+ add_column :adverts, :category_id, :integer, limit: 8, null: false
381
+
382
+ add_index :adverts, [:category_id], name: 'my_index'
383
+ EOS
384
+ )
335
385
 
336
386
  Advert.field_specs.delete(:category_id)
337
- Advert.index_specs.delete_if { |spec| spec.fields == ["category_id"] }
387
+ Advert.index_definitions.delete_if { |spec| spec.fields == ["category_id"] }
338
388
 
339
389
  ### Timestamps and Optimimistic Locking
340
390
 
@@ -347,17 +397,19 @@ RSpec.describe 'DeclareSchema Migration Generator' do
347
397
  optimistic_lock
348
398
  end
349
399
  end
350
- up, down = Generators::DeclareSchema::Migration::Migrator.run
351
- expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
352
- add_column :adverts, :created_at, :datetime
353
- add_column :adverts, :updated_at, :datetime
354
- add_column :adverts, :lock_version, :integer, null: false, default: 1
355
- EOS
356
- expect(down.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
357
- remove_column :adverts, :created_at
358
- remove_column :adverts, :updated_at
359
- remove_column :adverts, :lock_version
360
- EOS
400
+
401
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
402
+ migrate_up(<<~EOS.strip)
403
+ add_column :adverts, :created_at, :datetime
404
+ add_column :adverts, :updated_at, :datetime
405
+ add_column :adverts, :lock_version, :integer, null: false, default: 1
406
+ EOS
407
+ .and migrate_down(<<~EOS.strip)
408
+ remove_column :adverts, :created_at
409
+ remove_column :adverts, :updated_at
410
+ remove_column :adverts, :lock_version
411
+ EOS
412
+ )
361
413
 
362
414
  Advert.field_specs.delete(:updated_at)
363
415
  Advert.field_specs.delete(:created_at)
@@ -372,13 +424,16 @@ RSpec.describe 'DeclareSchema Migration Generator' do
372
424
  title :string, index: true, limit: 255, null: true
373
425
  end
374
426
  end
375
- up, down = Generators::DeclareSchema::Migration::Migrator.run
376
- expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
377
- add_column :adverts, :title, :string, limit: 255
378
- add_index :adverts, [:title], name: 'on_title'
379
- EOS
380
427
 
381
- Advert.index_specs.delete_if { |spec| spec.fields==["title"] }
428
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
429
+ migrate_up(<<~EOS.strip)
430
+ add_column :adverts, :title, :string, limit: 255
431
+
432
+ add_index :adverts, [:title], name: 'on_title'
433
+ EOS
434
+ )
435
+
436
+ Advert.index_definitions.delete_if { |spec| spec.fields==["title"] }
382
437
 
383
438
  # You can ask for a unique index
384
439
 
@@ -387,13 +442,16 @@ RSpec.describe 'DeclareSchema Migration Generator' do
387
442
  title :string, index: true, unique: true, null: true, limit: 255
388
443
  end
389
444
  end
390
- up, down = Generators::DeclareSchema::Migration::Migrator.run
391
- expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
392
- add_column :adverts, :title, :string, limit: 255
393
- add_index :adverts, [:title], unique: true, name: 'on_title'
394
- EOS
395
445
 
396
- Advert.index_specs.delete_if { |spec| spec.fields == ["title"] }
446
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
447
+ migrate_up(<<~EOS.strip)
448
+ add_column :adverts, :title, :string, limit: 255
449
+
450
+ add_index :adverts, [:title], unique: true, name: 'on_title'
451
+ EOS
452
+ )
453
+
454
+ Advert.index_definitions.delete_if { |spec| spec.fields == ["title"] }
397
455
 
398
456
  # You can specify the name for the index
399
457
 
@@ -402,52 +460,64 @@ RSpec.describe 'DeclareSchema Migration Generator' do
402
460
  title :string, index: 'my_index', limit: 255, null: true
403
461
  end
404
462
  end
405
- up, down = Generators::DeclareSchema::Migration::Migrator.run
406
- expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
407
- add_column :adverts, :title, :string, limit: 255
408
- add_index :adverts, [:title], name: 'my_index'
409
- EOS
410
463
 
411
- Advert.index_specs.delete_if {|spec| spec.fields==["title"]}
464
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
465
+ migrate_up(<<~EOS.strip)
466
+ add_column :adverts, :title, :string, limit: 255
467
+
468
+ add_index :adverts, [:title], name: 'my_index'
469
+ EOS
470
+ )
471
+
472
+ Advert.index_definitions.delete_if { |spec| spec.fields==["title"] }
412
473
 
413
474
  # You can ask for an index outside of the fields block
414
475
 
415
476
  class Advert < ActiveRecord::Base
416
477
  index :title
417
478
  end
418
- up, down = Generators::DeclareSchema::Migration::Migrator.run
419
- expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
420
- add_column :adverts, :title, :string, limit: 255
421
- add_index :adverts, [:title], name: 'on_title'
422
- EOS
423
479
 
424
- Advert.index_specs.delete_if { |spec| spec.fields == ["title"] }
480
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
481
+ migrate_up(<<~EOS.strip)
482
+ add_column :adverts, :title, :string, limit: 255
483
+
484
+ add_index :adverts, [:title], name: 'on_title'
485
+ EOS
486
+ )
487
+
488
+ Advert.index_definitions.delete_if { |spec| spec.fields == ["title"] }
425
489
 
426
490
  # The available options for the index function are `:unique` and `:name`
427
491
 
428
492
  class Advert < ActiveRecord::Base
429
493
  index :title, unique: true, name: 'my_index'
430
494
  end
431
- up, down = Generators::DeclareSchema::Migration::Migrator.run
432
- expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
433
- add_column :adverts, :title, :string, limit: 255
434
- add_index :adverts, [:title], unique: true, name: 'my_index'
435
- EOS
436
495
 
437
- Advert.index_specs.delete_if { |spec| spec.fields == ["title"] }
496
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
497
+ migrate_up(<<~EOS.strip)
498
+ add_column :adverts, :title, :string, limit: 255
499
+
500
+ add_index :adverts, [:title], unique: true, name: 'my_index'
501
+ EOS
502
+ )
503
+
504
+ Advert.index_definitions.delete_if { |spec| spec.fields == ["title"] }
438
505
 
439
506
  # You can create an index on more than one field
440
507
 
441
508
  class Advert < ActiveRecord::Base
442
509
  index [:title, :category_id]
443
510
  end
444
- up, down = Generators::DeclareSchema::Migration::Migrator.run
445
- expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
446
- add_column :adverts, :title, :string, limit: 255
447
- add_index :adverts, [:title, :category_id], name: 'on_title_and_category_id'
448
- EOS
449
511
 
450
- Advert.index_specs.delete_if { |spec| spec.fields==["title", "category_id"] }
512
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
513
+ migrate_up(<<~EOS.strip)
514
+ add_column :adverts, :title, :string, limit: 255
515
+
516
+ add_index :adverts, [:title, :category_id], name: 'on_title_and_category_id'
517
+ EOS
518
+ )
519
+
520
+ Advert.index_definitions.delete_if { |spec| spec.fields==["title", "category_id"] }
451
521
 
452
522
  # Finally, you can specify that the migration generator should completely ignore an
453
523
  # index by passing its name to ignore_index in the model.
@@ -468,19 +538,24 @@ RSpec.describe 'DeclareSchema Migration Generator' do
468
538
  Advert.connection.schema_cache.clear!
469
539
  Advert.reset_column_information
470
540
 
471
- up, down = Generators::DeclareSchema::Migration::Migrator.run("adverts" => "ads")
472
- expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
473
- rename_table :adverts, :ads
474
- add_column :ads, :title, :string, limit: 255
475
- add_column :ads, :body, :text
476
- add_index :ads, [:id], unique: true, name: 'PRIMARY_KEY'
477
- EOS
478
- expect(down.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
479
- remove_column :ads, :title
480
- remove_column :ads, :body
481
- rename_table :ads, :adverts
482
- add_index :adverts, [:id], unique: true, name: 'PRIMARY_KEY'
483
- EOS
541
+ expect(Generators::DeclareSchema::Migration::Migrator.run("adverts" => "ads")).to(
542
+ migrate_up(<<~EOS.strip)
543
+ rename_table :adverts, :ads
544
+
545
+ add_column :ads, :title, :string, limit: 255
546
+ add_column :ads, :body, :text
547
+
548
+ add_index :ads, [:id], unique: true, name: 'PRIMARY'
549
+ EOS
550
+ .and migrate_down(<<~EOS.strip)
551
+ remove_column :ads, :title
552
+ remove_column :ads, :body
553
+
554
+ rename_table :ads, :adverts
555
+
556
+ add_index :adverts, [:id], unique: true, name: 'PRIMARY'
557
+ EOS
558
+ )
484
559
 
485
560
  # Set the table name back to what it should be and confirm we're in sync:
486
561
 
@@ -504,21 +579,27 @@ RSpec.describe 'DeclareSchema Migration Generator' do
504
579
  body :text, null: true
505
580
  end
506
581
  end
507
- up, down = Generators::DeclareSchema::Migration::Migrator.run("adverts" => "advertisements")
508
- expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
509
- rename_table :adverts, :advertisements
510
- add_column :advertisements, :title, :string, limit: 255
511
- add_column :advertisements, :body, :text
512
- remove_column :advertisements, :name
513
- add_index :advertisements, [:id], unique: true, name: 'PRIMARY_KEY'
514
- EOS
515
- expect(down.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
516
- remove_column :advertisements, :title
517
- remove_column :advertisements, :body
518
- add_column :adverts, :name, :string, limit: 255
519
- rename_table :advertisements, :adverts
520
- add_index :adverts, [:id], unique: true, name: 'PRIMARY_KEY'
521
- EOS
582
+
583
+ expect(Generators::DeclareSchema::Migration::Migrator.run("adverts" => "advertisements")).to(
584
+ migrate_up(<<~EOS.strip)
585
+ rename_table :adverts, :advertisements
586
+
587
+ add_column :advertisements, :title, :string, limit: 255
588
+ add_column :advertisements, :body, :text
589
+ remove_column :advertisements, :name
590
+
591
+ add_index :advertisements, [:id], unique: true, name: 'PRIMARY'
592
+ EOS
593
+ .and migrate_down(<<~EOS.strip)
594
+ remove_column :advertisements, :title
595
+ remove_column :advertisements, :body
596
+ add_column :adverts, :name, :string, limit: 255
597
+
598
+ rename_table :advertisements, :adverts
599
+
600
+ add_index :adverts, [:id], unique: true, name: 'PRIMARY'
601
+ EOS
602
+ )
522
603
 
523
604
  ### Drop a table
524
605
 
@@ -528,9 +609,25 @@ RSpec.describe 'DeclareSchema Migration Generator' do
528
609
 
529
610
  # Dropping tables is where the automatic down-migration really comes in handy:
530
611
 
531
- up, down = Generators::DeclareSchema::Migration::Migrator.run
532
- expect(up).to eq("drop_table :adverts")
533
- expect(down.gsub(/,.*/m, '')).to eq("create_table \"adverts\"")
612
+ rails4_table_create = <<~EOS.strip
613
+ create_table "adverts", id: false, force: :cascade do |t|
614
+ t.integer "id", limit: 8
615
+ t.string "name", limit: 255
616
+ end
617
+ EOS
618
+
619
+ rails5_table_create = <<~EOS.strip
620
+ create_table "adverts", id: :integer, force: :cascade do |t|
621
+ t.string "name", limit: 255
622
+ end
623
+ EOS
624
+
625
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
626
+ migrate_up(<<~EOS.strip)
627
+ drop_table :adverts
628
+ EOS
629
+ .and migrate_down(Rails::VERSION::MAJOR >= 5 ? rails5_table_create : rails4_table_create)
630
+ )
534
631
 
535
632
  ## STI
536
633
 
@@ -544,27 +641,33 @@ RSpec.describe 'DeclareSchema Migration Generator' do
544
641
  title :string, default: "Untitled", limit: 255, null: true
545
642
  end
546
643
  end
547
- up, down = Generators::DeclareSchema::Migration::Migrator.run
644
+ up = Generators::DeclareSchema::Migration::Migrator.run.first
548
645
  ActiveRecord::Migration.class_eval(up)
549
646
 
550
647
  class FancyAdvert < Advert
551
648
  end
552
649
  class SuperFancyAdvert < FancyAdvert
553
650
  end
554
- up, down = Generators::DeclareSchema::Migration::Migrator.run
555
- expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
556
- add_column :adverts, :type, :string, limit: 255
557
- add_index :adverts, [:type], name: 'on_type'
558
- EOS
559
- expect(down.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
560
- remove_column :adverts, :type
561
- remove_index :adverts, name: :on_type rescue ActiveRecord::StatementInvalid
562
- EOS
651
+
652
+ up, _ = Generators::DeclareSchema::Migration::Migrator.run do |migrations|
653
+ expect(migrations).to(
654
+ migrate_up(<<~EOS.strip)
655
+ add_column :adverts, :type, :string, limit: 255
656
+
657
+ add_index :adverts, [:type], name: 'on_type'
658
+ EOS
659
+ .and migrate_down(<<~EOS.strip)
660
+ remove_column :adverts, :type
661
+
662
+ remove_index :adverts, name: :on_type rescue ActiveRecord::StatementInvalid
663
+ EOS
664
+ )
665
+ end
563
666
 
564
667
  Advert.field_specs.delete(:type)
565
668
  nuke_model_class(SuperFancyAdvert)
566
669
  nuke_model_class(FancyAdvert)
567
- Advert.index_specs.delete_if { |spec| spec.fields==["type"] }
670
+ Advert.index_definitions.delete_if { |spec| spec.fields==["type"] }
568
671
 
569
672
  ## Coping with multiple changes
570
673
 
@@ -592,16 +695,17 @@ RSpec.describe 'DeclareSchema Migration Generator' do
592
695
  body :text, null: true
593
696
  end
594
697
  end
595
- up, down = Generators::DeclareSchema::Migration::Migrator.run(adverts: { title: :name })
596
- expect(up).to eq(<<~EOS.strip)
597
- rename_column :adverts, :title, :name
598
- change_column :adverts, :name, :string, limit: 255, default: \"No Name\"
599
- EOS
600
698
 
601
- expect(down).to eq(<<~EOS.strip)
602
- rename_column :adverts, :name, :title
603
- change_column :adverts, :title, :string, limit: 255, default: \"Untitled\"
604
- EOS
699
+ expect(Generators::DeclareSchema::Migration::Migrator.run(adverts: { title: :name })).to(
700
+ migrate_up(<<~EOS.strip)
701
+ rename_column :adverts, :title, :name
702
+ change_column :adverts, :name, :string, limit: 255, default: "No Name"
703
+ EOS
704
+ .and migrate_down(<<~EOS.strip)
705
+ rename_column :adverts, :name, :title
706
+ change_column :adverts, :title, :string, limit: 255, default: "Untitled"
707
+ EOS
708
+ )
605
709
 
606
710
  ### Rename a table and add a column
607
711
 
@@ -613,13 +717,17 @@ RSpec.describe 'DeclareSchema Migration Generator' do
613
717
  created_at :datetime
614
718
  end
615
719
  end
616
- up, down = Generators::DeclareSchema::Migration::Migrator.run(adverts: :ads)
617
- expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
618
- rename_table :adverts, :ads
619
- add_column :ads, :created_at, :datetime, null: false
620
- change_column :ads, :title, :string, limit: 255, null: false, default: \"Untitled\"
621
- add_index :ads, [:id], unique: true, name: 'PRIMARY_KEY'
622
- EOS
720
+
721
+ expect(Generators::DeclareSchema::Migration::Migrator.run(adverts: :ads)).to(
722
+ migrate_up(<<~EOS.strip)
723
+ rename_table :adverts, :ads
724
+
725
+ add_column :ads, :created_at, :datetime, null: false
726
+ change_column :ads, :title, :string, limit: 255, null: false, default: \"Untitled\"
727
+
728
+ add_index :ads, [:id], unique: true, name: 'PRIMARY'
729
+ EOS
730
+ )
623
731
 
624
732
  class Advert < ActiveRecord::Base
625
733
  fields do
@@ -640,11 +748,14 @@ RSpec.describe 'DeclareSchema Migration Generator' do
640
748
  end
641
749
  self.primary_key = "advert_id"
642
750
  end
643
- up, _down = Generators::DeclareSchema::Migration::Migrator.run(adverts: { id: :advert_id })
644
- expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
645
- rename_column :adverts, :id, :advert_id
646
- add_index :adverts, [:advert_id], unique: true, name: 'PRIMARY_KEY'
647
- EOS
751
+
752
+ expect(Generators::DeclareSchema::Migration::Migrator.run(adverts: { id: :advert_id })).to(
753
+ migrate_up(<<~EOS.strip)
754
+ rename_column :adverts, :id, :advert_id
755
+
756
+ add_index :adverts, [:advert_id], unique: true, name: 'PRIMARY'
757
+ EOS
758
+ )
648
759
 
649
760
  nuke_model_class(Advert)
650
761
  ActiveRecord::Base.connection.execute("drop table `adverts`;")
@@ -682,5 +793,299 @@ RSpec.describe 'DeclareSchema Migration Generator' do
682
793
  ActiveRecord::Migration.class_eval(up)
683
794
  expect(Ad.field_specs['company'].options[:validates].inspect).to eq("{:presence=>true, :uniqueness=>{:case_sensitive=>false}}")
684
795
  end
685
- end
686
796
 
797
+ describe 'serialize' do
798
+ before do
799
+ class Ad < ActiveRecord::Base
800
+ @serialize_args = []
801
+
802
+ class << self
803
+ attr_reader :serialize_args
804
+
805
+ def serialize(*args)
806
+ @serialize_args << args
807
+ end
808
+ end
809
+ end
810
+ end
811
+
812
+ describe 'untyped' do
813
+ it 'allows serialize: true' do
814
+ class Ad < ActiveRecord::Base
815
+ fields do
816
+ allow_list :text, limit: 0xFFFF, serialize: true
817
+ end
818
+ end
819
+
820
+ expect(Ad.serialize_args).to eq([[:allow_list]])
821
+ end
822
+
823
+ it 'converts defaults with .to_yaml' do
824
+ class Ad < ActiveRecord::Base
825
+ fields do
826
+ allow_list :string, limit: 255, serialize: true, null: true, default: []
827
+ allow_hash :string, limit: 255, serialize: true, null: true, default: {}
828
+ allow_string :string, limit: 255, serialize: true, null: true, default: ['abc']
829
+ allow_null :string, limit: 255, serialize: true, null: true, default: nil
830
+ end
831
+ end
832
+
833
+ expect(Ad.field_specs['allow_list'].default).to eq("--- []\n")
834
+ expect(Ad.field_specs['allow_hash'].default).to eq("--- {}\n")
835
+ expect(Ad.field_specs['allow_string'].default).to eq("---\n- abc\n")
836
+ expect(Ad.field_specs['allow_null'].default).to eq(nil)
837
+ end
838
+ end
839
+
840
+ describe 'Array' do
841
+ it 'allows serialize: Array' do
842
+ class Ad < ActiveRecord::Base
843
+ fields do
844
+ allow_list :string, limit: 255, serialize: Array, null: true
845
+ end
846
+ end
847
+
848
+ expect(Ad.serialize_args).to eq([[:allow_list, Array]])
849
+ end
850
+
851
+ it 'allows Array defaults' do
852
+ class Ad < ActiveRecord::Base
853
+ fields do
854
+ allow_list :string, limit: 255, serialize: Array, null: true, default: [2]
855
+ allow_string :string, limit: 255, serialize: Array, null: true, default: ['abc']
856
+ allow_empty :string, limit: 255, serialize: Array, null: true, default: []
857
+ allow_null :string, limit: 255, serialize: Array, null: true, default: nil
858
+ end
859
+ end
860
+
861
+ expect(Ad.field_specs['allow_list'].default).to eq("---\n- 2\n")
862
+ expect(Ad.field_specs['allow_string'].default).to eq("---\n- abc\n")
863
+ expect(Ad.field_specs['allow_empty'].default).to eq(nil)
864
+ expect(Ad.field_specs['allow_null'].default).to eq(nil)
865
+ end
866
+ end
867
+
868
+ describe 'Hash' do
869
+ it 'allows serialize: Hash' do
870
+ class Ad < ActiveRecord::Base
871
+ fields do
872
+ allow_list :string, limit: 255, serialize: Hash, null: true
873
+ end
874
+ end
875
+
876
+ expect(Ad.serialize_args).to eq([[:allow_list, Hash]])
877
+ end
878
+
879
+ it 'allows Hash defaults' do
880
+ class Ad < ActiveRecord::Base
881
+ fields do
882
+ allow_loc :string, limit: 255, serialize: Hash, null: true, default: { 'state' => 'CA' }
883
+ allow_hash :string, limit: 255, serialize: Hash, null: true, default: {}
884
+ allow_null :string, limit: 255, serialize: Hash, null: true, default: nil
885
+ end
886
+ end
887
+
888
+ expect(Ad.field_specs['allow_loc'].default).to eq("---\nstate: CA\n")
889
+ expect(Ad.field_specs['allow_hash'].default).to eq(nil)
890
+ expect(Ad.field_specs['allow_null'].default).to eq(nil)
891
+ end
892
+ end
893
+
894
+ describe 'JSON' do
895
+ it 'allows serialize: JSON' do
896
+ class Ad < ActiveRecord::Base
897
+ fields do
898
+ allow_list :string, limit: 255, serialize: JSON
899
+ end
900
+ end
901
+
902
+ expect(Ad.serialize_args).to eq([[:allow_list, JSON]])
903
+ end
904
+
905
+ it 'allows JSON defaults' do
906
+ class Ad < ActiveRecord::Base
907
+ fields do
908
+ allow_hash :string, limit: 255, serialize: JSON, null: true, default: { 'state' => 'CA' }
909
+ allow_empty_array :string, limit: 255, serialize: JSON, null: true, default: []
910
+ allow_empty_hash :string, limit: 255, serialize: JSON, null: true, default: {}
911
+ allow_null :string, limit: 255, serialize: JSON, null: true, default: nil
912
+ end
913
+ end
914
+
915
+ expect(Ad.field_specs['allow_hash'].default).to eq("{\"state\":\"CA\"}")
916
+ expect(Ad.field_specs['allow_empty_array'].default).to eq("[]")
917
+ expect(Ad.field_specs['allow_empty_hash'].default).to eq("{}")
918
+ expect(Ad.field_specs['allow_null'].default).to eq(nil)
919
+ end
920
+ end
921
+
922
+ class ValueClass
923
+ delegate :present?, :inspect, to: :@value
924
+
925
+ def initialize(value)
926
+ @value = value
927
+ end
928
+
929
+ class << self
930
+ def dump(object)
931
+ if object&.present?
932
+ object.inspect
933
+ end
934
+ end
935
+
936
+ def load(serialized)
937
+ if serialized
938
+ raise 'not used ???'
939
+ end
940
+ end
941
+ end
942
+ end
943
+
944
+ describe 'custom coder' do
945
+ it 'allows serialize: ValueClass' do
946
+ class Ad < ActiveRecord::Base
947
+ fields do
948
+ allow_list :string, limit: 255, serialize: ValueClass
949
+ end
950
+ end
951
+
952
+ expect(Ad.serialize_args).to eq([[:allow_list, ValueClass]])
953
+ end
954
+
955
+ it 'allows ValueClass defaults' do
956
+ class Ad < ActiveRecord::Base
957
+ fields do
958
+ allow_hash :string, limit: 255, serialize: ValueClass, null: true, default: ValueClass.new([2])
959
+ allow_empty_array :string, limit: 255, serialize: ValueClass, null: true, default: ValueClass.new([])
960
+ allow_null :string, limit: 255, serialize: ValueClass, null: true, default: nil
961
+ end
962
+ end
963
+
964
+ expect(Ad.field_specs['allow_hash'].default).to eq("[2]")
965
+ expect(Ad.field_specs['allow_empty_array'].default).to eq(nil)
966
+ expect(Ad.field_specs['allow_null'].default).to eq(nil)
967
+ end
968
+ end
969
+
970
+ it 'disallows serialize: with a non-string column type' do
971
+ expect do
972
+ class Ad < ActiveRecord::Base
973
+ fields do
974
+ allow_list :integer, limit: 8, serialize: true
975
+ end
976
+ end
977
+ end.to raise_exception(ArgumentError, /must be :string or :text/)
978
+ end
979
+ end
980
+
981
+ context "for Rails #{Rails::VERSION::MAJOR}" do
982
+ if Rails::VERSION::MAJOR >= 5
983
+ let(:optional_true) { { optional: true } }
984
+ let(:optional_false) { { optional: false } }
985
+ else
986
+ let(:optional_true) { {} }
987
+ let(:optional_false) { {} }
988
+ end
989
+ let(:optional_flag) { { false => optional_false, true => optional_true } }
990
+
991
+ describe 'belongs_to' do
992
+ before do
993
+ unless defined?(AdCategory)
994
+ class AdCategory < ActiveRecord::Base
995
+ fields { }
996
+ end
997
+ end
998
+
999
+ class Advert < ActiveRecord::Base
1000
+ fields do
1001
+ name :string, limit: 255, null: true
1002
+ category_id :integer, limit: 8
1003
+ nullable_category_id :integer, limit: 8, null: true
1004
+ end
1005
+ end
1006
+ up = Generators::DeclareSchema::Migration::Migrator.run.first
1007
+ ActiveRecord::Migration.class_eval(up)
1008
+ end
1009
+
1010
+ it 'passes through optional: when given' do
1011
+ class AdvertBelongsTo < ActiveRecord::Base
1012
+ self.table_name = 'adverts'
1013
+ fields { }
1014
+ reset_column_information
1015
+ belongs_to :ad_category, optional: true
1016
+ end
1017
+ expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_true)
1018
+ end
1019
+
1020
+ describe 'contradictory settings' do # contradictory settings are ok--for example, during migration
1021
+ it 'passes through optional: true, null: false' do
1022
+ class AdvertBelongsTo < ActiveRecord::Base
1023
+ self.table_name = 'adverts'
1024
+ fields { }
1025
+ reset_column_information
1026
+ belongs_to :ad_category, optional: true, null: false
1027
+ end
1028
+ expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_true)
1029
+ expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(false)
1030
+ end
1031
+
1032
+ it 'passes through optional: false, null: true' do
1033
+ class AdvertBelongsTo < ActiveRecord::Base
1034
+ self.table_name = 'adverts'
1035
+ fields { }
1036
+ reset_column_information
1037
+ belongs_to :ad_category, optional: false, null: true
1038
+ end
1039
+ expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_false)
1040
+ expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(true)
1041
+ end
1042
+ end
1043
+
1044
+ [false, true].each do |nullable|
1045
+ context "nullable=#{nullable}" do
1046
+ it 'infers optional: from null:' do
1047
+ eval <<~EOS
1048
+ class AdvertBelongsTo < ActiveRecord::Base
1049
+ fields { }
1050
+ belongs_to :ad_category, null: #{nullable}
1051
+ end
1052
+ EOS
1053
+ expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_flag[nullable])
1054
+ expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(nullable)
1055
+ end
1056
+
1057
+ it 'infers null: from optional:' do
1058
+ eval <<~EOS
1059
+ class AdvertBelongsTo < ActiveRecord::Base
1060
+ fields { }
1061
+ belongs_to :ad_category, optional: #{nullable}
1062
+ end
1063
+ EOS
1064
+ expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_flag[nullable])
1065
+ expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(nullable)
1066
+ end
1067
+ end
1068
+ end
1069
+ end
1070
+ end
1071
+
1072
+ describe 'migration base class' do
1073
+ it 'adapts to Rails 4' do
1074
+ class Advert < active_record_base_class.constantize
1075
+ fields do
1076
+ title :string, limit: 100
1077
+ end
1078
+ end
1079
+
1080
+ generate_migrations '-n', '-m'
1081
+
1082
+ migrations = Dir.glob('db/migrate/*declare_schema_migration*.rb')
1083
+ expect(migrations.size).to eq(1), migrations.inspect
1084
+
1085
+ migration_content = File.read(migrations.first)
1086
+ first_line = migration_content.split("\n").first
1087
+ base_class = first_line.split(' < ').last
1088
+ expect(base_class).to eq("(Rails::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration)")
1089
+ end
1090
+ end
1091
+ end