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.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeclareSchema
4
- VERSION = "0.6.4"
4
+ VERSION = "0.7.0"
5
5
  end
@@ -96,7 +96,7 @@ module DeclareSchema
96
96
  end
97
97
  end
98
98
  end
99
- rescue ::DeclareSchema::Model::FieldSpec::UnknownSqlTypeError => ex
99
+ rescue ::DeclareSchema::UnknownSqlTypeError => ex
100
100
  say "Invalid field type: #{ex}"
101
101
  end
102
102
 
@@ -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
- revert_table(t)
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
- disable_auto_increment ? "t.integer :id, limit: 8, auto_increment: false, primary_key: true" : nil,
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).merge(field_spec.sql_options)
394
- args = [field_spec.name.inspect] + format_options(options, field_spec.sql_type)
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).merge(spec.sql_options)
433
- [":#{spec.sql_type}", *format_options(options, spec.sql_type)]
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
- revert_column(current_table_name, c)
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 |c|
454
- col_name = old_names[c] || c
455
- col = db_columns[col_name]
456
- spec = model.field_specs[c]
457
- if spec.different_to?(current_table_name, col) # TODO: TECH-4814 DRY this up to a diff function that returns the differences. It's different if it has differences. -Colin
458
- change_spec = fk_field_options(model, c)
459
- change_spec[:limit] ||= spec.limit if (spec.sql_type != :text ||
460
- ::DeclareSchema::Model::FieldSpec.mysql_text_limits?) &&
461
- (spec.limit || col.limit)
462
- change_spec[:precision] = spec.precision unless spec.precision.nil?
463
- change_spec[:scale] = spec.scale unless spec.scale.nil?
464
- change_spec[:null] = spec.null unless spec.null && col.null
465
- change_spec[:default] = spec.default unless spec.default.nil? && col.default.nil?
466
- change_spec[:charset] = spec.charset unless spec.charset.nil?
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.compact
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) * "\n",
469
+ [(renames + adds + removes + changes) * "\n",
489
470
  (undo_renames + undo_adds + undo_removes + undo_changes) * "\n",
490
- index_changes * "\n",
491
- undo_index_changes * "\n",
492
- fk_changes * "\n",
493
- undo_fk_changes * "\n",
494
- table_options_changes * "\n",
495
- undo_table_options_changes * "\n"]
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 [[], []] if Migrator.disable_constraints
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 ex = existing_indexes.find { |e| i != e && e.equivalent?(i) }
507
- i.with_name(ex.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
- undo_drop_indexes = []
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
- drop_indexes = drop_indexes_init.map do |i|
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 [[], []] if Migrator.disable_indexing
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
- add_fks.map! do |fk|
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.compact
532
+ end
557
533
 
558
- drop_fks.map! do |fk|
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, type, changing: false)
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.compact
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
- # TODO: TECH-4814 remove all methods from here through end of file
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
- def revert_table(table)
634
- res = StringIO.new
635
- schema_dumper_klass = case Rails::VERSION::MAJOR
636
- when 4
637
- ActiveRecord::SchemaDumper
638
- else
639
- ActiveRecord::ConnectionAdapters::SchemaDumper
640
- end
641
- schema_dumper_klass.send(:new, ActiveRecord::Base.connection).send(:table, table, res)
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
- result = res.string.strip.gsub("\n ", "\n")
639
+ dumped_schema = dumped_schema_stream.string.strip.gsub!("\n ", "\n")
644
640
  if connection.class.name.match?(/mysql/i)
645
- if !result['options: ']
646
- result = result.sub('",', "\", options: \"DEFAULT CHARSET=#{Generators::DeclareSchema::Migration::Migrator.default_charset} "+
647
- "COLLATE=#{Generators::DeclareSchema::Migration::Migrator.default_collation}\",")
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
- def column_options_from_reverted_table(table, column)
670
- revert = revert_table(table)
671
- if (md = revert.match(/\s*t\.column\s+"#{column}",\s+(:[a-zA-Z0-9_]+)(?:,\s+(.*?)$)?/m))
672
- # Ugly migration
673
- _, type, options = *md
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
- type or raise "unable to find column options for #{table}.#{column} in #{revert.inspect}"
680
- [type, options]
681
- end
682
-
683
- def change_column_back(table, column)
684
- type, options = column_options_from_reverted_table(table, column)
685
- ["change_column :#{table}, :#{column}, #{type}", options&.strip].compact.join(', ')
686
- end
687
-
688
- def revert_column(table, column)
689
- type, options = column_options_from_reverted_table(table, column)
690
- ["add_column :#{table}, :#{column}, #{type}", options&.strip].compact.join(', ')
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
- RSpec.describe 'DeclareSchema Model FieldSpec' do
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
- context 'There are no model columns to change' do
9
- it '#different_to should return false for int8 == int8' do
10
- subject = DeclareSchema::Model::FieldSpec.new(Object, :price, :integer, limit: 8, null: false, position: 0)
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
- case Rails::VERSION::MAJOR
13
- when 4
14
- cast_type = ActiveRecord::Type::Integer.new(limit: 8)
15
- col = ActiveRecord::ConnectionAdapters::Column.new("price", nil, cast_type, "integer(8)", false)
16
- else
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
- it '#different_to should return false for bigint == bigint' do
25
- subject = DeclareSchema::Model::FieldSpec.new(Object, :price, :bigint, null: false, position: 0)
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
- case Rails::VERSION::MAJOR
28
- when 4
29
- cast_type = ActiveRecord::Type::BigInteger.new(limit: 8)
30
- col = ActiveRecord::ConnectionAdapters::Column.new("price", nil, cast_type, "bigint(20)", false)
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
- expect(subject.different_to?(subject.name, col)).to eq(false)
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
- it '#different_to should return false for int8 == bigint' do
40
- subject = DeclareSchema::Model::FieldSpec.new(Object, :price, :integer, limit: 8, null: false, position: 0)
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
- case Rails::VERSION::MAJOR
43
- when 4
44
- cast_type = ActiveRecord::Type::BigInteger.new(limit: 8)
45
- col = ActiveRecord::ConnectionAdapters::Column.new("price", nil, cast_type, "bigint(20)", false)
46
- else
47
- sql_type_metadata = ActiveRecord::ConnectionAdapters::SqlTypeMetadata.new(sql_type: "bigint(20)", type: :integer, limit: 8)
48
- col = ActiveRecord::ConnectionAdapters::Column.new("price", nil, sql_type_metadata, false, "adverts")
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
- expect(subject.different_to?(subject.name, col)).to eq(false)
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
- it '#different_to should return false for bigint == int8' do
55
- subject = DeclareSchema::Model::FieldSpec.new(Object, :price, :bigint, null: false, position: 0)
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
- col = ActiveRecord::ConnectionAdapters::Column.new("price", nil, cast_type, "integer(8)", false)
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
- col = ActiveRecord::ConnectionAdapters::Column.new("price", nil, sql_type_metadata, false, "adverts")
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
- expect(subject.different_to?(subject.name, col)).to eq(false)
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