declare_schema 0.3.0.pre.1 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +35 -4
- data/Gemfile.lock +14 -14
- data/README.md +20 -0
- data/lib/declare_schema.rb +2 -1
- data/lib/declare_schema/model.rb +62 -24
- data/lib/declare_schema/model/foreign_key_definition.rb +73 -0
- data/lib/declare_schema/model/index_definition.rb +138 -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 +7 -8
- 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 +594 -233
- data/spec/lib/declare_schema/model/index_definition_spec.rb +123 -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
|
+
if Rails::VERSION::MAJOR == 5
|
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,17 +28,27 @@ 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"])
|
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
|
-
|
55
|
-
|
56
|
-
# TODO: ^ add_index should not be there?
|
60
|
+
Advert.connection.schema_cache.clear!
|
61
|
+
Advert.reset_column_information
|
57
62
|
|
58
|
-
expect(
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
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
|
-
|
108
|
-
|
109
|
-
|
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
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
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.
|
133
|
-
|
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
|
-
|
145
|
-
|
146
|
-
|
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
|
-
|
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
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
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
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
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
|
-
|
241
|
-
|
242
|
-
|
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
|
-
|
256
|
-
|
257
|
-
|
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
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
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.
|
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
|
-
|
308
|
-
expect(
|
309
|
-
|
310
|
-
|
311
|
-
|
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.
|
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
|
-
|
324
|
-
expect(
|
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.
|
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
|
-
|
337
|
-
expect(
|
338
|
-
|
339
|
-
|
340
|
-
|
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.
|
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
|
-
|
357
|
-
expect(
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
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
|
-
|
514
|
-
expect(
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
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
|
-
|
538
|
-
|
539
|
-
|
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
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
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.
|
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(
|
608
|
-
|
609
|
-
|
610
|
-
|
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
|
-
|
623
|
-
expect(
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
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
|
-
|
650
|
-
expect(
|
651
|
-
|
652
|
-
|
653
|
-
|
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 '
|
802
|
+
describe 'serialize' do
|
693
803
|
before do
|
694
|
-
|
695
|
-
|
696
|
-
|
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
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
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
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
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
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
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
|