declare_schema 0.6.4 → 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/CHANGELOG.md +6 -0
- data/Gemfile.lock +4 -4
- data/lib/declare_schema/model/column.rb +168 -0
- data/lib/declare_schema/model/field_spec.rb +59 -143
- 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 +107 -129
- 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/migration_generator_spec.rb +45 -42
- data/spec/lib/declare_schema/model/column_spec.rb +141 -0
- data/spec/lib/generators/declare_schema/migration/migrator_spec.rb +2 -11
- metadata +4 -2
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'active_record'
|
4
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
4
5
|
|
5
6
|
module Generators
|
6
7
|
module DeclareSchema
|
@@ -116,20 +117,6 @@ module Generators
|
|
116
117
|
ActiveRecord::Base.connection
|
117
118
|
end
|
118
119
|
|
119
|
-
def fix_native_types(types)
|
120
|
-
case connection.class.name
|
121
|
-
when /mysql/i
|
122
|
-
types[:integer][:limit] ||= 11
|
123
|
-
types[:text][:limit] ||= 0xffff
|
124
|
-
types[:binary][:limit] ||= 0xffff
|
125
|
-
end
|
126
|
-
types
|
127
|
-
end
|
128
|
-
|
129
|
-
def native_types
|
130
|
-
@native_types ||= fix_native_types(connection.native_database_types)
|
131
|
-
end
|
132
|
-
|
133
120
|
def before_generating_migration(&block)
|
134
121
|
block or raise ArgumentError, 'A block is required when setting the before_generating_migration callback'
|
135
122
|
@before_generating_migration_callback = block
|
@@ -299,7 +286,7 @@ module Generators
|
|
299
286
|
"drop_table :#{t}"
|
300
287
|
end * "\n"
|
301
288
|
undo_drops = to_drop.map do |t|
|
302
|
-
|
289
|
+
add_table_back(t)
|
303
290
|
end * "\n\n"
|
304
291
|
|
305
292
|
creates = to_create.map do |t|
|
@@ -345,7 +332,7 @@ module Generators
|
|
345
332
|
disable_auto_increment = model.respond_to?(:disable_auto_increment) && model.disable_auto_increment
|
346
333
|
table_options_definition = ::DeclareSchema::Model::TableOptionsDefinition.new(model.table_name, table_options_for_model(model))
|
347
334
|
field_definitions = [
|
348
|
-
|
335
|
+
("t.integer :id, limit: 8, auto_increment: false, primary_key: true" if disable_auto_increment),
|
349
336
|
*(model.field_specs.values.sort_by(&:position).map { |f| create_field(f, longest_field_name) })
|
350
337
|
].compact
|
351
338
|
|
@@ -390,8 +377,8 @@ module Generators
|
|
390
377
|
end
|
391
378
|
|
392
379
|
def create_field(field_spec, field_name_width)
|
393
|
-
options = fk_field_options(field_spec.model, field_spec.name)
|
394
|
-
args = [field_spec.name.inspect] + format_options(options
|
380
|
+
options = field_spec.sql_options.merge(fk_field_options(field_spec.model, field_spec.name))
|
381
|
+
args = [field_spec.name.inspect] + format_options(options.compact)
|
395
382
|
format("t.%-*s %s", field_name_width, field_spec.sql_type, args.join(', '))
|
396
383
|
end
|
397
384
|
|
@@ -429,8 +416,8 @@ module Generators
|
|
429
416
|
adds = to_add.map do |c|
|
430
417
|
args =
|
431
418
|
if (spec = model.field_specs[c])
|
432
|
-
options = fk_field_options(model, c)
|
433
|
-
[":#{spec.sql_type}", *format_options(options
|
419
|
+
options = spec.sql_options.merge(fk_field_options(model, c))
|
420
|
+
[":#{spec.sql_type}", *format_options(options.compact)]
|
434
421
|
else
|
435
422
|
[":integer"]
|
436
423
|
end
|
@@ -444,34 +431,28 @@ module Generators
|
|
444
431
|
"remove_column :#{new_table_name}, :#{c}"
|
445
432
|
end
|
446
433
|
undo_removes = to_remove.map do |c|
|
447
|
-
|
434
|
+
add_column_back(model, current_table_name, c)
|
448
435
|
end
|
449
436
|
|
450
437
|
old_names = to_rename.invert
|
451
438
|
changes = []
|
452
439
|
undo_changes = []
|
453
|
-
to_change.each do |
|
454
|
-
|
455
|
-
|
456
|
-
spec
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
change_spec[:collation] = spec.collation unless spec.collation.nil?
|
468
|
-
|
469
|
-
changes << "change_column :#{new_table_name}, :#{c}, " +
|
470
|
-
([":#{spec.sql_type}"] + format_options(change_spec, spec.sql_type, changing: true)).join(", ")
|
471
|
-
back = change_column_back(current_table_name, col_name)
|
472
|
-
undo_changes << back unless back.blank?
|
440
|
+
to_change.each do |col_name_to_change|
|
441
|
+
orig_col_name = old_names[col_name_to_change] || col_name_to_change
|
442
|
+
column = db_columns[orig_col_name] or raise "failed to find column info for #{orig_col_name.inspect}"
|
443
|
+
spec = model.field_specs[col_name_to_change] or raise "failed to find field spec for #{col_name_to_change.inspect}"
|
444
|
+
spec_attrs = spec.schema_attributes(column)
|
445
|
+
column_declaration = ::DeclareSchema::Model::Column.new(model, current_table_name, column)
|
446
|
+
col_attrs = column_declaration.schema_attributes
|
447
|
+
if !::DeclareSchema::Model::Column.equivalent_schema_attributes?(spec_attrs, col_attrs)
|
448
|
+
normalized_schema_attributes = spec_attrs.merge(fk_field_options(model, col_name_to_change))
|
449
|
+
|
450
|
+
type = normalized_schema_attributes.delete(:type) or raise "no :type found in #{normalized_schema_attributes.inspect}"
|
451
|
+
changes << ["change_column #{new_table_name.to_sym.inspect}", col_name_to_change.to_sym.inspect,
|
452
|
+
type.to_sym.inspect, *format_options(normalized_schema_attributes)].join(", ")
|
453
|
+
undo_changes << change_column_back(model, current_table_name, orig_col_name)
|
473
454
|
end
|
474
|
-
end
|
455
|
+
end
|
475
456
|
|
476
457
|
index_changes, undo_index_changes = change_indexes(model, current_table_name, to_remove)
|
477
458
|
fk_changes, undo_fk_changes = if ActiveRecord::Base.connection.class.name.match?(/SQLite3Adapter/)
|
@@ -485,26 +466,26 @@ module Generators
|
|
485
466
|
[[], []]
|
486
467
|
end
|
487
468
|
|
488
|
-
[(renames + adds + removes + changes)
|
469
|
+
[(renames + adds + removes + changes) * "\n",
|
489
470
|
(undo_renames + undo_adds + undo_removes + undo_changes) * "\n",
|
490
|
-
index_changes
|
491
|
-
undo_index_changes
|
492
|
-
fk_changes
|
493
|
-
undo_fk_changes
|
494
|
-
table_options_changes
|
495
|
-
undo_table_options_changes
|
471
|
+
index_changes * "\n",
|
472
|
+
undo_index_changes * "\n",
|
473
|
+
fk_changes * "\n",
|
474
|
+
undo_fk_changes * "\n",
|
475
|
+
table_options_changes * "\n",
|
476
|
+
undo_table_options_changes * "\n"]
|
496
477
|
end
|
497
478
|
|
498
479
|
def change_indexes(model, old_table_name, to_remove)
|
499
|
-
return [[], []]
|
480
|
+
Migrator.disable_constraints and return [[], []]
|
500
481
|
|
501
482
|
new_table_name = model.table_name
|
502
483
|
existing_indexes = ::DeclareSchema::Model::IndexDefinition.for_model(model, old_table_name)
|
503
484
|
model_indexes_with_equivalents = model.index_definitions_with_primary_key
|
504
485
|
model_indexes = model_indexes_with_equivalents.map do |i|
|
505
486
|
if i.explicit_name.nil?
|
506
|
-
if
|
507
|
-
i.with_name(
|
487
|
+
if (existing = existing_indexes.find { |e| i != e && e.equivalent?(i) })
|
488
|
+
i.with_name(existing.name)
|
508
489
|
end
|
509
490
|
end || i
|
510
491
|
end
|
@@ -514,15 +495,13 @@ module Generators
|
|
514
495
|
end
|
515
496
|
model_has_primary_key = model_indexes.any? { |i| i.name == ::DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME }
|
516
497
|
|
517
|
-
add_indexes_init = model_indexes - existing_indexes
|
518
|
-
drop_indexes_init = existing_indexes - model_indexes
|
519
498
|
undo_add_indexes = []
|
520
|
-
|
521
|
-
add_indexes = add_indexes_init.map do |i|
|
499
|
+
add_indexes = (model_indexes - existing_indexes).map do |i|
|
522
500
|
undo_add_indexes << drop_index(old_table_name, i.name) unless i.name == ::DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME
|
523
501
|
i.to_add_statement(new_table_name, existing_has_primary_key)
|
524
502
|
end
|
525
|
-
|
503
|
+
undo_drop_indexes = []
|
504
|
+
drop_indexes = (existing_indexes - model_indexes).map do |i|
|
526
505
|
undo_drop_indexes << i.to_add_statement(old_table_name, model_has_primary_key)
|
527
506
|
drop_index(new_table_name, i.name) unless i.name == ::DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME
|
528
507
|
end.compact
|
@@ -538,24 +517,22 @@ module Generators
|
|
538
517
|
end
|
539
518
|
|
540
519
|
def change_foreign_key_constraints(model, old_table_name)
|
541
|
-
ActiveRecord::Base.connection.class.name.match?(/SQLite3Adapter/) and raise 'SQLite does not support foreign keys'
|
542
|
-
return [[], []]
|
520
|
+
ActiveRecord::Base.connection.class.name.match?(/SQLite3Adapter/) and raise ArgumentError, 'SQLite does not support foreign keys'
|
521
|
+
Migrator.disable_indexing and return [[], []]
|
543
522
|
|
544
523
|
new_table_name = model.table_name
|
545
524
|
existing_fks = ::DeclareSchema::Model::ForeignKeyDefinition.for_model(model, old_table_name)
|
546
525
|
model_fks = model.constraint_specs
|
547
|
-
add_fks = model_fks - existing_fks
|
548
|
-
drop_fks = existing_fks - model_fks
|
549
|
-
undo_add_fks = []
|
550
|
-
undo_drop_fks = []
|
551
526
|
|
552
|
-
|
527
|
+
undo_add_fks = []
|
528
|
+
add_fks = (model_fks - existing_fks).map do |fk|
|
553
529
|
# next if fk.parent.constantize.abstract_class || fk.parent == fk.model.class_name
|
554
530
|
undo_add_fks << remove_foreign_key(old_table_name, fk.constraint_name)
|
555
531
|
fk.to_add_statement
|
556
|
-
end
|
532
|
+
end
|
557
533
|
|
558
|
-
|
534
|
+
undo_drop_fks = []
|
535
|
+
drop_fks = (existing_fks - model_fks).map do |fk|
|
559
536
|
undo_drop_fks << fk.to_add_statement
|
560
537
|
remove_foreign_key(new_table_name, fk.constraint_name)
|
561
538
|
end
|
@@ -567,22 +544,14 @@ module Generators
|
|
567
544
|
"remove_foreign_key(#{old_table_name.inspect}, name: #{fk_name.to_s.inspect})"
|
568
545
|
end
|
569
546
|
|
570
|
-
def format_options(options
|
547
|
+
def format_options(options)
|
571
548
|
options.map do |k, v|
|
572
|
-
if !changing && ((k == :limit && type == :decimal) || (k == :null && v == true))
|
573
|
-
next
|
574
|
-
end
|
575
|
-
|
576
|
-
if !::DeclareSchema::Model::FieldSpec.mysql_text_limits? && k == :limit && type == :text
|
577
|
-
next
|
578
|
-
end
|
579
|
-
|
580
549
|
if k.is_a?(Symbol)
|
581
550
|
"#{k}: #{v.inspect}"
|
582
551
|
else
|
583
552
|
"#{k.inspect} => #{v.inspect}"
|
584
553
|
end
|
585
|
-
end
|
554
|
+
end
|
586
555
|
end
|
587
556
|
|
588
557
|
def fk_field_options(model, field_name)
|
@@ -620,7 +589,33 @@ module Generators
|
|
620
589
|
end
|
621
590
|
end
|
622
591
|
|
623
|
-
|
592
|
+
def with_previous_model_table_name(model, table_name)
|
593
|
+
model_table_name, model.table_name = model.table_name, table_name
|
594
|
+
yield
|
595
|
+
ensure
|
596
|
+
model.table_name = model_table_name
|
597
|
+
end
|
598
|
+
|
599
|
+
def add_column_back(model, current_table_name, col_name)
|
600
|
+
with_previous_model_table_name(model, current_table_name) do
|
601
|
+
column = model.columns_hash[col_name] or raise "no columns_hash entry found for #{col_name} in #{model.inspect}"
|
602
|
+
col_spec = ::DeclareSchema::Model::Column.new(model, current_table_name, column)
|
603
|
+
schema_attributes = col_spec.schema_attributes
|
604
|
+
type = schema_attributes.delete(:type) or raise "no :type found in #{schema_attributes.inspect}"
|
605
|
+
["add_column :#{current_table_name}, :#{col_name}, #{type.inspect}", *format_options(schema_attributes)].join(', ')
|
606
|
+
end
|
607
|
+
end
|
608
|
+
|
609
|
+
def change_column_back(model, current_table_name, col_name)
|
610
|
+
with_previous_model_table_name(model, current_table_name) do
|
611
|
+
column = model.columns_hash[col_name] or raise "no columns_hash entry found for #{col_name} in #{model.inspect}"
|
612
|
+
col_spec = ::DeclareSchema::Model::Column.new(model, current_table_name, column)
|
613
|
+
schema_attributes = col_spec.schema_attributes
|
614
|
+
type = schema_attributes.delete(:type) or raise "no :type found in #{schema_attributes.inspect}"
|
615
|
+
["change_column #{current_table_name.to_sym.inspect}", col_name.to_sym.inspect, type.to_sym.inspect, *format_options(schema_attributes)].join(', ')
|
616
|
+
end
|
617
|
+
end
|
618
|
+
|
624
619
|
def default_collation_from_charset(charset)
|
625
620
|
case charset
|
626
621
|
when "utf8"
|
@@ -630,64 +625,47 @@ module Generators
|
|
630
625
|
end
|
631
626
|
end
|
632
627
|
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
628
|
+
SchemaDumper = case Rails::VERSION::MAJOR
|
629
|
+
when 4
|
630
|
+
ActiveRecord::SchemaDumper
|
631
|
+
else
|
632
|
+
ActiveRecord::ConnectionAdapters::SchemaDumper
|
633
|
+
end
|
634
|
+
|
635
|
+
def add_table_back(table)
|
636
|
+
dumped_schema_stream = StringIO.new
|
637
|
+
SchemaDumper.send(:new, ActiveRecord::Base.connection).send(:table, table, dumped_schema_stream)
|
642
638
|
|
643
|
-
|
639
|
+
dumped_schema = dumped_schema_stream.string.strip.gsub!("\n ", "\n")
|
644
640
|
if connection.class.name.match?(/mysql/i)
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
end
|
649
|
-
default_charset = result[/CHARSET=(\w+)/, 1] or raise "unable to find charset in #{result.inspect}"
|
650
|
-
default_collation = result[/COLLATE=(\w+)/, 1] || default_collation_from_charset(default_charset) or
|
651
|
-
raise "unable to find collation in #{result.inspect} or charset #{default_charset.inspect}"
|
652
|
-
result = result.split("\n").map do |line|
|
653
|
-
if line['t.text'] || line['t.string']
|
654
|
-
if !line['charset: ']
|
655
|
-
if line['collation: ']
|
656
|
-
line = line.sub('collation: ', "charset: #{default_charset.inspect}, collation: ")
|
657
|
-
else
|
658
|
-
line += ", charset: #{default_charset.inspect}"
|
659
|
-
end
|
660
|
-
end
|
661
|
-
line['collation: '] or line += ", collation: #{default_collation.inspect}"
|
662
|
-
end
|
663
|
-
line
|
664
|
-
end.join("\n")
|
641
|
+
fix_mysql_charset_and_collation(dumped_schema)
|
642
|
+
else
|
643
|
+
dumped_schema
|
665
644
|
end
|
666
|
-
result
|
667
645
|
end
|
668
646
|
|
669
|
-
|
670
|
-
|
671
|
-
if
|
672
|
-
|
673
|
-
|
674
|
-
elsif (md = revert.match(/\s*t\.([a-z_]+)\s+"#{column}"(?:,\s+(.*?)$)?/m))
|
675
|
-
# Sexy migration
|
676
|
-
_, string_type, options = *md
|
677
|
-
type = ":#{string_type}"
|
647
|
+
# TODO: rewrite this method to use charset and collation variables rather than manipulating strings. -Colin
|
648
|
+
def fix_mysql_charset_and_collation(dumped_schema)
|
649
|
+
if !dumped_schema['options: ']
|
650
|
+
dumped_schema.sub!('",', "\", options: \"DEFAULT CHARSET=#{Generators::DeclareSchema::Migration::Migrator.default_charset} "+
|
651
|
+
"COLLATE=#{Generators::DeclareSchema::Migration::Migrator.default_collation}\",")
|
678
652
|
end
|
679
|
-
|
680
|
-
[
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
653
|
+
default_charset = dumped_schema[/CHARSET=(\w+)/, 1] or raise "unable to find charset in #{dumped_schema.inspect}"
|
654
|
+
default_collation = dumped_schema[/COLLATE=(\w+)/, 1] || default_collation_from_charset(default_charset) or
|
655
|
+
raise "unable to find collation in #{dumped_schema.inspect} or charset #{default_charset.inspect}"
|
656
|
+
dumped_schema.split("\n").map do |line|
|
657
|
+
if line['t.text'] || line['t.string']
|
658
|
+
if !line['charset: ']
|
659
|
+
if line['collation: ']
|
660
|
+
line.sub!('collation: ', "charset: #{default_charset.inspect}, collation: ")
|
661
|
+
else
|
662
|
+
line << ", charset: #{default_charset.inspect}"
|
663
|
+
end
|
664
|
+
end
|
665
|
+
line['collation: '] or line << ", collation: #{default_collation.inspect}"
|
666
|
+
end
|
667
|
+
line
|
668
|
+
end.join("\n")
|
691
669
|
end
|
692
670
|
end
|
693
671
|
end
|
@@ -25,7 +25,7 @@ RSpec.describe DeclareSchema::FieldDeclarationDsl do
|
|
25
25
|
end
|
26
26
|
|
27
27
|
it 'stores limits' do
|
28
|
-
expect(TestModel.field_specs['name'].limit).to eq(127)
|
28
|
+
expect(TestModel.field_specs['name'].limit).to eq(127), TestModel.field_specs['name'].inspect
|
29
29
|
end
|
30
30
|
|
31
31
|
# TODO: fill out remaining tests
|
@@ -1,69 +1,166 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
begin
|
4
|
+
require 'mysql2'
|
5
|
+
rescue LoadError
|
6
|
+
end
|
7
|
+
|
8
|
+
RSpec.describe DeclareSchema::Model::FieldSpec do
|
9
|
+
let(:model) { double('model', table_options: {}) }
|
10
|
+
let(:col_spec) { double('col_spec', sql_type: 'varchar') }
|
11
|
+
|
4
12
|
before do
|
5
13
|
load File.expand_path('prepare_testapp.rb', __dir__)
|
14
|
+
|
15
|
+
if Rails::VERSION::MAJOR < 5
|
16
|
+
allow(col_spec).to receive(:type_cast_from_database, &:itself)
|
17
|
+
end
|
6
18
|
end
|
7
19
|
|
8
|
-
|
9
|
-
it '
|
10
|
-
subject =
|
20
|
+
describe '#initialize' do
|
21
|
+
it 'normalizes option order' do
|
22
|
+
subject = described_class.new(model, :price, :integer, anonymize_using: 'x', null: false, position: 0, limit: 4)
|
23
|
+
expect(subject.options.keys).to eq([:limit, :null, :anonymize_using])
|
24
|
+
end
|
25
|
+
end
|
11
26
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
sql_type_metadata = ActiveRecord::ConnectionAdapters::SqlTypeMetadata.new(sql_type: "integer(8)", type: :integer, limit: 8)
|
18
|
-
col = ActiveRecord::ConnectionAdapters::Column.new("price", nil, sql_type_metadata, false, "adverts")
|
27
|
+
describe '#schema_attributes' do
|
28
|
+
describe 'integer 4' do
|
29
|
+
it 'returns schema attributes' do
|
30
|
+
subject = described_class.new(model, :price, :integer, limit: 4, null: false, position: 0)
|
31
|
+
expect(subject.schema_attributes(col_spec)).to eq(type: :integer, limit: 4, null: false)
|
19
32
|
end
|
20
|
-
|
21
|
-
expect(subject.different_to?(subject.name, col)).to eq(false)
|
22
33
|
end
|
23
34
|
|
24
|
-
|
25
|
-
|
35
|
+
describe 'integer 8' do
|
36
|
+
it 'returns schema attributes' do
|
37
|
+
subject = described_class.new(model, :price, :integer, limit: 8, null: true, position: 2)
|
38
|
+
expect(subject.schema_attributes(col_spec)).to eq(type: :integer, limit: 8, null: true)
|
39
|
+
end
|
40
|
+
end
|
26
41
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
else
|
32
|
-
sql_type_metadata = ActiveRecord::ConnectionAdapters::SqlTypeMetadata.new(sql_type: "bigint(20)", type: :integer, limit: 8)
|
33
|
-
col = ActiveRecord::ConnectionAdapters::Column.new("price", nil, sql_type_metadata, false, "adverts")
|
42
|
+
describe 'bigint' do
|
43
|
+
it 'returns schema attributes' do
|
44
|
+
subject = described_class.new(model, :price, :bigint, null: false, position: 2)
|
45
|
+
expect(subject.schema_attributes(col_spec)).to eq(type: :integer, limit: 8, null: false)
|
34
46
|
end
|
47
|
+
end
|
35
48
|
|
36
|
-
|
49
|
+
describe 'string' do
|
50
|
+
it 'returns schema attributes (including charset/collation iff mysql)' do
|
51
|
+
subject = described_class.new(model, :title, :string, limit: 100, null: true, charset: 'utf8mb4', position: 0)
|
52
|
+
if defined?(Mysql2)
|
53
|
+
expect(subject.schema_attributes(col_spec)).to eq(type: :string, limit: 100, null: true, charset: 'utf8mb4', collation: 'utf8mb4_bin')
|
54
|
+
else
|
55
|
+
expect(subject.schema_attributes(col_spec)).to eq(type: :string, limit: 100, null: true)
|
56
|
+
end
|
57
|
+
end
|
37
58
|
end
|
38
59
|
|
39
|
-
|
40
|
-
|
60
|
+
describe 'text' do
|
61
|
+
it 'returns schema attributes (including charset/collation iff mysql)' do
|
62
|
+
subject = described_class.new(model, :title, :text, limit: 200, null: true, charset: 'utf8mb4', position: 2)
|
63
|
+
if defined?(Mysql2)
|
64
|
+
expect(subject.schema_attributes(col_spec)).to eq(type: :text, limit: 255, null: true, charset: 'utf8mb4', collation: 'utf8mb4_bin')
|
65
|
+
else
|
66
|
+
expect(subject.schema_attributes(col_spec)).to eq(type: :text, null: true)
|
67
|
+
end
|
68
|
+
end
|
41
69
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
70
|
+
it 'allows a default to be set unless mysql' do
|
71
|
+
if defined?(Mysql2)
|
72
|
+
expect do
|
73
|
+
described_class.new(model, :title, :text, limit: 200, null: true, default: 'none', charset: 'utf8mb4', position: 2)
|
74
|
+
end.to raise_exception(DeclareSchema::MysqlTextMayNotHaveDefault)
|
75
|
+
else
|
76
|
+
subject = described_class.new(model, :title, :text, limit: 200, null: true, default: 'none', charset: 'utf8mb4', position: 2)
|
77
|
+
expect(subject.schema_attributes(col_spec)).to eq(type: :text, null: true, default: 'none')
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe 'decimal' do
|
82
|
+
it 'allows precision: and scale:' do
|
83
|
+
subject = described_class.new(model, :quantity, :decimal, precision: 8, scale: 10, null: true, position: 3)
|
84
|
+
expect(subject.schema_attributes(col_spec)).to eq(type: :decimal, precision: 8, scale: 10, null: true)
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'requires precision:' do
|
88
|
+
expect_any_instance_of(described_class).to receive(:warn).with(/precision: required for :decimal type/)
|
89
|
+
described_class.new(model, :quantity, :decimal, scale: 10, null: true, position: 3)
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'requires scale:' do
|
93
|
+
expect_any_instance_of(described_class).to receive(:warn).with(/scale: required for :decimal type/)
|
94
|
+
described_class.new(model, :quantity, :decimal, precision: 8, null: true, position: 3)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
[:integer, :bigint, :string, :text, :binary, :datetime, :date, :time].each do |t|
|
99
|
+
describe t.to_s do
|
100
|
+
let(:extra) { t == :string ? { limit: 100 } : {} }
|
101
|
+
|
102
|
+
it 'does not allow precision:' do
|
103
|
+
expect_any_instance_of(described_class).to receive(:warn).with(/precision: only allowed for :decimal type/)
|
104
|
+
described_class.new(model, :quantity, t, { precision: 8, null: true, position: 3 }.merge(extra))
|
105
|
+
end unless t == :datetime
|
106
|
+
|
107
|
+
it 'does not allow scale:' do
|
108
|
+
expect_any_instance_of(described_class).to receive(:warn).with(/scale: only allowed for :decimal type/)
|
109
|
+
described_class.new(model, :quantity, t, { scale: 10, null: true, position: 3 }.merge(extra))
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe 'datetime' do
|
116
|
+
it 'keeps type as "datetime"' do
|
117
|
+
subject = described_class.new(model, :created_at, :datetime, null: false, position: 1)
|
118
|
+
expect(subject.schema_attributes(col_spec)).to eq(type: :datetime, null: false)
|
49
119
|
end
|
120
|
+
end
|
50
121
|
|
51
|
-
|
122
|
+
describe 'timestamp' do
|
123
|
+
it 'normalizes type to "datetime"' do
|
124
|
+
subject = described_class.new(model, :created_at, :timestamp, null: true, position: 2)
|
125
|
+
expect(subject.schema_attributes(col_spec)).to eq(type: :datetime, null: true)
|
126
|
+
end
|
52
127
|
end
|
53
128
|
|
54
|
-
|
55
|
-
|
129
|
+
describe 'default:' do
|
130
|
+
let(:col_spec) { double('col_spec', sql_type: :integer) }
|
56
131
|
|
132
|
+
it 'typecasts default value' do
|
133
|
+
allow(col_spec).to receive(:type_cast_from_database) { |default| Integer(default) }
|
134
|
+
subject = described_class.new(model, :price, :integer, limit: 4, default: '42', null: true, position: 2)
|
135
|
+
expect(subject.schema_attributes(col_spec)).to eq(type: :integer, limit: 4, default: 42, null: true)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe '#schema_attributes' do
|
141
|
+
let(:col_spec) do
|
57
142
|
case Rails::VERSION::MAJOR
|
58
143
|
when 4
|
59
144
|
cast_type = ActiveRecord::Type::Integer.new(limit: 8)
|
60
|
-
|
145
|
+
ActiveRecord::ConnectionAdapters::Column.new("price", nil, cast_type, "integer(8)", false)
|
61
146
|
else
|
62
147
|
sql_type_metadata = ActiveRecord::ConnectionAdapters::SqlTypeMetadata.new(sql_type: "integer(8)", type: :integer, limit: 8)
|
63
|
-
|
148
|
+
ActiveRecord::ConnectionAdapters::Column.new("price", nil, sql_type_metadata, false, "adverts")
|
64
149
|
end
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'returns the attributes except name, position' do
|
153
|
+
subject = described_class.new(model, :price, :bigint, null: true, default: 0, position: 2)
|
154
|
+
expect(subject.schema_attributes(col_spec)).to eq(type: :integer, limit: 8, null: true, default: 0)
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'aliases :bigint and :integer limit: 8' do
|
158
|
+
int8 = described_class.new(model, :price, :integer, limit: 8, null: false, position: 0)
|
159
|
+
bigint = described_class.new(model, :price, :bigint, null: false, position: 0)
|
65
160
|
|
66
|
-
|
161
|
+
expected_attributes = { type: :integer, limit: 8, null: false }
|
162
|
+
expect(int8.schema_attributes(col_spec)).to eq(expected_attributes)
|
163
|
+
expect(bigint.schema_attributes(col_spec)).to eq(expected_attributes)
|
67
164
|
end
|
68
165
|
end
|
69
166
|
end
|