declare_schema 0.3.0.pre.2 → 0.4.2
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 +40 -2
- data/Gemfile.lock +14 -14
- 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 +138 -0
- data/lib/declare_schema/version.rb +1 -1
- data/lib/generators/declare_schema/migration/migrator.rb +15 -15
- 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 +618 -208
- data/spec/lib/declare_schema/model/index_definition_spec.rb +123 -0
- data/spec/lib/declare_schema/prepare_testapp.rb +2 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/support/acceptance_spec_helpers.rb +57 -0
- metadata +9 -6
- 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
|
@@ -341,7 +341,7 @@ module Generators
|
|
341
341
|
end
|
342
342
|
|
343
343
|
def create_indexes(model)
|
344
|
-
model.
|
344
|
+
model.index_definitions.map { |i| i.to_add_statement(model.table_name) }
|
345
345
|
end
|
346
346
|
|
347
347
|
def create_constraints(model)
|
@@ -413,7 +413,7 @@ module Generators
|
|
413
413
|
col_name = old_names[c] || c
|
414
414
|
col = db_columns[col_name]
|
415
415
|
spec = model.field_specs[c]
|
416
|
-
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
|
417
417
|
change_spec = fk_field_options(model, c)
|
418
418
|
change_spec[:limit] ||= spec.limit if (spec.sql_type != :text ||
|
419
419
|
::DeclareSchema::Model::FieldSpec.mysql_text_limits?) &&
|
@@ -449,8 +449,8 @@ module Generators
|
|
449
449
|
return [[], []] if Migrator.disable_constraints
|
450
450
|
|
451
451
|
new_table_name = model.table_name
|
452
|
-
existing_indexes = ::DeclareSchema::Model::
|
453
|
-
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
|
454
454
|
model_indexes = model_indexes_with_equivalents.map do |i|
|
455
455
|
if i.explicit_name.nil?
|
456
456
|
if ex = existing_indexes.find { |e| i != e && e.equivalent?(i) }
|
@@ -458,20 +458,20 @@ module Generators
|
|
458
458
|
end
|
459
459
|
end || i
|
460
460
|
end
|
461
|
-
existing_has_primary_key = existing_indexes.any? { |i| i.name ==
|
462
|
-
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 }
|
463
463
|
|
464
464
|
add_indexes_init = model_indexes - existing_indexes
|
465
465
|
drop_indexes_init = existing_indexes - model_indexes
|
466
466
|
undo_add_indexes = []
|
467
467
|
undo_drop_indexes = []
|
468
468
|
add_indexes = add_indexes_init.map do |i|
|
469
|
-
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
|
470
470
|
i.to_add_statement(new_table_name, existing_has_primary_key)
|
471
471
|
end
|
472
472
|
drop_indexes = drop_indexes_init.map do |i|
|
473
473
|
undo_drop_indexes << i.to_add_statement(old_table_name, model_has_primary_key)
|
474
|
-
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
|
475
475
|
end.compact
|
476
476
|
|
477
477
|
# the order is important here - adding a :unique, for instance needs to remove then add
|
@@ -489,7 +489,7 @@ module Generators
|
|
489
489
|
return [[], []] if Migrator.disable_indexing
|
490
490
|
|
491
491
|
new_table_name = model.table_name
|
492
|
-
existing_fks = ::DeclareSchema::Model::
|
492
|
+
existing_fks = ::DeclareSchema::Model::ForeignKeyDefinition.for_model(model, old_table_name)
|
493
493
|
model_fks = model.constraint_specs
|
494
494
|
add_fks = model_fks - existing_fks
|
495
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,
|
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
|
@@ -266,7 +303,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
266
303
|
end
|
267
304
|
end
|
268
305
|
|
269
|
-
up
|
306
|
+
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
270
307
|
ActiveRecord::Migration.class_eval up
|
271
308
|
Advert.connection.schema_cache.clear!
|
272
309
|
Advert.reset_column_information
|
@@ -278,63 +315,82 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
278
315
|
|
279
316
|
class Category < ActiveRecord::Base; end
|
280
317
|
class Advert < ActiveRecord::Base
|
318
|
+
fields do
|
319
|
+
name :string, limit: 255, null: true
|
320
|
+
end
|
281
321
|
belongs_to :category
|
282
322
|
end
|
283
323
|
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
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
|
+
)
|
293
336
|
|
294
337
|
Advert.field_specs.delete(:category_id)
|
295
|
-
Advert.
|
338
|
+
Advert.index_definitions.delete_if { |spec| spec.fields==["category_id"] }
|
296
339
|
|
297
340
|
# If you specify a custom foreign key, the migration generator observes that:
|
298
341
|
|
299
342
|
class Category < ActiveRecord::Base; end
|
300
343
|
class Advert < ActiveRecord::Base
|
344
|
+
fields { }
|
301
345
|
belongs_to :category, foreign_key: "c_id", class_name: 'Category'
|
302
346
|
end
|
303
|
-
|
304
|
-
expect(
|
305
|
-
|
306
|
-
|
307
|
-
|
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
|
+
)
|
308
355
|
|
309
356
|
Advert.field_specs.delete(:c_id)
|
310
|
-
Advert.
|
357
|
+
Advert.index_definitions.delete_if { |spec| spec.fields == ["c_id"] }
|
311
358
|
|
312
359
|
# You can avoid generating the index by specifying `index: false`
|
313
360
|
|
314
361
|
class Category < ActiveRecord::Base; end
|
315
362
|
class Advert < ActiveRecord::Base
|
363
|
+
fields { }
|
316
364
|
belongs_to :category, index: false
|
317
365
|
end
|
318
|
-
|
319
|
-
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
|
+
)
|
320
372
|
|
321
373
|
Advert.field_specs.delete(:category_id)
|
322
|
-
Advert.
|
374
|
+
Advert.index_definitions.delete_if { |spec| spec.fields == ["category_id"] }
|
323
375
|
|
324
376
|
# You can specify the index name with :index
|
325
377
|
|
326
378
|
class Category < ActiveRecord::Base; end
|
327
379
|
class Advert < ActiveRecord::Base
|
380
|
+
fields { }
|
328
381
|
belongs_to :category, index: 'my_index'
|
329
382
|
end
|
330
|
-
|
331
|
-
expect(
|
332
|
-
|
333
|
-
|
334
|
-
|
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
|
+
)
|
335
391
|
|
336
392
|
Advert.field_specs.delete(:category_id)
|
337
|
-
Advert.
|
393
|
+
Advert.index_definitions.delete_if { |spec| spec.fields == ["category_id"] }
|
338
394
|
|
339
395
|
### Timestamps and Optimimistic Locking
|
340
396
|
|
@@ -347,17 +403,19 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
347
403
|
optimistic_lock
|
348
404
|
end
|
349
405
|
end
|
350
|
-
|
351
|
-
expect(
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
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
|
+
)
|
361
419
|
|
362
420
|
Advert.field_specs.delete(:updated_at)
|
363
421
|
Advert.field_specs.delete(:created_at)
|
@@ -372,13 +430,16 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
372
430
|
title :string, index: true, limit: 255, null: true
|
373
431
|
end
|
374
432
|
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
433
|
|
381
|
-
|
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"] }
|
382
443
|
|
383
444
|
# You can ask for a unique index
|
384
445
|
|
@@ -387,13 +448,16 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
387
448
|
title :string, index: true, unique: true, null: true, limit: 255
|
388
449
|
end
|
389
450
|
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
451
|
|
396
|
-
|
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"] }
|
397
461
|
|
398
462
|
# You can specify the name for the index
|
399
463
|
|
@@ -402,52 +466,64 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
402
466
|
title :string, index: 'my_index', limit: 255, null: true
|
403
467
|
end
|
404
468
|
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
469
|
|
411
|
-
|
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"] }
|
412
479
|
|
413
480
|
# You can ask for an index outside of the fields block
|
414
481
|
|
415
482
|
class Advert < ActiveRecord::Base
|
416
483
|
index :title
|
417
484
|
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
485
|
|
424
|
-
|
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"] }
|
425
495
|
|
426
496
|
# The available options for the index function are `:unique` and `:name`
|
427
497
|
|
428
498
|
class Advert < ActiveRecord::Base
|
429
499
|
index :title, unique: true, name: 'my_index'
|
430
500
|
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
501
|
|
437
|
-
|
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"] }
|
438
511
|
|
439
512
|
# You can create an index on more than one field
|
440
513
|
|
441
514
|
class Advert < ActiveRecord::Base
|
442
515
|
index [:title, :category_id]
|
443
516
|
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
517
|
|
450
|
-
|
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"] }
|
451
527
|
|
452
528
|
# Finally, you can specify that the migration generator should completely ignore an
|
453
529
|
# index by passing its name to ignore_index in the model.
|
@@ -468,19 +544,24 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
468
544
|
Advert.connection.schema_cache.clear!
|
469
545
|
Advert.reset_column_information
|
470
546
|
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
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
|
+
)
|
484
565
|
|
485
566
|
# Set the table name back to what it should be and confirm we're in sync:
|
486
567
|
|
@@ -504,21 +585,27 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
504
585
|
body :text, null: true
|
505
586
|
end
|
506
587
|
end
|
507
|
-
|
508
|
-
expect(
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
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
|
+
)
|
522
609
|
|
523
610
|
### Drop a table
|
524
611
|
|
@@ -528,9 +615,24 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
528
615
|
|
529
616
|
# Dropping tables is where the automatic down-migration really comes in handy:
|
530
617
|
|
531
|
-
|
532
|
-
|
533
|
-
|
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
|
+
)
|
534
636
|
|
535
637
|
## STI
|
536
638
|
|
@@ -544,27 +646,33 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
544
646
|
title :string, default: "Untitled", limit: 255, null: true
|
545
647
|
end
|
546
648
|
end
|
547
|
-
up
|
649
|
+
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
548
650
|
ActiveRecord::Migration.class_eval(up)
|
549
651
|
|
550
652
|
class FancyAdvert < Advert
|
551
653
|
end
|
552
654
|
class SuperFancyAdvert < FancyAdvert
|
553
655
|
end
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
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
|
563
671
|
|
564
672
|
Advert.field_specs.delete(:type)
|
565
673
|
nuke_model_class(SuperFancyAdvert)
|
566
674
|
nuke_model_class(FancyAdvert)
|
567
|
-
Advert.
|
675
|
+
Advert.index_definitions.delete_if { |spec| spec.fields==["type"] }
|
568
676
|
|
569
677
|
## Coping with multiple changes
|
570
678
|
|
@@ -592,16 +700,17 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
592
700
|
body :text, null: true
|
593
701
|
end
|
594
702
|
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
703
|
|
601
|
-
expect(
|
602
|
-
|
603
|
-
|
604
|
-
|
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
|
+
)
|
605
714
|
|
606
715
|
### Rename a table and add a column
|
607
716
|
|
@@ -613,13 +722,17 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
613
722
|
created_at :datetime
|
614
723
|
end
|
615
724
|
end
|
616
|
-
|
617
|
-
expect(
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
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
|
+
)
|
623
736
|
|
624
737
|
class Advert < ActiveRecord::Base
|
625
738
|
fields do
|
@@ -640,11 +753,14 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
640
753
|
end
|
641
754
|
self.primary_key = "advert_id"
|
642
755
|
end
|
643
|
-
|
644
|
-
expect(
|
645
|
-
|
646
|
-
|
647
|
-
|
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
|
+
)
|
648
764
|
|
649
765
|
nuke_model_class(Advert)
|
650
766
|
ActiveRecord::Base.connection.execute("drop table `adverts`;")
|
@@ -682,5 +798,299 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
682
798
|
ActiveRecord::Migration.class_eval(up)
|
683
799
|
expect(Ad.field_specs['company'].options[:validates].inspect).to eq("{:presence=>true, :uniqueness=>{:case_sensitive=>false}}")
|
684
800
|
end
|
685
|
-
end
|
686
801
|
|
802
|
+
describe 'serialize' do
|
803
|
+
before do
|
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
|
813
|
+
end
|
814
|
+
end
|
815
|
+
end
|
816
|
+
|
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
|
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)
|
924
|
+
end
|
925
|
+
end
|
926
|
+
|
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
|
946
|
+
end
|
947
|
+
end
|
948
|
+
|
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
|
955
|
+
end
|
956
|
+
|
957
|
+
expect(Ad.serialize_args).to eq([[:allow_list, ValueClass]])
|
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)")
|
1094
|
+
end
|
1095
|
+
end
|
1096
|
+
end
|