declare_schema 0.9.0 → 0.11.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/.github/workflows/declare_schema_build.yml +1 -1
- data/CHANGELOG.md +32 -0
- data/Gemfile.lock +1 -1
- data/README.md +12 -2
- data/lib/declare_schema.rb +12 -1
- data/lib/declare_schema/dsl.rb +39 -0
- data/lib/declare_schema/extensions/active_record/fields_declaration.rb +22 -3
- data/lib/declare_schema/model.rb +4 -6
- data/lib/declare_schema/model/column.rb +1 -1
- data/lib/declare_schema/model/foreign_key_definition.rb +6 -11
- data/lib/declare_schema/model/index_definition.rb +1 -20
- data/lib/declare_schema/schema_change/all.rb +22 -0
- data/lib/declare_schema/schema_change/base.rb +45 -0
- data/lib/declare_schema/schema_change/column_add.rb +27 -0
- data/lib/declare_schema/schema_change/column_change.rb +32 -0
- data/lib/declare_schema/schema_change/column_remove.rb +20 -0
- data/lib/declare_schema/schema_change/column_rename.rb +23 -0
- data/lib/declare_schema/schema_change/foreign_key_add.rb +25 -0
- data/lib/declare_schema/schema_change/foreign_key_remove.rb +20 -0
- data/lib/declare_schema/schema_change/index_add.rb +33 -0
- data/lib/declare_schema/schema_change/index_remove.rb +20 -0
- data/lib/declare_schema/schema_change/primary_key_change.rb +33 -0
- data/lib/declare_schema/schema_change/table_add.rb +37 -0
- data/lib/declare_schema/schema_change/table_change.rb +36 -0
- data/lib/declare_schema/schema_change/table_remove.rb +22 -0
- data/lib/declare_schema/schema_change/table_rename.rb +22 -0
- data/lib/declare_schema/version.rb +1 -1
- data/lib/generators/declare_schema/migration/USAGE +14 -24
- data/lib/generators/declare_schema/migration/migration_generator.rb +40 -38
- data/lib/generators/declare_schema/migration/migrator.rb +175 -177
- data/lib/generators/declare_schema/migration/templates/migration.rb.erb +3 -3
- data/lib/generators/declare_schema/support/model.rb +4 -4
- data/spec/lib/declare_schema/api_spec.rb +8 -8
- data/spec/lib/declare_schema/field_declaration_dsl_spec.rb +41 -15
- data/spec/lib/declare_schema/field_spec_spec.rb +2 -2
- data/spec/lib/declare_schema/generator_spec.rb +5 -5
- data/spec/lib/declare_schema/interactive_primary_key_spec.rb +117 -28
- data/spec/lib/declare_schema/migration_generator_spec.rb +1990 -843
- data/spec/lib/declare_schema/model/column_spec.rb +49 -23
- data/spec/lib/declare_schema/model/foreign_key_definition_spec.rb +158 -57
- data/spec/lib/declare_schema/model/habtm_model_shim_spec.rb +0 -2
- data/spec/lib/declare_schema/model/index_definition_spec.rb +189 -78
- data/spec/lib/declare_schema/model/table_options_definition_spec.rb +75 -11
- data/spec/lib/declare_schema/schema_change/base_spec.rb +75 -0
- data/spec/lib/declare_schema/schema_change/column_add_spec.rb +30 -0
- data/spec/lib/declare_schema/schema_change/column_change_spec.rb +33 -0
- data/spec/lib/declare_schema/schema_change/column_remove_spec.rb +30 -0
- data/spec/lib/declare_schema/schema_change/column_rename_spec.rb +28 -0
- data/spec/lib/declare_schema/schema_change/foreign_key_add_spec.rb +29 -0
- data/spec/lib/declare_schema/schema_change/foreign_key_remove_spec.rb +29 -0
- data/spec/lib/declare_schema/schema_change/index_add_spec.rb +56 -0
- data/spec/lib/declare_schema/schema_change/index_remove_spec.rb +29 -0
- data/spec/lib/declare_schema/schema_change/primary_key_change_spec.rb +69 -0
- data/spec/lib/declare_schema/schema_change/table_add_spec.rb +50 -0
- data/spec/lib/declare_schema/schema_change/table_change_spec.rb +30 -0
- data/spec/lib/declare_schema/schema_change/table_remove_spec.rb +27 -0
- data/spec/lib/declare_schema/schema_change/table_rename_spec.rb +27 -0
- data/spec/lib/generators/declare_schema/migration/migrator_spec.rb +59 -11
- data/spec/spec_helper.rb +1 -1
- data/spec/support/acceptance_spec_helpers.rb +2 -2
- metadata +35 -6
- data/test_responses.txt +0 -2
@@ -1,9 +1,9 @@
|
|
1
|
-
class <%= @migration_class_name %> < (
|
1
|
+
class <%= @migration_class_name %> < (ActiveSupport::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration)
|
2
2
|
def self.up
|
3
|
-
<%= @up %>
|
3
|
+
<%= @up.presence or raise "no @up given!" %>
|
4
4
|
end
|
5
5
|
|
6
6
|
def self.down
|
7
|
-
<%= @down %>
|
7
|
+
<%= @down.presence or raise "no @down given!" %>
|
8
8
|
end
|
9
9
|
end
|
@@ -69,11 +69,11 @@ module DeclareSchema
|
|
69
69
|
def declare_model_fields_and_associations
|
70
70
|
buffer = ::DeclareSchema::Support::IndentedBuffer.new(indent: 2)
|
71
71
|
buffer.newline!
|
72
|
-
buffer << '
|
72
|
+
buffer << 'declare_schema do'
|
73
73
|
buffer.indent! do
|
74
74
|
field_attributes.each do |attribute|
|
75
|
-
decl = "%-#{max_attribute_length}s" % attribute.
|
76
|
-
attribute.
|
75
|
+
decl = "%-#{max_attribute_length}s" % attribute.type + ' ' +
|
76
|
+
attribute.name.to_sym.inspect +
|
77
77
|
case attribute.type.to_s
|
78
78
|
when 'string'
|
79
79
|
', limit: 255'
|
@@ -113,7 +113,7 @@ module DeclareSchema
|
|
113
113
|
end
|
114
114
|
|
115
115
|
def max_attribute_length
|
116
|
-
attributes.map { |attribute| attribute.
|
116
|
+
attributes.map { |attribute| attribute.type.length }.max
|
117
117
|
end
|
118
118
|
|
119
119
|
def field_attributes
|
@@ -20,9 +20,9 @@ RSpec.describe 'DeclareSchema API' do
|
|
20
20
|
expect_model_definition_to_eq('advert', <<~EOS)
|
21
21
|
class Advert < #{active_record_base_class}
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
body
|
23
|
+
declare_schema do
|
24
|
+
string :title, limit: 255
|
25
|
+
text :body
|
26
26
|
end
|
27
27
|
|
28
28
|
end
|
@@ -39,7 +39,7 @@ RSpec.describe 'DeclareSchema API' do
|
|
39
39
|
|
40
40
|
load_models
|
41
41
|
|
42
|
-
if
|
42
|
+
if ActiveSupport::VERSION::MAJOR == 5
|
43
43
|
# TODO: get this to work on Travis for Rails 6
|
44
44
|
generate_migrations '-n', '-m'
|
45
45
|
end
|
@@ -91,8 +91,8 @@ RSpec.describe 'DeclareSchema API' do
|
|
91
91
|
class AdvertWithRequiredTitle < ActiveRecord::Base
|
92
92
|
self.table_name = 'adverts'
|
93
93
|
|
94
|
-
|
95
|
-
|
94
|
+
declare_schema do
|
95
|
+
string :title, :required, limit: 255
|
96
96
|
end
|
97
97
|
end
|
98
98
|
|
@@ -110,8 +110,8 @@ RSpec.describe 'DeclareSchema API' do
|
|
110
110
|
class AdvertWithUniqueTitle < ActiveRecord::Base
|
111
111
|
self.table_name = 'adverts'
|
112
112
|
|
113
|
-
|
114
|
-
|
113
|
+
declare_schema do
|
114
|
+
string :title, :unique, limit: 255
|
115
115
|
end
|
116
116
|
end
|
117
117
|
|
@@ -3,29 +3,55 @@
|
|
3
3
|
require_relative '../../../lib/declare_schema/field_declaration_dsl'
|
4
4
|
|
5
5
|
RSpec.describe DeclareSchema::FieldDeclarationDsl do
|
6
|
-
|
7
|
-
|
6
|
+
let(:model) { TestModel.new }
|
7
|
+
subject { declared_class.new(model) }
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
context 'Using fields' do
|
10
|
+
before do
|
11
|
+
load File.expand_path('prepare_testapp.rb', __dir__)
|
12
12
|
|
13
|
-
|
13
|
+
class TestModel < ActiveRecord::Base
|
14
|
+
fields do
|
15
|
+
name :string, limit: 127
|
16
|
+
|
17
|
+
timestamps
|
18
|
+
end
|
14
19
|
end
|
15
20
|
end
|
16
|
-
end
|
17
21
|
|
18
|
-
|
19
|
-
|
22
|
+
it 'has fields' do
|
23
|
+
expect(TestModel.field_specs).to be_kind_of(Hash)
|
24
|
+
expect(TestModel.field_specs.keys).to eq(['name', 'created_at', 'updated_at'])
|
25
|
+
expect(TestModel.field_specs.values.map(&:type)).to eq([:string, :datetime, :datetime])
|
26
|
+
end
|
20
27
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
expect(TestModel.field_specs.values.map(&:type)).to eq([:string, :datetime, :datetime])
|
28
|
+
it 'stores limits' do
|
29
|
+
expect(TestModel.field_specs['name'].limit).to eq(127), TestModel.field_specs['name'].inspect
|
30
|
+
end
|
25
31
|
end
|
26
32
|
|
27
|
-
|
28
|
-
|
33
|
+
context 'Using declare_schema' do
|
34
|
+
before do
|
35
|
+
load File.expand_path('prepare_testapp.rb', __dir__)
|
36
|
+
|
37
|
+
class TestModel < ActiveRecord::Base
|
38
|
+
declare_schema do
|
39
|
+
string :name, limit: 127
|
40
|
+
|
41
|
+
timestamps
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'has fields' do
|
47
|
+
expect(TestModel.field_specs).to be_kind_of(Hash)
|
48
|
+
expect(TestModel.field_specs.keys).to eq(['name', 'created_at', 'updated_at'])
|
49
|
+
expect(TestModel.field_specs.values.map(&:type)).to eq([:string, :datetime, :datetime])
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'stores limits' do
|
53
|
+
expect(TestModel.field_specs['name'].limit).to eq(127), TestModel.field_specs['name'].inspect
|
54
|
+
end
|
29
55
|
end
|
30
56
|
|
31
57
|
# TODO: fill out remaining tests
|
@@ -12,7 +12,7 @@ RSpec.describe DeclareSchema::Model::FieldSpec do
|
|
12
12
|
before do
|
13
13
|
load File.expand_path('prepare_testapp.rb', __dir__)
|
14
14
|
|
15
|
-
if
|
15
|
+
if ActiveSupport::VERSION::MAJOR < 5
|
16
16
|
allow(col_spec).to receive(:type_cast_from_database, &:itself)
|
17
17
|
end
|
18
18
|
end
|
@@ -184,7 +184,7 @@ RSpec.describe DeclareSchema::Model::FieldSpec do
|
|
184
184
|
|
185
185
|
describe '#schema_attributes' do
|
186
186
|
let(:col_spec) do
|
187
|
-
case
|
187
|
+
case ActiveSupport::VERSION::MAJOR
|
188
188
|
when 4
|
189
189
|
cast_type = ActiveRecord::Type::Integer.new(limit: 8)
|
190
190
|
ActiveRecord::ConnectionAdapters::Column.new("price", nil, cast_type, "integer(8)", false)
|
@@ -11,9 +11,9 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
11
11
|
expect_model_definition_to_eq('alpha/beta', <<~EOS)
|
12
12
|
class Alpha::Beta < #{active_record_base_class}
|
13
13
|
|
14
|
-
|
15
|
-
one
|
16
|
-
|
14
|
+
declare_schema do
|
15
|
+
string :one, limit: 255
|
16
|
+
integer :two
|
17
17
|
end
|
18
18
|
|
19
19
|
end
|
@@ -27,7 +27,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
27
27
|
end
|
28
28
|
EOS
|
29
29
|
|
30
|
-
case
|
30
|
+
case ActiveSupport::VERSION::MAJOR
|
31
31
|
when 4, 5
|
32
32
|
expect_test_definition_to_eq('alpha/beta', <<~EOS)
|
33
33
|
require "test_helper"
|
@@ -50,7 +50,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
50
50
|
EOS
|
51
51
|
end
|
52
52
|
|
53
|
-
case
|
53
|
+
case ActiveSupport::VERSION::MAJOR
|
54
54
|
when 4
|
55
55
|
expect_test_fixture_to_eq('alpha/beta', <<~EOS)
|
56
56
|
# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'rails'
|
4
3
|
begin
|
5
4
|
require 'mysql2'
|
6
5
|
rescue LoadError
|
@@ -11,49 +10,139 @@ RSpec.describe 'DeclareSchema Migration Generator interactive primary key' do
|
|
11
10
|
load File.expand_path('prepare_testapp.rb', __dir__)
|
12
11
|
end
|
13
12
|
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
context 'Using fields' do
|
14
|
+
it "allows alternate primary keys" do
|
15
|
+
class Foo < ActiveRecord::Base
|
16
|
+
fields do
|
17
|
+
end
|
18
|
+
self.primary_key = "foo_id"
|
17
19
|
end
|
18
|
-
self.primary_key = "foo_id"
|
19
|
-
end
|
20
20
|
|
21
|
-
|
22
|
-
|
21
|
+
generate_migrations '-n', '-m'
|
22
|
+
expect(Foo._defined_primary_key).to eq('foo_id')
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
24
|
+
### migrate from
|
25
|
+
# rename from custom primary_key
|
26
|
+
class Foo < ActiveRecord::Base
|
27
|
+
fields do
|
28
|
+
end
|
29
|
+
self.primary_key = "id"
|
28
30
|
end
|
29
|
-
self.primary_key = "id"
|
30
|
-
end
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
|
32
|
+
allow_any_instance_of(DeclareSchema::Support::ThorShell).to receive(:ask).with(/one of the rename choices or press enter to keep/) { 'id' }
|
33
|
+
generate_migrations '-n', '-m'
|
34
|
+
expect(Foo._defined_primary_key).to eq('id')
|
35
|
+
|
36
|
+
nuke_model_class(Foo)
|
37
|
+
|
38
|
+
# The ActiveRecord sqlite3 driver has a bug where rename_column recreates the entire table, but forgets to set the primary key:
|
39
|
+
#
|
40
|
+
# [7] pry(#<RSpec::ExampleGroups::DeclareSchemaMigrationGeneratorInteractivePrimaryKey>)> u = 'rename_column :foos, :foo_id, :id'
|
41
|
+
# => "rename_column :foos, :foo_id, :id"
|
42
|
+
# [8] pry(#<RSpec::ExampleGroups::DeclareSchemaMigrationGeneratorInteractivePrimaryKey>)> ActiveRecord::Migration.class_eval(u)
|
43
|
+
# (0.0ms) begin transaction
|
44
|
+
# (pry):17
|
45
|
+
# (0.2ms) CREATE TEMPORARY TABLE "afoos" ("id" integer NOT NULL)
|
46
|
+
# (pry):17
|
47
|
+
# (0.1ms) INSERT INTO "afoos" ("id")
|
48
|
+
#
|
49
|
+
# (pry):17
|
50
|
+
# (0.4ms) DROP TABLE "foos"
|
51
|
+
# (pry):17
|
52
|
+
# (0.1ms) CREATE TABLE "foos" ("id" integer NOT NULL)
|
53
|
+
# (pry):17
|
54
|
+
# (0.1ms) INSERT INTO "foos" ("id")
|
55
|
+
#
|
56
|
+
# (pry):17
|
57
|
+
# (0.1ms) DROP TABLE "afoos"
|
58
|
+
# (pry):17
|
59
|
+
# (0.9ms) commit transaction
|
60
|
+
if defined?(SQLite3)
|
61
|
+
ActiveRecord::Base.connection.execute("drop table foos")
|
62
|
+
ActiveRecord::Base.connection.execute("CREATE TABLE foos (id integer PRIMARY KEY AUTOINCREMENT NOT NULL)")
|
63
|
+
end
|
35
64
|
|
36
|
-
|
65
|
+
if ActiveSupport::VERSION::MAJOR >= 5 && !defined?(Mysql2) # TODO TECH-4814 Put this test back for Mysql2
|
66
|
+
# replace custom primary_key
|
67
|
+
class Foo < ActiveRecord::Base
|
68
|
+
fields do
|
69
|
+
end
|
70
|
+
self.primary_key = "foo_id"
|
71
|
+
end
|
37
72
|
|
38
|
-
|
73
|
+
allow_any_instance_of(DeclareSchema::Support::ThorShell).to receive(:ask).with(/one of the rename choices or press enter to keep/) { 'drop id' }
|
74
|
+
generate_migrations '-n', '-m'
|
75
|
+
expect(Foo._defined_primary_key).to eq('foo_id')
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
39
79
|
|
40
|
-
|
41
|
-
|
80
|
+
context 'Using declare_schema' do
|
81
|
+
it "allows alternate primary keys" do
|
42
82
|
class Foo < ActiveRecord::Base
|
43
|
-
|
83
|
+
declare_schema do
|
44
84
|
end
|
45
85
|
self.primary_key = "foo_id"
|
46
86
|
end
|
47
87
|
|
48
|
-
puts "\n\e[45m Please enter 'drop id' (no quotes) at the next prompt \e[0m"
|
49
88
|
generate_migrations '-n', '-m'
|
50
|
-
expect(Foo.
|
89
|
+
expect(Foo.primary_key).to eq('foo_id')
|
90
|
+
|
91
|
+
### migrate from
|
92
|
+
# rename from custom primary_key
|
93
|
+
class Foo < ActiveRecord::Base
|
94
|
+
declare_schema do
|
95
|
+
end
|
96
|
+
self.primary_key = "id"
|
97
|
+
end
|
98
|
+
|
99
|
+
puts "\n\e[45m Please enter 'id' (no quotes) at the next prompt \e[0m"
|
100
|
+
allow_any_instance_of(DeclareSchema::Support::ThorShell).to receive(:ask).with(/one of the rename choices or press enter to keep/) { 'id' }
|
101
|
+
generate_migrations '-n', '-m'
|
102
|
+
expect(Foo.primary_key).to eq('id')
|
103
|
+
|
104
|
+
nuke_model_class(Foo)
|
51
105
|
|
52
|
-
|
106
|
+
# The ActiveRecord sqlite3 driver has a bug where rename_column recreates the entire table, but forgets to set the primary key:
|
107
|
+
#
|
108
|
+
# [7] pry(#<RSpec::ExampleGroups::DeclareSchemaMigrationGeneratorInteractivePrimaryKey>)> u = 'rename_column :foos, :foo_id, :id'
|
109
|
+
# => "rename_column :foos, :foo_id, :id"
|
110
|
+
# [8] pry(#<RSpec::ExampleGroups::DeclareSchemaMigrationGeneratorInteractivePrimaryKey>)> ActiveRecord::Migration.class_eval(u)
|
111
|
+
# (0.0ms) begin transaction
|
112
|
+
# (pry):17
|
113
|
+
# (0.2ms) CREATE TEMPORARY TABLE "afoos" ("id" integer NOT NULL)
|
114
|
+
# (pry):17
|
115
|
+
# (0.1ms) INSERT INTO "afoos" ("id")
|
116
|
+
#
|
117
|
+
# (pry):17
|
118
|
+
# (0.4ms) DROP TABLE "foos"
|
119
|
+
# (pry):17
|
120
|
+
# (0.1ms) CREATE TABLE "foos" ("id" integer NOT NULL)
|
121
|
+
# (pry):17
|
122
|
+
# (0.1ms) INSERT INTO "foos" ("id")
|
123
|
+
#
|
124
|
+
# (pry):17
|
125
|
+
# (0.1ms) DROP TABLE "afoos"
|
126
|
+
# (pry):17
|
127
|
+
# (0.9ms) commit transaction
|
128
|
+
if defined?(SQLite3)
|
129
|
+
ActiveRecord::Base.connection.execute("drop table foos")
|
130
|
+
ActiveRecord::Base.connection.execute("CREATE TABLE foos (id integer PRIMARY KEY AUTOINCREMENT NOT NULL)")
|
131
|
+
end
|
132
|
+
|
133
|
+
if ActiveSupport::VERSION::MAJOR >= 5 && !defined?(Mysql2) # TODO TECH-4814 Put this test back for Mysql2
|
134
|
+
# replace custom primary_key
|
135
|
+
class Foo < ActiveRecord::Base
|
136
|
+
declare_schema do
|
137
|
+
end
|
138
|
+
self.primary_key = "foo_id"
|
139
|
+
end
|
53
140
|
|
54
|
-
|
55
|
-
|
56
|
-
|
141
|
+
puts "\n\e[45m Please enter 'drop id' (no quotes) at the next prompt \e[0m"
|
142
|
+
allow_any_instance_of(DeclareSchema::Support::ThorShell).to receive(:ask).with(/one of the rename choices or press enter to keep/) { 'drop id' }
|
143
|
+
generate_migrations '-n', '-m'
|
144
|
+
expect(Foo.primary_key).to eq('foo_id')
|
145
|
+
end
|
57
146
|
end
|
58
147
|
end
|
59
148
|
end
|
@@ -1,25 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'rails'
|
4
3
|
begin
|
5
4
|
require 'mysql2'
|
6
5
|
rescue LoadError
|
7
6
|
end
|
8
7
|
|
8
|
+
begin
|
9
|
+
require 'sqlite3'
|
10
|
+
rescue LoadError
|
11
|
+
end
|
12
|
+
|
9
13
|
RSpec.describe 'DeclareSchema Migration Generator' do
|
10
14
|
before do
|
11
15
|
load File.expand_path('prepare_testapp.rb', __dir__)
|
12
16
|
end
|
13
|
-
|
14
|
-
let(:charset_alter_table) do
|
15
|
-
if defined?(Mysql2)
|
16
|
-
<<~EOS
|
17
|
-
|
18
|
-
|
19
|
-
execute "ALTER TABLE `adverts` CHARACTER SET utf8mb4 COLLATE utf8mb4_bin"
|
20
|
-
EOS
|
21
|
-
end
|
22
|
-
end
|
23
17
|
let(:text_limit) do
|
24
18
|
if defined?(Mysql2)
|
25
19
|
", limit: 4294967295"
|
@@ -30,21 +24,26 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
30
24
|
', charset: "utf8mb4", collation: "utf8mb4_bin"'
|
31
25
|
end
|
32
26
|
end
|
27
|
+
let(:create_table_charset_and_collation) do
|
28
|
+
if defined?(Mysql2)
|
29
|
+
", options: \"CHARACTER SET utf8mb4 COLLATE utf8mb4_bin\""
|
30
|
+
end
|
31
|
+
end
|
33
32
|
let(:datetime_precision) do
|
34
|
-
if defined?(Mysql2) &&
|
33
|
+
if defined?(Mysql2) && ActiveSupport::VERSION::MAJOR >= 5
|
35
34
|
', precision: 0'
|
36
35
|
end
|
37
36
|
end
|
38
37
|
let(:table_options) do
|
39
38
|
if defined?(Mysql2)
|
40
|
-
", options: \"#{'ENGINE=InnoDB ' if
|
41
|
-
if
|
39
|
+
", options: \"#{'ENGINE=InnoDB ' if ActiveSupport::VERSION::MAJOR == 5}DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin\"" +
|
40
|
+
if ActiveSupport::VERSION::MAJOR >= 6
|
42
41
|
', charset: "utf8mb4", collation: "utf8mb4_bin"'
|
43
42
|
else
|
44
43
|
''
|
45
44
|
end
|
46
45
|
else
|
47
|
-
", id: :integer" unless
|
46
|
+
", id: :integer" unless ActiveSupport::VERSION::MAJOR < 5
|
48
47
|
end
|
49
48
|
end
|
50
49
|
let(:lock_version_limit) do
|
@@ -55,1153 +54,2279 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
55
54
|
end
|
56
55
|
end
|
57
56
|
|
58
|
-
|
59
|
-
|
60
|
-
|
57
|
+
context 'Using fields' do
|
58
|
+
# DeclareSchema - Migration Generator
|
59
|
+
it 'generates migrations' do
|
60
|
+
## The migration generator -- introduction
|
61
61
|
|
62
|
-
|
62
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to migrate_up("").and migrate_down("")
|
63
63
|
|
64
|
-
|
65
|
-
|
64
|
+
class Advert < ActiveRecord::Base
|
65
|
+
end
|
66
66
|
|
67
|
-
|
67
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to migrate_up("").and migrate_down("")
|
68
68
|
|
69
|
-
|
69
|
+
Generators::DeclareSchema::Migration::Migrator.ignore_tables = ["green_fishes"]
|
70
70
|
|
71
|
-
|
72
|
-
|
71
|
+
Advert.connection.schema_cache.clear!
|
72
|
+
Advert.reset_column_information
|
73
73
|
|
74
|
-
|
75
|
-
|
76
|
-
|
74
|
+
class Advert < ActiveRecord::Base
|
75
|
+
fields do
|
76
|
+
name :string, limit: 250, null: true
|
77
|
+
end
|
77
78
|
end
|
78
|
-
end
|
79
79
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
80
|
+
up, _ = Generators::DeclareSchema::Migration::Migrator.run.tap do |migrations|
|
81
|
+
expect(migrations).to(
|
82
|
+
migrate_up(<<~EOS.strip)
|
83
|
+
create_table :adverts, id: :bigint#{create_table_charset_and_collation} do |t|
|
84
|
+
t.string :name, limit: 250, null: true#{charset_and_collation}
|
85
|
+
end
|
86
|
+
EOS
|
87
|
+
.and migrate_down("drop_table :adverts")
|
88
|
+
)
|
89
|
+
end
|
90
90
|
|
91
|
-
|
92
|
-
|
91
|
+
ActiveRecord::Migration.class_eval(up)
|
92
|
+
expect(Advert.columns.map(&:name)).to eq(["id", "name"])
|
93
93
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
94
|
+
if ActiveSupport::VERSION::MAJOR < 5
|
95
|
+
# Rails 4 drivers don't always create PK properly. Fix that by dropping and recreating.
|
96
|
+
ActiveRecord::Base.connection.execute("drop table adverts")
|
97
|
+
if defined?(Mysql2)
|
98
|
+
ActiveRecord::Base.connection.execute("CREATE TABLE adverts (id integer PRIMARY KEY AUTO_INCREMENT NOT NULL, name varchar(250)) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin")
|
99
|
+
else
|
100
|
+
ActiveRecord::Base.connection.execute("CREATE TABLE adverts (id integer PRIMARY KEY AUTOINCREMENT NOT NULL, name varchar(250))")
|
101
|
+
end
|
101
102
|
end
|
102
|
-
end
|
103
103
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
104
|
+
class Advert < ActiveRecord::Base
|
105
|
+
fields do
|
106
|
+
name :string, limit: 250, null: true
|
107
|
+
body :text, null: true
|
108
|
+
published_at :datetime, null: true
|
109
|
+
end
|
109
110
|
end
|
110
|
-
end
|
111
111
|
|
112
|
-
|
113
|
-
|
112
|
+
Advert.connection.schema_cache.clear!
|
113
|
+
Advert.reset_column_information
|
114
114
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
115
|
+
expect(migrate).to(
|
116
|
+
migrate_up(<<~EOS.strip)
|
117
|
+
add_column :adverts, :body, :text#{text_limit}, null: true#{charset_and_collation}
|
118
|
+
add_column :adverts, :published_at, :datetime, null: true
|
119
|
+
EOS
|
120
|
+
.and migrate_down(<<~EOS.strip)
|
121
|
+
remove_column :adverts, :published_at
|
122
|
+
remove_column :adverts, :body
|
123
|
+
EOS
|
124
|
+
)
|
125
125
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
126
|
+
Advert.field_specs.clear # not normally needed
|
127
|
+
class Advert < ActiveRecord::Base
|
128
|
+
fields do
|
129
|
+
name :string, limit: 250, null: true
|
130
|
+
body :text, null: true
|
131
|
+
end
|
131
132
|
end
|
132
|
-
end
|
133
133
|
|
134
|
-
|
135
|
-
|
136
|
-
|
134
|
+
expect(migrate).to(
|
135
|
+
migrate_up("remove_column :adverts, :published_at").and(
|
136
|
+
migrate_down("add_column :adverts, :published_at, :datetime#{datetime_precision}, null: true")
|
137
|
+
)
|
137
138
|
)
|
138
|
-
)
|
139
139
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
140
|
+
nuke_model_class(Advert)
|
141
|
+
class Advert < ActiveRecord::Base
|
142
|
+
fields do
|
143
|
+
title :string, limit: 250, null: true
|
144
|
+
body :text, null: true
|
145
|
+
end
|
145
146
|
end
|
146
|
-
end
|
147
147
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
148
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
149
|
+
migrate_up(<<~EOS.strip)
|
150
|
+
add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
151
|
+
remove_column :adverts, :name
|
152
|
+
EOS
|
153
|
+
.and migrate_down(<<~EOS.strip)
|
154
|
+
add_column :adverts, :name, :string, limit: 250, null: true#{charset_and_collation}
|
155
|
+
remove_column :adverts, :title
|
156
|
+
EOS
|
157
|
+
)
|
158
158
|
|
159
|
-
|
160
|
-
|
161
|
-
|
159
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run(adverts: { name: :title })).to(
|
160
|
+
migrate_up("rename_column :adverts, :name, :title").and(
|
161
|
+
migrate_down("rename_column :adverts, :title, :name")
|
162
|
+
)
|
162
163
|
)
|
163
|
-
)
|
164
164
|
|
165
|
-
|
165
|
+
migrate
|
166
166
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
167
|
+
class Advert < ActiveRecord::Base
|
168
|
+
fields do
|
169
|
+
title :text, null: true
|
170
|
+
body :text, null: true
|
171
|
+
end
|
171
172
|
end
|
172
|
-
end
|
173
173
|
|
174
|
-
|
175
|
-
|
176
|
-
|
174
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
175
|
+
migrate_up("change_column :adverts, :title, :text#{text_limit}, null: true#{charset_and_collation}").and(
|
176
|
+
migrate_down("change_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}")
|
177
|
+
)
|
177
178
|
)
|
178
|
-
)
|
179
179
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
180
|
+
class Advert < ActiveRecord::Base
|
181
|
+
fields do
|
182
|
+
title :string, default: "Untitled", limit: 250, null: true
|
183
|
+
body :text, null: true
|
184
|
+
end
|
184
185
|
end
|
185
|
-
end
|
186
186
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
187
|
+
expect(migrate).to(
|
188
|
+
migrate_up(<<~EOS.strip)
|
189
|
+
change_column :adverts, :title, :string, limit: 250, null: true, default: "Untitled"#{charset_and_collation}
|
190
|
+
EOS
|
191
|
+
.and migrate_down(<<~EOS.strip)
|
192
|
+
change_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
193
|
+
EOS
|
194
|
+
)
|
195
195
|
|
196
|
-
|
196
|
+
### Limits
|
197
197
|
|
198
|
-
|
199
|
-
|
200
|
-
|
198
|
+
class Advert < ActiveRecord::Base
|
199
|
+
fields do
|
200
|
+
price :integer, null: true, limit: 2
|
201
|
+
end
|
201
202
|
end
|
202
|
-
end
|
203
|
-
|
204
|
-
up, _ = Generators::DeclareSchema::Migration::Migrator.run.tap do |migrations|
|
205
|
-
expect(migrations).to migrate_up("add_column :adverts, :price, :integer, limit: 2, null: true")
|
206
|
-
end
|
207
203
|
|
208
|
-
|
209
|
-
|
210
|
-
ActiveRecord::Migration.class_eval(up)
|
211
|
-
class Advert < ActiveRecord::Base
|
212
|
-
fields do
|
213
|
-
price :integer, null: true, limit: 3
|
204
|
+
up, _ = Generators::DeclareSchema::Migration::Migrator.run.tap do |migrations|
|
205
|
+
expect(migrations).to migrate_up("add_column :adverts, :price, :integer, limit: 2, null: true")
|
214
206
|
end
|
215
|
-
end
|
216
207
|
|
217
|
-
|
218
|
-
migrate_up(<<~EOS.strip)
|
219
|
-
change_column :adverts, :price, :integer, limit: 3, null: true
|
220
|
-
EOS
|
221
|
-
.and migrate_down(<<~EOS.strip)
|
222
|
-
change_column :adverts, :price, :integer, limit: 2, null: true
|
223
|
-
EOS
|
224
|
-
)
|
208
|
+
# Now run the migration, then change the limit:
|
225
209
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
210
|
+
ActiveRecord::Migration.class_eval(up)
|
211
|
+
class Advert < ActiveRecord::Base
|
212
|
+
fields do
|
213
|
+
price :integer, null: true, limit: 3
|
214
|
+
end
|
230
215
|
end
|
231
|
-
end
|
232
216
|
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
217
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
218
|
+
migrate_up(<<~EOS.strip)
|
219
|
+
change_column :adverts, :price, :integer, limit: 3, null: true
|
220
|
+
EOS
|
221
|
+
.and migrate_down(<<~EOS.strip)
|
222
|
+
change_column :adverts, :price, :integer, limit: 2, null: true
|
223
|
+
EOS
|
224
|
+
)
|
241
225
|
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
226
|
+
ActiveRecord::Migration.class_eval("remove_column :adverts, :price")
|
227
|
+
class Advert < ActiveRecord::Base
|
228
|
+
fields do
|
229
|
+
price :decimal, precision: 4, scale: 1, null: true
|
230
|
+
end
|
246
231
|
end
|
247
|
-
end
|
248
232
|
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
add_column :adverts, :description, :text#{', limit: 65535' if defined?(Mysql2)}, null: false#{charset_and_collation}
|
254
|
-
EOS
|
255
|
-
)
|
256
|
-
|
257
|
-
Advert.field_specs.delete :price
|
258
|
-
Advert.field_specs.delete :notes
|
259
|
-
Advert.field_specs.delete :description
|
233
|
+
# Limits are generally not needed for `text` fields, because by default, `text` fields will use the maximum size
|
234
|
+
# allowed for that database type (0xffffffff for LONGTEXT in MySQL unlimited in Postgres, 1 billion in Sqlite).
|
235
|
+
# If a `limit` is given, it will only be used in MySQL, to choose the smallest TEXT field that will accommodate
|
236
|
+
# that limit (0xff for TINYTEXT, 0xffff for TEXT, 0xffffff for MEDIUMTEXT, 0xffffffff for LONGTEXT).
|
260
237
|
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
expect(::DeclareSchema::Model::FieldSpec.mysql_text_limits?).to be_truthy
|
238
|
+
if defined?(SQLite3)
|
239
|
+
expect(::DeclareSchema::Model::FieldSpec.mysql_text_limits?).to be_falsey
|
240
|
+
end
|
265
241
|
|
266
242
|
class Advert < ActiveRecord::Base
|
267
243
|
fields do
|
268
244
|
notes :text
|
269
|
-
description :text, limit:
|
245
|
+
description :text, limit: 30000
|
270
246
|
end
|
271
247
|
end
|
272
248
|
|
273
249
|
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
274
250
|
migrate_up(<<~EOS.strip)
|
275
|
-
add_column :adverts, :
|
276
|
-
add_column :adverts, :
|
251
|
+
add_column :adverts, :price, :decimal, precision: 4, scale: 1, null: true
|
252
|
+
add_column :adverts, :notes, :text#{text_limit}, null: false#{charset_and_collation}
|
253
|
+
add_column :adverts, :description, :text#{', limit: 65535' if defined?(Mysql2)}, null: false#{charset_and_collation}
|
277
254
|
EOS
|
278
255
|
)
|
279
256
|
|
257
|
+
Advert.field_specs.delete :price
|
280
258
|
Advert.field_specs.delete :notes
|
259
|
+
Advert.field_specs.delete :description
|
260
|
+
|
261
|
+
# In MySQL, limits are applied, rounded up:
|
281
262
|
|
282
|
-
|
263
|
+
if defined?(Mysql2)
|
264
|
+
expect(::DeclareSchema::Model::FieldSpec.mysql_text_limits?).to be_truthy
|
283
265
|
|
284
|
-
expect do
|
285
266
|
class Advert < ActiveRecord::Base
|
286
267
|
fields do
|
287
268
|
notes :text
|
288
|
-
description :text, limit:
|
269
|
+
description :text, limit: 250
|
289
270
|
end
|
290
271
|
end
|
291
|
-
end.to raise_exception(ArgumentError, "limit of 4294967296 is too large for MySQL")
|
292
272
|
|
293
|
-
|
273
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
274
|
+
migrate_up(<<~EOS.strip)
|
275
|
+
add_column :adverts, :notes, :text, limit: 4294967295, null: false#{charset_and_collation}
|
276
|
+
add_column :adverts, :description, :text, limit: 255, null: false#{charset_and_collation}
|
277
|
+
EOS
|
278
|
+
)
|
279
|
+
|
280
|
+
Advert.field_specs.delete :notes
|
281
|
+
|
282
|
+
# Limits that are too high for MySQL will raise an exception.
|
283
|
+
|
284
|
+
expect do
|
285
|
+
class Advert < ActiveRecord::Base
|
286
|
+
fields do
|
287
|
+
notes :text
|
288
|
+
description :text, limit: 0x1_0000_0000
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end.to raise_exception(ArgumentError, "limit of 4294967296 is too large for MySQL")
|
292
|
+
|
293
|
+
Advert.field_specs.delete :notes
|
294
|
+
|
295
|
+
# And in MySQL, unstated text limits are treated as the maximum (LONGTEXT) limit.
|
296
|
+
|
297
|
+
# To start, we'll set the database schema for `description` to match the above limit of 250.
|
298
|
+
|
299
|
+
Advert.connection.execute "ALTER TABLE adverts ADD COLUMN description TINYTEXT"
|
300
|
+
Advert.connection.schema_cache.clear!
|
301
|
+
Advert.reset_column_information
|
302
|
+
expect(Advert.connection.tables - Generators::DeclareSchema::Migration::Migrator.always_ignore_tables).
|
303
|
+
to eq(["adverts"])
|
304
|
+
expect(Advert.columns.map(&:name)).to eq(["id", "body", "title", "description"])
|
305
|
+
|
306
|
+
# Now migrate to an unstated text limit:
|
307
|
+
|
308
|
+
class Advert < ActiveRecord::Base
|
309
|
+
fields do
|
310
|
+
description :text
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
315
|
+
migrate_up(<<~EOS.strip)
|
316
|
+
change_column :adverts, :description, :text, limit: 4294967295, null: false#{charset_and_collation}
|
317
|
+
EOS
|
318
|
+
.and migrate_down(<<~EOS.strip)
|
319
|
+
change_column :adverts, :description, :text#{', limit: 255' if defined?(Mysql2)}, null: true#{charset_and_collation}
|
320
|
+
EOS
|
321
|
+
)
|
322
|
+
|
323
|
+
# And migrate to a stated text limit that is the same as the unstated one:
|
294
324
|
|
295
|
-
|
325
|
+
class Advert < ActiveRecord::Base
|
326
|
+
fields do
|
327
|
+
description :text, limit: 0xffffffff
|
328
|
+
end
|
329
|
+
end
|
296
330
|
|
297
|
-
|
331
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
332
|
+
migrate_up(<<~EOS.strip)
|
333
|
+
change_column :adverts, :description, :text, limit: 4294967295, null: false#{charset_and_collation}
|
334
|
+
EOS
|
335
|
+
.and migrate_down(<<~EOS.strip)
|
336
|
+
change_column :adverts, :description, :text#{', limit: 255' if defined?(Mysql2)}, null: true#{charset_and_collation}
|
337
|
+
EOS
|
338
|
+
)
|
339
|
+
end
|
298
340
|
|
299
|
-
Advert.
|
341
|
+
Advert.field_specs.clear
|
300
342
|
Advert.connection.schema_cache.clear!
|
301
343
|
Advert.reset_column_information
|
302
|
-
|
303
|
-
|
304
|
-
|
344
|
+
class Advert < ActiveRecord::Base
|
345
|
+
fields do
|
346
|
+
name :string, limit: 250, null: true
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
351
|
+
ActiveRecord::Migration.class_eval up
|
352
|
+
|
353
|
+
Advert.connection.schema_cache.clear!
|
354
|
+
Advert.reset_column_information
|
355
|
+
|
356
|
+
### Foreign Keys
|
305
357
|
|
306
|
-
#
|
358
|
+
# DeclareSchema extends the `belongs_to` macro so that it also declares the
|
359
|
+
# foreign-key field. It also generates an index on the field.
|
307
360
|
|
361
|
+
class Category < ActiveRecord::Base; end
|
308
362
|
class Advert < ActiveRecord::Base
|
309
363
|
fields do
|
310
|
-
|
364
|
+
name :string, limit: 250, null: true
|
311
365
|
end
|
366
|
+
belongs_to :category
|
312
367
|
end
|
313
368
|
|
314
369
|
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
315
370
|
migrate_up(<<~EOS.strip)
|
316
|
-
|
371
|
+
add_column :adverts, :category_id, :integer, limit: 8, null: false
|
372
|
+
add_index :adverts, [:category_id], name: :on_category_id
|
373
|
+
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id" if defined?(Mysql2)}
|
317
374
|
EOS
|
318
375
|
.and migrate_down(<<~EOS.strip)
|
319
|
-
|
376
|
+
#{"remove_foreign_key :adverts, name: :index_adverts_on_category_id" if defined?(Mysql2)}
|
377
|
+
remove_index :adverts, name: :on_category_id
|
378
|
+
remove_column :adverts, :category_id
|
320
379
|
EOS
|
321
380
|
)
|
322
381
|
|
323
|
-
|
382
|
+
Advert.field_specs.delete(:category_id)
|
383
|
+
Advert.index_definitions.delete_if { |spec| spec.fields==["category_id"] }
|
324
384
|
|
385
|
+
# If you specify a custom foreign key, the migration generator observes that:
|
386
|
+
|
387
|
+
class Category < ActiveRecord::Base; end
|
325
388
|
class Advert < ActiveRecord::Base
|
326
|
-
fields
|
327
|
-
|
328
|
-
end
|
389
|
+
fields { }
|
390
|
+
belongs_to :category, foreign_key: "c_id", class_name: 'Category'
|
329
391
|
end
|
330
392
|
|
331
393
|
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
332
394
|
migrate_up(<<~EOS.strip)
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
395
|
+
add_column :adverts, :c_id, :integer, limit: 8, null: false
|
396
|
+
add_index :adverts, [:c_id], name: :on_c_id
|
397
|
+
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
398
|
+
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
337
399
|
EOS
|
338
400
|
)
|
339
|
-
end
|
340
|
-
|
341
|
-
Advert.field_specs.clear
|
342
|
-
Advert.connection.schema_cache.clear!
|
343
|
-
Advert.reset_column_information
|
344
|
-
class Advert < ActiveRecord::Base
|
345
|
-
fields do
|
346
|
-
name :string, limit: 250, null: true
|
347
|
-
end
|
348
|
-
end
|
349
|
-
|
350
|
-
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
351
|
-
ActiveRecord::Migration.class_eval up
|
352
401
|
|
353
|
-
|
354
|
-
|
402
|
+
Advert.field_specs.delete(:c_id)
|
403
|
+
Advert.index_definitions.delete_if { |spec| spec.fields == ["c_id"] }
|
355
404
|
|
356
|
-
|
405
|
+
# You can avoid generating the index by specifying `index: false`
|
357
406
|
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
class Advert < ActiveRecord::Base
|
363
|
-
fields do
|
364
|
-
name :string, limit: 250, null: true
|
407
|
+
class Category < ActiveRecord::Base; end
|
408
|
+
class Advert < ActiveRecord::Base
|
409
|
+
fields { }
|
410
|
+
belongs_to :category, index: false
|
365
411
|
end
|
366
|
-
belongs_to :category
|
367
|
-
end
|
368
412
|
|
369
|
-
|
370
|
-
|
371
|
-
|
413
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
414
|
+
migrate_up(<<~EOS.strip)
|
415
|
+
add_column :adverts, :category_id, :integer, limit: 8, null: false
|
416
|
+
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
417
|
+
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
418
|
+
EOS
|
419
|
+
)
|
372
420
|
|
373
|
-
|
421
|
+
Advert.field_specs.delete(:category_id)
|
422
|
+
Advert.index_definitions.delete_if { |spec| spec.fields == ["category_id"] }
|
374
423
|
|
375
|
-
|
376
|
-
EOS
|
377
|
-
.and migrate_down(<<~EOS.strip)
|
378
|
-
remove_column :adverts, :category_id
|
424
|
+
# You can specify the index name with :index
|
379
425
|
|
380
|
-
|
426
|
+
class Category < ActiveRecord::Base; end
|
427
|
+
class Advert < ActiveRecord::Base
|
428
|
+
fields { }
|
429
|
+
belongs_to :category, index: 'my_index'
|
430
|
+
end
|
381
431
|
|
382
|
-
|
383
|
-
|
384
|
-
|
432
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
433
|
+
migrate_up(<<~EOS.strip)
|
434
|
+
add_column :adverts, :category_id, :integer, limit: 8, null: false
|
435
|
+
add_index :adverts, [:category_id], name: :my_index
|
436
|
+
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
437
|
+
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
438
|
+
EOS
|
439
|
+
)
|
385
440
|
|
386
|
-
|
387
|
-
|
441
|
+
Advert.field_specs.delete(:category_id)
|
442
|
+
Advert.index_definitions.delete_if { |spec| spec.fields == ["category_id"] }
|
388
443
|
|
389
|
-
|
444
|
+
### Timestamps and Optimimistic Locking
|
390
445
|
|
391
|
-
|
392
|
-
|
393
|
-
fields { }
|
394
|
-
belongs_to :category, foreign_key: "c_id", class_name: 'Category'
|
395
|
-
end
|
446
|
+
# `updated_at` and `created_at` can be declared with the shorthand `timestamps`.
|
447
|
+
# Similarly, `lock_version` can be declared with the "shorthand" `optimimistic_lock`.
|
396
448
|
|
397
|
-
|
398
|
-
|
399
|
-
|
449
|
+
class Advert < ActiveRecord::Base
|
450
|
+
fields do
|
451
|
+
timestamps
|
452
|
+
optimistic_lock
|
453
|
+
end
|
454
|
+
end
|
400
455
|
|
401
|
-
|
456
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
457
|
+
migrate_up(<<~EOS.strip)
|
458
|
+
add_column :adverts, :created_at, :datetime, null: true
|
459
|
+
add_column :adverts, :updated_at, :datetime, null: true
|
460
|
+
add_column :adverts, :lock_version, :integer#{lock_version_limit}, null: false, default: 1
|
461
|
+
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
462
|
+
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
463
|
+
EOS
|
464
|
+
.and migrate_down(<<~EOS.strip)
|
465
|
+
#{"remove_foreign_key :adverts, name: :index_adverts_on_c_id\n" +
|
466
|
+
"remove_foreign_key :adverts, name: :index_adverts_on_category_id" if defined?(Mysql2)}
|
467
|
+
remove_column :adverts, :lock_version
|
468
|
+
remove_column :adverts, :updated_at
|
469
|
+
remove_column :adverts, :created_at
|
470
|
+
EOS
|
471
|
+
)
|
402
472
|
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
)
|
473
|
+
Advert.field_specs.delete(:updated_at)
|
474
|
+
Advert.field_specs.delete(:created_at)
|
475
|
+
Advert.field_specs.delete(:lock_version)
|
407
476
|
|
408
|
-
|
409
|
-
Advert.index_definitions.delete_if { |spec| spec.fields == ["c_id"] }
|
477
|
+
### Indices
|
410
478
|
|
411
|
-
|
479
|
+
# You can add an index to a field definition
|
412
480
|
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
481
|
+
class Advert < ActiveRecord::Base
|
482
|
+
fields do
|
483
|
+
title :string, index: true, limit: 250, null: true
|
484
|
+
end
|
485
|
+
end
|
418
486
|
|
419
|
-
|
420
|
-
|
421
|
-
|
487
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
488
|
+
migrate_up(<<~EOS.strip)
|
489
|
+
add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
490
|
+
add_index :adverts, [:title], name: :on_title
|
491
|
+
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
492
|
+
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
493
|
+
EOS
|
494
|
+
)
|
422
495
|
|
423
|
-
|
424
|
-
"add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")" if defined?(Mysql2)}
|
425
|
-
EOS
|
426
|
-
)
|
496
|
+
Advert.index_definitions.delete_if { |spec| spec.fields==["title"] }
|
427
497
|
|
428
|
-
|
429
|
-
Advert.index_definitions.delete_if { |spec| spec.fields == ["category_id"] }
|
498
|
+
# You can ask for a unique index
|
430
499
|
|
431
|
-
|
500
|
+
class Advert < ActiveRecord::Base
|
501
|
+
fields do
|
502
|
+
title :string, index: true, unique: true, null: true, limit: 250
|
503
|
+
end
|
504
|
+
end
|
432
505
|
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
506
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
507
|
+
migrate_up(<<~EOS.strip)
|
508
|
+
add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
509
|
+
add_index :adverts, [:title], name: :on_title, unique: true
|
510
|
+
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
511
|
+
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
512
|
+
EOS
|
513
|
+
)
|
438
514
|
|
439
|
-
|
440
|
-
migrate_up(<<~EOS.strip)
|
441
|
-
add_column :adverts, :category_id, :integer, limit: 8, null: false
|
515
|
+
Advert.index_definitions.delete_if { |spec| spec.fields == ["title"] }
|
442
516
|
|
443
|
-
|
517
|
+
# You can specify the name for the index
|
444
518
|
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
519
|
+
class Advert < ActiveRecord::Base
|
520
|
+
fields do
|
521
|
+
title :string, index: 'my_index', limit: 250, null: true
|
522
|
+
end
|
523
|
+
end
|
449
524
|
|
450
|
-
|
451
|
-
|
525
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
526
|
+
migrate_up(<<~EOS.strip)
|
527
|
+
add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
528
|
+
add_index :adverts, [:title], name: :my_index
|
529
|
+
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
530
|
+
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
531
|
+
EOS
|
532
|
+
)
|
452
533
|
|
453
|
-
|
534
|
+
Advert.index_definitions.delete_if { |spec| spec.fields==["title"] }
|
454
535
|
|
455
|
-
|
456
|
-
# Similarly, `lock_version` can be declared with the "shorthand" `optimimistic_lock`.
|
536
|
+
# You can ask for an index outside of the fields block
|
457
537
|
|
458
|
-
|
459
|
-
|
460
|
-
timestamps
|
461
|
-
optimistic_lock
|
538
|
+
class Advert < ActiveRecord::Base
|
539
|
+
index :title
|
462
540
|
end
|
463
|
-
end
|
464
541
|
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
542
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
543
|
+
migrate_up(<<~EOS.strip)
|
544
|
+
add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
545
|
+
add_index :adverts, [:title], name: :on_title
|
546
|
+
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
547
|
+
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
548
|
+
EOS
|
549
|
+
)
|
470
550
|
|
471
|
-
|
472
|
-
"add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")" if defined?(Mysql2)}
|
473
|
-
EOS
|
474
|
-
.and migrate_down(<<~EOS.strip)
|
475
|
-
remove_column :adverts, :created_at
|
476
|
-
remove_column :adverts, :updated_at
|
477
|
-
remove_column :adverts, :lock_version
|
551
|
+
Advert.index_definitions.delete_if { |spec| spec.fields == ["title"] }
|
478
552
|
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
553
|
+
# The available options for the index function are `:unique` and `:name`
|
554
|
+
|
555
|
+
class Advert < ActiveRecord::Base
|
556
|
+
index :title, unique: true, name: 'my_index'
|
557
|
+
end
|
483
558
|
|
484
|
-
|
485
|
-
|
486
|
-
|
559
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
560
|
+
migrate_up(<<~EOS.strip)
|
561
|
+
add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
562
|
+
add_index :adverts, [:title], name: :my_index, unique: true
|
563
|
+
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
564
|
+
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
565
|
+
EOS
|
566
|
+
)
|
487
567
|
|
488
|
-
|
568
|
+
Advert.index_definitions.delete_if { |spec| spec.fields == ["title"] }
|
489
569
|
|
490
|
-
|
570
|
+
# You can create an index on more than one field
|
491
571
|
|
492
|
-
|
493
|
-
|
494
|
-
title :string, index: true, limit: 250, null: true
|
572
|
+
class Advert < ActiveRecord::Base
|
573
|
+
index [:title, :category_id]
|
495
574
|
end
|
496
|
-
end
|
497
575
|
|
498
|
-
|
499
|
-
|
500
|
-
|
576
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
577
|
+
migrate_up(<<~EOS.strip)
|
578
|
+
add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
579
|
+
add_index :adverts, [:title, :category_id], name: :on_title_and_category_id
|
580
|
+
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
581
|
+
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
582
|
+
EOS
|
583
|
+
)
|
501
584
|
|
502
|
-
|
585
|
+
Advert.index_definitions.delete_if { |spec| spec.fields==["title", "category_id"] }
|
503
586
|
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
)
|
587
|
+
# Finally, you can specify that the migration generator should completely ignore an
|
588
|
+
# index by passing its name to ignore_index in the model.
|
589
|
+
# This is helpful for preserving indices that can't be automatically generated, such as prefix indices in MySQL.
|
508
590
|
|
509
|
-
|
591
|
+
### Rename a table
|
510
592
|
|
511
|
-
|
593
|
+
# The migration generator respects the `set_table_name` declaration, although as before,
|
594
|
+
# we need to explicitly tell the generator that we want a rename rather than a create and a drop.
|
512
595
|
|
513
|
-
|
514
|
-
|
515
|
-
|
596
|
+
class Advert < ActiveRecord::Base
|
597
|
+
self.table_name = "ads"
|
598
|
+
fields do
|
599
|
+
title :string, limit: 250, null: true
|
600
|
+
body :text, null: true
|
601
|
+
end
|
516
602
|
end
|
517
|
-
end
|
518
|
-
|
519
|
-
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
520
|
-
migrate_up(<<~EOS.strip)
|
521
|
-
add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
522
603
|
|
523
|
-
|
604
|
+
Advert.connection.schema_cache.clear!
|
605
|
+
Advert.reset_column_information
|
524
606
|
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
607
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run("adverts" => "ads")).to(
|
608
|
+
migrate_up(<<~EOS.strip)
|
609
|
+
rename_table :adverts, :ads
|
610
|
+
add_column :ads, :title, :string, limit: 250, null: true#{charset_and_collation}
|
611
|
+
add_column :ads, :body, :text#{', limit: 4294967295' if defined?(Mysql2)}, null: true#{charset_and_collation}
|
612
|
+
#{if defined?(Mysql2)
|
613
|
+
"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
614
|
+
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id"
|
615
|
+
end}
|
616
|
+
EOS
|
617
|
+
.and migrate_down(<<~EOS.strip)
|
618
|
+
#{if defined?(Mysql2)
|
619
|
+
"remove_foreign_key :adverts, name: :index_adverts_on_c_id\n" +
|
620
|
+
"remove_foreign_key :adverts, name: :index_adverts_on_category_id"
|
621
|
+
end}
|
622
|
+
remove_column :ads, :body
|
623
|
+
remove_column :ads, :title
|
624
|
+
rename_table :ads, :adverts
|
625
|
+
EOS
|
626
|
+
)
|
529
627
|
|
530
|
-
|
628
|
+
# Set the table name back to what it should be and confirm we're in sync:
|
531
629
|
|
532
|
-
|
630
|
+
nuke_model_class(Advert)
|
533
631
|
|
534
|
-
|
535
|
-
|
536
|
-
title :string, index: 'my_index', limit: 250, null: true
|
632
|
+
class Advert < ActiveRecord::Base
|
633
|
+
self.table_name = "adverts"
|
537
634
|
end
|
538
|
-
end
|
539
635
|
|
540
|
-
|
541
|
-
migrate_up(<<~EOS.strip)
|
542
|
-
add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
636
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to eq(["", ""])
|
543
637
|
|
544
|
-
|
638
|
+
### Rename a table
|
545
639
|
|
546
|
-
|
547
|
-
"add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")" if defined?(Mysql2)}
|
548
|
-
EOS
|
549
|
-
)
|
640
|
+
# As with renaming columns, we have to tell the migration generator about the rename. Here we create a new class 'Advertisement', and tell ActiveRecord to forget about the Advert class. This requires code that shouldn't be shown to impressionable children.
|
550
641
|
|
551
|
-
|
642
|
+
nuke_model_class(Advert)
|
552
643
|
|
553
|
-
|
644
|
+
class Advertisement < ActiveRecord::Base
|
645
|
+
fields do
|
646
|
+
title :string, limit: 250, null: true
|
647
|
+
body :text, null: true
|
648
|
+
end
|
649
|
+
end
|
554
650
|
|
555
|
-
|
556
|
-
|
557
|
-
|
651
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run("adverts" => "advertisements")).to(
|
652
|
+
migrate_up(<<~EOS.strip)
|
653
|
+
rename_table :adverts, :advertisements
|
654
|
+
add_column :advertisements, :title, :string, limit: 250, null: true#{charset_and_collation}
|
655
|
+
add_column :advertisements, :body, :text#{', limit: 4294967295' if defined?(Mysql2)}, null: true#{charset_and_collation}
|
656
|
+
remove_column :advertisements, :name
|
657
|
+
EOS
|
658
|
+
.and migrate_down(<<~EOS.strip)
|
659
|
+
add_column :advertisements, :name, :string, limit: 250, null: true#{charset_and_collation}
|
660
|
+
remove_column :advertisements, :body
|
661
|
+
remove_column :advertisements, :title
|
662
|
+
rename_table :advertisements, :adverts
|
663
|
+
EOS
|
664
|
+
)
|
558
665
|
|
559
|
-
|
560
|
-
migrate_up(<<~EOS.strip)
|
561
|
-
add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
666
|
+
### Drop a table
|
562
667
|
|
563
|
-
|
668
|
+
nuke_model_class(Advertisement)
|
564
669
|
|
565
|
-
|
566
|
-
|
670
|
+
# If you delete a model, the migration generator will create a `drop_table` migration.
|
671
|
+
|
672
|
+
# Dropping tables is where the automatic down-migration really comes in handy:
|
673
|
+
|
674
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
675
|
+
migrate_up(<<~EOS.strip)
|
676
|
+
drop_table :adverts
|
677
|
+
EOS
|
678
|
+
.and migrate_down(<<~EOS.strip)
|
679
|
+
create_table "adverts"#{table_options}, force: :cascade do |t|
|
680
|
+
t.string "name", limit: 250#{charset_and_collation}
|
681
|
+
end
|
682
|
+
EOS
|
683
|
+
)
|
684
|
+
|
685
|
+
## STI
|
686
|
+
|
687
|
+
### Adding an STI subclass
|
688
|
+
|
689
|
+
# Adding a subclass or two should introduce the 'type' column and no other changes
|
690
|
+
|
691
|
+
class Advert < ActiveRecord::Base
|
692
|
+
fields do
|
693
|
+
body :text, null: true
|
694
|
+
title :string, default: "Untitled", limit: 250, null: true
|
695
|
+
end
|
696
|
+
end
|
697
|
+
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
698
|
+
ActiveRecord::Migration.class_eval(up)
|
699
|
+
|
700
|
+
class FancyAdvert < Advert
|
701
|
+
end
|
702
|
+
class SuperFancyAdvert < FancyAdvert
|
703
|
+
end
|
704
|
+
|
705
|
+
up, _ = Generators::DeclareSchema::Migration::Migrator.run do |migrations|
|
706
|
+
expect(migrations).to(
|
707
|
+
migrate_up(<<~EOS.strip)
|
708
|
+
add_column :adverts, :type, :string, limit: 250, null: true#{charset_and_collation}
|
709
|
+
add_index :adverts, [:type], name: :on_type
|
710
|
+
EOS
|
711
|
+
.and migrate_down(<<~EOS.strip)
|
712
|
+
remove_column :adverts, :type
|
713
|
+
remove_index :adverts, name: :on_type
|
714
|
+
EOS
|
715
|
+
)
|
716
|
+
end
|
717
|
+
|
718
|
+
Advert.field_specs.delete(:type)
|
719
|
+
nuke_model_class(SuperFancyAdvert)
|
720
|
+
nuke_model_class(FancyAdvert)
|
721
|
+
Advert.index_definitions.delete_if { |spec| spec.fields==["type"] }
|
722
|
+
|
723
|
+
## Coping with multiple changes
|
724
|
+
|
725
|
+
# The migration generator is designed to create complete migrations even if many changes to the models have taken place.
|
726
|
+
|
727
|
+
# First let's confirm we're in a known state. One model, 'Advert', with a string 'title' and text 'body':
|
728
|
+
|
729
|
+
ActiveRecord::Migration.class_eval up.gsub(/.*type.*/, '')
|
730
|
+
Advert.connection.schema_cache.clear!
|
731
|
+
Advert.reset_column_information
|
732
|
+
|
733
|
+
expect(Advert.connection.tables - Generators::DeclareSchema::Migration::Migrator.always_ignore_tables).
|
734
|
+
to eq(["adverts"])
|
735
|
+
expect(Advert.columns.map(&:name).sort).to eq(["body", "id", "title"])
|
736
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to eq(["", ""])
|
737
|
+
|
738
|
+
|
739
|
+
### Rename a column and change the default
|
740
|
+
|
741
|
+
Advert.field_specs.clear
|
742
|
+
|
743
|
+
class Advert < ActiveRecord::Base
|
744
|
+
fields do
|
745
|
+
name :string, default: "No Name", limit: 250, null: true
|
746
|
+
body :text, null: true
|
747
|
+
end
|
748
|
+
end
|
749
|
+
|
750
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run(adverts: { title: :name })).to(
|
751
|
+
migrate_up(<<~EOS.strip)
|
752
|
+
rename_column :adverts, :title, :name
|
753
|
+
change_column :adverts, :name, :string, limit: 250, null: true, default: "No Name"#{charset_and_collation}
|
754
|
+
EOS
|
755
|
+
.and migrate_down(<<~EOS.strip)
|
756
|
+
change_column :adverts, :name, :string, limit: 250, null: true, default: "Untitled"#{charset_and_collation}
|
757
|
+
rename_column :adverts, :name, :title
|
758
|
+
EOS
|
759
|
+
)
|
760
|
+
|
761
|
+
### Rename a table and add a column
|
762
|
+
|
763
|
+
nuke_model_class(Advert)
|
764
|
+
class Ad < ActiveRecord::Base
|
765
|
+
fields do
|
766
|
+
title :string, default: "Untitled", limit: 250
|
767
|
+
body :text, null: true
|
768
|
+
created_at :datetime
|
769
|
+
end
|
770
|
+
end
|
771
|
+
|
772
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run(adverts: :ads)).to(
|
773
|
+
migrate_up(<<~EOS.strip)
|
774
|
+
rename_table :adverts, :ads
|
775
|
+
add_column :ads, :created_at, :datetime, null: false
|
776
|
+
change_column :ads, :title, :string, limit: 250, null: false, default: \"Untitled\"#{charset_and_collation}
|
777
|
+
EOS
|
778
|
+
)
|
779
|
+
|
780
|
+
class Advert < ActiveRecord::Base
|
781
|
+
fields do
|
782
|
+
body :text, null: true
|
783
|
+
title :string, default: "Untitled", limit: 250, null: true
|
784
|
+
end
|
785
|
+
end
|
786
|
+
|
787
|
+
## Legacy Keys
|
788
|
+
|
789
|
+
# DeclareSchema has some support for legacy keys.
|
790
|
+
|
791
|
+
nuke_model_class(Ad)
|
792
|
+
|
793
|
+
class Advert < ActiveRecord::Base
|
794
|
+
fields do
|
795
|
+
body :text, null: true
|
796
|
+
end
|
797
|
+
self.primary_key = "advert_id"
|
798
|
+
end
|
799
|
+
|
800
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run(adverts: { id: :advert_id })).to(
|
801
|
+
migrate_up(<<~EOS.strip)
|
802
|
+
rename_column :adverts, :id, :advert_id
|
803
|
+
EOS
|
804
|
+
)
|
805
|
+
|
806
|
+
nuke_model_class(Advert)
|
807
|
+
ActiveRecord::Base.connection.execute("drop table `adverts`;")
|
808
|
+
|
809
|
+
## DSL
|
810
|
+
|
811
|
+
# The DSL allows lambdas and constants
|
812
|
+
|
813
|
+
class User < ActiveRecord::Base
|
814
|
+
fields do
|
815
|
+
company :string, limit: 250, ruby_default: -> { "BigCorp" }
|
816
|
+
end
|
817
|
+
end
|
818
|
+
expect(User.field_specs.keys).to eq(['company'])
|
819
|
+
expect(User.field_specs['company'].options[:ruby_default]&.call).to eq("BigCorp")
|
820
|
+
|
821
|
+
## validates
|
822
|
+
|
823
|
+
# DeclareSchema can accept a validates hash in the field options.
|
824
|
+
|
825
|
+
class Ad < ActiveRecord::Base
|
826
|
+
class << self
|
827
|
+
def validates(field_name, options)
|
828
|
+
end
|
829
|
+
end
|
830
|
+
end
|
831
|
+
expect(Ad).to receive(:validates).with(:company, presence: true, uniqueness: { case_sensitive: false })
|
832
|
+
class Ad < ActiveRecord::Base
|
833
|
+
fields do
|
834
|
+
company :string, limit: 250, index: true, unique: true, validates: { presence: true, uniqueness: { case_sensitive: false } }
|
835
|
+
end
|
836
|
+
self.primary_key = "advert_id"
|
837
|
+
end
|
838
|
+
up, _down = Generators::DeclareSchema::Migration::Migrator.run
|
839
|
+
ActiveRecord::Migration.class_eval(up)
|
840
|
+
expect(Ad.field_specs['company'].options[:validates].inspect).to eq("{:presence=>true, :uniqueness=>{:case_sensitive=>false}}")
|
841
|
+
end
|
842
|
+
|
843
|
+
context 'models with the same parent foreign key relation' do
|
844
|
+
before do
|
845
|
+
class Category < ActiveRecord::Base
|
846
|
+
fields do
|
847
|
+
name :string, limit: 250, null: true
|
848
|
+
end
|
849
|
+
end
|
850
|
+
class Advertiser < ActiveRecord::Base
|
851
|
+
fields do
|
852
|
+
name :string, limit: 250, null: true
|
853
|
+
end
|
854
|
+
belongs_to :category, limit: 8
|
855
|
+
end
|
856
|
+
class Affiliate < ActiveRecord::Base
|
857
|
+
fields do
|
858
|
+
name :string, limit: 250, null: true
|
859
|
+
end
|
860
|
+
belongs_to :category, limit: 8
|
861
|
+
end
|
862
|
+
end
|
863
|
+
|
864
|
+
it 'will genereate unique constraint names' do
|
865
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
866
|
+
migrate_up(<<~EOS.strip)
|
867
|
+
create_table :categories, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
|
868
|
+
t.string :name, limit: 250, null: true, charset: "utf8mb4", collation: "utf8mb4_bin"
|
869
|
+
end
|
870
|
+
create_table :advertisers, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
|
871
|
+
t.string :name, limit: 250, null: true, charset: "utf8mb4", collation: "utf8mb4_bin"
|
872
|
+
t.integer :category_id, limit: 8, null: false
|
873
|
+
end
|
874
|
+
create_table :affiliates, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
|
875
|
+
t.string :name, limit: 250, null: true, charset: "utf8mb4", collation: "utf8mb4_bin"
|
876
|
+
t.integer :category_id, limit: 8, null: false
|
877
|
+
end
|
878
|
+
add_index :advertisers, [:category_id], name: :on_category_id
|
879
|
+
add_index :affiliates, [:category_id], name: :on_category_id
|
880
|
+
add_foreign_key :advertisers, :categories, column: :category_id, name: :index_advertisers_on_category_id
|
881
|
+
add_foreign_key :affiliates, :categories, column: :category_id, name: :index_affiliates_on_category_id
|
882
|
+
EOS
|
883
|
+
)
|
884
|
+
migrate
|
885
|
+
|
886
|
+
nuke_model_class(Advertiser)
|
887
|
+
nuke_model_class(Affiliate)
|
888
|
+
end
|
889
|
+
end if !defined?(SQLite3) && ActiveRecord::VERSION::MAJOR >= 5
|
890
|
+
|
891
|
+
describe 'serialize' do
|
892
|
+
before do
|
893
|
+
class Ad < ActiveRecord::Base
|
894
|
+
@serialize_args = []
|
895
|
+
|
896
|
+
class << self
|
897
|
+
attr_reader :serialize_args
|
898
|
+
|
899
|
+
def serialize(*args)
|
900
|
+
@serialize_args << args
|
901
|
+
end
|
902
|
+
end
|
903
|
+
end
|
904
|
+
end
|
905
|
+
|
906
|
+
describe 'untyped' do
|
907
|
+
it 'allows serialize: true' do
|
908
|
+
class Ad < ActiveRecord::Base
|
909
|
+
fields do
|
910
|
+
allow_list :text, limit: 0xFFFF, serialize: true
|
911
|
+
end
|
912
|
+
end
|
913
|
+
|
914
|
+
expect(Ad.serialize_args).to eq([[:allow_list]])
|
915
|
+
end
|
916
|
+
|
917
|
+
it 'converts defaults with .to_yaml' do
|
918
|
+
class Ad < ActiveRecord::Base
|
919
|
+
fields do
|
920
|
+
allow_list :string, limit: 250, serialize: true, null: true, default: []
|
921
|
+
allow_hash :string, limit: 250, serialize: true, null: true, default: {}
|
922
|
+
allow_string :string, limit: 250, serialize: true, null: true, default: ['abc']
|
923
|
+
allow_null :string, limit: 250, serialize: true, null: true, default: nil
|
924
|
+
end
|
925
|
+
end
|
926
|
+
|
927
|
+
expect(Ad.field_specs['allow_list'].default).to eq("--- []\n")
|
928
|
+
expect(Ad.field_specs['allow_hash'].default).to eq("--- {}\n")
|
929
|
+
expect(Ad.field_specs['allow_string'].default).to eq("---\n- abc\n")
|
930
|
+
expect(Ad.field_specs['allow_null'].default).to eq(nil)
|
931
|
+
end
|
932
|
+
end
|
933
|
+
|
934
|
+
describe 'Array' do
|
935
|
+
it 'allows serialize: Array' do
|
936
|
+
class Ad < ActiveRecord::Base
|
937
|
+
fields do
|
938
|
+
allow_list :string, limit: 250, serialize: Array, null: true
|
939
|
+
end
|
940
|
+
end
|
941
|
+
|
942
|
+
expect(Ad.serialize_args).to eq([[:allow_list, Array]])
|
943
|
+
end
|
944
|
+
|
945
|
+
it 'allows Array defaults' do
|
946
|
+
class Ad < ActiveRecord::Base
|
947
|
+
fields do
|
948
|
+
allow_list :string, limit: 250, serialize: Array, null: true, default: [2]
|
949
|
+
allow_string :string, limit: 250, serialize: Array, null: true, default: ['abc']
|
950
|
+
allow_empty :string, limit: 250, serialize: Array, null: true, default: []
|
951
|
+
allow_null :string, limit: 250, serialize: Array, null: true, default: nil
|
952
|
+
end
|
953
|
+
end
|
954
|
+
|
955
|
+
expect(Ad.field_specs['allow_list'].default).to eq("---\n- 2\n")
|
956
|
+
expect(Ad.field_specs['allow_string'].default).to eq("---\n- abc\n")
|
957
|
+
expect(Ad.field_specs['allow_empty'].default).to eq(nil)
|
958
|
+
expect(Ad.field_specs['allow_null'].default).to eq(nil)
|
959
|
+
end
|
960
|
+
end
|
961
|
+
|
962
|
+
describe 'Hash' do
|
963
|
+
it 'allows serialize: Hash' do
|
964
|
+
class Ad < ActiveRecord::Base
|
965
|
+
fields do
|
966
|
+
allow_list :string, limit: 250, serialize: Hash, null: true
|
967
|
+
end
|
968
|
+
end
|
969
|
+
|
970
|
+
expect(Ad.serialize_args).to eq([[:allow_list, Hash]])
|
971
|
+
end
|
972
|
+
|
973
|
+
it 'allows Hash defaults' do
|
974
|
+
class Ad < ActiveRecord::Base
|
975
|
+
fields do
|
976
|
+
allow_loc :string, limit: 250, serialize: Hash, null: true, default: { 'state' => 'CA' }
|
977
|
+
allow_hash :string, limit: 250, serialize: Hash, null: true, default: {}
|
978
|
+
allow_null :string, limit: 250, serialize: Hash, null: true, default: nil
|
979
|
+
end
|
980
|
+
end
|
981
|
+
|
982
|
+
expect(Ad.field_specs['allow_loc'].default).to eq("---\nstate: CA\n")
|
983
|
+
expect(Ad.field_specs['allow_hash'].default).to eq(nil)
|
984
|
+
expect(Ad.field_specs['allow_null'].default).to eq(nil)
|
985
|
+
end
|
986
|
+
end
|
987
|
+
|
988
|
+
describe 'JSON' do
|
989
|
+
it 'allows serialize: JSON' do
|
990
|
+
class Ad < ActiveRecord::Base
|
991
|
+
fields do
|
992
|
+
allow_list :string, limit: 250, serialize: JSON
|
993
|
+
end
|
994
|
+
end
|
995
|
+
|
996
|
+
expect(Ad.serialize_args).to eq([[:allow_list, JSON]])
|
997
|
+
end
|
998
|
+
|
999
|
+
it 'allows JSON defaults' do
|
1000
|
+
class Ad < ActiveRecord::Base
|
1001
|
+
fields do
|
1002
|
+
allow_hash :string, limit: 250, serialize: JSON, null: true, default: { 'state' => 'CA' }
|
1003
|
+
allow_empty_array :string, limit: 250, serialize: JSON, null: true, default: []
|
1004
|
+
allow_empty_hash :string, limit: 250, serialize: JSON, null: true, default: {}
|
1005
|
+
allow_null :string, limit: 250, serialize: JSON, null: true, default: nil
|
1006
|
+
end
|
1007
|
+
end
|
1008
|
+
|
1009
|
+
expect(Ad.field_specs['allow_hash'].default).to eq("{\"state\":\"CA\"}")
|
1010
|
+
expect(Ad.field_specs['allow_empty_array'].default).to eq("[]")
|
1011
|
+
expect(Ad.field_specs['allow_empty_hash'].default).to eq("{}")
|
1012
|
+
expect(Ad.field_specs['allow_null'].default).to eq(nil)
|
1013
|
+
end
|
1014
|
+
end
|
1015
|
+
|
1016
|
+
class ValueClass
|
1017
|
+
delegate :present?, :inspect, to: :@value
|
1018
|
+
|
1019
|
+
def initialize(value)
|
1020
|
+
@value = value
|
1021
|
+
end
|
1022
|
+
|
1023
|
+
class << self
|
1024
|
+
def dump(object)
|
1025
|
+
if object&.present?
|
1026
|
+
object.inspect
|
1027
|
+
end
|
1028
|
+
end
|
1029
|
+
|
1030
|
+
def load(serialized)
|
1031
|
+
if serialized
|
1032
|
+
raise 'not used ???'
|
1033
|
+
end
|
1034
|
+
end
|
1035
|
+
end
|
1036
|
+
end
|
1037
|
+
|
1038
|
+
describe 'custom coder' do
|
1039
|
+
it 'allows serialize: ValueClass' do
|
1040
|
+
class Ad < ActiveRecord::Base
|
1041
|
+
fields do
|
1042
|
+
allow_list :string, limit: 250, serialize: ValueClass
|
1043
|
+
end
|
1044
|
+
end
|
1045
|
+
|
1046
|
+
expect(Ad.serialize_args).to eq([[:allow_list, ValueClass]])
|
1047
|
+
end
|
1048
|
+
|
1049
|
+
it 'allows ValueClass defaults' do
|
1050
|
+
class Ad < ActiveRecord::Base
|
1051
|
+
fields do
|
1052
|
+
allow_hash :string, limit: 250, serialize: ValueClass, null: true, default: ValueClass.new([2])
|
1053
|
+
allow_empty_array :string, limit: 250, serialize: ValueClass, null: true, default: ValueClass.new([])
|
1054
|
+
allow_null :string, limit: 250, serialize: ValueClass, null: true, default: nil
|
1055
|
+
end
|
1056
|
+
end
|
1057
|
+
|
1058
|
+
expect(Ad.field_specs['allow_hash'].default).to eq("[2]")
|
1059
|
+
expect(Ad.field_specs['allow_empty_array'].default).to eq(nil)
|
1060
|
+
expect(Ad.field_specs['allow_null'].default).to eq(nil)
|
1061
|
+
end
|
1062
|
+
end
|
1063
|
+
|
1064
|
+
it 'disallows serialize: with a non-string column type' do
|
1065
|
+
expect do
|
1066
|
+
class Ad < ActiveRecord::Base
|
1067
|
+
fields do
|
1068
|
+
allow_list :integer, limit: 8, serialize: true
|
1069
|
+
end
|
1070
|
+
end
|
1071
|
+
end.to raise_exception(ArgumentError, /must be :string or :text/)
|
1072
|
+
end
|
1073
|
+
end
|
1074
|
+
|
1075
|
+
context "for Rails #{ActiveSupport::VERSION::MAJOR}" do
|
1076
|
+
if ActiveSupport::VERSION::MAJOR >= 5
|
1077
|
+
let(:optional_true) { { optional: true } }
|
1078
|
+
let(:optional_false) { { optional: false } }
|
1079
|
+
else
|
1080
|
+
let(:optional_true) { {} }
|
1081
|
+
let(:optional_false) { {} }
|
1082
|
+
end
|
1083
|
+
let(:optional_flag) { { false => optional_false, true => optional_true } }
|
1084
|
+
|
1085
|
+
describe 'belongs_to' do
|
1086
|
+
before do
|
1087
|
+
unless defined?(AdCategory)
|
1088
|
+
class AdCategory < ActiveRecord::Base
|
1089
|
+
fields { }
|
1090
|
+
end
|
1091
|
+
end
|
1092
|
+
|
1093
|
+
class Advert < ActiveRecord::Base
|
1094
|
+
fields do
|
1095
|
+
name :string, limit: 250, null: true
|
1096
|
+
category_id :integer, limit: 8
|
1097
|
+
nullable_category_id :integer, limit: 8, null: true
|
1098
|
+
end
|
1099
|
+
end
|
1100
|
+
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
1101
|
+
ActiveRecord::Migration.class_eval(up)
|
1102
|
+
end
|
1103
|
+
|
1104
|
+
it 'passes through optional: when given' do
|
1105
|
+
class AdvertBelongsTo < ActiveRecord::Base
|
1106
|
+
self.table_name = 'adverts'
|
1107
|
+
fields { }
|
1108
|
+
reset_column_information
|
1109
|
+
belongs_to :ad_category, optional: true
|
1110
|
+
end
|
1111
|
+
expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_true)
|
1112
|
+
end
|
1113
|
+
|
1114
|
+
describe 'contradictory settings' do # contradictory settings are ok--for example, during migration
|
1115
|
+
it 'passes through optional: true, null: false' do
|
1116
|
+
class AdvertBelongsTo < ActiveRecord::Base
|
1117
|
+
self.table_name = 'adverts'
|
1118
|
+
fields { }
|
1119
|
+
reset_column_information
|
1120
|
+
belongs_to :ad_category, optional: true, null: false
|
1121
|
+
end
|
1122
|
+
expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_true)
|
1123
|
+
expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(false)
|
1124
|
+
end
|
1125
|
+
|
1126
|
+
it 'passes through optional: false, null: true' do
|
1127
|
+
class AdvertBelongsTo < ActiveRecord::Base
|
1128
|
+
self.table_name = 'adverts'
|
1129
|
+
fields { }
|
1130
|
+
reset_column_information
|
1131
|
+
belongs_to :ad_category, optional: false, null: true
|
1132
|
+
end
|
1133
|
+
expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_false)
|
1134
|
+
expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(true)
|
1135
|
+
end
|
1136
|
+
end
|
1137
|
+
|
1138
|
+
[false, true].each do |nullable|
|
1139
|
+
context "nullable=#{nullable}" do
|
1140
|
+
it 'infers optional: from null:' do
|
1141
|
+
eval <<~EOS
|
1142
|
+
class AdvertBelongsTo < ActiveRecord::Base
|
1143
|
+
fields { }
|
1144
|
+
belongs_to :ad_category, null: #{nullable}
|
1145
|
+
end
|
1146
|
+
EOS
|
1147
|
+
expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_flag[nullable])
|
1148
|
+
expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(nullable)
|
1149
|
+
end
|
1150
|
+
|
1151
|
+
it 'infers null: from optional:' do
|
1152
|
+
eval <<~EOS
|
1153
|
+
class AdvertBelongsTo < ActiveRecord::Base
|
1154
|
+
fields { }
|
1155
|
+
belongs_to :ad_category, optional: #{nullable}
|
1156
|
+
end
|
1157
|
+
EOS
|
1158
|
+
expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_flag[nullable])
|
1159
|
+
expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(nullable)
|
1160
|
+
end
|
1161
|
+
end
|
1162
|
+
end
|
1163
|
+
end
|
1164
|
+
end
|
1165
|
+
|
1166
|
+
describe 'migration base class' do
|
1167
|
+
it 'adapts to Rails 4' do
|
1168
|
+
class Advert < active_record_base_class.constantize
|
1169
|
+
fields do
|
1170
|
+
title :string, limit: 100
|
1171
|
+
end
|
1172
|
+
end
|
1173
|
+
|
1174
|
+
generate_migrations '-n', '-m'
|
1175
|
+
|
1176
|
+
migrations = Dir.glob('db/migrate/*declare_schema_migration*.rb')
|
1177
|
+
expect(migrations.size).to eq(1), migrations.inspect
|
1178
|
+
|
1179
|
+
migration_content = File.read(migrations.first)
|
1180
|
+
first_line = migration_content.split("\n").first
|
1181
|
+
base_class = first_line.split(' < ').last
|
1182
|
+
expect(base_class).to eq("(ActiveSupport::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration)")
|
1183
|
+
end
|
1184
|
+
end
|
1185
|
+
|
1186
|
+
context 'Does not generate migrations' do
|
1187
|
+
it 'for aliased fields bigint -> integer limit 8' do
|
1188
|
+
if ActiveSupport::VERSION::MAJOR >= 5 || !ActiveRecord::Base.connection.class.name.match?(/SQLite3Adapter/)
|
1189
|
+
class Advert < active_record_base_class.constantize
|
1190
|
+
fields do
|
1191
|
+
price :bigint
|
1192
|
+
end
|
1193
|
+
end
|
1194
|
+
|
1195
|
+
generate_migrations '-n', '-m'
|
1196
|
+
|
1197
|
+
migrations = Dir.glob('db/migrate/*declare_schema_migration*.rb')
|
1198
|
+
expect(migrations.size).to eq(1), migrations.inspect
|
1199
|
+
|
1200
|
+
if defined?(Mysql2) && ActiveSupport::VERSION::MAJOR < 5
|
1201
|
+
ActiveRecord::Base.connection.execute("ALTER TABLE adverts ADD PRIMARY KEY (id)")
|
1202
|
+
end
|
1203
|
+
|
1204
|
+
class Advert < active_record_base_class.constantize
|
1205
|
+
fields do
|
1206
|
+
price :integer, limit: 8
|
1207
|
+
end
|
1208
|
+
end
|
1209
|
+
|
1210
|
+
expect { generate_migrations '-n', '-g' }.to output("Database and models match -- nothing to change\n").to_stdout
|
1211
|
+
end
|
1212
|
+
end
|
1213
|
+
end
|
1214
|
+
end
|
1215
|
+
|
1216
|
+
context 'Using declare_schema' do
|
1217
|
+
# DeclareSchema - Migration Generator
|
1218
|
+
it 'generates migrations' do
|
1219
|
+
## The migration generator -- introduction
|
1220
|
+
|
1221
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to migrate_up("").and migrate_down("")
|
1222
|
+
|
1223
|
+
class Advert < ActiveRecord::Base
|
1224
|
+
end
|
1225
|
+
|
1226
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to migrate_up("").and migrate_down("")
|
1227
|
+
|
1228
|
+
Generators::DeclareSchema::Migration::Migrator.ignore_tables = ["green_fishes"]
|
1229
|
+
|
1230
|
+
Advert.connection.schema_cache.clear!
|
1231
|
+
Advert.reset_column_information
|
1232
|
+
|
1233
|
+
class Advert < ActiveRecord::Base
|
1234
|
+
declare_schema do
|
1235
|
+
string :name, limit: 250, null: true
|
1236
|
+
end
|
1237
|
+
end
|
1238
|
+
|
1239
|
+
up, _ = Generators::DeclareSchema::Migration::Migrator.run.tap do |migrations|
|
1240
|
+
expect(migrations).to(
|
1241
|
+
migrate_up(<<~EOS.strip)
|
1242
|
+
create_table :adverts, id: :bigint#{create_table_charset_and_collation} do |t|
|
1243
|
+
t.string :name, limit: 250, null: true#{charset_and_collation}
|
1244
|
+
end
|
1245
|
+
EOS
|
1246
|
+
.and migrate_down("drop_table :adverts")
|
1247
|
+
)
|
1248
|
+
end
|
1249
|
+
|
1250
|
+
ActiveRecord::Migration.class_eval(up)
|
1251
|
+
expect(Advert.columns.map(&:name)).to eq(["id", "name"])
|
1252
|
+
|
1253
|
+
if ActiveSupport::VERSION::MAJOR < 5
|
1254
|
+
# Rails 4 drivers don't always create PK properly. Fix that by dropping and recreating.
|
1255
|
+
ActiveRecord::Base.connection.execute("drop table adverts")
|
1256
|
+
if defined?(Mysql2)
|
1257
|
+
ActiveRecord::Base.connection.execute("CREATE TABLE adverts (id integer PRIMARY KEY AUTO_INCREMENT NOT NULL, name varchar(250)) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin")
|
1258
|
+
else
|
1259
|
+
ActiveRecord::Base.connection.execute("CREATE TABLE adverts (id integer PRIMARY KEY AUTOINCREMENT NOT NULL, name varchar(250))")
|
1260
|
+
end
|
1261
|
+
end
|
1262
|
+
|
1263
|
+
class Advert < ActiveRecord::Base
|
1264
|
+
declare_schema do
|
1265
|
+
string :name, limit: 250, null: true
|
1266
|
+
text :body, null: true
|
1267
|
+
datetime :published_at, null: true
|
1268
|
+
end
|
1269
|
+
end
|
1270
|
+
|
1271
|
+
Advert.connection.schema_cache.clear!
|
1272
|
+
Advert.reset_column_information
|
1273
|
+
|
1274
|
+
expect(migrate).to(
|
1275
|
+
migrate_up(<<~EOS.strip)
|
1276
|
+
add_column :adverts, :body, :text#{text_limit}, null: true#{charset_and_collation}
|
1277
|
+
add_column :adverts, :published_at, :datetime, null: true
|
1278
|
+
EOS
|
1279
|
+
.and migrate_down(<<~EOS.strip)
|
1280
|
+
remove_column :adverts, :published_at
|
1281
|
+
remove_column :adverts, :body
|
1282
|
+
EOS
|
1283
|
+
)
|
1284
|
+
|
1285
|
+
Advert.field_specs.clear # not normally needed
|
1286
|
+
class Advert < ActiveRecord::Base
|
1287
|
+
declare_schema do
|
1288
|
+
string :name, limit: 250, null: true
|
1289
|
+
text :body, null: true
|
1290
|
+
end
|
1291
|
+
end
|
1292
|
+
|
1293
|
+
expect(migrate).to(
|
1294
|
+
migrate_up("remove_column :adverts, :published_at").and(
|
1295
|
+
migrate_down("add_column :adverts, :published_at, :datetime#{datetime_precision}, null: true")
|
1296
|
+
)
|
1297
|
+
)
|
1298
|
+
|
1299
|
+
nuke_model_class(Advert)
|
1300
|
+
class Advert < ActiveRecord::Base
|
1301
|
+
declare_schema do
|
1302
|
+
string :title, limit: 250, null: true
|
1303
|
+
text :body, null: true
|
1304
|
+
end
|
1305
|
+
end
|
1306
|
+
|
1307
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
1308
|
+
migrate_up(<<~EOS.strip)
|
1309
|
+
add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
1310
|
+
remove_column :adverts, :name
|
1311
|
+
EOS
|
1312
|
+
.and migrate_down(<<~EOS.strip)
|
1313
|
+
add_column :adverts, :name, :string, limit: 250, null: true#{charset_and_collation}
|
1314
|
+
remove_column :adverts, :title
|
1315
|
+
EOS
|
1316
|
+
)
|
1317
|
+
|
1318
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run(adverts: { name: :title })).to(
|
1319
|
+
migrate_up("rename_column :adverts, :name, :title").and(
|
1320
|
+
migrate_down("rename_column :adverts, :title, :name")
|
1321
|
+
)
|
1322
|
+
)
|
1323
|
+
|
1324
|
+
migrate
|
1325
|
+
|
1326
|
+
class Advert < ActiveRecord::Base
|
1327
|
+
declare_schema do
|
1328
|
+
text :title, null: true
|
1329
|
+
text :body, null: true
|
1330
|
+
end
|
1331
|
+
end
|
1332
|
+
|
1333
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
1334
|
+
migrate_up("change_column :adverts, :title, :text#{text_limit}, null: true#{charset_and_collation}").and(
|
1335
|
+
migrate_down("change_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}")
|
1336
|
+
)
|
1337
|
+
)
|
1338
|
+
|
1339
|
+
class Advert < ActiveRecord::Base
|
1340
|
+
declare_schema do
|
1341
|
+
string :title, default: "Untitled", limit: 250, null: true
|
1342
|
+
text :body, null: true
|
1343
|
+
end
|
1344
|
+
end
|
1345
|
+
|
1346
|
+
expect(migrate).to(
|
1347
|
+
migrate_up(<<~EOS.strip)
|
1348
|
+
change_column :adverts, :title, :string, limit: 250, null: true, default: "Untitled"#{charset_and_collation}
|
1349
|
+
EOS
|
1350
|
+
.and migrate_down(<<~EOS.strip)
|
1351
|
+
change_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
1352
|
+
EOS
|
1353
|
+
)
|
1354
|
+
|
1355
|
+
### Limits
|
1356
|
+
|
1357
|
+
class Advert < ActiveRecord::Base
|
1358
|
+
declare_schema do
|
1359
|
+
integer :price, null: true, limit: 2
|
1360
|
+
end
|
1361
|
+
end
|
1362
|
+
|
1363
|
+
up, _ = Generators::DeclareSchema::Migration::Migrator.run.tap do |migrations|
|
1364
|
+
expect(migrations).to migrate_up("add_column :adverts, :price, :integer, limit: 2, null: true")
|
1365
|
+
end
|
1366
|
+
|
1367
|
+
# Now run the migration, then change the limit:
|
1368
|
+
|
1369
|
+
ActiveRecord::Migration.class_eval(up)
|
1370
|
+
class Advert < ActiveRecord::Base
|
1371
|
+
declare_schema do
|
1372
|
+
integer :price, null: true, limit: 3
|
1373
|
+
end
|
1374
|
+
end
|
1375
|
+
|
1376
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
1377
|
+
migrate_up(<<~EOS.strip)
|
1378
|
+
change_column :adverts, :price, :integer, limit: 3, null: true
|
1379
|
+
EOS
|
1380
|
+
.and migrate_down(<<~EOS.strip)
|
1381
|
+
change_column :adverts, :price, :integer, limit: 2, null: true
|
567
1382
|
EOS
|
568
|
-
|
1383
|
+
)
|
1384
|
+
|
1385
|
+
ActiveRecord::Migration.class_eval("remove_column :adverts, :price")
|
1386
|
+
class Advert < ActiveRecord::Base
|
1387
|
+
declare_schema do
|
1388
|
+
decimal :price, precision: 4, scale: 1, null: true
|
1389
|
+
end
|
1390
|
+
end
|
1391
|
+
|
1392
|
+
# Limits are generally not needed for `text` fields, because by default, `text` fields will use the maximum size
|
1393
|
+
# allowed for that database type (0xffffffff for LONGTEXT in MySQL unlimited in Postgres, 1 billion in Sqlite).
|
1394
|
+
# If a `limit` is given, it will only be used in MySQL, to choose the smallest TEXT field that will accommodate
|
1395
|
+
# that limit (0xff for TINYTEXT, 0xffff for TEXT, 0xffffff for MEDIUMTEXT, 0xffffffff for LONGTEXT).
|
1396
|
+
|
1397
|
+
if defined?(SQLite3)
|
1398
|
+
expect(::DeclareSchema::Model::FieldSpec.mysql_text_limits?).to be_falsey
|
1399
|
+
end
|
1400
|
+
|
1401
|
+
class Advert < ActiveRecord::Base
|
1402
|
+
declare_schema do
|
1403
|
+
text :notes
|
1404
|
+
text :description, limit: 30000
|
1405
|
+
end
|
1406
|
+
end
|
1407
|
+
|
1408
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
1409
|
+
migrate_up(<<~EOS.strip)
|
1410
|
+
add_column :adverts, :price, :decimal, precision: 4, scale: 1, null: true
|
1411
|
+
add_column :adverts, :notes, :text#{text_limit}, null: false#{charset_and_collation}
|
1412
|
+
add_column :adverts, :description, :text#{', limit: 65535' if defined?(Mysql2)}, null: false#{charset_and_collation}
|
1413
|
+
EOS
|
1414
|
+
)
|
1415
|
+
|
1416
|
+
Advert.field_specs.delete :price
|
1417
|
+
Advert.field_specs.delete :notes
|
1418
|
+
Advert.field_specs.delete :description
|
1419
|
+
|
1420
|
+
# In MySQL, limits are applied, rounded up:
|
1421
|
+
|
1422
|
+
if defined?(Mysql2)
|
1423
|
+
expect(::DeclareSchema::Model::FieldSpec.mysql_text_limits?).to be_truthy
|
1424
|
+
|
1425
|
+
class Advert < ActiveRecord::Base
|
1426
|
+
declare_schema do
|
1427
|
+
text :notes
|
1428
|
+
text :description, limit: 250
|
1429
|
+
end
|
1430
|
+
end
|
1431
|
+
|
1432
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
1433
|
+
migrate_up(<<~EOS.strip)
|
1434
|
+
add_column :adverts, :notes, :text, limit: 4294967295, null: false#{charset_and_collation}
|
1435
|
+
add_column :adverts, :description, :text, limit: 255, null: false#{charset_and_collation}
|
1436
|
+
EOS
|
1437
|
+
)
|
1438
|
+
|
1439
|
+
Advert.field_specs.delete :notes
|
1440
|
+
|
1441
|
+
# Limits that are too high for MySQL will raise an exception.
|
1442
|
+
|
1443
|
+
expect do
|
1444
|
+
class Advert < ActiveRecord::Base
|
1445
|
+
declare_schema do
|
1446
|
+
text :notes
|
1447
|
+
text :description, limit: 0x1_0000_0000
|
1448
|
+
end
|
1449
|
+
end
|
1450
|
+
end.to raise_exception(ArgumentError, "limit of 4294967296 is too large for MySQL")
|
1451
|
+
|
1452
|
+
Advert.field_specs.delete :notes
|
1453
|
+
|
1454
|
+
# And in MySQL, unstated text limits are treated as the maximum (LONGTEXT) limit.
|
1455
|
+
|
1456
|
+
# To start, we'll set the database schema for `description` to match the above limit of 250.
|
1457
|
+
|
1458
|
+
Advert.connection.execute "ALTER TABLE adverts ADD COLUMN description TINYTEXT"
|
1459
|
+
Advert.connection.schema_cache.clear!
|
1460
|
+
Advert.reset_column_information
|
1461
|
+
expect(Advert.connection.tables - Generators::DeclareSchema::Migration::Migrator.always_ignore_tables).
|
1462
|
+
to eq(["adverts"])
|
1463
|
+
expect(Advert.columns.map(&:name)).to eq(["id", "body", "title", "description"])
|
1464
|
+
|
1465
|
+
# Now migrate to an unstated text limit:
|
1466
|
+
|
1467
|
+
class Advert < ActiveRecord::Base
|
1468
|
+
declare_schema do
|
1469
|
+
text :description
|
1470
|
+
end
|
1471
|
+
end
|
1472
|
+
|
1473
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
1474
|
+
migrate_up(<<~EOS.strip)
|
1475
|
+
change_column :adverts, :description, :text, limit: 4294967295, null: false#{charset_and_collation}
|
1476
|
+
EOS
|
1477
|
+
.and migrate_down(<<~EOS.strip)
|
1478
|
+
change_column :adverts, :description, :text#{', limit: 255' if defined?(Mysql2)}, null: true#{charset_and_collation}
|
1479
|
+
EOS
|
1480
|
+
)
|
1481
|
+
|
1482
|
+
# And migrate to a stated text limit that is the same as the unstated one:
|
1483
|
+
|
1484
|
+
class Advert < ActiveRecord::Base
|
1485
|
+
declare_schema do
|
1486
|
+
text :description, limit: 0xffffffff
|
1487
|
+
end
|
1488
|
+
end
|
1489
|
+
|
1490
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
1491
|
+
migrate_up(<<~EOS.strip)
|
1492
|
+
change_column :adverts, :description, :text, limit: 4294967295, null: false#{charset_and_collation}
|
1493
|
+
EOS
|
1494
|
+
.and migrate_down(<<~EOS.strip)
|
1495
|
+
change_column :adverts, :description, :text#{', limit: 255' if defined?(Mysql2)}, null: true#{charset_and_collation}
|
1496
|
+
EOS
|
1497
|
+
)
|
1498
|
+
end
|
1499
|
+
|
1500
|
+
Advert.field_specs.clear
|
1501
|
+
Advert.connection.schema_cache.clear!
|
1502
|
+
Advert.reset_column_information
|
1503
|
+
class Advert < ActiveRecord::Base
|
1504
|
+
declare_schema do
|
1505
|
+
string :name, limit: 250, null: true
|
1506
|
+
end
|
1507
|
+
end
|
1508
|
+
|
1509
|
+
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
1510
|
+
ActiveRecord::Migration.class_eval up
|
1511
|
+
|
1512
|
+
Advert.connection.schema_cache.clear!
|
1513
|
+
Advert.reset_column_information
|
1514
|
+
|
1515
|
+
### Foreign Keys
|
1516
|
+
|
1517
|
+
# DeclareSchema extends the `belongs_to` macro so that it also declares the
|
1518
|
+
# foreign-key field. It also generates an index on the field.
|
1519
|
+
|
1520
|
+
class Category < ActiveRecord::Base; end
|
1521
|
+
class Advert < ActiveRecord::Base
|
1522
|
+
declare_schema do
|
1523
|
+
string :name, limit: 250, null: true
|
1524
|
+
end
|
1525
|
+
belongs_to :category
|
1526
|
+
end
|
1527
|
+
|
1528
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
1529
|
+
migrate_up(<<~EOS.strip)
|
1530
|
+
add_column :adverts, :category_id, :integer, limit: 8, null: false
|
1531
|
+
add_index :adverts, [:category_id], name: :on_category_id
|
1532
|
+
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" if defined?(Mysql2)}
|
1533
|
+
EOS
|
1534
|
+
.and migrate_down(<<~EOS.strip)
|
1535
|
+
#{"remove_foreign_key :adverts, name: :index_adverts_on_category_id" if defined?(Mysql2)}
|
1536
|
+
remove_index :adverts, name: :on_category_id
|
1537
|
+
remove_column :adverts, :category_id
|
1538
|
+
EOS
|
1539
|
+
)
|
1540
|
+
|
1541
|
+
Advert.field_specs.delete(:category_id)
|
1542
|
+
Advert.index_definitions.delete_if { |spec| spec.fields==["category_id"] }
|
1543
|
+
|
1544
|
+
# If you specify a custom foreign key, the migration generator observes that:
|
1545
|
+
|
1546
|
+
class Category < ActiveRecord::Base; end
|
1547
|
+
class Advert < ActiveRecord::Base
|
1548
|
+
declare_schema { }
|
1549
|
+
belongs_to :category, foreign_key: "c_id", class_name: 'Category'
|
1550
|
+
end
|
1551
|
+
|
1552
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
1553
|
+
migrate_up(<<~EOS.strip)
|
1554
|
+
add_column :adverts, :c_id, :integer, limit: 8, null: false
|
1555
|
+
add_index :adverts, [:c_id], name: :on_c_id
|
1556
|
+
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
1557
|
+
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
1558
|
+
EOS
|
1559
|
+
)
|
1560
|
+
|
1561
|
+
Advert.field_specs.delete(:c_id)
|
1562
|
+
Advert.index_definitions.delete_if { |spec| spec.fields == ["c_id"] }
|
1563
|
+
|
1564
|
+
# You can avoid generating the index by specifying `index: false`
|
1565
|
+
|
1566
|
+
class Category < ActiveRecord::Base; end
|
1567
|
+
class Advert < ActiveRecord::Base
|
1568
|
+
declare_schema { }
|
1569
|
+
belongs_to :category, index: false
|
1570
|
+
end
|
1571
|
+
|
1572
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
1573
|
+
migrate_up(<<~EOS.strip)
|
1574
|
+
add_column :adverts, :category_id, :integer, limit: 8, null: false
|
1575
|
+
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
1576
|
+
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
1577
|
+
EOS
|
1578
|
+
)
|
1579
|
+
|
1580
|
+
Advert.field_specs.delete(:category_id)
|
1581
|
+
Advert.index_definitions.delete_if { |spec| spec.fields == ["category_id"] }
|
1582
|
+
|
1583
|
+
# You can specify the index name with :index
|
1584
|
+
|
1585
|
+
class Category < ActiveRecord::Base; end
|
1586
|
+
class Advert < ActiveRecord::Base
|
1587
|
+
declare_schema { }
|
1588
|
+
belongs_to :category, index: 'my_index'
|
1589
|
+
end
|
1590
|
+
|
1591
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
1592
|
+
migrate_up(<<~EOS.strip)
|
1593
|
+
add_column :adverts, :category_id, :integer, limit: 8, null: false
|
1594
|
+
add_index :adverts, [:category_id], name: :my_index
|
1595
|
+
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
1596
|
+
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
1597
|
+
EOS
|
1598
|
+
)
|
1599
|
+
|
1600
|
+
Advert.field_specs.delete(:category_id)
|
1601
|
+
Advert.index_definitions.delete_if { |spec| spec.fields == ["category_id"] }
|
1602
|
+
|
1603
|
+
### Timestamps and Optimimistic Locking
|
1604
|
+
|
1605
|
+
# `updated_at` and `created_at` can be declared with the shorthand `timestamps`.
|
1606
|
+
# Similarly, `lock_version` can be declared with the "shorthand" `optimimistic_lock`.
|
1607
|
+
|
1608
|
+
class Advert < ActiveRecord::Base
|
1609
|
+
declare_schema do
|
1610
|
+
timestamps
|
1611
|
+
optimistic_lock
|
1612
|
+
end
|
1613
|
+
end
|
569
1614
|
|
570
|
-
|
1615
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
1616
|
+
migrate_up(<<~EOS.strip)
|
1617
|
+
add_column :adverts, :created_at, :datetime, null: true
|
1618
|
+
add_column :adverts, :updated_at, :datetime, null: true
|
1619
|
+
add_column :adverts, :lock_version, :integer#{lock_version_limit}, null: false, default: 1
|
1620
|
+
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
1621
|
+
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
1622
|
+
EOS
|
1623
|
+
.and migrate_down(<<~EOS.strip)
|
1624
|
+
#{"remove_foreign_key :adverts, name: :index_adverts_on_c_id\n" +
|
1625
|
+
"remove_foreign_key :adverts, name: :index_adverts_on_category_id" if defined?(Mysql2)}
|
1626
|
+
remove_column :adverts, :lock_version
|
1627
|
+
remove_column :adverts, :updated_at
|
1628
|
+
remove_column :adverts, :created_at
|
1629
|
+
EOS
|
1630
|
+
)
|
571
1631
|
|
572
|
-
|
1632
|
+
Advert.field_specs.delete(:updated_at)
|
1633
|
+
Advert.field_specs.delete(:created_at)
|
1634
|
+
Advert.field_specs.delete(:lock_version)
|
573
1635
|
|
574
|
-
|
575
|
-
index :title, unique: true, name: 'my_index'
|
576
|
-
end
|
1636
|
+
### Indices
|
577
1637
|
|
578
|
-
|
579
|
-
migrate_up(<<~EOS.strip)
|
580
|
-
add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
1638
|
+
# You can add an index to a field definition
|
581
1639
|
|
582
|
-
|
1640
|
+
class Advert < ActiveRecord::Base
|
1641
|
+
declare_schema do
|
1642
|
+
string :title, index: true, limit: 250, null: true
|
1643
|
+
end
|
1644
|
+
end
|
583
1645
|
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
1646
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
1647
|
+
migrate_up(<<~EOS.strip)
|
1648
|
+
add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
1649
|
+
add_index :adverts, [:title], name: :on_title
|
1650
|
+
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
1651
|
+
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
1652
|
+
EOS
|
1653
|
+
)
|
588
1654
|
|
589
|
-
|
1655
|
+
Advert.index_definitions.delete_if { |spec| spec.fields==["title"] }
|
590
1656
|
|
591
|
-
|
1657
|
+
# You can ask for a unique index
|
592
1658
|
|
593
|
-
|
594
|
-
|
595
|
-
|
1659
|
+
class Advert < ActiveRecord::Base
|
1660
|
+
declare_schema do
|
1661
|
+
string :title, index: true, unique: true, null: true, limit: 250
|
1662
|
+
end
|
1663
|
+
end
|
596
1664
|
|
597
|
-
|
598
|
-
|
599
|
-
|
1665
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
1666
|
+
migrate_up(<<~EOS.strip)
|
1667
|
+
add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
1668
|
+
add_index :adverts, [:title], name: :on_title, unique: true
|
1669
|
+
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
1670
|
+
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
1671
|
+
EOS
|
1672
|
+
)
|
600
1673
|
|
601
|
-
|
1674
|
+
Advert.index_definitions.delete_if { |spec| spec.fields == ["title"] }
|
602
1675
|
|
603
|
-
|
604
|
-
"add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")" if defined?(Mysql2)}
|
605
|
-
EOS
|
606
|
-
)
|
1676
|
+
# You can specify the name for the index
|
607
1677
|
|
608
|
-
|
1678
|
+
class Advert < ActiveRecord::Base
|
1679
|
+
declare_schema do
|
1680
|
+
string :title, index: 'my_index', limit: 250, null: true
|
1681
|
+
end
|
1682
|
+
end
|
609
1683
|
|
610
|
-
|
611
|
-
|
612
|
-
|
1684
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
1685
|
+
migrate_up(<<~EOS.strip)
|
1686
|
+
add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
1687
|
+
add_index :adverts, [:title], name: :my_index
|
1688
|
+
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
1689
|
+
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
1690
|
+
EOS
|
1691
|
+
)
|
613
1692
|
|
614
|
-
|
1693
|
+
Advert.index_definitions.delete_if { |spec| spec.fields==["title"] }
|
615
1694
|
|
616
|
-
|
1695
|
+
# You can ask for an index outside of the fields block
|
617
1696
|
|
618
|
-
|
619
|
-
|
620
|
-
fields do
|
621
|
-
title :string, limit: 250, null: true
|
622
|
-
body :text, null: true
|
1697
|
+
class Advert < ActiveRecord::Base
|
1698
|
+
index :title
|
623
1699
|
end
|
624
|
-
end
|
625
1700
|
|
626
|
-
|
627
|
-
|
1701
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
1702
|
+
migrate_up(<<~EOS.strip)
|
1703
|
+
add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
1704
|
+
add_index :adverts, [:title], name: :on_title
|
1705
|
+
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
1706
|
+
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
1707
|
+
EOS
|
1708
|
+
)
|
628
1709
|
|
629
|
-
|
630
|
-
migrate_up(<<~EOS.strip)
|
631
|
-
rename_table :adverts, :ads
|
1710
|
+
Advert.index_definitions.delete_if { |spec| spec.fields == ["title"] }
|
632
1711
|
|
633
|
-
|
634
|
-
add_column :ads, :body, :text#{', limit: 4294967295' if defined?(Mysql2)}, null: true#{charset_and_collation}
|
1712
|
+
# The available options for the index function are `:unique` and `:name`
|
635
1713
|
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
"execute \"ALTER TABLE ads DROP PRIMARY KEY, ADD PRIMARY KEY (id)\"\n\n" +
|
640
|
-
"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
|
641
|
-
"add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")"
|
642
|
-
end}
|
643
|
-
EOS
|
644
|
-
.and migrate_down(<<~EOS.strip)
|
645
|
-
remove_column :ads, :title
|
646
|
-
remove_column :ads, :body
|
647
|
-
|
648
|
-
rename_table :ads, :adverts
|
649
|
-
|
650
|
-
#{if defined?(SQLite3)
|
651
|
-
"add_index :adverts, [:id], unique: true, name: 'PRIMARY'\n"
|
652
|
-
elsif defined?(Mysql2)
|
653
|
-
"execute \"ALTER TABLE adverts DROP PRIMARY KEY, ADD PRIMARY KEY (id)\"\n\n" +
|
654
|
-
"remove_foreign_key(\"adverts\", name: \"on_category_id\")\n" +
|
655
|
-
"remove_foreign_key(\"adverts\", name: \"on_c_id\")"
|
656
|
-
end}
|
657
|
-
EOS
|
658
|
-
)
|
1714
|
+
class Advert < ActiveRecord::Base
|
1715
|
+
index :title, unique: true, name: 'my_index'
|
1716
|
+
end
|
659
1717
|
|
660
|
-
|
1718
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
1719
|
+
migrate_up(<<~EOS.strip)
|
1720
|
+
add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
1721
|
+
add_index :adverts, [:title], name: :my_index, unique: true
|
1722
|
+
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
1723
|
+
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
1724
|
+
EOS
|
1725
|
+
)
|
661
1726
|
|
662
|
-
|
1727
|
+
Advert.index_definitions.delete_if { |spec| spec.fields == ["title"] }
|
663
1728
|
|
664
|
-
|
665
|
-
self.table_name = "adverts"
|
666
|
-
end
|
1729
|
+
# You can create an index on more than one field
|
667
1730
|
|
668
|
-
|
1731
|
+
class Advert < ActiveRecord::Base
|
1732
|
+
index [:title, :category_id]
|
1733
|
+
end
|
669
1734
|
|
670
|
-
|
1735
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
1736
|
+
migrate_up(<<~EOS.strip)
|
1737
|
+
add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
1738
|
+
add_index :adverts, [:title, :category_id], name: :on_title_and_category_id
|
1739
|
+
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
1740
|
+
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
1741
|
+
EOS
|
1742
|
+
)
|
671
1743
|
|
672
|
-
|
1744
|
+
Advert.index_definitions.delete_if { |spec| spec.fields==["title", "category_id"] }
|
673
1745
|
|
674
|
-
|
1746
|
+
# Finally, you can specify that the migration generator should completely ignore an
|
1747
|
+
# index by passing its name to ignore_index in the model.
|
1748
|
+
# This is helpful for preserving indices that can't be automatically generated, such as prefix indices in MySQL.
|
675
1749
|
|
676
|
-
|
677
|
-
fields do
|
678
|
-
title :string, limit: 250, null: true
|
679
|
-
body :text, null: true
|
680
|
-
end
|
681
|
-
end
|
1750
|
+
### Rename a table
|
682
1751
|
|
683
|
-
|
684
|
-
migrate_up(<<~EOS.strip)
|
685
|
-
rename_table :adverts, :advertisements
|
1752
|
+
# The migration generator respects the `set_table_name` declaration, although as before, we need to explicitly tell the generator that we want a rename rather than a create and a drop.
|
686
1753
|
|
687
|
-
|
688
|
-
|
689
|
-
|
1754
|
+
class Advert < ActiveRecord::Base
|
1755
|
+
self.table_name = "ads"
|
1756
|
+
declare_schema do
|
1757
|
+
string :title, limit: 250, null: true
|
1758
|
+
text :body, null: true
|
1759
|
+
end
|
1760
|
+
end
|
690
1761
|
|
691
|
-
|
692
|
-
|
693
|
-
elsif defined?(Mysql2)
|
694
|
-
"execute \"ALTER TABLE advertisements DROP PRIMARY KEY, ADD PRIMARY KEY (id)\""
|
695
|
-
end}
|
696
|
-
EOS
|
697
|
-
.and migrate_down(<<~EOS.strip)
|
698
|
-
remove_column :advertisements, :title
|
699
|
-
remove_column :advertisements, :body
|
700
|
-
add_column :adverts, :name, :string, limit: 250, null: true#{charset_and_collation}
|
701
|
-
|
702
|
-
rename_table :advertisements, :adverts
|
703
|
-
|
704
|
-
#{if defined?(SQLite3)
|
705
|
-
"add_index :adverts, [:id], unique: true, name: 'PRIMARY'"
|
706
|
-
elsif defined?(Mysql2)
|
707
|
-
"execute \"ALTER TABLE adverts DROP PRIMARY KEY, ADD PRIMARY KEY (id)\""
|
708
|
-
end}
|
709
|
-
EOS
|
710
|
-
)
|
1762
|
+
Advert.connection.schema_cache.clear!
|
1763
|
+
Advert.reset_column_information
|
711
1764
|
|
712
|
-
|
1765
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run("adverts" => "ads")).to(
|
1766
|
+
migrate_up(<<~EOS.strip)
|
1767
|
+
rename_table :adverts, :ads
|
1768
|
+
add_column :ads, :title, :string, limit: 250, null: true#{charset_and_collation}
|
1769
|
+
add_column :ads, :body, :text#{', limit: 4294967295' if defined?(Mysql2)}, null: true#{charset_and_collation}
|
1770
|
+
#{if defined?(Mysql2)
|
1771
|
+
"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
1772
|
+
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id"
|
1773
|
+
end}
|
1774
|
+
EOS
|
1775
|
+
.and migrate_down(<<~EOS.strip)
|
1776
|
+
#{if defined?(Mysql2)
|
1777
|
+
"remove_foreign_key :adverts, name: :index_adverts_on_c_id\n" +
|
1778
|
+
"remove_foreign_key :adverts, name: :index_adverts_on_category_id"
|
1779
|
+
end}
|
1780
|
+
remove_column :ads, :body
|
1781
|
+
remove_column :ads, :title
|
1782
|
+
rename_table :ads, :adverts
|
1783
|
+
EOS
|
1784
|
+
)
|
713
1785
|
|
714
|
-
|
1786
|
+
# Set the table name back to what it should be and confirm we're in sync:
|
715
1787
|
|
716
|
-
|
1788
|
+
nuke_model_class(Advert)
|
717
1789
|
|
718
|
-
|
1790
|
+
class Advert < ActiveRecord::Base
|
1791
|
+
self.table_name = "adverts"
|
1792
|
+
end
|
719
1793
|
|
720
|
-
|
721
|
-
migrate_up(<<~EOS.strip)
|
722
|
-
drop_table :adverts
|
723
|
-
EOS
|
724
|
-
.and migrate_down(<<~EOS.strip)
|
725
|
-
create_table "adverts"#{table_options}, force: :cascade do |t|
|
726
|
-
t.string "name", limit: 250#{charset_and_collation}
|
727
|
-
end
|
728
|
-
EOS
|
729
|
-
)
|
1794
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to eq(["", ""])
|
730
1795
|
|
731
|
-
|
1796
|
+
### Rename a table
|
732
1797
|
|
733
|
-
|
1798
|
+
# As with renaming columns, we have to tell the migration generator about the rename. Here we create a new class 'Advertisement', and tell ActiveRecord to forget about the Advert class. This requires code that shouldn't be shown to impressionable children.
|
734
1799
|
|
735
|
-
|
1800
|
+
nuke_model_class(Advert)
|
736
1801
|
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
1802
|
+
class Advertisement < ActiveRecord::Base
|
1803
|
+
declare_schema do
|
1804
|
+
string :title, limit: 250, null: true
|
1805
|
+
text :body, null: true
|
1806
|
+
end
|
741
1807
|
end
|
742
|
-
end
|
743
|
-
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
744
|
-
ActiveRecord::Migration.class_eval(up)
|
745
1808
|
|
746
|
-
|
747
|
-
end
|
748
|
-
class SuperFancyAdvert < FancyAdvert
|
749
|
-
end
|
750
|
-
|
751
|
-
up, _ = Generators::DeclareSchema::Migration::Migrator.run do |migrations|
|
752
|
-
expect(migrations).to(
|
1809
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run("adverts" => "advertisements")).to(
|
753
1810
|
migrate_up(<<~EOS.strip)
|
754
|
-
|
755
|
-
|
756
|
-
|
1811
|
+
rename_table :adverts, :advertisements
|
1812
|
+
add_column :advertisements, :title, :string, limit: 250, null: true#{charset_and_collation}
|
1813
|
+
add_column :advertisements, :body, :text#{', limit: 4294967295' if defined?(Mysql2)}, null: true#{charset_and_collation}
|
1814
|
+
remove_column :advertisements, :name
|
757
1815
|
EOS
|
758
1816
|
.and migrate_down(<<~EOS.strip)
|
759
|
-
|
760
|
-
|
761
|
-
|
1817
|
+
add_column :advertisements, :name, :string, limit: 250, null: true#{charset_and_collation}
|
1818
|
+
remove_column :advertisements, :body
|
1819
|
+
remove_column :advertisements, :title
|
1820
|
+
rename_table :advertisements, :adverts
|
762
1821
|
EOS
|
763
1822
|
)
|
764
|
-
end
|
765
1823
|
|
766
|
-
|
767
|
-
nuke_model_class(SuperFancyAdvert)
|
768
|
-
nuke_model_class(FancyAdvert)
|
769
|
-
Advert.index_definitions.delete_if { |spec| spec.fields==["type"] }
|
1824
|
+
### Drop a table
|
770
1825
|
|
771
|
-
|
1826
|
+
nuke_model_class(Advertisement)
|
772
1827
|
|
773
|
-
|
1828
|
+
# If you delete a model, the migration generator will create a `drop_table` migration.
|
774
1829
|
|
775
|
-
|
1830
|
+
# Dropping tables is where the automatic down-migration really comes in handy:
|
776
1831
|
|
777
|
-
|
778
|
-
|
779
|
-
|
1832
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
1833
|
+
migrate_up(<<~EOS.strip)
|
1834
|
+
drop_table :adverts
|
1835
|
+
EOS
|
1836
|
+
.and migrate_down(<<~EOS.strip)
|
1837
|
+
create_table "adverts"#{table_options}, force: :cascade do |t|
|
1838
|
+
t.string "name", limit: 250#{charset_and_collation}
|
1839
|
+
end
|
1840
|
+
EOS
|
1841
|
+
)
|
780
1842
|
|
781
|
-
|
782
|
-
to eq(["adverts"])
|
783
|
-
expect(Advert.columns.map(&:name).sort).to eq(["body", "id", "title"])
|
784
|
-
expect(Generators::DeclareSchema::Migration::Migrator.run).to eq(["", ""])
|
1843
|
+
## STI
|
785
1844
|
|
1845
|
+
### Adding an STI subclass
|
786
1846
|
|
787
|
-
|
1847
|
+
# Adding a subclass or two should introduce the 'type' column and no other changes
|
788
1848
|
|
789
|
-
|
1849
|
+
class Advert < ActiveRecord::Base
|
1850
|
+
declare_schema do
|
1851
|
+
text :body, null: true
|
1852
|
+
string :title, default: "Untitled", limit: 250, null: true
|
1853
|
+
end
|
1854
|
+
end
|
1855
|
+
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
1856
|
+
ActiveRecord::Migration.class_eval(up)
|
790
1857
|
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
body :text, null: true
|
1858
|
+
class FancyAdvert < Advert
|
1859
|
+
end
|
1860
|
+
class SuperFancyAdvert < FancyAdvert
|
795
1861
|
end
|
796
|
-
end
|
797
1862
|
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
1863
|
+
up, _ = Generators::DeclareSchema::Migration::Migrator.run do |migrations|
|
1864
|
+
expect(migrations).to(
|
1865
|
+
migrate_up(<<~EOS.strip)
|
1866
|
+
add_column :adverts, :type, :string, limit: 250, null: true#{charset_and_collation}
|
1867
|
+
add_index :adverts, [:type], name: :on_type
|
1868
|
+
EOS
|
1869
|
+
.and migrate_down(<<~EOS.strip)
|
1870
|
+
remove_index :adverts, name: :on_type
|
1871
|
+
remove_column :adverts, :type
|
1872
|
+
EOS
|
1873
|
+
)
|
1874
|
+
end
|
808
1875
|
|
809
|
-
|
1876
|
+
Advert.field_specs.delete(:type)
|
1877
|
+
nuke_model_class(SuperFancyAdvert)
|
1878
|
+
nuke_model_class(FancyAdvert)
|
1879
|
+
Advert.index_definitions.delete_if { |spec| spec.fields==["type"] }
|
810
1880
|
|
811
|
-
|
812
|
-
class Ad < ActiveRecord::Base
|
813
|
-
fields do
|
814
|
-
title :string, default: "Untitled", limit: 250
|
815
|
-
body :text, null: true
|
816
|
-
created_at :datetime
|
817
|
-
end
|
818
|
-
end
|
1881
|
+
## Coping with multiple changes
|
819
1882
|
|
820
|
-
|
821
|
-
migrate_up(<<~EOS.strip)
|
822
|
-
rename_table :adverts, :ads
|
1883
|
+
# The migration generator is designed to create complete migrations even if many changes to the models have taken place.
|
823
1884
|
|
824
|
-
|
825
|
-
change_column :ads, :title, :string, limit: 250, null: false, default: \"Untitled\"#{charset_and_collation}
|
1885
|
+
# First let's confirm we're in a known state. One model, 'Advert', with a string 'title' and text 'body':
|
826
1886
|
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
'execute "ALTER TABLE ads DROP PRIMARY KEY, ADD PRIMARY KEY (id)"'
|
831
|
-
end}
|
832
|
-
EOS
|
833
|
-
)
|
1887
|
+
ActiveRecord::Migration.class_eval up.gsub(/.*type.*/, '')
|
1888
|
+
Advert.connection.schema_cache.clear!
|
1889
|
+
Advert.reset_column_information
|
834
1890
|
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
end
|
840
|
-
end
|
1891
|
+
expect(Advert.connection.tables - Generators::DeclareSchema::Migration::Migrator.always_ignore_tables).
|
1892
|
+
to eq(["adverts"])
|
1893
|
+
expect(Advert.columns.map(&:name).sort).to eq(["body", "id", "title"])
|
1894
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to eq(["", ""])
|
841
1895
|
|
842
|
-
## Legacy Keys
|
843
1896
|
|
844
|
-
|
1897
|
+
### Rename a column and change the default
|
845
1898
|
|
846
|
-
|
1899
|
+
Advert.field_specs.clear
|
847
1900
|
|
848
|
-
|
849
|
-
|
850
|
-
|
1901
|
+
class Advert < ActiveRecord::Base
|
1902
|
+
declare_schema do
|
1903
|
+
string :name, default: "No Name", limit: 250, null: true
|
1904
|
+
text :body, null: true
|
1905
|
+
end
|
851
1906
|
end
|
852
|
-
self.primary_key = "advert_id"
|
853
|
-
end
|
854
1907
|
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
)
|
1908
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run(adverts: { title: :name })).to(
|
1909
|
+
migrate_up(<<~EOS.strip)
|
1910
|
+
rename_column :adverts, :title, :name
|
1911
|
+
change_column :adverts, :name, :string, limit: 250, null: true, default: "No Name"#{charset_and_collation}
|
1912
|
+
EOS
|
1913
|
+
.and migrate_down(<<~EOS.strip)
|
1914
|
+
change_column :adverts, :name, :string, limit: 250, null: true, default: "Untitled"#{charset_and_collation}
|
1915
|
+
rename_column :adverts, :name, :title
|
1916
|
+
EOS
|
1917
|
+
)
|
866
1918
|
|
867
|
-
|
868
|
-
ActiveRecord::Base.connection.execute("drop table `adverts`;")
|
1919
|
+
### Rename a table and add a column
|
869
1920
|
|
870
|
-
|
1921
|
+
nuke_model_class(Advert)
|
1922
|
+
class Ad < ActiveRecord::Base
|
1923
|
+
declare_schema do
|
1924
|
+
string :title, default: "Untitled", limit: 250
|
1925
|
+
text :body, null: true
|
1926
|
+
datetime :created_at
|
1927
|
+
end
|
1928
|
+
end
|
871
1929
|
|
872
|
-
|
1930
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run(adverts: :ads)).to(
|
1931
|
+
migrate_up(<<~EOS.strip)
|
1932
|
+
rename_table :adverts, :ads
|
1933
|
+
add_column :ads, :created_at, :datetime, null: false
|
1934
|
+
change_column :ads, :title, :string, limit: 250, null: false, default: \"Untitled\"#{charset_and_collation}
|
1935
|
+
EOS
|
1936
|
+
)
|
873
1937
|
|
874
|
-
|
875
|
-
|
876
|
-
|
1938
|
+
class Advert < ActiveRecord::Base
|
1939
|
+
declare_schema do
|
1940
|
+
text :body, null: true
|
1941
|
+
string :title, default: "Untitled", limit: 250, null: true
|
1942
|
+
end
|
877
1943
|
end
|
878
|
-
end
|
879
|
-
expect(User.field_specs.keys).to eq(['company'])
|
880
|
-
expect(User.field_specs['company'].options[:ruby_default]&.call).to eq("BigCorp")
|
881
1944
|
|
882
|
-
|
1945
|
+
## Legacy Keys
|
883
1946
|
|
884
|
-
|
1947
|
+
# DeclareSchema has some support for legacy keys.
|
885
1948
|
|
886
|
-
|
887
|
-
|
888
|
-
|
1949
|
+
nuke_model_class(Ad)
|
1950
|
+
|
1951
|
+
class Advert < ActiveRecord::Base
|
1952
|
+
declare_schema do
|
1953
|
+
text :body, null: true
|
889
1954
|
end
|
1955
|
+
self.primary_key = "advert_id"
|
890
1956
|
end
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
1957
|
+
|
1958
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run(adverts: { id: :advert_id })).to(
|
1959
|
+
migrate_up(<<~EOS.strip)
|
1960
|
+
rename_column :adverts, :id, :advert_id
|
1961
|
+
EOS
|
1962
|
+
)
|
1963
|
+
|
1964
|
+
nuke_model_class(Advert)
|
1965
|
+
ActiveRecord::Base.connection.execute("drop table `adverts`;")
|
1966
|
+
|
1967
|
+
## DSL
|
1968
|
+
|
1969
|
+
# The DSL allows lambdas and constants
|
1970
|
+
|
1971
|
+
class User < ActiveRecord::Base
|
1972
|
+
declare_schema do
|
1973
|
+
string :company, limit: 250, ruby_default: -> { "BigCorp" }
|
1974
|
+
end
|
896
1975
|
end
|
897
|
-
|
898
|
-
|
899
|
-
up, _down = Generators::DeclareSchema::Migration::Migrator.run
|
900
|
-
ActiveRecord::Migration.class_eval(up)
|
901
|
-
expect(Ad.field_specs['company'].options[:validates].inspect).to eq("{:presence=>true, :uniqueness=>{:case_sensitive=>false}}")
|
902
|
-
end
|
1976
|
+
expect(User.field_specs.keys).to eq(['company'])
|
1977
|
+
expect(User.field_specs['company'].options[:ruby_default]&.call).to eq("BigCorp")
|
903
1978
|
|
904
|
-
|
905
|
-
before do
|
906
|
-
class Ad < ActiveRecord::Base
|
907
|
-
@serialize_args = []
|
1979
|
+
## validates
|
908
1980
|
|
909
|
-
|
910
|
-
attr_reader :serialize_args
|
1981
|
+
# DeclareSchema can accept a validates hash in the field options.
|
911
1982
|
|
912
|
-
|
913
|
-
|
1983
|
+
class Ad < ActiveRecord::Base
|
1984
|
+
class << self
|
1985
|
+
def validates(field_name, options)
|
914
1986
|
end
|
915
1987
|
end
|
916
1988
|
end
|
1989
|
+
expect(Ad).to receive(:validates).with(:company, presence: true, uniqueness: { case_sensitive: false })
|
1990
|
+
class Ad < ActiveRecord::Base
|
1991
|
+
declare_schema do
|
1992
|
+
string :company, limit: 250, index: true, unique: true, validates: { presence: true, uniqueness: { case_sensitive: false } }
|
1993
|
+
end
|
1994
|
+
self.primary_key = "advert_id"
|
1995
|
+
end
|
1996
|
+
up, _down = Generators::DeclareSchema::Migration::Migrator.run
|
1997
|
+
ActiveRecord::Migration.class_eval(up)
|
1998
|
+
expect(Ad.field_specs['company'].options[:validates].inspect).to eq("{:presence=>true, :uniqueness=>{:case_sensitive=>false}}")
|
917
1999
|
end
|
918
2000
|
|
919
|
-
|
920
|
-
|
921
|
-
class
|
2001
|
+
context 'models with the same parent foreign key relation' do
|
2002
|
+
before do
|
2003
|
+
class Category < ActiveRecord::Base
|
922
2004
|
fields do
|
923
|
-
|
2005
|
+
name :string, limit: 250, null: true
|
924
2006
|
end
|
925
2007
|
end
|
926
|
-
|
927
|
-
expect(Ad.serialize_args).to eq([[:allow_list]])
|
928
|
-
end
|
929
|
-
|
930
|
-
it 'converts defaults with .to_yaml' do
|
931
|
-
class Ad < ActiveRecord::Base
|
2008
|
+
class Advertiser < ActiveRecord::Base
|
932
2009
|
fields do
|
933
|
-
|
934
|
-
allow_hash :string, limit: 250, serialize: true, null: true, default: {}
|
935
|
-
allow_string :string, limit: 250, serialize: true, null: true, default: ['abc']
|
936
|
-
allow_null :string, limit: 250, serialize: true, null: true, default: nil
|
2010
|
+
name :string, limit: 250, null: true
|
937
2011
|
end
|
2012
|
+
belongs_to :category, limit: 8
|
938
2013
|
end
|
939
|
-
|
940
|
-
expect(Ad.field_specs['allow_list'].default).to eq("--- []\n")
|
941
|
-
expect(Ad.field_specs['allow_hash'].default).to eq("--- {}\n")
|
942
|
-
expect(Ad.field_specs['allow_string'].default).to eq("---\n- abc\n")
|
943
|
-
expect(Ad.field_specs['allow_null'].default).to eq(nil)
|
944
|
-
end
|
945
|
-
end
|
946
|
-
|
947
|
-
describe 'Array' do
|
948
|
-
it 'allows serialize: Array' do
|
949
|
-
class Ad < ActiveRecord::Base
|
2014
|
+
class Affiliate < ActiveRecord::Base
|
950
2015
|
fields do
|
951
|
-
|
2016
|
+
name :string, limit: 250, null: true
|
952
2017
|
end
|
2018
|
+
belongs_to :category, limit: 8
|
953
2019
|
end
|
954
|
-
|
955
|
-
expect(Ad.serialize_args).to eq([[:allow_list, Array]])
|
956
2020
|
end
|
957
2021
|
|
958
|
-
it '
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
allow_empty :string, limit: 250, serialize: Array, null: true, default: []
|
964
|
-
allow_null :string, limit: 250, serialize: Array, null: true, default: nil
|
2022
|
+
it 'will genereate unique constraint names' do
|
2023
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
2024
|
+
migrate_up(<<~EOS.strip)
|
2025
|
+
create_table :categories, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
|
2026
|
+
t.string :name, limit: 250, null: true, charset: "utf8mb4", collation: "utf8mb4_bin"
|
965
2027
|
end
|
966
|
-
|
2028
|
+
create_table :advertisers, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
|
2029
|
+
t.string :name, limit: 250, null: true, charset: "utf8mb4", collation: "utf8mb4_bin"
|
2030
|
+
t.integer :category_id, limit: 8, null: false
|
2031
|
+
end
|
2032
|
+
create_table :affiliates, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
|
2033
|
+
t.string :name, limit: 250, null: true, charset: "utf8mb4", collation: "utf8mb4_bin"
|
2034
|
+
t.integer :category_id, limit: 8, null: false
|
2035
|
+
end
|
2036
|
+
add_index :advertisers, [:category_id], name: :on_category_id
|
2037
|
+
add_index :affiliates, [:category_id], name: :on_category_id
|
2038
|
+
add_foreign_key :advertisers, :categories, column: :category_id, name: :index_advertisers_on_category_id
|
2039
|
+
add_foreign_key :affiliates, :categories, column: :category_id, name: :index_affiliates_on_category_id
|
2040
|
+
EOS
|
2041
|
+
)
|
2042
|
+
binding.pry
|
2043
|
+
migrate
|
967
2044
|
|
968
|
-
|
969
|
-
|
970
|
-
expect(Ad.field_specs['allow_empty'].default).to eq(nil)
|
971
|
-
expect(Ad.field_specs['allow_null'].default).to eq(nil)
|
2045
|
+
nuke_model_class(Advertiser)
|
2046
|
+
nuke_model_class(Affiliate)
|
972
2047
|
end
|
973
|
-
end
|
2048
|
+
end if !defined?(SQLite3) && ActiveRecord::VERSION::MAJOR >= 5
|
974
2049
|
|
975
|
-
describe '
|
976
|
-
|
2050
|
+
describe 'serialize' do
|
2051
|
+
before do
|
977
2052
|
class Ad < ActiveRecord::Base
|
978
|
-
|
979
|
-
|
2053
|
+
@serialize_args = []
|
2054
|
+
|
2055
|
+
class << self
|
2056
|
+
attr_reader :serialize_args
|
2057
|
+
|
2058
|
+
def serialize(*args)
|
2059
|
+
@serialize_args << args
|
2060
|
+
end
|
980
2061
|
end
|
981
2062
|
end
|
982
|
-
|
983
|
-
expect(Ad.serialize_args).to eq([[:allow_list, Hash]])
|
984
2063
|
end
|
985
2064
|
|
986
|
-
|
987
|
-
|
988
|
-
|
989
|
-
|
990
|
-
|
991
|
-
|
2065
|
+
describe 'untyped' do
|
2066
|
+
it 'allows serialize: true' do
|
2067
|
+
class Ad < ActiveRecord::Base
|
2068
|
+
declare_schema do
|
2069
|
+
text :allow_list, limit: 0xFFFF, serialize: true
|
2070
|
+
end
|
992
2071
|
end
|
2072
|
+
|
2073
|
+
expect(Ad.serialize_args).to eq([[:allow_list]])
|
993
2074
|
end
|
994
2075
|
|
995
|
-
|
996
|
-
|
997
|
-
|
2076
|
+
it 'converts defaults with .to_yaml' do
|
2077
|
+
class Ad < ActiveRecord::Base
|
2078
|
+
declare_schema do
|
2079
|
+
string :allow_list, limit: 250, serialize: true, null: true, default: []
|
2080
|
+
string :allow_hash, limit: 250, serialize: true, null: true, default: {}
|
2081
|
+
string :allow_string, limit: 250, serialize: true, null: true, default: ['abc']
|
2082
|
+
string :allow_null, limit: 250, serialize: true, null: true, default: nil
|
2083
|
+
end
|
2084
|
+
end
|
2085
|
+
|
2086
|
+
expect(Ad.field_specs['allow_list'].default).to eq("--- []\n")
|
2087
|
+
expect(Ad.field_specs['allow_hash'].default).to eq("--- {}\n")
|
2088
|
+
expect(Ad.field_specs['allow_string'].default).to eq("---\n- abc\n")
|
2089
|
+
expect(Ad.field_specs['allow_null'].default).to eq(nil)
|
2090
|
+
end
|
998
2091
|
end
|
999
|
-
end
|
1000
2092
|
|
1001
|
-
|
1002
|
-
|
1003
|
-
|
1004
|
-
|
1005
|
-
|
2093
|
+
describe 'Array' do
|
2094
|
+
it 'allows serialize: Array' do
|
2095
|
+
class Ad < ActiveRecord::Base
|
2096
|
+
declare_schema do
|
2097
|
+
string :allow_list, limit: 250, serialize: Array, null: true
|
2098
|
+
end
|
1006
2099
|
end
|
2100
|
+
|
2101
|
+
expect(Ad.serialize_args).to eq([[:allow_list, Array]])
|
1007
2102
|
end
|
1008
2103
|
|
1009
|
-
|
2104
|
+
it 'allows Array defaults' do
|
2105
|
+
class Ad < ActiveRecord::Base
|
2106
|
+
declare_schema do
|
2107
|
+
string :allow_list, limit: 250, serialize: Array, null: true, default: [2]
|
2108
|
+
string :allow_string, limit: 250, serialize: Array, null: true, default: ['abc']
|
2109
|
+
string :allow_empty, limit: 250, serialize: Array, null: true, default: []
|
2110
|
+
string :allow_null, limit: 250, serialize: Array, null: true, default: nil
|
2111
|
+
end
|
2112
|
+
end
|
2113
|
+
|
2114
|
+
expect(Ad.field_specs['allow_list'].default).to eq("---\n- 2\n")
|
2115
|
+
expect(Ad.field_specs['allow_string'].default).to eq("---\n- abc\n")
|
2116
|
+
expect(Ad.field_specs['allow_empty'].default).to eq(nil)
|
2117
|
+
expect(Ad.field_specs['allow_null'].default).to eq(nil)
|
2118
|
+
end
|
1010
2119
|
end
|
1011
2120
|
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
allow_null :string, limit: 250, serialize: JSON, null: true, default: nil
|
2121
|
+
describe 'Hash' do
|
2122
|
+
it 'allows serialize: Hash' do
|
2123
|
+
class Ad < ActiveRecord::Base
|
2124
|
+
declare_schema do
|
2125
|
+
string :allow_list, limit: 250, serialize: Hash, null: true
|
2126
|
+
end
|
1019
2127
|
end
|
1020
|
-
end
|
1021
2128
|
|
1022
|
-
|
1023
|
-
|
1024
|
-
expect(Ad.field_specs['allow_empty_hash'].default).to eq("{}")
|
1025
|
-
expect(Ad.field_specs['allow_null'].default).to eq(nil)
|
1026
|
-
end
|
1027
|
-
end
|
2129
|
+
expect(Ad.serialize_args).to eq([[:allow_list, Hash]])
|
2130
|
+
end
|
1028
2131
|
|
1029
|
-
|
1030
|
-
|
2132
|
+
it 'allows Hash defaults' do
|
2133
|
+
class Ad < ActiveRecord::Base
|
2134
|
+
declare_schema do
|
2135
|
+
string :allow_loc, limit: 250, serialize: Hash, null: true, default: { 'state' => 'CA' }
|
2136
|
+
string :allow_hash, limit: 250, serialize: Hash, null: true, default: {}
|
2137
|
+
string :allow_null, limit: 250, serialize: Hash, null: true, default: nil
|
2138
|
+
end
|
2139
|
+
end
|
1031
2140
|
|
1032
|
-
|
1033
|
-
|
2141
|
+
expect(Ad.field_specs['allow_loc'].default).to eq("---\nstate: CA\n")
|
2142
|
+
expect(Ad.field_specs['allow_hash'].default).to eq(nil)
|
2143
|
+
expect(Ad.field_specs['allow_null'].default).to eq(nil)
|
2144
|
+
end
|
1034
2145
|
end
|
1035
2146
|
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1039
|
-
|
2147
|
+
describe 'JSON' do
|
2148
|
+
it 'allows serialize: JSON' do
|
2149
|
+
class Ad < ActiveRecord::Base
|
2150
|
+
declare_schema do
|
2151
|
+
string :allow_list, limit: 250, serialize: JSON
|
2152
|
+
end
|
1040
2153
|
end
|
2154
|
+
|
2155
|
+
expect(Ad.serialize_args).to eq([[:allow_list, JSON]])
|
1041
2156
|
end
|
1042
2157
|
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
2158
|
+
it 'allows JSON defaults' do
|
2159
|
+
class Ad < ActiveRecord::Base
|
2160
|
+
declare_schema do
|
2161
|
+
string :allow_hash, limit: 250, serialize: JSON, null: true, default: { 'state' => 'CA' }
|
2162
|
+
string :allow_empty_array, limit: 250, serialize: JSON, null: true, default: []
|
2163
|
+
string :allow_empty_hash, limit: 250, serialize: JSON, null: true, default: {}
|
2164
|
+
string :allow_null, limit: 250, serialize: JSON, null: true, default: nil
|
2165
|
+
end
|
1046
2166
|
end
|
2167
|
+
|
2168
|
+
expect(Ad.field_specs['allow_hash'].default).to eq("{\"state\":\"CA\"}")
|
2169
|
+
expect(Ad.field_specs['allow_empty_array'].default).to eq("[]")
|
2170
|
+
expect(Ad.field_specs['allow_empty_hash'].default).to eq("{}")
|
2171
|
+
expect(Ad.field_specs['allow_null'].default).to eq(nil)
|
1047
2172
|
end
|
1048
2173
|
end
|
1049
|
-
end
|
1050
2174
|
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1056
|
-
end
|
2175
|
+
class ValueClass
|
2176
|
+
delegate :present?, :inspect, to: :@value
|
2177
|
+
|
2178
|
+
def initialize(value)
|
2179
|
+
@value = value
|
1057
2180
|
end
|
1058
2181
|
|
1059
|
-
|
1060
|
-
|
2182
|
+
class << self
|
2183
|
+
def dump(object)
|
2184
|
+
if object&.present?
|
2185
|
+
object.inspect
|
2186
|
+
end
|
2187
|
+
end
|
1061
2188
|
|
1062
|
-
|
1063
|
-
|
1064
|
-
|
1065
|
-
|
1066
|
-
allow_empty_array :string, limit: 250, serialize: ValueClass, null: true, default: ValueClass.new([])
|
1067
|
-
allow_null :string, limit: 250, serialize: ValueClass, null: true, default: nil
|
2189
|
+
def load(serialized)
|
2190
|
+
if serialized
|
2191
|
+
raise 'not used ???'
|
2192
|
+
end
|
1068
2193
|
end
|
1069
2194
|
end
|
1070
|
-
|
1071
|
-
expect(Ad.field_specs['allow_hash'].default).to eq("[2]")
|
1072
|
-
expect(Ad.field_specs['allow_empty_array'].default).to eq(nil)
|
1073
|
-
expect(Ad.field_specs['allow_null'].default).to eq(nil)
|
1074
2195
|
end
|
1075
|
-
end
|
1076
2196
|
|
1077
|
-
|
1078
|
-
|
1079
|
-
|
1080
|
-
|
1081
|
-
|
2197
|
+
describe 'custom coder' do
|
2198
|
+
it 'allows serialize: ValueClass' do
|
2199
|
+
class Ad < ActiveRecord::Base
|
2200
|
+
declare_schema do
|
2201
|
+
string :allow_list, limit: 250, serialize: ValueClass
|
2202
|
+
end
|
1082
2203
|
end
|
1083
|
-
end
|
1084
|
-
end.to raise_exception(ArgumentError, /must be :string or :text/)
|
1085
|
-
end
|
1086
|
-
end
|
1087
2204
|
|
1088
|
-
|
1089
|
-
|
1090
|
-
let(:optional_true) { { optional: true } }
|
1091
|
-
let(:optional_false) { { optional: false } }
|
1092
|
-
else
|
1093
|
-
let(:optional_true) { {} }
|
1094
|
-
let(:optional_false) { {} }
|
1095
|
-
end
|
1096
|
-
let(:optional_flag) { { false => optional_false, true => optional_true } }
|
2205
|
+
expect(Ad.serialize_args).to eq([[:allow_list, ValueClass]])
|
2206
|
+
end
|
1097
2207
|
|
1098
|
-
|
1099
|
-
|
1100
|
-
|
1101
|
-
|
1102
|
-
|
2208
|
+
it 'allows ValueClass defaults' do
|
2209
|
+
class Ad < ActiveRecord::Base
|
2210
|
+
declare_schema do
|
2211
|
+
string :allow_hash, limit: 250, serialize: ValueClass, null: true, default: ValueClass.new([2])
|
2212
|
+
string :allow_empty_array, limit: 250, serialize: ValueClass, null: true, default: ValueClass.new([])
|
2213
|
+
string :allow_null, limit: 250, serialize: ValueClass, null: true, default: nil
|
2214
|
+
end
|
1103
2215
|
end
|
2216
|
+
|
2217
|
+
expect(Ad.field_specs['allow_hash'].default).to eq("[2]")
|
2218
|
+
expect(Ad.field_specs['allow_empty_array'].default).to eq(nil)
|
2219
|
+
expect(Ad.field_specs['allow_null'].default).to eq(nil)
|
1104
2220
|
end
|
2221
|
+
end
|
1105
2222
|
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
1109
|
-
|
1110
|
-
|
2223
|
+
it 'disallows serialize: with a non-string column type' do
|
2224
|
+
expect do
|
2225
|
+
class Ad < ActiveRecord::Base
|
2226
|
+
declare_schema do
|
2227
|
+
integer :allow_list, limit: 8, serialize: true
|
2228
|
+
end
|
1111
2229
|
end
|
1112
|
-
end
|
1113
|
-
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
1114
|
-
ActiveRecord::Migration.class_eval(up)
|
2230
|
+
end.to raise_exception(ArgumentError, /must be :string or :text/)
|
1115
2231
|
end
|
2232
|
+
end
|
1116
2233
|
|
1117
|
-
|
1118
|
-
|
1119
|
-
|
1120
|
-
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
1124
|
-
expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_true)
|
2234
|
+
context "for Rails #{ActiveSupport::VERSION::MAJOR}" do
|
2235
|
+
if ActiveSupport::VERSION::MAJOR >= 5
|
2236
|
+
let(:optional_true) { { optional: true } }
|
2237
|
+
let(:optional_false) { { optional: false } }
|
2238
|
+
else
|
2239
|
+
let(:optional_true) { {} }
|
2240
|
+
let(:optional_false) { {} }
|
1125
2241
|
end
|
2242
|
+
let(:optional_flag) { { false => optional_false, true => optional_true } }
|
2243
|
+
|
2244
|
+
describe 'belongs_to' do
|
2245
|
+
before do
|
2246
|
+
unless defined?(AdCategory)
|
2247
|
+
class AdCategory < ActiveRecord::Base
|
2248
|
+
declare_schema { }
|
2249
|
+
end
|
2250
|
+
end
|
1126
2251
|
|
1127
|
-
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1131
|
-
|
1132
|
-
|
1133
|
-
belongs_to :ad_category, optional: true, null: false
|
2252
|
+
class Advert < ActiveRecord::Base
|
2253
|
+
declare_schema do
|
2254
|
+
string :name, limit: 250, null: true
|
2255
|
+
integer :category_id, limit: 8
|
2256
|
+
integer :nullable_category_id, limit: 8, null: true
|
2257
|
+
end
|
1134
2258
|
end
|
1135
|
-
|
1136
|
-
|
2259
|
+
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
2260
|
+
ActiveRecord::Migration.class_eval(up)
|
1137
2261
|
end
|
1138
2262
|
|
1139
|
-
it 'passes through optional:
|
2263
|
+
it 'passes through optional: when given' do
|
1140
2264
|
class AdvertBelongsTo < ActiveRecord::Base
|
1141
2265
|
self.table_name = 'adverts'
|
1142
|
-
|
2266
|
+
declare_schema { }
|
1143
2267
|
reset_column_information
|
1144
|
-
belongs_to :ad_category, optional:
|
2268
|
+
belongs_to :ad_category, optional: true
|
1145
2269
|
end
|
1146
|
-
expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(
|
1147
|
-
expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(true)
|
2270
|
+
expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_true)
|
1148
2271
|
end
|
1149
|
-
end
|
1150
2272
|
|
1151
|
-
|
1152
|
-
|
1153
|
-
|
1154
|
-
|
1155
|
-
|
1156
|
-
|
1157
|
-
|
1158
|
-
|
1159
|
-
|
1160
|
-
expect(AdvertBelongsTo.
|
1161
|
-
expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(nullable)
|
2273
|
+
describe 'contradictory settings' do # contradictory settings are ok--for example, during migration
|
2274
|
+
it 'passes through optional: true, null: false' do
|
2275
|
+
class AdvertBelongsTo < ActiveRecord::Base
|
2276
|
+
self.table_name = 'adverts'
|
2277
|
+
declare_schema { }
|
2278
|
+
reset_column_information
|
2279
|
+
belongs_to :ad_category, optional: true, null: false
|
2280
|
+
end
|
2281
|
+
expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_true)
|
2282
|
+
expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(false)
|
1162
2283
|
end
|
1163
2284
|
|
1164
|
-
it '
|
1165
|
-
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1171
|
-
expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(
|
1172
|
-
expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(
|
2285
|
+
it 'passes through optional: false, null: true' do
|
2286
|
+
class AdvertBelongsTo < ActiveRecord::Base
|
2287
|
+
self.table_name = 'adverts'
|
2288
|
+
declare_schema { }
|
2289
|
+
reset_column_information
|
2290
|
+
belongs_to :ad_category, optional: false, null: true
|
2291
|
+
end
|
2292
|
+
expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_false)
|
2293
|
+
expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(true)
|
1173
2294
|
end
|
1174
2295
|
end
|
1175
|
-
end
|
1176
|
-
end
|
1177
|
-
end
|
1178
2296
|
|
1179
|
-
|
1180
|
-
|
1181
|
-
|
1182
|
-
|
1183
|
-
|
2297
|
+
[false, true].each do |nullable|
|
2298
|
+
context "nullable=#{nullable}" do
|
2299
|
+
it 'infers optional: from null:' do
|
2300
|
+
eval <<~EOS
|
2301
|
+
class AdvertBelongsTo < ActiveRecord::Base
|
2302
|
+
declare_schema { }
|
2303
|
+
belongs_to :ad_category, null: #{nullable}
|
2304
|
+
end
|
2305
|
+
EOS
|
2306
|
+
expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_flag[nullable])
|
2307
|
+
expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(nullable)
|
2308
|
+
end
|
2309
|
+
|
2310
|
+
it 'infers null: from optional:' do
|
2311
|
+
eval <<~EOS
|
2312
|
+
class AdvertBelongsTo < ActiveRecord::Base
|
2313
|
+
declare_schema { }
|
2314
|
+
belongs_to :ad_category, optional: #{nullable}
|
2315
|
+
end
|
2316
|
+
EOS
|
2317
|
+
expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_flag[nullable])
|
2318
|
+
expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(nullable)
|
2319
|
+
end
|
2320
|
+
end
|
1184
2321
|
end
|
1185
2322
|
end
|
1186
|
-
|
1187
|
-
generate_migrations '-n', '-m'
|
1188
|
-
|
1189
|
-
migrations = Dir.glob('db/migrate/*declare_schema_migration*.rb')
|
1190
|
-
expect(migrations.size).to eq(1), migrations.inspect
|
1191
|
-
|
1192
|
-
migration_content = File.read(migrations.first)
|
1193
|
-
first_line = migration_content.split("\n").first
|
1194
|
-
base_class = first_line.split(' < ').last
|
1195
|
-
expect(base_class).to eq("(Rails::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration)")
|
1196
2323
|
end
|
1197
|
-
end
|
1198
2324
|
|
1199
|
-
|
1200
|
-
|
1201
|
-
if Rails::VERSION::MAJOR >= 5 || !ActiveRecord::Base.connection.class.name.match?(/SQLite3Adapter/)
|
2325
|
+
describe 'migration base class' do
|
2326
|
+
it 'adapts to Rails 4' do
|
1202
2327
|
class Advert < active_record_base_class.constantize
|
1203
|
-
|
1204
|
-
|
2328
|
+
declare_schema do
|
2329
|
+
string :title, limit: 100
|
1205
2330
|
end
|
1206
2331
|
end
|
1207
2332
|
|
@@ -1210,17 +2335,39 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
1210
2335
|
migrations = Dir.glob('db/migrate/*declare_schema_migration*.rb')
|
1211
2336
|
expect(migrations.size).to eq(1), migrations.inspect
|
1212
2337
|
|
1213
|
-
|
1214
|
-
|
1215
|
-
|
2338
|
+
migration_content = File.read(migrations.first)
|
2339
|
+
first_line = migration_content.split("\n").first
|
2340
|
+
base_class = first_line.split(' < ').last
|
2341
|
+
expect(base_class).to eq("(ActiveSupport::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration)")
|
2342
|
+
end
|
2343
|
+
end
|
1216
2344
|
|
1217
|
-
|
1218
|
-
|
1219
|
-
|
2345
|
+
context 'Does not generate migrations' do
|
2346
|
+
it 'for aliased fields bigint -> integer limit 8' do
|
2347
|
+
if ActiveSupport::VERSION::MAJOR >= 5 || !ActiveRecord::Base.connection.class.name.match?(/SQLite3Adapter/)
|
2348
|
+
class Advert < active_record_base_class.constantize
|
2349
|
+
declare_schema do
|
2350
|
+
bigint :price
|
2351
|
+
end
|
2352
|
+
end
|
2353
|
+
|
2354
|
+
generate_migrations '-n', '-m'
|
2355
|
+
|
2356
|
+
migrations = Dir.glob('db/migrate/*declare_schema_migration*.rb')
|
2357
|
+
expect(migrations.size).to eq(1), migrations.inspect
|
2358
|
+
|
2359
|
+
if defined?(Mysql2) && ActiveSupport::VERSION::MAJOR < 5
|
2360
|
+
ActiveRecord::Base.connection.execute("ALTER TABLE adverts ADD PRIMARY KEY (id)")
|
2361
|
+
end
|
2362
|
+
|
2363
|
+
class Advert < active_record_base_class.constantize
|
2364
|
+
declare_schema do
|
2365
|
+
integer :price, limit: 8
|
2366
|
+
end
|
1220
2367
|
end
|
1221
|
-
end
|
1222
2368
|
|
1223
|
-
|
2369
|
+
expect { generate_migrations '-n', '-g' }.to output("Database and models match -- nothing to change\n").to_stdout
|
2370
|
+
end
|
1224
2371
|
end
|
1225
2372
|
end
|
1226
2373
|
end
|