convergence 1.0.5 → 1.1.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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +55 -0
  3. data/.gitignore +1 -0
  4. data/CHANGELOG.md +52 -0
  5. data/CONTRIBUTING.md +80 -0
  6. data/Gemfile.lock +24 -9
  7. data/README.md +131 -1
  8. data/Rakefile +48 -5
  9. data/convergence.gemspec +3 -1
  10. data/lib/convergence/cli.rb +10 -2
  11. data/lib/convergence/command/apply.rb +38 -9
  12. data/lib/convergence/command/dryrun.rb +5 -5
  13. data/lib/convergence/command/export.rb +19 -1
  14. data/lib/convergence/command.rb +6 -0
  15. data/lib/convergence/config.rb +1 -1
  16. data/lib/convergence/database_connector/postgres_connector.rb +25 -0
  17. data/lib/convergence/database_connector/sqlite_connector.rb +22 -0
  18. data/lib/convergence/database_connector.rb +6 -0
  19. data/lib/convergence/default_parameter/mysql_default_parameter.rb +1 -1
  20. data/lib/convergence/default_parameter/postgres_default_parameter.rb +57 -0
  21. data/lib/convergence/default_parameter/sqlite_default_parameter.rb +57 -0
  22. data/lib/convergence/default_parameter.rb +6 -0
  23. data/lib/convergence/diff.rb +9 -2
  24. data/lib/convergence/dsl.rb +18 -3
  25. data/lib/convergence/dumper/mysql_schema_dumper.rb +37 -9
  26. data/lib/convergence/dumper/postgres_schema_dumper.rb +193 -0
  27. data/lib/convergence/dumper/sqlite_schema_dumper.rb +132 -0
  28. data/lib/convergence/dumper.rb +113 -0
  29. data/lib/convergence/sql_generator/mysql_generator.rb +18 -3
  30. data/lib/convergence/sql_generator/postgres_generator.rb +256 -0
  31. data/lib/convergence/sql_generator/sqlite_generator.rb +178 -0
  32. data/lib/convergence/version.rb +1 -1
  33. data/spec/config/spec_database.yml +15 -2
  34. data/spec/convergence/config_spec.rb +75 -0
  35. data/spec/convergence/diff_spec.rb +125 -45
  36. data/spec/convergence/dsl_spec.rb +26 -0
  37. data/spec/convergence/dumper/mysql_schema_dumper_spec.rb +25 -2
  38. data/spec/convergence/dumper/postgres_schema_dumper_spec.rb +103 -0
  39. data/spec/convergence/dumper/sqlite_schema_dumper_spec.rb +85 -0
  40. data/spec/convergence/dumper_spec.rb +110 -16
  41. data/spec/convergence/foreign_key_spec.rb +33 -0
  42. data/spec/convergence/index_spec.rb +52 -0
  43. data/spec/convergence/pretty_diff_spec.rb +85 -0
  44. data/spec/convergence/table_spec.rb +23 -0
  45. data/spec/fixtures/add_table_with_enum_set.schema +5 -0
  46. data/spec/fixtures/change_table_comment_to_paper.schema +2 -0
  47. data/spec/fixtures/execute_raw_sql.schema +28 -0
  48. data/spec/fixtures/postgres/add_columns_to_paper.schema +29 -0
  49. data/spec/fixtures/postgres/add_table.schema +32 -0
  50. data/spec/fixtures/postgres/change_comment_columns_to_paper.schema +28 -0
  51. data/spec/fixtures/postgres/change_table_comment_to_paper.schema +28 -0
  52. data/spec/fixtures/postgres/drop_foreign_key.schema +25 -0
  53. data/spec/fixtures/postgres/drop_table.schema +19 -0
  54. data/spec/fixtures/postgres/remove_columns_to_paper.schema +27 -0
  55. data/spec/fixtures/postgres_test_db.sql +41 -0
  56. data/spec/fixtures/sqlite/add_columns_to_paper.schema +29 -0
  57. data/spec/fixtures/sqlite/add_table.schema +32 -0
  58. data/spec/fixtures/sqlite/change_columns_to_paper.schema +28 -0
  59. data/spec/fixtures/sqlite/drop_foreign_key.schema +25 -0
  60. data/spec/fixtures/sqlite/drop_table.schema +19 -0
  61. data/spec/fixtures/sqlite/remove_columns_to_paper.schema +27 -0
  62. data/spec/fixtures/sqlite_test_db.sql +31 -0
  63. data/spec/fixtures/test_db.sql +10 -0
  64. data/spec/integrations/command_diff.rb +35 -0
  65. data/spec/integrations/command_dryrun.rb +37 -2
  66. data/spec/integrations/command_export.rb +28 -0
  67. data/spec/postgres_integrations/command_dryrun.rb +79 -0
  68. data/spec/spec_helper.rb +13 -0
  69. data/spec/sqlite_integrations/command_dryrun.rb +70 -0
  70. metadata +97 -9
  71. data/.travis.yml +0 -9
@@ -104,6 +104,7 @@ describe Convergence::DSL do
104
104
  let(:table_to) do
105
105
  Convergence::Table.new('table1').tap do |t|
106
106
  t.int('id')
107
+ t.varchar('name', limit: 200, null: false)
107
108
  end
108
109
  end
109
110
 
@@ -135,130 +136,209 @@ describe Convergence::DSL do
135
136
  end
136
137
 
137
138
  describe '#diff_table' do
138
- let(:table_from) do
139
- Convergence::Table.new('table1').tap do |t|
140
- t.int('id', primary_key: true)
141
- t.varchar('name', limit: 200, null: false)
139
+ subject(:results) { Convergence::Diff.new(**diff_options).diff_table(table_from, table_to) }
140
+ let(:diff_options) { {} }
141
+
142
+ context 'change auto_increment table option' do
143
+ let(:table_from) do
144
+ Convergence::Table.new('table1').tap do |t|
145
+ t.int('id', primary_key: true)
146
+ t.table_options = { auto_increment: 1 }
147
+ end
148
+ end
149
+ let(:table_to) do
150
+ Convergence::Table.new('table1').tap do |t|
151
+ t.int('id', primary_key: true)
152
+ t.table_options = { auto_increment: 5000 }
153
+ end
154
+ end
155
+
156
+ it 'detects the auto_increment change by default' do
157
+ expect(results[:change_table_option][:auto_increment]).to eq(5000)
158
+ end
159
+
160
+ context 'when ignore_auto_increment is enabled' do
161
+ let(:diff_options) { { ignore_auto_increment: true } }
142
162
 
143
- t.index('name')
144
- t.foreign_key('id', reference: 'ref_tables', reference_column: 'ref_id')
163
+ it 'ignores the auto_increment change' do
164
+ expect(results[:change_table_option]).not_to have_key(:auto_increment)
165
+ end
145
166
  end
146
167
  end
147
168
 
148
169
  context 'change column options' do
170
+ let(:table_from) do
171
+ Convergence::Table.new('table1').tap do |t|
172
+ t.int('id', primary_key: true)
173
+ t.varchar('name', limit: 200, null: false)
174
+ t.datetime('created_at', default: -> { "CURRENT_TIMESTAMP" })
175
+ end
176
+ end
149
177
  let(:table_to) do
150
178
  Convergence::Table.new('table1').tap do |t|
151
179
  t.int('id', primary_key: true)
152
- t.varchar('name', limit: 300, null: true, unsigned: true)
153
-
154
- t.index('name')
180
+ t.varchar('name', limit: 300, null: true, unsigned: true) # option changed
181
+ t.datetime('created_at', default: -> { "CURRENT_TIMESTAMP" }) # option[:default] not changed
155
182
  end
156
183
  end
157
184
 
158
185
  it do
159
- results = Convergence::Diff.new.diff_table(table_from, table_to)
160
186
  expect(results[:change_column]['name']).not_to be_nil
161
187
  expect(results[:change_column]['name'][:limit]).to eq('300')
162
188
  expect(results[:change_column]['name'][:null]).to eq('true')
163
189
  expect(results[:change_column]['name'][:unsigned]).to eq('true')
190
+ expect(results[:change_column]['created_at']).to be_nil
191
+ end
192
+ end
193
+
194
+ context 'change enum/set column values' do
195
+ let(:table_from) do
196
+ Convergence::Table.new('table1').tap do |t|
197
+ t.int('id', primary_key: true)
198
+ t.enum('status', values: %w(Active Inactive))
199
+ end
200
+ end
201
+
202
+ context 'when values are identical' do
203
+ let(:table_to) do
204
+ Convergence::Table.new('table1').tap do |t|
205
+ t.int('id', primary_key: true)
206
+ t.enum('status', values: %w(Active Inactive))
207
+ end
208
+ end
209
+
210
+ it 'does not detect a change' do
211
+ expect(results[:change_column]['status']).to be_nil
212
+ end
213
+ end
214
+
215
+ context 'when only the letter case differs' do
216
+ let(:table_to) do
217
+ Convergence::Table.new('table1').tap do |t|
218
+ t.int('id', primary_key: true)
219
+ t.enum('status', values: %w(active inactive))
220
+ end
221
+ end
222
+
223
+ it 'detects the change since values are case-sensitive' do
224
+ expect(results[:change_column]['status']).not_to be_nil
225
+ expect(results[:change_column]['status'][:values]).to eq('["active", "inactive"]')
226
+ end
164
227
  end
165
228
  end
166
229
 
167
230
  context 'change column order' do
231
+ let(:table_from) do
232
+ Convergence::Table.new('table1').tap do |t|
233
+ t.int('id', primary_key: true)
234
+ t.varchar('name', limit: 200, null: false)
235
+ end
236
+ end
168
237
  let(:table_to) do
169
238
  Convergence::Table.new('table1').tap do |t|
170
- t.varchar('name', limit: 300, null: true)
239
+ t.varchar('name', limit: 200, null: false)
171
240
  t.int('id', primary_key: true)
172
-
173
- t.index('name')
174
241
  end
175
242
  end
176
243
 
177
244
  it do
178
- results = Convergence::Diff.new.diff_table(table_from, table_to)
179
- expect(results[:change_column]['name']).not_to be_nil
245
+ expect(results[:change_column]['name']).to be_nil
180
246
  expect(results[:change_column]['id']).not_to be_nil
181
247
  expect(results[:change_column]['id'][:after]).to eq('name')
182
248
  end
183
249
  end
184
250
 
185
251
  context 'remove index' do
252
+ let(:table_from) do
253
+ Convergence::Table.new('table1').tap do |t|
254
+ t.int('id', primary_key: true)
255
+ t.varchar('name', limit: 200, null: false)
256
+
257
+ t.index('name')
258
+ end
259
+ end
186
260
  let(:table_to) do
187
261
  Convergence::Table.new('table1').tap do |t|
188
262
  t.int('id', primary_key: true)
189
- t.varchar('name', limit: 300, null: true)
263
+ t.varchar('name', limit: 200, null: false)
190
264
  end
191
265
  end
192
266
 
193
- it do
194
- results = Convergence::Diff.new.diff_table(table_from, table_to)
195
- expect(results[:remove_index].values.first.index_columns).to eq(['name'])
196
- end
267
+ it { expect(results[:remove_index].values.first.index_columns).to eq(['name']) }
197
268
  end
198
269
 
199
270
  context 'add index' do
271
+ let(:table_from) do
272
+ Convergence::Table.new('table1').tap do |t|
273
+ t.int('id', primary_key: true)
274
+ t.varchar('name', limit: 200, null: false)
275
+ end
276
+ end
200
277
  let(:table_to) do
201
278
  Convergence::Table.new('table1').tap do |t|
202
279
  t.int('id', primary_key: true)
203
- t.varchar('name', limit: 300, null: true)
280
+ t.varchar('name', limit: 200, null: false)
204
281
 
205
- t.index('id')
206
282
  t.index('name')
207
283
  end
208
284
  end
209
285
 
210
- it do
211
- results = Convergence::Diff.new.diff_table(table_from, table_to)
212
- expect(results[:add_index].values.first.index_columns).to eq(['id'])
213
- end
286
+ it { expect(results[:add_index].values.first.index_columns).to eq(['name']) }
214
287
  end
215
288
 
216
289
  context 'remove foreign key' do
217
- let(:table_to) do
290
+ let(:table_from) do
218
291
  Convergence::Table.new('table1').tap do |t|
219
292
  t.int('id', primary_key: true)
220
- t.varchar('name', limit: 300, null: true)
293
+ t.varchar('name', limit: 200, null: false)
221
294
 
222
- t.index('name')
295
+ t.foreign_key('id', reference: 'ref_tables', reference_column: 'ref_id')
223
296
  end
224
297
  end
225
-
226
- it do
227
- results = Convergence::Diff.new.diff_table(table_from, table_to)
228
- expect(results[:remove_foreign_key].values.first.from_columns).to eq(['id'])
298
+ let(:table_to) do
299
+ Convergence::Table.new('table1').tap do |t|
300
+ t.int('id', primary_key: true)
301
+ t.varchar('name', limit: 200, null: false)
302
+ end
229
303
  end
304
+
305
+ it { expect(results[:remove_foreign_key].values.first.from_columns).to eq(['id']) }
230
306
  end
231
307
 
232
308
  context 'add foreign key' do
309
+ let(:table_from) do
310
+ Convergence::Table.new('table1').tap do |t|
311
+ t.int('id', primary_key: true)
312
+ t.varchar('name', limit: 200, null: false)
313
+ end
314
+ end
233
315
  let(:table_to) do
234
316
  Convergence::Table.new('table1').tap do |t|
235
317
  t.int('id', primary_key: true)
236
- t.varchar('name', limit: 300, null: true)
318
+ t.varchar('name', limit: 200, null: false)
237
319
 
238
- t.index('name')
239
320
  t.foreign_key('id', reference: 'ref_tables', reference_column: 'ref_id')
240
- t.foreign_key('id2', reference: 'ref_tables2', reference_column: 'ref_id2')
241
321
  end
242
322
  end
243
323
 
244
- it do
245
- results = Convergence::Diff.new.diff_table(table_from, table_to)
246
- expect(results[:add_foreign_key].values.first.from_columns).to eq(['id2'])
247
- end
324
+ it { expect(results[:add_foreign_key].values.first.from_columns).to eq(['id']) }
248
325
  end
249
326
 
250
327
  context 'change table options' do
328
+ let(:table_from) do
329
+ Convergence::Table.new('table1').tap do |t|
330
+ t.int('id', primary_key: true)
331
+ t.varchar('name', limit: 200, null: false)
332
+ end
333
+ end
251
334
  let(:table_to) do
252
335
  Convergence::Table.new('table1', engine: 'MyISAM').tap do |t|
253
- t.varchar('name', limit: 300, null: true)
254
336
  t.int('id', primary_key: true)
337
+ t.varchar('name', limit: 200, null: false)
255
338
  end
256
339
  end
257
340
 
258
- it do
259
- results = Convergence::Diff.new.diff_table(table_from, table_to)
260
- expect(results[:change_table_option][:engine]).to eq('MyISAM')
261
- end
341
+ it { expect(results[:change_table_option][:engine]).to eq('MyISAM') }
262
342
  end
263
343
  end
264
344
  end
@@ -89,4 +89,30 @@ describe Convergence::DSL do
89
89
  end
90
90
  end
91
91
  end
92
+
93
+ describe '#parse_dsl' do
94
+ let(:dsl_with_execute) do
95
+ <<-DSL
96
+ create_table "users" do |t|
97
+ t.int "id", primary_key: true, extra: "auto_increment"
98
+ end
99
+
100
+ execute "CREATE OR REPLACE VIEW active_users AS SELECT * FROM users"
101
+ execute "GRANT SELECT ON users TO readonly"
102
+ DSL
103
+ end
104
+
105
+ subject { Convergence::DSL.parse_dsl(dsl_with_execute, '') }
106
+
107
+ it 'should be able to parse tables' do
108
+ expect(subject.tables['users']).not_to be_nil
109
+ end
110
+
111
+ it 'should be able to collect raw sql statements in order' do
112
+ expect(subject.raw_sqls).to eq([
113
+ 'CREATE OR REPLACE VIEW active_users AS SELECT * FROM users',
114
+ 'GRANT SELECT ON users TO readonly'
115
+ ])
116
+ end
117
+ end
92
118
  end
@@ -37,14 +37,17 @@ describe Convergence::Dumper::MysqlSchemaDumper do
37
37
  expect(papers.columns['title1']).not_to be_nil
38
38
  expect(papers.columns['title2']).not_to be_nil
39
39
  expect(papers.columns['description']).not_to be_nil
40
+ expect(papers.columns['edition_number']).not_to be_nil
41
+ expect(papers.columns['published_at']).not_to be_nil
40
42
  end
41
43
 
42
44
  it 'should be dump columns in the correct order' do
43
45
  papers = subject['papers']
44
- expect(papers.columns.keys).to eq(%w(id slug title1 title2 description))
46
+ expect(papers.columns.keys)
47
+ .to eq(%w(id slug title1 title2 description edition_number published_at))
45
48
  end
46
49
 
47
- describe 'table options' do
50
+ describe 'table column options' do
48
51
  it 'should be dump primary key' do
49
52
  expect(subject['papers'].columns['id'].options[:primary_key]).to be_truthy
50
53
  end
@@ -69,6 +72,13 @@ describe Convergence::Dumper::MysqlSchemaDumper do
69
72
  it 'should be dump unsigned definition' do
70
73
  expect(subject['authors'].columns['age'].options[:unsigned]).to be_truthy
71
74
  end
75
+
76
+ it 'should be dump default' do
77
+ expect(subject['papers'].columns['edition_number'].options[:default]).to eq "0"
78
+ expect(subject['papers'].columns['published_at'].options[:default])
79
+ .to be_a(Proc)
80
+ .and have_attributes(call: "CURRENT_TIMESTAMP")
81
+ end
72
82
  end
73
83
  end
74
84
 
@@ -96,6 +106,19 @@ describe Convergence::Dumper::MysqlSchemaDumper do
96
106
  end
97
107
  end
98
108
 
109
+ describe 'enum/set columns' do
110
+ it 'should be dump enum values' do
111
+ expect(subject['enum_set_samples'].columns['status'].options[:values])
112
+ .to eq(%w(active inactive pending))
113
+ expect(subject['enum_set_samples'].columns['status'].options[:default]).to eq('active')
114
+ end
115
+
116
+ it 'should be dump set values, including values containing quotes' do
117
+ expect(subject['enum_set_samples'].columns['flags'].options[:values])
118
+ .to eq(['a', 'b', "it's tricky"])
119
+ end
120
+ end
121
+
99
122
  describe 'foreign keys' do
100
123
  it do
101
124
  foreign_key = subject['paper_authors'].foreign_keys['paper_authors_author_id_fk']
@@ -0,0 +1,103 @@
1
+ require 'spec_helper'
2
+ require 'convergence/dumper/postgres_schema_dumper'
3
+ require 'convergence/database_connector'
4
+
5
+ describe Convergence::Dumper::PostgresSchemaDumper do
6
+ let(:connector) do
7
+ Convergence::DatabaseConnector.new(postgres_settings)
8
+ end
9
+ let(:dumper) do
10
+ Convergence::Dumper::PostgresSchemaDumper.new(connector)
11
+ end
12
+
13
+ describe '#dump' do
14
+ subject { dumper.dump }
15
+
16
+ it 'should be dump tables' do
17
+ expect(subject['papers']).not_to be_nil
18
+ expect(subject['authors']).not_to be_nil
19
+ expect(subject['paper_authors']).not_to be_nil
20
+ end
21
+
22
+ describe 'table options' do
23
+ it 'should be dump comment' do
24
+ expect(subject['papers'].table_options[:comment]).to eq('Paper')
25
+ end
26
+ end
27
+
28
+ describe 'table columns' do
29
+ it 'should be dump table columns' do
30
+ papers = subject['papers']
31
+ expect(papers.columns['id']).not_to be_nil
32
+ expect(papers.columns['slug']).not_to be_nil
33
+ expect(papers.columns['title1']).not_to be_nil
34
+ expect(papers.columns['title2']).not_to be_nil
35
+ expect(papers.columns['description']).not_to be_nil
36
+ expect(papers.columns['edition_number']).not_to be_nil
37
+ expect(papers.columns['published_at']).not_to be_nil
38
+ end
39
+
40
+ it 'should be dump columns in the correct order' do
41
+ papers = subject['papers']
42
+ expect(papers.columns.keys)
43
+ .to eq(%w(id slug title1 title2 description edition_number published_at))
44
+ end
45
+
46
+ describe 'table column options' do
47
+ it 'should be dump primary key' do
48
+ expect(subject['papers'].columns['id'].options[:primary_key]).to be_truthy
49
+ end
50
+
51
+ it 'should be dump extra (identity columns as auto_increment)' do
52
+ expect(subject['papers'].columns['id'].options[:extra]).to eq('auto_increment')
53
+ end
54
+
55
+ it 'should be dump comment' do
56
+ expect(subject['papers'].columns['title1'].options[:comment]).to eq('Title 1')
57
+ end
58
+
59
+ it 'should be dump limit' do
60
+ expect(subject['papers'].columns['title1'].options[:limit]).to eq('300')
61
+ end
62
+
63
+ it 'should be dump not null definition' do
64
+ expect(subject['papers'].columns['title1'].options[:null]).to be_falsy
65
+ expect(subject['authors'].columns['created_at'].options[:null]).to be_truthy
66
+ end
67
+
68
+ it 'should be dump default' do
69
+ expect(subject['papers'].columns['edition_number'].options[:default]).to eq('0')
70
+ expect(subject['papers'].columns['published_at'].options[:default])
71
+ .to be_a(Proc)
72
+ .and have_attributes(call: 'CURRENT_TIMESTAMP')
73
+ end
74
+ end
75
+ end
76
+
77
+ describe 'indexes' do
78
+ it 'should be dump index of authors' do
79
+ index = subject['authors'].indexes['index_authors_on_created_at']
80
+ expect(index).not_to be_nil
81
+ expect(index.index_columns).to eq(['created_at'])
82
+ expect(index.options[:unique]).to eq(false)
83
+ end
84
+
85
+ it 'should be dump unique index of papers' do
86
+ index = subject['papers'].indexes['index_papers_on_slug']
87
+ expect(index).not_to be_nil
88
+ expect(index.index_columns).to eq(['slug'])
89
+ expect(index.options[:unique]).to eq(true)
90
+ end
91
+ end
92
+
93
+ describe 'foreign keys' do
94
+ it do
95
+ foreign_key = subject['paper_authors'].foreign_keys['paper_authors_author_id_fk']
96
+ expect(foreign_key).not_to be_nil
97
+ expect(foreign_key.from_columns).to eq(['author_id'])
98
+ expect(foreign_key.to_table).to eq('authors')
99
+ expect(foreign_key.to_columns).to eq(['id'])
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,85 @@
1
+ require 'spec_helper'
2
+ require 'convergence/dumper/sqlite_schema_dumper'
3
+ require 'convergence/database_connector'
4
+
5
+ describe Convergence::Dumper::SqliteSchemaDumper do
6
+ let(:connector) do
7
+ Convergence::DatabaseConnector.new(sqlite_settings)
8
+ end
9
+ let(:dumper) do
10
+ Convergence::Dumper::SqliteSchemaDumper.new(connector)
11
+ end
12
+
13
+ describe '#dump' do
14
+ subject { dumper.dump }
15
+
16
+ it 'should be dump tables' do
17
+ expect(subject['papers']).not_to be_nil
18
+ expect(subject['authors']).not_to be_nil
19
+ expect(subject['paper_authors']).not_to be_nil
20
+ end
21
+
22
+ describe 'table columns' do
23
+ it 'should be dump table columns' do
24
+ papers = subject['papers']
25
+ expect(papers.columns['id']).not_to be_nil
26
+ expect(papers.columns['slug']).not_to be_nil
27
+ expect(papers.columns['title1']).not_to be_nil
28
+ expect(papers.columns['title2']).not_to be_nil
29
+ expect(papers.columns['description']).not_to be_nil
30
+ expect(papers.columns['edition_number']).not_to be_nil
31
+ expect(papers.columns['published_at']).not_to be_nil
32
+ end
33
+
34
+ describe 'table column options' do
35
+ it 'should be dump primary key as auto_increment' do
36
+ expect(subject['papers'].columns['id'].options[:primary_key]).to be_truthy
37
+ expect(subject['papers'].columns['id'].options[:extra]).to eq('auto_increment')
38
+ expect(subject['papers'].columns['id'].options[:null]).to be_falsy
39
+ end
40
+
41
+ it 'should be dump limit' do
42
+ expect(subject['papers'].columns['title1'].options[:limit]).to eq('300')
43
+ end
44
+
45
+ it 'should be dump not null definition' do
46
+ expect(subject['papers'].columns['title1'].options[:null]).to be_falsy
47
+ expect(subject['authors'].columns['created_at'].options[:null]).to be_truthy
48
+ end
49
+
50
+ it 'should be dump default' do
51
+ expect(subject['papers'].columns['edition_number'].options[:default]).to eq('0')
52
+ expect(subject['papers'].columns['published_at'].options[:default])
53
+ .to be_a(Proc)
54
+ .and have_attributes(call: 'CURRENT_TIMESTAMP')
55
+ end
56
+ end
57
+ end
58
+
59
+ describe 'indexes' do
60
+ it 'should be dump index of authors' do
61
+ index = subject['authors'].indexes['index_authors_on_created_at']
62
+ expect(index).not_to be_nil
63
+ expect(index.index_columns).to eq(['created_at'])
64
+ expect(index.options[:unique]).to eq(false)
65
+ end
66
+
67
+ it 'should be dump unique index of papers' do
68
+ index = subject['papers'].indexes['index_papers_on_slug']
69
+ expect(index).not_to be_nil
70
+ expect(index.index_columns).to eq(['slug'])
71
+ expect(index.options[:unique]).to eq(true)
72
+ end
73
+ end
74
+
75
+ describe 'foreign keys' do
76
+ it 'should be dump the foreign key with its original constraint name' do
77
+ foreign_key = subject['paper_authors'].foreign_keys['paper_authors_author_id_fk']
78
+ expect(foreign_key).not_to be_nil
79
+ expect(foreign_key.from_columns).to eq(['author_id'])
80
+ expect(foreign_key.to_table).to eq('authors')
81
+ expect(foreign_key.to_columns).to eq(['id'])
82
+ end
83
+ end
84
+ end
85
+ end
@@ -13,15 +13,15 @@ describe Convergence::Dumper do
13
13
  end
14
14
  end
15
15
  let(:table1_dsl) do
16
- dsl = <<-DSL
17
- create_table :dummy_table, engine: "MyISAM" do |t|
18
- t.int :id, limit: 11
19
- t.varchar :name, limit: 100, null: true, comment: "name"
16
+ dsl = <<~DSL
17
+ create_table :dummy_table, engine: "MyISAM" do |t|
18
+ t.int :id, limit: 11
19
+ t.varchar :name, limit: 100, null: true, comment: "name"
20
20
 
21
- t.index :name, name: "idx_name"
22
- t.foreign_key :id, reference: :dummy_ref, reference_column: :id, name: "dummy_table_id_fk"
23
- end
24
- DSL
21
+ t.index :name, name: "idx_name"
22
+ t.foreign_key :id, reference: :dummy_ref, reference_column: :id, name: "dummy_table_id_fk"
23
+ end
24
+ DSL
25
25
  dsl.strip
26
26
  end
27
27
 
@@ -51,15 +51,15 @@ end
51
51
  end
52
52
 
53
53
  let(:table1_dsl) do
54
- dsl = <<-DSL
55
- create_table :"dummy-table", engine: "MyISAM" do |t|
56
- t.int :id, limit: 11
57
- t.varchar :"column-1", limit: 100, null: true, comment: "column 1"
54
+ dsl = <<~DSL
55
+ create_table :"dummy-table", engine: "MyISAM" do |t|
56
+ t.int :id, limit: 11
57
+ t.varchar :"column-1", limit: 100, null: true, comment: "column 1"
58
58
 
59
- t.index :"column-1", name: "idx_column-1"
60
- t.foreign_key :"column-1", reference: :"dummy-ref", reference_column: :"dummy-column", name: "dummy-table_column-1_fk"
61
- end
62
- DSL
59
+ t.index :"column-1", name: "idx_column-1"
60
+ t.foreign_key :"column-1", reference: :"dummy-ref", reference_column: :"dummy-column", name: "dummy-table_column-1_fk"
61
+ end
62
+ DSL
63
63
  dsl.strip
64
64
  end
65
65
 
@@ -68,5 +68,99 @@ end
68
68
  expect(dsl).to eq(table1_dsl)
69
69
  end
70
70
  end
71
+
72
+ context "when the table column has default option with proc" do
73
+ let(:table1) do
74
+ Convergence::Table.new('dummy_table').tap do |t|
75
+ t.int :edition_number, default: 0
76
+ t.datetime :created_at, default: -> { "CURRENT_TIMESTAMP" }
77
+ end
78
+ end
79
+
80
+ let(:table1_dsl) do
81
+ dsl = <<~DSL
82
+ create_table :dummy_table do |t|
83
+ t.int :edition_number, default: 0
84
+ t.datetime :created_at, default: -> { "CURRENT_TIMESTAMP" }
85
+ end
86
+ DSL
87
+ dsl.strip
88
+ end
89
+
90
+ it 'should be able to dump dsl' do
91
+ dsl = Convergence::Dumper.new.dump_table_dsl(table1)
92
+ expect(dsl).to eq(table1_dsl)
93
+ end
94
+ end
95
+ end
96
+
97
+ describe '#dump_rails_migration' do
98
+ let(:table1) do
99
+ Convergence::Table.new('dummy_table', engine: 'MyISAM').tap do |t|
100
+ t.int :id, primary_key: true, extra: 'auto_increment'
101
+ t.varchar :name, limit: 100, null: true, comment: 'name'
102
+ t.datetime :created_at, null: false
103
+ t.datetime :updated_at, null: false
104
+
105
+ t.index :name, name: 'idx_name'
106
+ t.foreign_key :id, reference: 'dummy_ref', reference_column: :id
107
+ end
108
+ end
109
+
110
+ let(:migration_dsl) do
111
+ dsl = <<~DSL
112
+ class DummyMigration < ActiveRecord::Migration[7.0]
113
+ def change
114
+ create_table :dummy_table do |t|
115
+ t.string :name, limit: 100, null: true, comment: "name"
116
+ t.timestamps null: false
117
+ t.index :name, name: "idx_name"
118
+ t.foreign_key :dummy_ref, column: :id, name: "dummy_table_id_fk"
119
+ end
120
+ end
121
+ end
122
+ DSL
123
+ dsl.strip
124
+ end
125
+
126
+ it 'omits the implicit auto-increment id column' do
127
+ migration = Convergence::Dumper.new.dump_rails_migration({ 'dummy_table' => table1 }, 'DummyMigration')
128
+ expect(migration).to eq(migration_dsl)
129
+ end
130
+
131
+ it 'collapses created_at/updated_at into t.timestamps' do
132
+ migration = Convergence::Dumper.new.dump_rails_migration({ 'dummy_table' => table1 }, 'DummyMigration')
133
+ expect(migration).to be_include('t.timestamps null: false')
134
+ expect(migration).not_to be_include('t.datetime :created_at')
135
+ end
136
+
137
+ context 'when the column is a MySQL tinyint(1) boolean' do
138
+ let(:table1) do
139
+ Convergence::Table.new('dummy_table').tap do |t|
140
+ t.boolean :active, default: true
141
+ end
142
+ end
143
+
144
+ it 'maps it to a native boolean column' do
145
+ migration = Convergence::Dumper.new.dump_rails_migration({ 'dummy_table' => table1 }, 'DummyMigration')
146
+ expect(migration).to be_include('t.boolean :active, default: true')
147
+ end
148
+ end
149
+
150
+ context 'when created_at/updated_at have mismatched null options' do
151
+ let(:table1) do
152
+ Convergence::Table.new('dummy_table').tap do |t|
153
+ t.datetime :created_at, null: false
154
+ t.datetime :updated_at, null: true
155
+ end
156
+ end
157
+
158
+ it 'keeps them as separate columns instead of collapsing into t.timestamps' do
159
+ migration = Convergence::Dumper.new.dump_rails_migration({ 'dummy_table' => table1 }, 'DummyMigration')
160
+ expect(migration).not_to be_include('t.timestamps')
161
+ expect(migration).to be_include('t.datetime :created_at, null: false')
162
+ expect(migration).to be_include('t.datetime :updated_at, null: true')
163
+ end
164
+ end
71
165
  end
72
166
  end