declare_schema 0.14.2 → 1.0.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/CODEOWNERS +1 -0
- data/.github/workflows/declare_schema_build.yml +22 -23
- data/.ruby-version +1 -1
- data/Appraisals +8 -0
- data/CHANGELOG.md +22 -4
- data/Gemfile +3 -2
- data/Gemfile.lock +107 -77
- data/README.md +18 -8
- data/Rakefile +1 -1
- data/gemfiles/rails_5_mysql.gemfile +0 -1
- data/gemfiles/rails_5_sqlite.gemfile +0 -1
- data/gemfiles/rails_6_mysql.gemfile +2 -1
- data/gemfiles/rails_6_sqlite.gemfile +2 -1
- data/lib/declare_schema/command.rb +1 -1
- data/lib/declare_schema/dsl.rb +2 -2
- data/lib/declare_schema/extensions/active_record/fields_declaration.rb +0 -18
- data/lib/declare_schema/field_declaration_dsl.rb +4 -4
- data/lib/declare_schema/model/foreign_key_definition.rb +2 -2
- data/lib/declare_schema/model/index_definition.rb +2 -2
- data/lib/declare_schema/model/table_options_definition.rb +2 -2
- data/lib/declare_schema/model.rb +9 -9
- data/lib/declare_schema/version.rb +1 -1
- data/lib/generators/declare_schema/migration/migrator.rb +6 -6
- data/spec/lib/declare_schema/field_declaration_dsl_spec.rb +0 -24
- data/spec/lib/declare_schema/field_spec_spec.rb +2 -2
- data/spec/lib/declare_schema/interactive_primary_key_spec.rb +0 -67
- data/spec/lib/declare_schema/migration_generator_spec.rb +11 -1176
- data/spec/lib/declare_schema/model/column_spec.rb +1 -28
- data/spec/lib/declare_schema/model/foreign_key_definition_spec.rb +1 -94
- data/spec/lib/declare_schema/model/index_definition_spec.rb +0 -109
- data/spec/lib/declare_schema/model/table_options_definition_spec.rb +2 -65
- data/spec/lib/declare_schema/schema_change/column_add_spec.rb +1 -1
- data/spec/lib/declare_schema/schema_change/column_remove_spec.rb +1 -1
- data/spec/spec_helper.rb +1 -1
- metadata +7 -6
@@ -54,1171 +54,6 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
54
54
|
end
|
55
55
|
end
|
56
56
|
|
57
|
-
context 'Using fields' do
|
58
|
-
# DeclareSchema - Migration Generator
|
59
|
-
it 'generates migrations' do
|
60
|
-
## The migration generator -- introduction
|
61
|
-
|
62
|
-
expect(Generators::DeclareSchema::Migration::Migrator.run).to migrate_up("").and migrate_down("")
|
63
|
-
|
64
|
-
class Advert < ActiveRecord::Base
|
65
|
-
end
|
66
|
-
|
67
|
-
expect(Generators::DeclareSchema::Migration::Migrator.run).to migrate_up("").and migrate_down("")
|
68
|
-
|
69
|
-
Generators::DeclareSchema::Migration::Migrator.ignore_tables = ["green_fishes"]
|
70
|
-
|
71
|
-
Advert.connection.schema_cache.clear!
|
72
|
-
Advert.reset_column_information
|
73
|
-
|
74
|
-
class Advert < ActiveRecord::Base
|
75
|
-
fields do
|
76
|
-
name :string, limit: 250, null: true
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
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
|
-
|
91
|
-
ActiveRecord::Migration.class_eval(up)
|
92
|
-
expect(Advert.columns.map(&:name)).to eq(["id", "name"])
|
93
|
-
|
94
|
-
class Advert < ActiveRecord::Base
|
95
|
-
fields do
|
96
|
-
name :string, limit: 250, null: true
|
97
|
-
body :text, null: true
|
98
|
-
published_at :datetime, null: true
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
Advert.connection.schema_cache.clear!
|
103
|
-
Advert.reset_column_information
|
104
|
-
|
105
|
-
expect(migrate).to(
|
106
|
-
migrate_up(<<~EOS.strip)
|
107
|
-
add_column :adverts, :body, :text#{text_limit}, null: true#{charset_and_collation}
|
108
|
-
add_column :adverts, :published_at, :datetime, null: true
|
109
|
-
EOS
|
110
|
-
.and migrate_down(<<~EOS.strip)
|
111
|
-
remove_column :adverts, :published_at
|
112
|
-
remove_column :adverts, :body
|
113
|
-
EOS
|
114
|
-
)
|
115
|
-
|
116
|
-
Advert.field_specs.clear # not normally needed
|
117
|
-
class Advert < ActiveRecord::Base
|
118
|
-
fields do
|
119
|
-
name :string, limit: 250, null: true
|
120
|
-
body :text, null: true
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
expect(migrate).to(
|
125
|
-
migrate_up("remove_column :adverts, :published_at").and(
|
126
|
-
migrate_down("add_column :adverts, :published_at, :datetime#{datetime_precision}, null: true")
|
127
|
-
)
|
128
|
-
)
|
129
|
-
|
130
|
-
nuke_model_class(Advert)
|
131
|
-
class Advert < ActiveRecord::Base
|
132
|
-
fields do
|
133
|
-
title :string, limit: 250, null: true
|
134
|
-
body :text, null: true
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
139
|
-
migrate_up(<<~EOS.strip)
|
140
|
-
add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
141
|
-
remove_column :adverts, :name
|
142
|
-
EOS
|
143
|
-
.and migrate_down(<<~EOS.strip)
|
144
|
-
add_column :adverts, :name, :string, limit: 250, null: true#{charset_and_collation}
|
145
|
-
remove_column :adverts, :title
|
146
|
-
EOS
|
147
|
-
)
|
148
|
-
|
149
|
-
expect(Generators::DeclareSchema::Migration::Migrator.run(adverts: { name: :title })).to(
|
150
|
-
migrate_up("rename_column :adverts, :name, :title").and(
|
151
|
-
migrate_down("rename_column :adverts, :title, :name")
|
152
|
-
)
|
153
|
-
)
|
154
|
-
|
155
|
-
migrate
|
156
|
-
|
157
|
-
class Advert < ActiveRecord::Base
|
158
|
-
fields do
|
159
|
-
title :text, null: true
|
160
|
-
body :text, null: true
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
165
|
-
migrate_up("change_column :adverts, :title, :text#{text_limit}, null: true#{charset_and_collation}").and(
|
166
|
-
migrate_down("change_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}")
|
167
|
-
)
|
168
|
-
)
|
169
|
-
|
170
|
-
class Advert < ActiveRecord::Base
|
171
|
-
fields do
|
172
|
-
title :string, default: "Untitled", limit: 250, null: true
|
173
|
-
body :text, null: true
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
expect(migrate).to(
|
178
|
-
migrate_up(<<~EOS.strip)
|
179
|
-
change_column :adverts, :title, :string, limit: 250, null: true, default: "Untitled"#{charset_and_collation}
|
180
|
-
EOS
|
181
|
-
.and migrate_down(<<~EOS.strip)
|
182
|
-
change_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
183
|
-
EOS
|
184
|
-
)
|
185
|
-
|
186
|
-
### Limits
|
187
|
-
|
188
|
-
class Advert < ActiveRecord::Base
|
189
|
-
fields do
|
190
|
-
price :integer, null: true, limit: 2
|
191
|
-
end
|
192
|
-
end
|
193
|
-
|
194
|
-
up, _ = Generators::DeclareSchema::Migration::Migrator.run.tap do |migrations|
|
195
|
-
expect(migrations).to migrate_up("add_column :adverts, :price, :integer, limit: 2, null: true")
|
196
|
-
end
|
197
|
-
|
198
|
-
# Now run the migration, then change the limit:
|
199
|
-
|
200
|
-
ActiveRecord::Migration.class_eval(up)
|
201
|
-
class Advert < ActiveRecord::Base
|
202
|
-
fields do
|
203
|
-
price :integer, null: true, limit: 3
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
208
|
-
migrate_up(<<~EOS.strip)
|
209
|
-
change_column :adverts, :price, :integer, limit: 3, null: true
|
210
|
-
EOS
|
211
|
-
.and migrate_down(<<~EOS.strip)
|
212
|
-
change_column :adverts, :price, :integer, limit: 2, null: true
|
213
|
-
EOS
|
214
|
-
)
|
215
|
-
|
216
|
-
ActiveRecord::Migration.class_eval("remove_column :adverts, :price")
|
217
|
-
class Advert < ActiveRecord::Base
|
218
|
-
fields do
|
219
|
-
price :decimal, precision: 4, scale: 1, null: true
|
220
|
-
end
|
221
|
-
end
|
222
|
-
|
223
|
-
# Limits are generally not needed for `text` fields, because by default, `text` fields will use the maximum size
|
224
|
-
# allowed for that database type (0xffffffff for LONGTEXT in MySQL unlimited in Postgres, 1 billion in Sqlite).
|
225
|
-
# If a `limit` is given, it will only be used in MySQL, to choose the smallest TEXT field that will accommodate
|
226
|
-
# that limit (0xff for TINYTEXT, 0xffff for TEXT, 0xffffff for MEDIUMTEXT, 0xffffffff for LONGTEXT).
|
227
|
-
|
228
|
-
if defined?(SQLite3)
|
229
|
-
expect(::DeclareSchema::Model::FieldSpec.mysql_text_limits?).to be_falsey
|
230
|
-
end
|
231
|
-
|
232
|
-
class Advert < ActiveRecord::Base
|
233
|
-
fields do
|
234
|
-
notes :text
|
235
|
-
description :text, limit: 30000
|
236
|
-
end
|
237
|
-
end
|
238
|
-
|
239
|
-
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
240
|
-
migrate_up(<<~EOS.strip)
|
241
|
-
add_column :adverts, :price, :decimal, precision: 4, scale: 1, null: true
|
242
|
-
add_column :adverts, :notes, :text#{text_limit}, null: false#{charset_and_collation}
|
243
|
-
add_column :adverts, :description, :text#{', limit: 65535' if defined?(Mysql2)}, null: false#{charset_and_collation}
|
244
|
-
EOS
|
245
|
-
)
|
246
|
-
|
247
|
-
Advert.field_specs.delete :price
|
248
|
-
Advert.field_specs.delete :notes
|
249
|
-
Advert.field_specs.delete :description
|
250
|
-
|
251
|
-
# In MySQL, limits are applied, rounded up:
|
252
|
-
|
253
|
-
if defined?(Mysql2)
|
254
|
-
expect(::DeclareSchema::Model::FieldSpec.mysql_text_limits?).to be_truthy
|
255
|
-
|
256
|
-
class Advert < ActiveRecord::Base
|
257
|
-
fields do
|
258
|
-
notes :text
|
259
|
-
description :text, limit: 250
|
260
|
-
end
|
261
|
-
end
|
262
|
-
|
263
|
-
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
264
|
-
migrate_up(<<~EOS.strip)
|
265
|
-
add_column :adverts, :notes, :text, limit: 4294967295, null: false#{charset_and_collation}
|
266
|
-
add_column :adverts, :description, :text, limit: 255, null: false#{charset_and_collation}
|
267
|
-
EOS
|
268
|
-
)
|
269
|
-
|
270
|
-
Advert.field_specs.delete :notes
|
271
|
-
|
272
|
-
# Limits that are too high for MySQL will raise an exception.
|
273
|
-
|
274
|
-
expect do
|
275
|
-
class Advert < ActiveRecord::Base
|
276
|
-
fields do
|
277
|
-
notes :text
|
278
|
-
description :text, limit: 0x1_0000_0000
|
279
|
-
end
|
280
|
-
end
|
281
|
-
end.to raise_exception(ArgumentError, "limit of 4294967296 is too large for MySQL")
|
282
|
-
|
283
|
-
Advert.field_specs.delete :notes
|
284
|
-
|
285
|
-
# And in MySQL, unstated text limits are treated as the maximum (LONGTEXT) limit.
|
286
|
-
|
287
|
-
# To start, we'll set the database schema for `description` to match the above limit of 250.
|
288
|
-
|
289
|
-
Advert.connection.execute "ALTER TABLE adverts ADD COLUMN description TINYTEXT"
|
290
|
-
Advert.connection.schema_cache.clear!
|
291
|
-
Advert.reset_column_information
|
292
|
-
expect(Advert.connection.tables - Generators::DeclareSchema::Migration::Migrator.always_ignore_tables).
|
293
|
-
to eq(["adverts"])
|
294
|
-
expect(Advert.columns.map(&:name)).to eq(["id", "body", "title", "description"])
|
295
|
-
|
296
|
-
# Now migrate to an unstated text limit:
|
297
|
-
|
298
|
-
class Advert < ActiveRecord::Base
|
299
|
-
fields do
|
300
|
-
description :text
|
301
|
-
end
|
302
|
-
end
|
303
|
-
|
304
|
-
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
305
|
-
migrate_up(<<~EOS.strip)
|
306
|
-
change_column :adverts, :description, :text, limit: 4294967295, null: false#{charset_and_collation}
|
307
|
-
EOS
|
308
|
-
.and migrate_down(<<~EOS.strip)
|
309
|
-
change_column :adverts, :description, :text#{', limit: 255' if defined?(Mysql2)}, null: true#{charset_and_collation}
|
310
|
-
EOS
|
311
|
-
)
|
312
|
-
|
313
|
-
# And migrate to a stated text limit that is the same as the unstated one:
|
314
|
-
|
315
|
-
class Advert < ActiveRecord::Base
|
316
|
-
fields do
|
317
|
-
description :text, limit: 0xffffffff
|
318
|
-
end
|
319
|
-
end
|
320
|
-
|
321
|
-
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
322
|
-
migrate_up(<<~EOS.strip)
|
323
|
-
change_column :adverts, :description, :text, limit: 4294967295, null: false#{charset_and_collation}
|
324
|
-
EOS
|
325
|
-
.and migrate_down(<<~EOS.strip)
|
326
|
-
change_column :adverts, :description, :text#{', limit: 255' if defined?(Mysql2)}, null: true#{charset_and_collation}
|
327
|
-
EOS
|
328
|
-
)
|
329
|
-
end
|
330
|
-
|
331
|
-
Advert.field_specs.clear
|
332
|
-
Advert.connection.schema_cache.clear!
|
333
|
-
Advert.reset_column_information
|
334
|
-
class Advert < ActiveRecord::Base
|
335
|
-
fields do
|
336
|
-
name :string, limit: 250, null: true
|
337
|
-
end
|
338
|
-
end
|
339
|
-
|
340
|
-
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
341
|
-
ActiveRecord::Migration.class_eval up
|
342
|
-
|
343
|
-
Advert.connection.schema_cache.clear!
|
344
|
-
Advert.reset_column_information
|
345
|
-
|
346
|
-
### Foreign Keys
|
347
|
-
|
348
|
-
# DeclareSchema extends the `belongs_to` macro so that it also declares the
|
349
|
-
# foreign-key field. It also generates an index on the field.
|
350
|
-
|
351
|
-
class Category < ActiveRecord::Base; end
|
352
|
-
class Advert < ActiveRecord::Base
|
353
|
-
fields do
|
354
|
-
name :string, limit: 250, null: true
|
355
|
-
end
|
356
|
-
belongs_to :category
|
357
|
-
end
|
358
|
-
|
359
|
-
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
360
|
-
migrate_up(<<~EOS.strip)
|
361
|
-
add_column :adverts, :category_id, :integer, limit: 8, null: false
|
362
|
-
add_index :adverts, [:category_id], name: :on_category_id
|
363
|
-
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id" if defined?(Mysql2)}
|
364
|
-
EOS
|
365
|
-
.and migrate_down(<<~EOS.strip)
|
366
|
-
#{"remove_foreign_key :adverts, name: :index_adverts_on_category_id" if defined?(Mysql2)}
|
367
|
-
remove_index :adverts, name: :on_category_id
|
368
|
-
remove_column :adverts, :category_id
|
369
|
-
EOS
|
370
|
-
)
|
371
|
-
|
372
|
-
Advert.field_specs.delete(:category_id)
|
373
|
-
Advert.index_definitions.delete_if { |spec| spec.fields==["category_id"] }
|
374
|
-
|
375
|
-
# If you specify a custom foreign key, the migration generator observes that:
|
376
|
-
|
377
|
-
class Category < ActiveRecord::Base; end
|
378
|
-
class Advert < ActiveRecord::Base
|
379
|
-
fields { }
|
380
|
-
belongs_to :category, foreign_key: "c_id", class_name: 'Category'
|
381
|
-
end
|
382
|
-
|
383
|
-
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
384
|
-
migrate_up(<<~EOS.strip)
|
385
|
-
add_column :adverts, :c_id, :integer, limit: 8, null: false
|
386
|
-
add_index :adverts, [:c_id], name: :on_c_id
|
387
|
-
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
388
|
-
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
389
|
-
EOS
|
390
|
-
)
|
391
|
-
|
392
|
-
Advert.field_specs.delete(:c_id)
|
393
|
-
Advert.index_definitions.delete_if { |spec| spec.fields == ["c_id"] }
|
394
|
-
|
395
|
-
# You can avoid generating the index by specifying `index: false`
|
396
|
-
|
397
|
-
class Category < ActiveRecord::Base; end
|
398
|
-
class Advert < ActiveRecord::Base
|
399
|
-
fields { }
|
400
|
-
belongs_to :category, index: false
|
401
|
-
end
|
402
|
-
|
403
|
-
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
404
|
-
migrate_up(<<~EOS.strip)
|
405
|
-
add_column :adverts, :category_id, :integer, limit: 8, null: false
|
406
|
-
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
407
|
-
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
408
|
-
EOS
|
409
|
-
)
|
410
|
-
|
411
|
-
Advert.field_specs.delete(:category_id)
|
412
|
-
Advert.index_definitions.delete_if { |spec| spec.fields == ["category_id"] }
|
413
|
-
|
414
|
-
# You can specify the index name with :index
|
415
|
-
|
416
|
-
class Category < ActiveRecord::Base; end
|
417
|
-
class Advert < ActiveRecord::Base
|
418
|
-
fields { }
|
419
|
-
belongs_to :category, index: 'my_index'
|
420
|
-
end
|
421
|
-
|
422
|
-
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
423
|
-
migrate_up(<<~EOS.strip)
|
424
|
-
add_column :adverts, :category_id, :integer, limit: 8, null: false
|
425
|
-
add_index :adverts, [:category_id], name: :my_index
|
426
|
-
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
427
|
-
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
428
|
-
EOS
|
429
|
-
)
|
430
|
-
|
431
|
-
Advert.field_specs.delete(:category_id)
|
432
|
-
Advert.index_definitions.delete_if { |spec| spec.fields == ["category_id"] }
|
433
|
-
|
434
|
-
### Timestamps and Optimistic Locking
|
435
|
-
|
436
|
-
# `updated_at` and `created_at` can be declared with the shorthand `timestamps`.
|
437
|
-
# Similarly, `lock_version` can be declared with the "shorthand" `optimistic_lock`.
|
438
|
-
|
439
|
-
class Advert < ActiveRecord::Base
|
440
|
-
fields do
|
441
|
-
timestamps
|
442
|
-
optimistic_lock
|
443
|
-
end
|
444
|
-
end
|
445
|
-
|
446
|
-
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
447
|
-
migrate_up(<<~EOS.strip)
|
448
|
-
add_column :adverts, :created_at, :datetime, null: true
|
449
|
-
add_column :adverts, :updated_at, :datetime, null: true
|
450
|
-
add_column :adverts, :lock_version, :integer#{lock_version_limit}, null: false, default: 1
|
451
|
-
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
452
|
-
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
453
|
-
EOS
|
454
|
-
.and migrate_down(<<~EOS.strip)
|
455
|
-
#{"remove_foreign_key :adverts, name: :index_adverts_on_c_id\n" +
|
456
|
-
"remove_foreign_key :adverts, name: :index_adverts_on_category_id" if defined?(Mysql2)}
|
457
|
-
remove_column :adverts, :lock_version
|
458
|
-
remove_column :adverts, :updated_at
|
459
|
-
remove_column :adverts, :created_at
|
460
|
-
EOS
|
461
|
-
)
|
462
|
-
|
463
|
-
Advert.field_specs.delete(:updated_at)
|
464
|
-
Advert.field_specs.delete(:created_at)
|
465
|
-
Advert.field_specs.delete(:lock_version)
|
466
|
-
|
467
|
-
### Indices
|
468
|
-
|
469
|
-
# You can add an index to a field definition
|
470
|
-
|
471
|
-
class Advert < ActiveRecord::Base
|
472
|
-
fields do
|
473
|
-
title :string, index: true, limit: 250, null: true
|
474
|
-
end
|
475
|
-
end
|
476
|
-
|
477
|
-
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
478
|
-
migrate_up(<<~EOS.strip)
|
479
|
-
add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
480
|
-
add_index :adverts, [:title], name: :on_title
|
481
|
-
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
482
|
-
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
483
|
-
EOS
|
484
|
-
)
|
485
|
-
|
486
|
-
Advert.index_definitions.delete_if { |spec| spec.fields==["title"] }
|
487
|
-
|
488
|
-
# You can ask for a unique index
|
489
|
-
|
490
|
-
class Advert < ActiveRecord::Base
|
491
|
-
fields do
|
492
|
-
title :string, index: true, unique: true, null: true, limit: 250
|
493
|
-
end
|
494
|
-
end
|
495
|
-
|
496
|
-
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
497
|
-
migrate_up(<<~EOS.strip)
|
498
|
-
add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
499
|
-
add_index :adverts, [:title], name: :on_title, unique: true
|
500
|
-
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
501
|
-
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
502
|
-
EOS
|
503
|
-
)
|
504
|
-
|
505
|
-
Advert.index_definitions.delete_if { |spec| spec.fields == ["title"] }
|
506
|
-
|
507
|
-
# You can specify the name for the index
|
508
|
-
|
509
|
-
class Advert < ActiveRecord::Base
|
510
|
-
fields do
|
511
|
-
title :string, index: 'my_index', limit: 250, null: true
|
512
|
-
end
|
513
|
-
end
|
514
|
-
|
515
|
-
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
516
|
-
migrate_up(<<~EOS.strip)
|
517
|
-
add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
518
|
-
add_index :adverts, [:title], name: :my_index
|
519
|
-
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
520
|
-
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
521
|
-
EOS
|
522
|
-
)
|
523
|
-
|
524
|
-
Advert.index_definitions.delete_if { |spec| spec.fields==["title"] }
|
525
|
-
|
526
|
-
# You can ask for an index outside of the fields block
|
527
|
-
|
528
|
-
class Advert < ActiveRecord::Base
|
529
|
-
index :title
|
530
|
-
end
|
531
|
-
|
532
|
-
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
533
|
-
migrate_up(<<~EOS.strip)
|
534
|
-
add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
535
|
-
add_index :adverts, [:title], name: :on_title
|
536
|
-
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
537
|
-
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
538
|
-
EOS
|
539
|
-
)
|
540
|
-
|
541
|
-
Advert.index_definitions.delete_if { |spec| spec.fields == ["title"] }
|
542
|
-
|
543
|
-
# The available options for the index function are `:unique` and `:name`
|
544
|
-
|
545
|
-
class Advert < ActiveRecord::Base
|
546
|
-
index :title, unique: true, name: 'my_index'
|
547
|
-
end
|
548
|
-
|
549
|
-
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
550
|
-
migrate_up(<<~EOS.strip)
|
551
|
-
add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
552
|
-
add_index :adverts, [:title], name: :my_index, unique: true
|
553
|
-
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
554
|
-
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
555
|
-
EOS
|
556
|
-
)
|
557
|
-
|
558
|
-
Advert.index_definitions.delete_if { |spec| spec.fields == ["title"] }
|
559
|
-
|
560
|
-
# You can create an index on more than one field
|
561
|
-
|
562
|
-
class Advert < ActiveRecord::Base
|
563
|
-
index [:title, :category_id]
|
564
|
-
end
|
565
|
-
|
566
|
-
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
567
|
-
migrate_up(<<~EOS.strip)
|
568
|
-
add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
569
|
-
add_index :adverts, [:title, :category_id], name: :on_title_and_category_id
|
570
|
-
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
571
|
-
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
572
|
-
EOS
|
573
|
-
)
|
574
|
-
|
575
|
-
Advert.index_definitions.delete_if { |spec| spec.fields==["title", "category_id"] }
|
576
|
-
|
577
|
-
# Finally, you can specify that the migration generator should completely ignore an
|
578
|
-
# index by passing its name to ignore_index in the model.
|
579
|
-
# This is helpful for preserving indices that can't be automatically generated, such as prefix indices in MySQL.
|
580
|
-
|
581
|
-
### Rename a table
|
582
|
-
|
583
|
-
# The migration generator respects the `set_table_name` declaration, although as before,
|
584
|
-
# we need to explicitly tell the generator that we want a rename rather than a create and a drop.
|
585
|
-
|
586
|
-
class Advert < ActiveRecord::Base
|
587
|
-
self.table_name = "ads"
|
588
|
-
fields do
|
589
|
-
title :string, limit: 250, null: true
|
590
|
-
body :text, null: true
|
591
|
-
end
|
592
|
-
end
|
593
|
-
|
594
|
-
Advert.connection.schema_cache.clear!
|
595
|
-
Advert.reset_column_information
|
596
|
-
|
597
|
-
expect(Generators::DeclareSchema::Migration::Migrator.run("adverts" => "ads")).to(
|
598
|
-
migrate_up(<<~EOS.strip)
|
599
|
-
rename_table :adverts, :ads
|
600
|
-
add_column :ads, :title, :string, limit: 250, null: true#{charset_and_collation}
|
601
|
-
add_column :ads, :body, :text#{', limit: 4294967295' if defined?(Mysql2)}, null: true#{charset_and_collation}
|
602
|
-
#{if defined?(Mysql2)
|
603
|
-
"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
604
|
-
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id"
|
605
|
-
end}
|
606
|
-
EOS
|
607
|
-
.and migrate_down(<<~EOS.strip)
|
608
|
-
#{if defined?(Mysql2)
|
609
|
-
"remove_foreign_key :adverts, name: :index_adverts_on_c_id\n" +
|
610
|
-
"remove_foreign_key :adverts, name: :index_adverts_on_category_id"
|
611
|
-
end}
|
612
|
-
remove_column :ads, :body
|
613
|
-
remove_column :ads, :title
|
614
|
-
rename_table :ads, :adverts
|
615
|
-
EOS
|
616
|
-
)
|
617
|
-
|
618
|
-
# Set the table name back to what it should be and confirm we're in sync:
|
619
|
-
|
620
|
-
nuke_model_class(Advert)
|
621
|
-
|
622
|
-
class Advert < ActiveRecord::Base
|
623
|
-
self.table_name = "adverts"
|
624
|
-
end
|
625
|
-
|
626
|
-
expect(Generators::DeclareSchema::Migration::Migrator.run).to eq(["", ""])
|
627
|
-
|
628
|
-
### Rename a table
|
629
|
-
|
630
|
-
# 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.
|
631
|
-
|
632
|
-
nuke_model_class(Advert)
|
633
|
-
|
634
|
-
class Advertisement < ActiveRecord::Base
|
635
|
-
fields do
|
636
|
-
title :string, limit: 250, null: true
|
637
|
-
body :text, null: true
|
638
|
-
end
|
639
|
-
end
|
640
|
-
|
641
|
-
expect(Generators::DeclareSchema::Migration::Migrator.run("adverts" => "advertisements")).to(
|
642
|
-
migrate_up(<<~EOS.strip)
|
643
|
-
rename_table :adverts, :advertisements
|
644
|
-
add_column :advertisements, :title, :string, limit: 250, null: true#{charset_and_collation}
|
645
|
-
add_column :advertisements, :body, :text#{', limit: 4294967295' if defined?(Mysql2)}, null: true#{charset_and_collation}
|
646
|
-
remove_column :advertisements, :name
|
647
|
-
EOS
|
648
|
-
.and migrate_down(<<~EOS.strip)
|
649
|
-
add_column :advertisements, :name, :string, limit: 250, null: true#{charset_and_collation}
|
650
|
-
remove_column :advertisements, :body
|
651
|
-
remove_column :advertisements, :title
|
652
|
-
rename_table :advertisements, :adverts
|
653
|
-
EOS
|
654
|
-
)
|
655
|
-
|
656
|
-
### Drop a table
|
657
|
-
|
658
|
-
nuke_model_class(Advertisement)
|
659
|
-
|
660
|
-
# If you delete a model, the migration generator will create a `drop_table` migration.
|
661
|
-
|
662
|
-
# Dropping tables is where the automatic down-migration really comes in handy:
|
663
|
-
|
664
|
-
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
665
|
-
migrate_up(<<~EOS.strip)
|
666
|
-
drop_table :adverts
|
667
|
-
EOS
|
668
|
-
.and migrate_down(<<~EOS.strip)
|
669
|
-
create_table "adverts"#{table_options}, force: :cascade do |t|
|
670
|
-
t.string "name", limit: 250#{charset_and_collation}
|
671
|
-
end
|
672
|
-
EOS
|
673
|
-
)
|
674
|
-
|
675
|
-
## STI
|
676
|
-
|
677
|
-
### Adding an STI subclass
|
678
|
-
|
679
|
-
# Adding a subclass or two should introduce the 'type' column and no other changes
|
680
|
-
|
681
|
-
class Advert < ActiveRecord::Base
|
682
|
-
fields do
|
683
|
-
body :text, null: true
|
684
|
-
title :string, default: "Untitled", limit: 250, null: true
|
685
|
-
end
|
686
|
-
end
|
687
|
-
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
688
|
-
ActiveRecord::Migration.class_eval(up)
|
689
|
-
|
690
|
-
class FancyAdvert < Advert
|
691
|
-
end
|
692
|
-
class SuperFancyAdvert < FancyAdvert
|
693
|
-
end
|
694
|
-
|
695
|
-
up, _ = Generators::DeclareSchema::Migration::Migrator.run do |migrations|
|
696
|
-
expect(migrations).to(
|
697
|
-
migrate_up(<<~EOS.strip)
|
698
|
-
add_column :adverts, :type, :string, limit: 250, null: true#{charset_and_collation}
|
699
|
-
add_index :adverts, [:type], name: :on_type
|
700
|
-
EOS
|
701
|
-
.and migrate_down(<<~EOS.strip)
|
702
|
-
remove_column :adverts, :type
|
703
|
-
remove_index :adverts, name: :on_type
|
704
|
-
EOS
|
705
|
-
)
|
706
|
-
end
|
707
|
-
|
708
|
-
Advert.field_specs.delete(:type)
|
709
|
-
nuke_model_class(SuperFancyAdvert)
|
710
|
-
nuke_model_class(FancyAdvert)
|
711
|
-
Advert.index_definitions.delete_if { |spec| spec.fields==["type"] }
|
712
|
-
|
713
|
-
## Coping with multiple changes
|
714
|
-
|
715
|
-
# The migration generator is designed to create complete migrations even if many changes to the models have taken place.
|
716
|
-
|
717
|
-
# First let's confirm we're in a known state. One model, 'Advert', with a string 'title' and text 'body':
|
718
|
-
|
719
|
-
ActiveRecord::Migration.class_eval up.gsub(/.*type.*/, '')
|
720
|
-
Advert.connection.schema_cache.clear!
|
721
|
-
Advert.reset_column_information
|
722
|
-
|
723
|
-
expect(Advert.connection.tables - Generators::DeclareSchema::Migration::Migrator.always_ignore_tables).
|
724
|
-
to eq(["adverts"])
|
725
|
-
expect(Advert.columns.map(&:name).sort).to eq(["body", "id", "title"])
|
726
|
-
expect(Generators::DeclareSchema::Migration::Migrator.run).to eq(["", ""])
|
727
|
-
|
728
|
-
|
729
|
-
### Rename a column and change the default
|
730
|
-
|
731
|
-
Advert.field_specs.clear
|
732
|
-
|
733
|
-
class Advert < ActiveRecord::Base
|
734
|
-
fields do
|
735
|
-
name :string, default: "No Name", limit: 250, null: true
|
736
|
-
body :text, null: true
|
737
|
-
end
|
738
|
-
end
|
739
|
-
|
740
|
-
expect(Generators::DeclareSchema::Migration::Migrator.run(adverts: { title: :name })).to(
|
741
|
-
migrate_up(<<~EOS.strip)
|
742
|
-
rename_column :adverts, :title, :name
|
743
|
-
change_column :adverts, :name, :string, limit: 250, null: true, default: "No Name"#{charset_and_collation}
|
744
|
-
EOS
|
745
|
-
.and migrate_down(<<~EOS.strip)
|
746
|
-
change_column :adverts, :name, :string, limit: 250, null: true, default: "Untitled"#{charset_and_collation}
|
747
|
-
rename_column :adverts, :name, :title
|
748
|
-
EOS
|
749
|
-
)
|
750
|
-
|
751
|
-
### Rename a table and add a column
|
752
|
-
|
753
|
-
nuke_model_class(Advert)
|
754
|
-
class Ad < ActiveRecord::Base
|
755
|
-
fields do
|
756
|
-
title :string, default: "Untitled", limit: 250
|
757
|
-
body :text, null: true
|
758
|
-
created_at :datetime
|
759
|
-
end
|
760
|
-
end
|
761
|
-
|
762
|
-
expect(Generators::DeclareSchema::Migration::Migrator.run(adverts: :ads)).to(
|
763
|
-
migrate_up(<<~EOS.strip)
|
764
|
-
rename_table :adverts, :ads
|
765
|
-
add_column :ads, :created_at, :datetime, null: false
|
766
|
-
change_column :ads, :title, :string, limit: 250, null: false, default: \"Untitled\"#{charset_and_collation}
|
767
|
-
EOS
|
768
|
-
)
|
769
|
-
|
770
|
-
class Advert < ActiveRecord::Base
|
771
|
-
fields do
|
772
|
-
body :text, null: true
|
773
|
-
title :string, default: "Untitled", limit: 250, null: true
|
774
|
-
end
|
775
|
-
end
|
776
|
-
|
777
|
-
## Legacy Keys
|
778
|
-
|
779
|
-
# DeclareSchema has some support for legacy keys.
|
780
|
-
|
781
|
-
nuke_model_class(Ad)
|
782
|
-
|
783
|
-
class Advert < ActiveRecord::Base
|
784
|
-
fields do
|
785
|
-
body :text, null: true
|
786
|
-
end
|
787
|
-
self.primary_key = "advert_id"
|
788
|
-
end
|
789
|
-
|
790
|
-
expect(Generators::DeclareSchema::Migration::Migrator.run(adverts: { id: :advert_id })).to(
|
791
|
-
migrate_up(<<~EOS.strip)
|
792
|
-
rename_column :adverts, :id, :advert_id
|
793
|
-
EOS
|
794
|
-
)
|
795
|
-
|
796
|
-
Advert.connection.schema_cache.clear!
|
797
|
-
Advert.reset_column_information
|
798
|
-
|
799
|
-
nuke_model_class(Advert)
|
800
|
-
ActiveRecord::Base.connection.execute("drop table `adverts`;")
|
801
|
-
|
802
|
-
if !defined?(SQLite3) && ActiveSupport::VERSION::MAJOR >= 5
|
803
|
-
class Advert < ActiveRecord::Base
|
804
|
-
def self.disable_auto_increment
|
805
|
-
true
|
806
|
-
end
|
807
|
-
|
808
|
-
fields do
|
809
|
-
description :text, limit: 250, null: true
|
810
|
-
end
|
811
|
-
end
|
812
|
-
|
813
|
-
up, _ = Generators::DeclareSchema::Migration::Migrator.run.tap do |migrations|
|
814
|
-
expect(migrations).to(
|
815
|
-
migrate_up(<<~EOS.strip)
|
816
|
-
create_table :adverts, id: false, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
|
817
|
-
t.integer :id, limit: 8, auto_increment: false, primary_key: true
|
818
|
-
t.text :description, limit: 255, null: true, charset: "utf8mb4", collation: "utf8mb4_bin"
|
819
|
-
end
|
820
|
-
EOS
|
821
|
-
.and migrate_down("drop_table :adverts")
|
822
|
-
)
|
823
|
-
end
|
824
|
-
end
|
825
|
-
|
826
|
-
## DSL
|
827
|
-
|
828
|
-
# The DSL allows lambdas and constants
|
829
|
-
|
830
|
-
class User < ActiveRecord::Base
|
831
|
-
fields do
|
832
|
-
company :string, limit: 250, ruby_default: -> { "BigCorp" }
|
833
|
-
end
|
834
|
-
end
|
835
|
-
expect(User.field_specs.keys).to eq(['company'])
|
836
|
-
expect(User.field_specs['company'].options[:ruby_default]&.call).to eq("BigCorp")
|
837
|
-
|
838
|
-
## validates
|
839
|
-
|
840
|
-
# DeclareSchema can accept a validates hash in the field options.
|
841
|
-
|
842
|
-
class Ad < ActiveRecord::Base
|
843
|
-
class << self
|
844
|
-
def validates(field_name, options)
|
845
|
-
end
|
846
|
-
end
|
847
|
-
end
|
848
|
-
expect(Ad).to receive(:validates).with(:company, presence: true, uniqueness: { case_sensitive: false })
|
849
|
-
class Ad < ActiveRecord::Base
|
850
|
-
fields do
|
851
|
-
company :string, limit: 250, index: true, unique: true, validates: { presence: true, uniqueness: { case_sensitive: false } }
|
852
|
-
end
|
853
|
-
self.primary_key = "advert_id"
|
854
|
-
end
|
855
|
-
up, _down = Generators::DeclareSchema::Migration::Migrator.run
|
856
|
-
ActiveRecord::Migration.class_eval(up)
|
857
|
-
expect(Ad.field_specs['company'].options[:validates].inspect).to eq("{:presence=>true, :uniqueness=>{:case_sensitive=>false}}")
|
858
|
-
end
|
859
|
-
|
860
|
-
context 'models with the same parent foreign key relation' do
|
861
|
-
before do
|
862
|
-
class Category < ActiveRecord::Base
|
863
|
-
fields do
|
864
|
-
name :string, limit: 250, null: true
|
865
|
-
end
|
866
|
-
end
|
867
|
-
class Advertiser < ActiveRecord::Base
|
868
|
-
fields do
|
869
|
-
name :string, limit: 250, null: true
|
870
|
-
end
|
871
|
-
belongs_to :category, limit: 8
|
872
|
-
end
|
873
|
-
class Affiliate < ActiveRecord::Base
|
874
|
-
fields do
|
875
|
-
name :string, limit: 250, null: true
|
876
|
-
end
|
877
|
-
belongs_to :category, limit: 8
|
878
|
-
end
|
879
|
-
end
|
880
|
-
|
881
|
-
it 'will genereate unique constraint names' do
|
882
|
-
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
883
|
-
migrate_up(<<~EOS.strip)
|
884
|
-
create_table :categories, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
|
885
|
-
t.string :name, limit: 250, null: true, charset: "utf8mb4", collation: "utf8mb4_bin"
|
886
|
-
end
|
887
|
-
create_table :advertisers, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
|
888
|
-
t.string :name, limit: 250, null: true, charset: "utf8mb4", collation: "utf8mb4_bin"
|
889
|
-
t.integer :category_id, limit: 8, null: false
|
890
|
-
end
|
891
|
-
create_table :affiliates, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
|
892
|
-
t.string :name, limit: 250, null: true, charset: "utf8mb4", collation: "utf8mb4_bin"
|
893
|
-
t.integer :category_id, limit: 8, null: false
|
894
|
-
end
|
895
|
-
add_index :advertisers, [:category_id], name: :on_category_id
|
896
|
-
add_index :affiliates, [:category_id], name: :on_category_id
|
897
|
-
add_foreign_key :advertisers, :categories, column: :category_id, name: :index_advertisers_on_category_id
|
898
|
-
add_foreign_key :affiliates, :categories, column: :category_id, name: :index_affiliates_on_category_id
|
899
|
-
EOS
|
900
|
-
)
|
901
|
-
migrate
|
902
|
-
|
903
|
-
nuke_model_class(Advertiser)
|
904
|
-
nuke_model_class(Affiliate)
|
905
|
-
end
|
906
|
-
end if !defined?(SQLite3)
|
907
|
-
|
908
|
-
describe 'serialize' do
|
909
|
-
before do
|
910
|
-
class Ad < ActiveRecord::Base
|
911
|
-
@serialize_args = []
|
912
|
-
|
913
|
-
class << self
|
914
|
-
attr_reader :serialize_args
|
915
|
-
|
916
|
-
def serialize(*args)
|
917
|
-
@serialize_args << args
|
918
|
-
end
|
919
|
-
end
|
920
|
-
end
|
921
|
-
end
|
922
|
-
|
923
|
-
describe 'untyped' do
|
924
|
-
it 'allows serialize: true' do
|
925
|
-
class Ad < ActiveRecord::Base
|
926
|
-
fields do
|
927
|
-
allow_list :text, limit: 0xFFFF, serialize: true
|
928
|
-
end
|
929
|
-
end
|
930
|
-
|
931
|
-
expect(Ad.serialize_args).to eq([[:allow_list]])
|
932
|
-
end
|
933
|
-
|
934
|
-
it 'converts defaults with .to_yaml' do
|
935
|
-
class Ad < ActiveRecord::Base
|
936
|
-
fields do
|
937
|
-
allow_list :string, limit: 250, serialize: true, null: true, default: []
|
938
|
-
allow_hash :string, limit: 250, serialize: true, null: true, default: {}
|
939
|
-
allow_string :string, limit: 250, serialize: true, null: true, default: ['abc']
|
940
|
-
allow_null :string, limit: 250, serialize: true, null: true, default: nil
|
941
|
-
end
|
942
|
-
end
|
943
|
-
|
944
|
-
expect(Ad.field_specs['allow_list'].default).to eq("--- []\n")
|
945
|
-
expect(Ad.field_specs['allow_hash'].default).to eq("--- {}\n")
|
946
|
-
expect(Ad.field_specs['allow_string'].default).to eq("---\n- abc\n")
|
947
|
-
expect(Ad.field_specs['allow_null'].default).to eq(nil)
|
948
|
-
end
|
949
|
-
end
|
950
|
-
|
951
|
-
describe 'Array' do
|
952
|
-
it 'allows serialize: Array' do
|
953
|
-
class Ad < ActiveRecord::Base
|
954
|
-
fields do
|
955
|
-
allow_list :string, limit: 250, serialize: Array, null: true
|
956
|
-
end
|
957
|
-
end
|
958
|
-
|
959
|
-
expect(Ad.serialize_args).to eq([[:allow_list, Array]])
|
960
|
-
end
|
961
|
-
|
962
|
-
it 'allows Array defaults' do
|
963
|
-
class Ad < ActiveRecord::Base
|
964
|
-
fields do
|
965
|
-
allow_list :string, limit: 250, serialize: Array, null: true, default: [2]
|
966
|
-
allow_string :string, limit: 250, serialize: Array, null: true, default: ['abc']
|
967
|
-
allow_empty :string, limit: 250, serialize: Array, null: true, default: []
|
968
|
-
allow_null :string, limit: 250, serialize: Array, null: true, default: nil
|
969
|
-
end
|
970
|
-
end
|
971
|
-
|
972
|
-
expect(Ad.field_specs['allow_list'].default).to eq("---\n- 2\n")
|
973
|
-
expect(Ad.field_specs['allow_string'].default).to eq("---\n- abc\n")
|
974
|
-
expect(Ad.field_specs['allow_empty'].default).to eq(nil)
|
975
|
-
expect(Ad.field_specs['allow_null'].default).to eq(nil)
|
976
|
-
end
|
977
|
-
end
|
978
|
-
|
979
|
-
describe 'Hash' do
|
980
|
-
it 'allows serialize: Hash' do
|
981
|
-
class Ad < ActiveRecord::Base
|
982
|
-
fields do
|
983
|
-
allow_list :string, limit: 250, serialize: Hash, null: true
|
984
|
-
end
|
985
|
-
end
|
986
|
-
|
987
|
-
expect(Ad.serialize_args).to eq([[:allow_list, Hash]])
|
988
|
-
end
|
989
|
-
|
990
|
-
it 'allows Hash defaults' do
|
991
|
-
class Ad < ActiveRecord::Base
|
992
|
-
fields do
|
993
|
-
allow_loc :string, limit: 250, serialize: Hash, null: true, default: { 'state' => 'CA' }
|
994
|
-
allow_hash :string, limit: 250, serialize: Hash, null: true, default: {}
|
995
|
-
allow_null :string, limit: 250, serialize: Hash, null: true, default: nil
|
996
|
-
end
|
997
|
-
end
|
998
|
-
|
999
|
-
expect(Ad.field_specs['allow_loc'].default).to eq("---\nstate: CA\n")
|
1000
|
-
expect(Ad.field_specs['allow_hash'].default).to eq(nil)
|
1001
|
-
expect(Ad.field_specs['allow_null'].default).to eq(nil)
|
1002
|
-
end
|
1003
|
-
end
|
1004
|
-
|
1005
|
-
describe 'JSON' do
|
1006
|
-
it 'allows serialize: JSON' do
|
1007
|
-
class Ad < ActiveRecord::Base
|
1008
|
-
fields do
|
1009
|
-
allow_list :string, limit: 250, serialize: JSON
|
1010
|
-
end
|
1011
|
-
end
|
1012
|
-
|
1013
|
-
expect(Ad.serialize_args).to eq([[:allow_list, JSON]])
|
1014
|
-
end
|
1015
|
-
|
1016
|
-
it 'allows JSON defaults' do
|
1017
|
-
class Ad < ActiveRecord::Base
|
1018
|
-
fields do
|
1019
|
-
allow_hash :string, limit: 250, serialize: JSON, null: true, default: { 'state' => 'CA' }
|
1020
|
-
allow_empty_array :string, limit: 250, serialize: JSON, null: true, default: []
|
1021
|
-
allow_empty_hash :string, limit: 250, serialize: JSON, null: true, default: {}
|
1022
|
-
allow_null :string, limit: 250, serialize: JSON, null: true, default: nil
|
1023
|
-
end
|
1024
|
-
end
|
1025
|
-
|
1026
|
-
expect(Ad.field_specs['allow_hash'].default).to eq("{\"state\":\"CA\"}")
|
1027
|
-
expect(Ad.field_specs['allow_empty_array'].default).to eq("[]")
|
1028
|
-
expect(Ad.field_specs['allow_empty_hash'].default).to eq("{}")
|
1029
|
-
expect(Ad.field_specs['allow_null'].default).to eq(nil)
|
1030
|
-
end
|
1031
|
-
end
|
1032
|
-
|
1033
|
-
class ValueClass
|
1034
|
-
delegate :present?, :inspect, to: :@value
|
1035
|
-
|
1036
|
-
def initialize(value)
|
1037
|
-
@value = value
|
1038
|
-
end
|
1039
|
-
|
1040
|
-
class << self
|
1041
|
-
def dump(object)
|
1042
|
-
if object&.present?
|
1043
|
-
object.inspect
|
1044
|
-
end
|
1045
|
-
end
|
1046
|
-
|
1047
|
-
def load(serialized)
|
1048
|
-
if serialized
|
1049
|
-
raise 'not used ???'
|
1050
|
-
end
|
1051
|
-
end
|
1052
|
-
end
|
1053
|
-
end
|
1054
|
-
|
1055
|
-
describe 'custom coder' do
|
1056
|
-
it 'allows serialize: ValueClass' do
|
1057
|
-
class Ad < ActiveRecord::Base
|
1058
|
-
fields do
|
1059
|
-
allow_list :string, limit: 250, serialize: ValueClass
|
1060
|
-
end
|
1061
|
-
end
|
1062
|
-
|
1063
|
-
expect(Ad.serialize_args).to eq([[:allow_list, ValueClass]])
|
1064
|
-
end
|
1065
|
-
|
1066
|
-
it 'allows ValueClass defaults' do
|
1067
|
-
class Ad < ActiveRecord::Base
|
1068
|
-
fields do
|
1069
|
-
allow_hash :string, limit: 250, serialize: ValueClass, null: true, default: ValueClass.new([2])
|
1070
|
-
allow_empty_array :string, limit: 250, serialize: ValueClass, null: true, default: ValueClass.new([])
|
1071
|
-
allow_null :string, limit: 250, serialize: ValueClass, null: true, default: nil
|
1072
|
-
end
|
1073
|
-
end
|
1074
|
-
|
1075
|
-
expect(Ad.field_specs['allow_hash'].default).to eq("[2]")
|
1076
|
-
expect(Ad.field_specs['allow_empty_array'].default).to eq(nil)
|
1077
|
-
expect(Ad.field_specs['allow_null'].default).to eq(nil)
|
1078
|
-
end
|
1079
|
-
end
|
1080
|
-
|
1081
|
-
it 'disallows serialize: with a non-string column type' do
|
1082
|
-
expect do
|
1083
|
-
class Ad < ActiveRecord::Base
|
1084
|
-
fields do
|
1085
|
-
allow_list :integer, limit: 8, serialize: true
|
1086
|
-
end
|
1087
|
-
end
|
1088
|
-
end.to raise_exception(ArgumentError, /must be :string or :text/)
|
1089
|
-
end
|
1090
|
-
end
|
1091
|
-
|
1092
|
-
context "for Rails #{ActiveSupport::VERSION::MAJOR}" do
|
1093
|
-
let(:optional_true) { { optional: true } }
|
1094
|
-
let(:optional_false) { { optional: false } }
|
1095
|
-
let(:optional_flag) { { false => optional_false, true => optional_true } }
|
1096
|
-
|
1097
|
-
describe 'belongs_to' do
|
1098
|
-
before do
|
1099
|
-
unless defined?(AdCategory)
|
1100
|
-
class AdCategory < ActiveRecord::Base
|
1101
|
-
fields { }
|
1102
|
-
end
|
1103
|
-
end
|
1104
|
-
|
1105
|
-
class Advert < ActiveRecord::Base
|
1106
|
-
fields do
|
1107
|
-
name :string, limit: 250, null: true
|
1108
|
-
category_id :integer, limit: 8
|
1109
|
-
nullable_category_id :integer, limit: 8, null: true
|
1110
|
-
end
|
1111
|
-
end
|
1112
|
-
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
1113
|
-
ActiveRecord::Migration.class_eval(up)
|
1114
|
-
end
|
1115
|
-
|
1116
|
-
it 'passes through optional: when given' do
|
1117
|
-
class AdvertBelongsTo < ActiveRecord::Base
|
1118
|
-
self.table_name = 'adverts'
|
1119
|
-
fields { }
|
1120
|
-
reset_column_information
|
1121
|
-
belongs_to :ad_category, optional: true
|
1122
|
-
end
|
1123
|
-
expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_true)
|
1124
|
-
end
|
1125
|
-
|
1126
|
-
describe 'contradictory settings' do # contradictory settings are ok--for example, during migration
|
1127
|
-
it 'passes through optional: true, null: false' do
|
1128
|
-
class AdvertBelongsTo < ActiveRecord::Base
|
1129
|
-
self.table_name = 'adverts'
|
1130
|
-
fields { }
|
1131
|
-
reset_column_information
|
1132
|
-
belongs_to :ad_category, optional: true, null: false
|
1133
|
-
end
|
1134
|
-
expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_true)
|
1135
|
-
expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(false)
|
1136
|
-
end
|
1137
|
-
|
1138
|
-
it 'passes through optional: false, null: true' do
|
1139
|
-
class AdvertBelongsTo < ActiveRecord::Base
|
1140
|
-
self.table_name = 'adverts'
|
1141
|
-
fields { }
|
1142
|
-
reset_column_information
|
1143
|
-
belongs_to :ad_category, optional: false, null: true
|
1144
|
-
end
|
1145
|
-
expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_false)
|
1146
|
-
expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(true)
|
1147
|
-
end
|
1148
|
-
end
|
1149
|
-
|
1150
|
-
[false, true].each do |nullable|
|
1151
|
-
context "nullable=#{nullable}" do
|
1152
|
-
it 'infers optional: from null:' do
|
1153
|
-
eval <<~EOS
|
1154
|
-
class AdvertBelongsTo < ActiveRecord::Base
|
1155
|
-
fields { }
|
1156
|
-
belongs_to :ad_category, null: #{nullable}
|
1157
|
-
end
|
1158
|
-
EOS
|
1159
|
-
expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_flag[nullable])
|
1160
|
-
expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(nullable)
|
1161
|
-
end
|
1162
|
-
|
1163
|
-
it 'infers null: from optional:' do
|
1164
|
-
eval <<~EOS
|
1165
|
-
class AdvertBelongsTo < ActiveRecord::Base
|
1166
|
-
fields { }
|
1167
|
-
belongs_to :ad_category, optional: #{nullable}
|
1168
|
-
end
|
1169
|
-
EOS
|
1170
|
-
expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_flag[nullable])
|
1171
|
-
expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(nullable)
|
1172
|
-
end
|
1173
|
-
end
|
1174
|
-
end
|
1175
|
-
end
|
1176
|
-
end
|
1177
|
-
|
1178
|
-
describe 'migration base class' do
|
1179
|
-
it 'adapts to Rails 4' do
|
1180
|
-
class Advert < active_record_base_class.constantize
|
1181
|
-
fields do
|
1182
|
-
title :string, limit: 100
|
1183
|
-
end
|
1184
|
-
end
|
1185
|
-
|
1186
|
-
generate_migrations '-n', '-m'
|
1187
|
-
|
1188
|
-
migrations = Dir.glob('db/migrate/*declare_schema_migration*.rb')
|
1189
|
-
expect(migrations.size).to eq(1), migrations.inspect
|
1190
|
-
|
1191
|
-
migration_content = File.read(migrations.first)
|
1192
|
-
first_line = migration_content.split("\n").first
|
1193
|
-
base_class = first_line.split(' < ').last
|
1194
|
-
expect(base_class).to eq("(ActiveRecord::Migration[4.2])")
|
1195
|
-
end
|
1196
|
-
end
|
1197
|
-
|
1198
|
-
context 'Does not generate migrations' do
|
1199
|
-
it 'for aliased fields bigint -> integer limit 8' do
|
1200
|
-
class Advert < active_record_base_class.constantize
|
1201
|
-
fields do
|
1202
|
-
price :bigint
|
1203
|
-
end
|
1204
|
-
end
|
1205
|
-
|
1206
|
-
generate_migrations '-n', '-m'
|
1207
|
-
|
1208
|
-
migrations = Dir.glob('db/migrate/*declare_schema_migration*.rb')
|
1209
|
-
expect(migrations.size).to eq(1), migrations.inspect
|
1210
|
-
|
1211
|
-
class Advert < active_record_base_class.constantize
|
1212
|
-
fields do
|
1213
|
-
price :integer, limit: 8
|
1214
|
-
end
|
1215
|
-
end
|
1216
|
-
|
1217
|
-
expect { generate_migrations '-n', '-g' }.to output("Database and models match -- nothing to change\n").to_stdout
|
1218
|
-
end
|
1219
|
-
end
|
1220
|
-
end
|
1221
|
-
|
1222
57
|
context 'Using declare_schema' do
|
1223
58
|
# DeclareSchema - Migration Generator
|
1224
59
|
it 'generates migrations' do
|
@@ -1758,7 +593,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
1758
593
|
Advert.connection.schema_cache.clear!
|
1759
594
|
Advert.reset_column_information
|
1760
595
|
|
1761
|
-
expect(Generators::DeclareSchema::Migration::Migrator.run(
|
596
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run(adverts: "ads")).to(
|
1762
597
|
migrate_up(<<~EOS.strip)
|
1763
598
|
rename_table :adverts, :ads
|
1764
599
|
add_column :ads, :title, :string, limit: 250, null: true#{charset_and_collation}
|
@@ -1802,7 +637,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
1802
637
|
end
|
1803
638
|
end
|
1804
639
|
|
1805
|
-
expect(Generators::DeclareSchema::Migration::Migrator.run(
|
640
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run(adverts: "advertisements")).to(
|
1806
641
|
migrate_up(<<~EOS.strip)
|
1807
642
|
rename_table :adverts, :advertisements
|
1808
643
|
add_column :advertisements, :title, :string, limit: 250, null: true#{charset_and_collation}
|
@@ -1999,19 +834,19 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
1999
834
|
context 'models with the same parent foreign key relation' do
|
2000
835
|
before do
|
2001
836
|
class Category < ActiveRecord::Base
|
2002
|
-
|
2003
|
-
|
837
|
+
declare_schema do
|
838
|
+
string :name, limit: 250, null: true
|
2004
839
|
end
|
2005
840
|
end
|
2006
841
|
class Advertiser < ActiveRecord::Base
|
2007
|
-
|
2008
|
-
|
842
|
+
declare_schema do
|
843
|
+
string :name, limit: 250, null: true
|
2009
844
|
end
|
2010
845
|
belongs_to :category, limit: 8
|
2011
846
|
end
|
2012
847
|
class Affiliate < ActiveRecord::Base
|
2013
|
-
|
2014
|
-
|
848
|
+
declare_schema do
|
849
|
+
string :name, limit: 250, null: true
|
2015
850
|
end
|
2016
851
|
belongs_to :category, limit: 8
|
2017
852
|
end
|
@@ -2339,9 +1174,9 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
2339
1174
|
end
|
2340
1175
|
class Fk < ActiveRecord::Base
|
2341
1176
|
declare_schema { }
|
2342
|
-
belongs_to :id_default
|
2343
|
-
belongs_to :id4
|
2344
|
-
belongs_to :id8
|
1177
|
+
belongs_to :id_default
|
1178
|
+
belongs_to :id4
|
1179
|
+
belongs_to :id8
|
2345
1180
|
end
|
2346
1181
|
end
|
2347
1182
|
|