declare_schema 0.3.0.pre.1 → 0.4.1

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