declare_schema 0.2.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeclareSchema
4
- VERSION = "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