declare_schema 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/declare_schema_build.yml +21 -5
- data/Appraisals +21 -4
- data/CHANGELOG.md +40 -0
- data/Gemfile +1 -2
- data/Gemfile.lock +7 -9
- data/README.md +3 -3
- data/Rakefile +17 -4
- data/bin/declare_schema +1 -1
- data/declare_schema.gemspec +1 -1
- data/gemfiles/rails_4_mysql.gemfile +22 -0
- data/gemfiles/{rails_4.gemfile → rails_4_sqlite.gemfile} +1 -2
- data/gemfiles/rails_5_mysql.gemfile +22 -0
- data/gemfiles/{rails_5.gemfile → rails_5_sqlite.gemfile} +1 -2
- data/gemfiles/rails_6_mysql.gemfile +22 -0
- data/gemfiles/{rails_6.gemfile → rails_6_sqlite.gemfile} +2 -3
- data/lib/declare_schema/command.rb +10 -3
- data/lib/declare_schema/model/column.rb +168 -0
- data/lib/declare_schema/model/field_spec.rb +59 -143
- data/lib/declare_schema/model/foreign_key_definition.rb +36 -25
- data/lib/declare_schema/model/table_options_definition.rb +8 -6
- data/lib/declare_schema/version.rb +1 -1
- data/lib/generators/declare_schema/migration/migration_generator.rb +1 -1
- data/lib/generators/declare_schema/migration/migrator.rb +142 -116
- data/spec/lib/declare_schema/field_declaration_dsl_spec.rb +1 -1
- data/spec/lib/declare_schema/field_spec_spec.rb +135 -38
- data/spec/lib/declare_schema/generator_spec.rb +4 -2
- data/spec/lib/declare_schema/interactive_primary_key_spec.rb +8 -2
- data/spec/lib/declare_schema/migration_generator_spec.rb +277 -171
- data/spec/lib/declare_schema/model/column_spec.rb +141 -0
- data/spec/lib/declare_schema/model/foreign_key_definition_spec.rb +93 -0
- data/spec/lib/declare_schema/model/index_definition_spec.rb +4 -5
- data/spec/lib/declare_schema/model/table_options_definition_spec.rb +19 -29
- data/spec/lib/generators/declare_schema/migration/migrator_spec.rb +12 -26
- data/spec/support/acceptance_spec_helpers.rb +3 -3
- metadata +15 -9
@@ -0,0 +1,141 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'mysql2'
|
7
|
+
rescue LoadError
|
8
|
+
end
|
9
|
+
|
10
|
+
require_relative '../../../../lib/declare_schema/model/column'
|
11
|
+
|
12
|
+
RSpec.describe DeclareSchema::Model::Column do
|
13
|
+
before do
|
14
|
+
load File.expand_path('../prepare_testapp.rb', __dir__)
|
15
|
+
end
|
16
|
+
|
17
|
+
describe 'class methods' do
|
18
|
+
describe '.native_type?' do
|
19
|
+
if Rails::VERSION::MAJOR >= 5
|
20
|
+
let(:native_types) { [:string, :text, :integer, :float, :decimal, :datetime, :time, :date, :binary, :boolean, :json] }
|
21
|
+
else
|
22
|
+
let(:native_types) { [:string, :text, :integer, :float, :decimal, :datetime, :time, :date, :binary, :boolean] }
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'is falsey for :primary_key' do
|
26
|
+
expect(described_class.native_type?(:primary_key)).to be_falsey
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'is truthy for native types' do
|
30
|
+
native_types.each do |type|
|
31
|
+
expect(described_class.native_type?(type)).to be_truthy, type.inspect
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'is falsey for other types' do
|
36
|
+
[:email, :url].each do |type|
|
37
|
+
expect(described_class.native_type?(type)).to be_falsey
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '.native_types' do
|
43
|
+
subject { described_class.native_types }
|
44
|
+
|
45
|
+
it 'returns the native type for :primary_key' do
|
46
|
+
expect(subject[:primary_key]).to match(/auto_increment PRIMARY KEY|PRIMARY KEY AUTOINCREMENT NOT NULL/)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'returns the native type for :string' do
|
50
|
+
expect(subject.dig(:string, :name)).to eq('varchar')
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'returns the native type for :integer' do
|
54
|
+
expect(subject.dig(:integer, :name)).to match(/int/)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'returns the native type for :datetime' do
|
58
|
+
expect(subject.dig(:datetime, :name)).to eq('datetime')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe '.sql_type' do
|
63
|
+
it 'returns the sql type for :string' do
|
64
|
+
expect(described_class.sql_type(:string)).to eq(:string)
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'returns the sql type for :integer' do
|
68
|
+
expect(described_class.sql_type(:integer)).to match(:integer)
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'returns the sql type for :datetime' do
|
72
|
+
expect(described_class.sql_type(:datetime)).to eq(:datetime)
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'raises UnknownSqlType' do
|
76
|
+
expect do
|
77
|
+
described_class.sql_type(:email)
|
78
|
+
end.to raise_exception(::DeclareSchema::UnknownSqlTypeError, /:email for type :email/)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe '.deserialize_default_value' do
|
83
|
+
require 'rails'
|
84
|
+
|
85
|
+
if ::Rails::VERSION::MAJOR >= 5
|
86
|
+
it 'deserializes :boolean' do
|
87
|
+
expect(described_class.deserialize_default_value(nil, :boolean, 'true')).to eq(true)
|
88
|
+
expect(described_class.deserialize_default_value(nil, :boolean, 'false')).to eq(false)
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'deserializes :integer' do
|
92
|
+
expect(described_class.deserialize_default_value(nil, :integer, '12')).to eq(12)
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'deserializes :json' do
|
96
|
+
expect(described_class.deserialize_default_value(nil, :json, '{}')).to eq({})
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe 'instance methods' do
|
103
|
+
before do
|
104
|
+
class ColumnTestModel < ActiveRecord::Base
|
105
|
+
fields do
|
106
|
+
title :string, limit: 127, null: false
|
107
|
+
count :integer, null: false
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
let(:model) { ColumnTestModel }
|
112
|
+
let(:current_table_name) { model.table_name }
|
113
|
+
let(:column) { double("ActiveRecord Column",
|
114
|
+
name: 'count',
|
115
|
+
type: :integer,
|
116
|
+
limit: nil,
|
117
|
+
precision: nil,
|
118
|
+
scale: nil,
|
119
|
+
type_cast_from_database: nil,
|
120
|
+
null: false,
|
121
|
+
default: nil,
|
122
|
+
sql_type_metadata: {}) }
|
123
|
+
subject { described_class.new(model, current_table_name, column) }
|
124
|
+
|
125
|
+
describe '#sql_type' do
|
126
|
+
it 'returns sql type' do
|
127
|
+
expect(subject.sql_type).to match(/int/)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe '#schema_attributes' do
|
132
|
+
it 'returns a hash with relevant key/values' do
|
133
|
+
if defined?(Mysql2)
|
134
|
+
expect(subject.schema_attributes).to eq(type: :integer, null: false, limit: 4)
|
135
|
+
else
|
136
|
+
expect(subject.schema_attributes).to eq(type: :integer, null: false)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../../../lib/declare_schema/model/foreign_key_definition'
|
4
|
+
|
5
|
+
RSpec.describe DeclareSchema::Model::ForeignKeyDefinition do
|
6
|
+
before do
|
7
|
+
load File.expand_path('../prepare_testapp.rb', __dir__)
|
8
|
+
|
9
|
+
class Network < ActiveRecord::Base
|
10
|
+
fields do
|
11
|
+
name :string, limit: 127, index: true
|
12
|
+
|
13
|
+
timestamps
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
let(:model_class) { Network }
|
19
|
+
|
20
|
+
describe 'instance methods' do
|
21
|
+
let(:connection) { instance_double(ActiveRecord::Base.connection.class) }
|
22
|
+
let(:model) { instance_double('Model', table_name: 'models', connection: connection) }
|
23
|
+
let(:foreign_key) { :network_id }
|
24
|
+
let(:options) { {} }
|
25
|
+
subject { described_class.new(model, foreign_key, options)}
|
26
|
+
|
27
|
+
before do
|
28
|
+
allow(connection).to receive(:index_name).with('models', column: 'network_id') { 'on_network_id' }
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '#initialize' do
|
32
|
+
it 'normalizes symbols to strings' do
|
33
|
+
expect(subject.foreign_key).to eq('network_id')
|
34
|
+
expect(subject.parent_table_name).to eq('networks')
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'when most options passed' do
|
38
|
+
let(:options) { { parent_table: :networks, foreign_key: :the_network_id, index_name: :index_on_network_id } }
|
39
|
+
|
40
|
+
it 'normalizes symbols to strings' do
|
41
|
+
expect(subject.foreign_key).to eq('network_id')
|
42
|
+
expect(subject.foreign_key_name).to eq('the_network_id')
|
43
|
+
expect(subject.parent_table_name).to eq('networks')
|
44
|
+
expect(subject.foreign_key).to eq('network_id')
|
45
|
+
expect(subject.constraint_name).to eq('index_on_network_id')
|
46
|
+
expect(subject.on_delete_cascade).to be_falsey
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'when all options passed' do
|
51
|
+
let(:foreign_key) { nil }
|
52
|
+
let(:options) { { parent_table: :networks, foreign_key: :the_network_id, index_name: :index_on_network_id,
|
53
|
+
constraint_name: :constraint_1, dependent: :delete } }
|
54
|
+
|
55
|
+
it 'normalizes symbols to strings' do
|
56
|
+
expect(subject.foreign_key).to be_nil
|
57
|
+
expect(subject.foreign_key_name).to eq('the_network_id')
|
58
|
+
expect(subject.parent_table_name).to eq('networks')
|
59
|
+
expect(subject.constraint_name).to eq('constraint_1')
|
60
|
+
expect(subject.on_delete_cascade).to be_truthy
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe '#to_add_statement' do
|
66
|
+
it 'returns add_foreign_key command' do
|
67
|
+
expect(subject.to_add_statement).to eq('add_foreign_key("models", "networks", column: "network_id", name: "on_network_id")')
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe 'class << self' do
|
73
|
+
let(:connection) { instance_double(ActiveRecord::Base.connection.class) }
|
74
|
+
let(:model) { instance_double('Model', table_name: 'models', connection: connection) }
|
75
|
+
let(:old_table_name) { 'networks' }
|
76
|
+
before do
|
77
|
+
allow(connection).to receive(:quote_table_name).with('networks') { 'networks' }
|
78
|
+
allow(connection).to receive(:select_rows) { [['CONSTRAINT `constraint` FOREIGN KEY (`network_id`) REFERENCES `networks` (`id`)']] }
|
79
|
+
allow(connection).to receive(:index_name).with('models', column: 'network_id') { }
|
80
|
+
end
|
81
|
+
|
82
|
+
describe '.for_model' do
|
83
|
+
subject { described_class.for_model(model, old_table_name) }
|
84
|
+
|
85
|
+
it 'returns new object' do
|
86
|
+
expect(subject.size).to eq(1), subject.inspect
|
87
|
+
expect(subject.first).to be_kind_of(described_class)
|
88
|
+
expect(subject.first.foreign_key).to eq('network_id')
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
# TODO: fill out remaining tests
|
93
|
+
end
|
@@ -64,7 +64,7 @@ RSpec.describe DeclareSchema::Model::IndexDefinition do
|
|
64
64
|
ActiveRecord::Base.connection.execute <<~EOS
|
65
65
|
CREATE TABLE index_definition_test_models (
|
66
66
|
id INTEGER NOT NULL PRIMARY KEY,
|
67
|
-
name TEXT NOT NULL
|
67
|
+
name #{if defined?(Sqlite3) then 'TEXT' else 'VARCHAR(255)' end} NOT NULL
|
68
68
|
)
|
69
69
|
EOS
|
70
70
|
ActiveRecord::Base.connection.execute <<~EOS
|
@@ -72,8 +72,8 @@ RSpec.describe DeclareSchema::Model::IndexDefinition do
|
|
72
72
|
EOS
|
73
73
|
ActiveRecord::Base.connection.execute <<~EOS
|
74
74
|
CREATE TABLE index_definition_compound_index_models (
|
75
|
-
fk1_id INTEGER NULL,
|
76
|
-
fk2_id INTEGER NULL,
|
75
|
+
fk1_id INTEGER NOT NULL,
|
76
|
+
fk2_id INTEGER NOT NULL,
|
77
77
|
PRIMARY KEY (fk1_id, fk2_id)
|
78
78
|
)
|
79
79
|
EOS
|
@@ -99,10 +99,9 @@ RSpec.describe DeclareSchema::Model::IndexDefinition do
|
|
99
99
|
let(:model_class) { IndexDefinitionCompoundIndexModel }
|
100
100
|
|
101
101
|
it 'returns the indexes for the model' do
|
102
|
-
# Simulate MySQL for Rails 4 work-around
|
103
102
|
if Rails::VERSION::MAJOR < 5
|
104
103
|
expect(model_class.connection).to receive(:primary_key).with('index_definition_compound_index_models').and_return(nil)
|
105
|
-
connection_stub = instance_double(ActiveRecord::
|
104
|
+
connection_stub = instance_double(ActiveRecord::Base.connection.class, "connection")
|
106
105
|
expect(connection_stub).to receive(:indexes).
|
107
106
|
with('index_definition_compound_index_models').
|
108
107
|
and_return([DeclareSchema::Model::IndexDefinition.new(model_class, ['fk1_id', 'fk2_id'], name: 'PRIMARY')])
|
@@ -1,6 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
begin
|
4
|
+
require 'mysql2'
|
5
|
+
require 'active_record/connection_adapters/mysql2_adapter'
|
6
|
+
rescue LoadError
|
7
|
+
end
|
4
8
|
require_relative '../../../../lib/declare_schema/model/table_options_definition'
|
5
9
|
|
6
10
|
RSpec.describe DeclareSchema::Model::TableOptionsDefinition do
|
@@ -22,7 +26,7 @@ RSpec.describe DeclareSchema::Model::TableOptionsDefinition do
|
|
22
26
|
|
23
27
|
describe '#to_key' do
|
24
28
|
subject { model.to_key }
|
25
|
-
it { should eq([
|
29
|
+
it { should eq(['table_options_definition_test_models', '{:charset=>"utf8", :collation=>"utf8_general"}']) }
|
26
30
|
end
|
27
31
|
|
28
32
|
describe '#settings' do
|
@@ -32,7 +36,7 @@ RSpec.describe DeclareSchema::Model::TableOptionsDefinition do
|
|
32
36
|
|
33
37
|
describe '#hash' do
|
34
38
|
subject { model.hash }
|
35
|
-
it { should eq([
|
39
|
+
it { should eq(['table_options_definition_test_models', '{:charset=>"utf8", :collation=>"utf8_general"}'].hash) }
|
36
40
|
end
|
37
41
|
|
38
42
|
describe '#to_s' do
|
@@ -42,42 +46,28 @@ RSpec.describe DeclareSchema::Model::TableOptionsDefinition do
|
|
42
46
|
|
43
47
|
describe '#alter_table_statement' do
|
44
48
|
subject { model.alter_table_statement }
|
45
|
-
it { should
|
49
|
+
it { should match(/execute "ALTER TABLE .*table_options_definition_test_models.* CHARACTER SET utf8 COLLATE utf8_general"/) }
|
46
50
|
end
|
47
51
|
end
|
48
52
|
|
49
53
|
|
50
54
|
context 'class << self' do
|
51
55
|
describe '#for_model' do
|
52
|
-
context 'when
|
56
|
+
context 'when database migrated' do
|
57
|
+
let(:options) do
|
58
|
+
if defined?(Mysql2)
|
59
|
+
{ charset: "utf8mb4", collation: "utf8mb4_bin" }
|
60
|
+
else
|
61
|
+
{ }
|
62
|
+
end
|
63
|
+
end
|
53
64
|
subject { described_class.for_model(model_class) }
|
54
|
-
|
55
|
-
end
|
56
|
-
# TODO: Convert these tests to run against a MySQL database so that we can
|
57
|
-
# perform them without mocking out so much
|
58
|
-
context 'when using a MySQL connection' do
|
65
|
+
|
59
66
|
before do
|
60
|
-
|
61
|
-
expect(stub_connection).to receive(:class).and_return(ActiveRecord::ConnectionAdapters::Mysql2Adapter)
|
62
|
-
expect(stub_connection).to receive(:current_database).and_return('test_database')
|
63
|
-
expect(stub_connection).to receive(:quote_string).with('test_database').and_return('test_database')
|
64
|
-
expect(stub_connection).to receive(:quote_string).with(model_class.table_name).and_return(model_class.table_name)
|
65
|
-
expect(stub_connection).to(
|
66
|
-
receive(:select_one).with(<<~EOS)
|
67
|
-
SELECT CCSA.character_set_name, CCSA.collation_name
|
68
|
-
FROM information_schema.`TABLES` T, information_schema.`COLLATION_CHARACTER_SET_APPLICABILITY` CCSA
|
69
|
-
WHERE CCSA.collation_name = T.table_collation AND
|
70
|
-
T.table_schema = 'test_database' AND
|
71
|
-
T.table_name = '#{model_class.table_name}';
|
72
|
-
EOS
|
73
|
-
.and_return({ "character_set_name" => "utf8", "collation_name" => "utf8_general" })
|
74
|
-
)
|
75
|
-
allow(model_class).to receive(:connection).and_return(stub_connection)
|
76
|
-
end
|
67
|
+
generate_migrations '-n', '-m'
|
77
68
|
end
|
78
69
|
|
79
|
-
|
80
|
-
it { should eq(described_class.new(model_class.table_name, { charset: "utf8", collation: "utf8_general" })) }
|
70
|
+
it { should eq(described_class.new(model_class.table_name, options)) }
|
81
71
|
end
|
82
72
|
end
|
83
73
|
end
|
@@ -1,5 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
begin
|
4
|
+
require 'mysql2'
|
5
|
+
rescue LoadError
|
6
|
+
end
|
3
7
|
require 'rails'
|
4
8
|
require 'rails/generators'
|
5
9
|
|
@@ -14,26 +18,8 @@ module Generators
|
|
14
18
|
subject { described_class.new }
|
15
19
|
|
16
20
|
describe 'format_options' do
|
17
|
-
|
18
|
-
|
19
|
-
context 'MySQL' do
|
20
|
-
before do
|
21
|
-
expect(::DeclareSchema::Model::FieldSpec).to receive(:mysql_text_limits?).and_return(true)
|
22
|
-
end
|
23
|
-
|
24
|
-
it 'returns text limits' do
|
25
|
-
expect(subject.format_options({ limit: mysql_longtext_limit }, :text)).to eq(["limit: #{mysql_longtext_limit}"])
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
context 'non-MySQL' do
|
30
|
-
before do
|
31
|
-
expect(::DeclareSchema::Model::FieldSpec).to receive(:mysql_text_limits?).and_return(false)
|
32
|
-
end
|
33
|
-
|
34
|
-
it 'returns text limits' do
|
35
|
-
expect(subject.format_options({ limit: mysql_longtext_limit }, :text)).to eq([])
|
36
|
-
end
|
21
|
+
it 'returns an array of option .inspect strings, with symbols using the modern : hash notation' do
|
22
|
+
expect(subject.format_options({ limit: 4, 'key' => 'value "quoted"' })).to eq(["limit: 4", '"key" => "value \"quoted\""'])
|
37
23
|
end
|
38
24
|
end
|
39
25
|
|
@@ -47,13 +33,13 @@ module Generators
|
|
47
33
|
subject { described_class.default_charset }
|
48
34
|
|
49
35
|
context 'when not explicitly set' do
|
50
|
-
it { should eq(
|
36
|
+
it { should eq("utf8mb4") }
|
51
37
|
end
|
52
38
|
|
53
39
|
context 'when explicitly set' do
|
54
|
-
before { described_class.default_charset =
|
40
|
+
before { described_class.default_charset = "utf8" }
|
55
41
|
after { described_class.default_charset = described_class::DEFAULT_CHARSET }
|
56
|
-
it { should eq(
|
42
|
+
it { should eq("utf8") }
|
57
43
|
end
|
58
44
|
end
|
59
45
|
|
@@ -61,13 +47,13 @@ module Generators
|
|
61
47
|
subject { described_class.default_collation }
|
62
48
|
|
63
49
|
context 'when not explicitly set' do
|
64
|
-
it { should eq(
|
50
|
+
it { should eq("utf8mb4_bin") }
|
65
51
|
end
|
66
52
|
|
67
53
|
context 'when explicitly set' do
|
68
|
-
before { described_class.default_collation =
|
54
|
+
before { described_class.default_collation = "utf8mb4_general_ci" }
|
69
55
|
after { described_class.default_collation = described_class::DEFAULT_COLLATION }
|
70
|
-
it { should eq(
|
56
|
+
it { should eq("utf8mb4_general_ci") }
|
71
57
|
end
|
72
58
|
end
|
73
59
|
|
@@ -23,7 +23,7 @@ module AcceptanceSpecHelpers
|
|
23
23
|
|
24
24
|
def expect_file_to_eq(file_path, expectation)
|
25
25
|
expect(File.exist?(file_path)).to be_truthy
|
26
|
-
expect(File.read(file_path)).to eq(expectation)
|
26
|
+
expect(File.read(file_path).gsub(/require '([^']*)'/, 'require "\1"')).to eq(expectation)
|
27
27
|
end
|
28
28
|
|
29
29
|
def clean_up_model(model)
|
@@ -45,13 +45,13 @@ module AcceptanceSpecHelpers
|
|
45
45
|
|
46
46
|
class MigrationUpEquals < RSpec::Matchers::BuiltIn::Eq
|
47
47
|
def matches?(subject)
|
48
|
-
super(subject[0])
|
48
|
+
super(subject[0].gsub(/, +([a-z_]+:)/i, ', \1')) # normalize multiple spaces to one
|
49
49
|
end
|
50
50
|
end
|
51
51
|
|
52
52
|
class MigrationDownEquals < RSpec::Matchers::BuiltIn::Eq
|
53
53
|
def matches?(subject)
|
54
|
-
super(subject[1])
|
54
|
+
super(subject[1].gsub(/, +([a-z_]+:)/i, ', \1')) # normalize multiple spaces to one
|
55
55
|
end
|
56
56
|
end
|
57
57
|
end
|