declare_schema 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +35 -1
- data/Gemfile.lock +13 -13
- data/README.md +20 -0
- data/lib/declare_schema.rb +2 -1
- data/lib/declare_schema/model.rb +78 -41
- data/lib/declare_schema/model/foreign_key_definition.rb +73 -0
- data/lib/declare_schema/model/index_definition.rb +120 -0
- data/lib/declare_schema/version.rb +1 -1
- data/lib/generators/declare_schema/migration/migrator.rb +26 -21
- data/lib/generators/declare_schema/migration/templates/migration.rb.erb +1 -1
- data/spec/lib/declare_schema/api_spec.rb +6 -7
- data/spec/lib/declare_schema/generator_spec.rb +51 -10
- data/spec/lib/declare_schema/interactive_primary_key_spec.rb +16 -14
- data/spec/lib/declare_schema/migration_generator_spec.rb +613 -208
- data/spec/lib/declare_schema/model/index_definition_spec.rb +83 -0
- data/spec/lib/declare_schema/prepare_testapp.rb +2 -0
- data/spec/lib/generators/declare_schema/migration/migrator_spec.rb +30 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/support/acceptance_spec_helpers.rb +57 -0
- metadata +6 -3
- data/lib/declare_schema/model/index_spec.rb +0 -175
@@ -45,14 +45,14 @@ module Generators
|
|
45
45
|
false # no single-column primary key
|
46
46
|
end
|
47
47
|
|
48
|
-
def
|
48
|
+
def index_definitions_with_primary_key
|
49
49
|
[
|
50
|
-
::DeclareSchema::Model::
|
51
|
-
::DeclareSchema::Model::
|
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 :
|
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::
|
64
|
-
::DeclareSchema::Model::
|
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
|
-
@
|
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.
|
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::
|
448
|
-
model_indexes_with_equivalents = model.
|
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 ==
|
457
|
-
model_has_primary_key = model_indexes.any? { |i| i.name ==
|
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 ==
|
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 ==
|
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::
|
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
|
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
9
|
+
generate_model 'alpha/beta', 'one:string', 'two:integer'
|
10
10
|
|
11
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
35
|
-
|
36
|
-
|
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
|
-
|
42
|
-
|
43
|
-
|
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
|
-
|
46
|
+
### ensure it doesn't cause further migrations
|
46
47
|
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
-
|
55
|
-
|
56
|
-
# TODO: ^ add_index should not be there?
|
54
|
+
Advert.connection.schema_cache.clear!
|
55
|
+
Advert.reset_column_information
|
57
56
|
|
58
|
-
expect(
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
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
|
-
|
108
|
-
|
109
|
-
|
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
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
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,
|
133
|
-
|
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
|
-
|
145
|
-
|
146
|
-
|
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
|
-
|
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
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
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
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
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
|
-
|
241
|
-
|
242
|
-
|
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
|
-
|
256
|
-
|
257
|
-
|
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
|
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
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
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.
|
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
|
-
|
304
|
-
expect(
|
305
|
-
|
306
|
-
|
307
|
-
|
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.
|
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
|
-
|
319
|
-
expect(
|
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.
|
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
|
-
|
331
|
-
expect(
|
332
|
-
|
333
|
-
|
334
|
-
|
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.
|
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
|
-
|
351
|
-
expect(
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
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
|
-
|
508
|
-
expect(
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
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
|
-
|
532
|
-
|
533
|
-
|
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
|
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
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
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.
|
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(
|
602
|
-
|
603
|
-
|
604
|
-
|
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
|
-
|
617
|
-
expect(
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
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
|
-
|
644
|
-
expect(
|
645
|
-
|
646
|
-
|
647
|
-
|
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
|