declare_schema 1.3.6 → 1.4.0.colin.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -49,34 +49,22 @@ module DeclareSchema
49
49
  end
50
50
 
51
51
  module ClassMethods
52
- def index(columns, name: nil, allow_equivalent: false, unique: false, where: nil)
53
- index_definition =
54
- ::DeclareSchema::Model::IndexDefinition.new(
55
- columns,
56
- name: name, table_name: table_name, allow_equivalent: allow_equivalent, unique: unique, where: where
57
- )
58
-
59
- # add idempotently
60
- unless index_definitions.any? { |index_def| index_def.equivalent?(index_definition) }
61
- index_definitions << index_definition
52
+ def index(fields, **options)
53
+ # make index idempotent
54
+ index_fields_s = Array.wrap(fields).map(&:to_s)
55
+ unless index_definitions.any? { |index_spec| index_spec.fields == index_fields_s }
56
+ index_definitions << ::DeclareSchema::Model::IndexDefinition.new(self, fields, **options)
62
57
  end
63
58
  end
64
59
 
65
- def primary_key_index(*columns)
66
- index(columns.flatten, unique: true, name: ::DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME)
60
+ def primary_key_index(*fields)
61
+ index(fields.flatten, unique: true, name: ::DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME)
67
62
  end
68
63
 
69
- def constraint(foreign_key_column, parent_table: nil, constraint_name: nil, class_name: nil, dependent: nil)
70
- foreign_key_column = foreign_key_column.to_s
71
- constraint_definition =
72
- ::DeclareSchema::Model::ForeignKeyDefinition.new(
73
- foreign_key_column,
74
- constraint_name: constraint_name,
75
- child_table: table_name, parent_table: parent_table, class_name: class_name, dependent: dependent
76
- )
77
-
78
- unless constraint_specs.any? { |constraint_def| constraint_def.equivalent?(constraint_definition) }
79
- constraint_specs << constraint_definition
64
+ def constraint(fkey, **options)
65
+ fkey_s = fkey.to_s
66
+ unless constraint_specs.any? { |constraint_spec| constraint_spec.foreign_key == fkey_s }
67
+ constraint_specs << DeclareSchema::Model::ForeignKeyDefinition.new(self, fkey, **options)
80
68
  end
81
69
  end
82
70
 
@@ -110,8 +98,8 @@ module DeclareSchema
110
98
 
111
99
  # Extend belongs_to so that it
112
100
  # 1. creates a FieldSpec for the foreign key
113
- # 2. declares an index on the foreign key (optional)
114
- # 3. declares a foreign_key constraint (optional)
101
+ # 2. declares an index on the foreign key
102
+ # 3. declares a foreign_key constraint
115
103
  def belongs_to(name, scope = nil, **options)
116
104
  column_options = {}
117
105
 
@@ -126,26 +114,50 @@ module DeclareSchema
126
114
  ActiveSupport::Deprecation.warn("belongs_to limit: is deprecated since it is now inferred")
127
115
  end
128
116
 
129
- index_options = {}
130
- index_options[:name] = options.delete(:index) if options.has_key?(:index)
131
- index_options[:unique] = options.delete(:unique) if options.has_key?(:unique)
132
- index_options[:allow_equivalent] = options.delete(:allow_equivalent) if options.has_key?(:allow_equivalent)
117
+ # index: true means create an index on the foreign key
118
+ # index: false means do not create an index on the foreign key
119
+ # index: { ... } means create an index on the foreign key with the given options
120
+ index_value = options.delete(:index)
121
+ if index_value != false || options.has_key?(:unique) || options.has_key?(:allow_equivalent)
122
+ index_options = {}
123
+ case index_value
124
+ when String
125
+ Kernel.warn("belongs_to index: 'name' is deprecated; use index: { name: 'name' } instead")
126
+ index_options[:name] = index_value
127
+ # when false -- impossible since we checked that above
128
+ when true
129
+ when nil
130
+ when Hash
131
+ index_options = index_value
132
+ else
133
+ raise ArgumentError, "belongs_to index: must be true or false or a Hash; got #{index_value.inspect}"
134
+ end
133
135
 
134
- constraint_name = options.delete(:constraint)
135
- index_name = index_options[:name]
136
+ if options.has_key?(:unique)
137
+ Kernel.warn("belongs_to unique: true|false is deprecated; use index: { unique: true|false } instead")
138
+ index_options[:unique] = options.delete(:unique)
139
+ end
136
140
 
137
- dependent_delete = :delete if options.delete(:far_end_dependent) == :delete
141
+ index_options[:allow_equivalent] = options.delete(:allow_equivalent) if options.has_key?(:allow_equivalent)
142
+ end
143
+
144
+ fk_options = options.dup
145
+ fk_options[:constraint_name] = options.delete(:constraint) if options.has_key?(:constraint)
146
+ fk_options[:index_name] = index_options&.[](:name)
147
+
148
+ fk = options[:foreign_key]&.to_s || "#{name}_id"
138
149
 
139
- # infer :optional from :null
140
150
  if !options.has_key?(:optional)
141
- options[:optional] = column_options[:null]
151
+ options[:optional] = column_options[:null] # infer :optional from :null
142
152
  end
143
153
 
154
+ fk_options[:dependent] = options.delete(:far_end_dependent) if options.has_key?(:far_end_dependent)
155
+
144
156
  super
145
157
 
146
158
  refl = reflections[name.to_s] or raise "Couldn't find reflection #{name} in #{reflections.keys}"
147
- foreign_key_column = refl.foreign_key or raise "Couldn't find foreign_key for #{name} in #{refl.inspect}"
148
- foreign_key_column_options = column_options.dup
159
+ fkey = refl.foreign_key or raise "Couldn't find foreign_key for #{name} in #{refl.inspect}"
160
+ fkey_id_column_options = column_options.dup
149
161
 
150
162
  # Note: the foreign key limit: should match the primary key limit:. (If there is a foreign key constraint,
151
163
  # those limits _must_ match.) We'd like to call _infer_fk_limit and get the limit right from the PK.
@@ -156,34 +168,27 @@ module DeclareSchema
156
168
  # The one downside of this approach is that application code that asks the field_spec for the declared
157
169
  # foreign key limit: will always get 8 back even if this is a grandfathered foreign key that points to
158
170
  # a limit: 4 primary key. It seems unlikely that any application code would do this.
159
- foreign_key_column_options[:pre_migration] = ->(field_spec) do
160
- if (inferred_limit = _infer_fk_limit(foreign_key_column, refl))
171
+ fkey_id_column_options[:pre_migration] = ->(field_spec) do
172
+ if (inferred_limit = _infer_fk_limit(fkey, refl))
161
173
  field_spec.sql_options[:limit] = inferred_limit
162
174
  end
163
175
  end
164
176
 
165
- declare_field(foreign_key_column.to_sym, :bigint, **foreign_key_column_options)
177
+ declare_field(fkey.to_sym, :bigint, **fkey_id_column_options)
166
178
 
167
179
  if refl.options[:polymorphic]
168
180
  foreign_type = options[:foreign_type] || "#{name}_type"
169
181
  _declare_polymorphic_type_field(foreign_type, column_options)
170
- if ::DeclareSchema.default_generate_indexing && index_name != false
171
- index([foreign_type, foreign_key_column], **index_options)
172
- end
182
+ index([foreign_type, fkey], **index_options) if index_options
173
183
  else
174
- if ::DeclareSchema.default_generate_indexing && index_name != false
175
- index(foreign_key_column, **index_options)
176
- end
177
-
178
- if ::DeclareSchema.default_generate_foreign_keys && constraint_name != false
179
- constraint(foreign_key_column, parent_table: nil, constraint_name: constraint_name || index_name, class_name: refl.klass, dependent: dependent_delete)
180
- end
184
+ index(fkey, **index_options) if index_options
185
+ constraint(fkey, **fk_options) if fk_options[:constraint_name] != false
181
186
  end
182
187
  end
183
188
 
184
- def _infer_fk_limit(foreign_key_column, refl)
189
+ def _infer_fk_limit(fkey, refl)
185
190
  if refl.options[:polymorphic]
186
- if (fkey_column = columns_hash[foreign_key_column.to_s]) && fkey_column.type == :integer
191
+ if (fkey_column = columns_hash[fkey.to_s]) && fkey_column.type == :integer
187
192
  fkey_column.limit
188
193
  end
189
194
  else
@@ -225,7 +230,7 @@ module DeclareSchema
225
230
  end
226
231
 
227
232
  def _rails_default_primary_key
228
- ::DeclareSchema::Model::IndexDefinition.new([_declared_primary_key], name: DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME, unique: true)
233
+ ::DeclareSchema::Model::IndexDefinition.new(self, [_declared_primary_key.to_sym], unique: true, name: DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME)
229
234
  end
230
235
 
231
236
  # Declares the "foo_type" field that accompanies the "foo_id"
@@ -296,14 +301,15 @@ module DeclareSchema
296
301
  end
297
302
  end
298
303
 
299
- def _add_index_for_field(column_name, args, options)
300
- if (index_name = options.delete(:index))
301
- if index_name == true
302
- index_name = nil # index: true means generate default name
303
- end
304
- unique = args.include?(:unique) || options.delete(:unique) || false
305
-
306
- index([column_name], unique: unique, name: index_name)
304
+ def _add_index_for_field(name, args, options)
305
+ if (to_name = options.delete(:index))
306
+ index_opts =
307
+ {
308
+ unique: args.include?(:unique) || options.delete(:unique)
309
+ }
310
+ # support index: true declaration
311
+ index_opts[:name] = to_name unless to_name == true
312
+ index(name, **index_opts)
307
313
  end
308
314
  end
309
315
 
@@ -6,10 +6,8 @@ module DeclareSchema
6
6
  module SchemaChange
7
7
  class ColumnAdd < Base
8
8
  def initialize(table_name, column_name, column_type, **column_options)
9
- table_name.is_a?(String) || table_name.is_a?(Symbol) or raise ArgumentError, "must provide String|Symbol table_name; got #{table_name.inspect}"
10
- column_name.is_a?(String) || column_name.is_a?(Symbol) or raise ArgumentError, "must provide String|Symbol column_name; got #{column_name.inspect}"
11
- @table_name = table_name
12
- @column_name = column_name
9
+ @table_name = table_name or raise ArgumentError, "must provide table_name"
10
+ @column_name = column_name or raise ArgumentError, "must provide column_name"
13
11
  @column_type = column_type or raise ArgumentError, "must provide column_type"
14
12
  @column_options = column_options
15
13
  end
@@ -5,12 +5,13 @@ require_relative 'base'
5
5
  module DeclareSchema
6
6
  module SchemaChange
7
7
  class IndexAdd < Base
8
- def initialize(table_name, column_names, name:, unique:, where: nil)
8
+ def initialize(table_name, column_names, name:, unique:, where: nil, limit: nil)
9
9
  @table_name = table_name
10
10
  @column_names = column_names
11
11
  @name = name
12
12
  @unique = unique
13
13
  @where = where.presence
14
+ @limit = limit.presence
14
15
  end
15
16
 
16
17
  def up_command
@@ -19,6 +20,7 @@ module DeclareSchema
19
20
  }
20
21
  options[:unique] = true if @unique
21
22
  options[:where] = @where if @where
23
+ options[:limit] = @limit if @limit
22
24
 
23
25
  "add_index #{[@table_name.to_sym.inspect,
24
26
  @column_names.map(&:to_sym).inspect,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeclareSchema
4
- VERSION = "1.3.6"
4
+ VERSION = "1.4.0.colin.1"
5
5
  end
@@ -3,7 +3,6 @@
3
3
  require 'active_support'
4
4
  require 'active_support/all'
5
5
  require_relative 'declare_schema/version'
6
- require 'rubygems/version'
7
6
 
8
7
  ActiveSupport::Dependencies.autoload_paths |= [__dir__]
9
8
 
@@ -22,8 +21,6 @@ module DeclareSchema
22
21
  text: String
23
22
  }.freeze
24
23
 
25
- SEMVER_8 = Gem::Version.new('8.0.0').freeze
26
-
27
24
  @default_charset = "utf8mb4"
28
25
  @default_collation = "utf8mb4_bin"
29
26
  @default_text_limit = 0xffff_ffff
@@ -35,8 +32,7 @@ module DeclareSchema
35
32
  @max_index_and_constraint_name_length = 64 # limit for MySQL
36
33
 
37
34
  class << self
38
- attr_writer :mysql_version
39
- attr_reader :default_text_limit, :default_string_limit, :default_null,
35
+ attr_reader :default_charset, :default_collation, :default_text_limit, :default_string_limit, :default_null,
40
36
  :default_generate_foreign_keys, :default_generate_indexing, :db_migrate_command,
41
37
  :max_index_and_constraint_name_length
42
38
 
@@ -51,52 +47,14 @@ module DeclareSchema
51
47
  end
52
48
  end
53
49
 
54
- def mysql_version
55
- if defined?(@mysql_version)
56
- @mysql_version
57
- else
58
- @mysql_version =
59
- if ActiveRecord::Base.connection.class.name.match?(/mysql/i)
60
- version_string = ActiveRecord::Base.connection.select_value('SELECT VERSION()')
61
- Gem::Version.new(version_string)
62
- end
63
- end
64
- end
65
-
66
- def normalize_charset(charset)
67
- if mysql_version && mysql_version >= SEMVER_8 && charset == 'utf8'
68
- 'utf8mb3'
69
- else
70
- charset
71
- end
72
- end
73
-
74
- def normalize_collation(collation)
75
- if mysql_version && mysql_version >= SEMVER_8
76
- collation.sub(/\Autf8_/, 'utf8mb3_')
77
- else
78
- collation
79
- end
80
- end
81
-
82
50
  def default_charset=(charset)
83
51
  charset.is_a?(String) or raise ArgumentError, "charset must be a string (got #{charset.inspect})"
84
- @default_charset_before_normalization = charset
85
- @default_charset = nil
86
- end
87
-
88
- def default_charset
89
- @default_charset ||= normalize_charset(@default_charset_before_normalization)
52
+ @default_charset = charset
90
53
  end
91
54
 
92
55
  def default_collation=(collation)
93
56
  collation.is_a?(String) or raise ArgumentError, "collation must be a string (got #{collation.inspect})"
94
- @default_collation_before_normalization = collation
95
- @default_collation = nil
96
- end
97
-
98
- def default_collation
99
- @default_collation ||= normalize_collation(@default_collation_before_normalization)
57
+ @default_collation = collation
100
58
  end
101
59
 
102
60
  def default_text_limit=(text_limit)
@@ -3,7 +3,6 @@
3
3
  require 'active_record'
4
4
  require 'active_record/connection_adapters/abstract_adapter'
5
5
  require 'declare_schema/schema_change/all'
6
- require_relative '../../../declare_schema/model/habtm_model_shim'
7
6
 
8
7
  module Generators
9
8
  module DeclareSchema
@@ -204,8 +203,8 @@ module Generators
204
203
  end
205
204
  end
206
205
  # generate shims for HABTM models
207
- habtm_tables.each do |name, reflections|
208
- models_by_table_name[name] = ::DeclareSchema::Model::HabtmModelShim.from_reflection(reflections.first)
206
+ habtm_tables.each do |name, refls|
207
+ models_by_table_name[name] = ::DeclareSchema::Model::HabtmModelShim.from_reflection(refls.first)
209
208
  end
210
209
  model_table_names = models_by_table_name.keys
211
210
 
@@ -222,8 +221,8 @@ module Generators
222
221
  ::DeclareSchema::SchemaChange::TableRemove.new(t, add_table_back(t))
223
222
  end
224
223
 
225
- creates = to_create.map do |table_name|
226
- model = models_by_table_name[table_name]
224
+ creates = to_create.map do |t|
225
+ model = models_by_table_name[t]
227
226
  disable_auto_increment = model.try(:disable_auto_increment)
228
227
 
229
228
  primary_key_definition =
@@ -240,13 +239,10 @@ module Generators
240
239
  table_options_definition = ::DeclareSchema::Model::TableOptionsDefinition.new(model.table_name, **table_options_for_model(model))
241
240
  table_options = create_table_options(model, disable_auto_increment)
242
241
 
243
- table_add = ::DeclareSchema::SchemaChange::TableAdd.new(
244
- table_name,
245
- primary_key_definition + field_definitions,
246
- table_options,
247
- sql_options: table_options_definition.settings
248
- )
249
-
242
+ table_add = ::DeclareSchema::SchemaChange::TableAdd.new(t,
243
+ primary_key_definition + field_definitions,
244
+ table_options,
245
+ sql_options: table_options_definition.settings)
250
246
  [
251
247
  table_add,
252
248
  *Array((create_indexes(model) if ::DeclareSchema.default_generate_indexing)),
@@ -259,9 +255,9 @@ module Generators
259
255
  fk_changes = []
260
256
  table_options_changes = []
261
257
 
262
- to_change.each do |table_name|
263
- model = models_by_table_name[table_name]
264
- table = to_rename.key(table_name) || model.table_name
258
+ to_change.each do |t|
259
+ model = models_by_table_name[t]
260
+ table = to_rename.key(t) || model.table_name
265
261
  if table.in?(db_tables)
266
262
  change, index_change, fk_change, table_options_change = change_table(model, table)
267
263
  changes << change
@@ -315,8 +311,6 @@ module Generators
315
311
  { id: false }
316
312
  elsif primary_key == "id"
317
313
  { id: :bigint }
318
- elsif primary_key.is_a?(Array)
319
- { primary_key: primary_key.map(&:to_sym) }
320
314
  else
321
315
  { primary_key: primary_key.to_sym }
322
316
  end.merge(model._table_options)
@@ -336,14 +330,14 @@ module Generators
336
330
  # TODO: TECH-5338: optimize that index doesn't need to be dropped on undo since entire table will be dropped
337
331
  def create_indexes(model)
338
332
  model.index_definitions.map do |i|
339
- ::DeclareSchema::SchemaChange::IndexAdd.new(model.table_name, i.columns, unique: i.unique, where: i.where, name: i.name)
333
+ ::DeclareSchema::SchemaChange::IndexAdd.new(model.table_name, i.columns, **i.options)
340
334
  end
341
335
  end
342
336
 
343
337
  def create_constraints(model)
344
338
  model.constraint_specs.map do |fk|
345
339
  ::DeclareSchema::SchemaChange::ForeignKeyAdd.new(fk.child_table_name, fk.parent_table_name,
346
- column_name: fk.foreign_key_column, name: fk.constraint_name)
340
+ column_name: fk.foreign_key_name, name: fk.constraint_name)
347
341
  end
348
342
  end
349
343
 
@@ -352,7 +346,7 @@ module Generators
352
346
 
353
347
  db_columns = model.connection.columns(current_table_name).index_by(&:name)
354
348
  if (pk = model._declared_primary_key.presence)
355
- pk_was_in_db_columns = pk.is_a?(Array) || db_columns.delete(pk)
349
+ pk_was_in_db_columns = db_columns.delete(pk)
356
350
  end
357
351
 
358
352
  model_column_names = model.field_specs.keys.map(&:to_s)
@@ -431,7 +425,7 @@ module Generators
431
425
  ::DeclareSchema.default_generate_indexing or return []
432
426
 
433
427
  new_table_name = model.table_name
434
- existing_indexes = ::DeclareSchema::Model::IndexDefinition.for_table(old_table_name || new_table_name, model.ignore_indexes, model.connection)
428
+ existing_indexes = ::DeclareSchema::Model::IndexDefinition.for_model(model, old_table_name)
435
429
  model_indexes_with_equivalents = model.index_definitions_with_primary_key
436
430
  model_indexes = model_indexes_with_equivalents.map do |i|
437
431
  if i.explicit_name.nil?
@@ -491,11 +485,7 @@ module Generators
491
485
  ActiveRecord::Base.connection.class.name.match?(/SQLite3Adapter/) and raise ArgumentError, 'SQLite does not support foreign keys'
492
486
  ::DeclareSchema.default_generate_foreign_keys or return []
493
487
 
494
- if model.is_a?(::DeclareSchema::Model::HabtmModelShim)
495
- force_dependent_delete = :delete
496
- end
497
-
498
- existing_fks = ::DeclareSchema::Model::ForeignKeyDefinition.for_table(old_table_name || model.table_name, model.connection, dependent: force_dependent_delete)
488
+ existing_fks = ::DeclareSchema::Model::ForeignKeyDefinition.for_model(model, old_table_name)
499
489
  model_fks = model.constraint_specs
500
490
 
501
491
  fks_to_drop = existing_fks - model_fks
@@ -505,13 +495,13 @@ module Generators
505
495
 
506
496
  drop_fks = (fks_to_drop - renamed_fks_to_drop).map do |fk|
507
497
  ::DeclareSchema::SchemaChange::ForeignKeyRemove.new(fk.child_table_name, fk.parent_table_name,
508
- column_name: fk.foreign_key_column, name: fk.constraint_name)
498
+ column_name: fk.foreign_key_name, name: fk.constraint_name)
509
499
  end
510
500
 
511
501
  add_fks = (fks_to_add - renamed_fks_to_add).map do |fk|
512
502
  # next if fk.parent.constantize.abstract_class || fk.parent == fk.model.class_name
513
503
  ::DeclareSchema::SchemaChange::ForeignKeyAdd.new(fk.child_table_name, fk.parent_table_name,
514
- column_name: fk.foreign_key_column, name: fk.constraint_name)
504
+ column_name: fk.foreign_key_name, name: fk.constraint_name)
515
505
  end
516
506
 
517
507
  [drop_fks + add_fks]
@@ -533,7 +523,7 @@ module Generators
533
523
  end
534
524
 
535
525
  def fk_field_options(model, field_name)
536
- foreign_key = model.constraint_specs.find { |fk| field_name == fk.foreign_key_column }
526
+ foreign_key = model.constraint_specs.find { |fk| field_name == fk.foreign_key.to_s }
537
527
  if foreign_key && (parent_table = foreign_key.parent_table_name)
538
528
  parent_columns = connection.columns(parent_table) rescue []
539
529
  pk_limit =
@@ -5,11 +5,6 @@ begin
5
5
  rescue LoadError
6
6
  end
7
7
 
8
- begin
9
- require 'sqlite3'
10
- rescue LoadError
11
- end
12
-
13
8
  RSpec.describe DeclareSchema::Model::FieldSpec do
14
9
  let(:model) { double('model', _table_options: {}, _declared_primary_key: 'id') }
15
10
  let(:col_spec) { double('col_spec', type: :string) }
@@ -63,23 +58,8 @@ RSpec.describe DeclareSchema::Model::FieldSpec do
63
58
  end
64
59
  end
65
60
 
66
- if defined?(Mysql2)
67
- context 'when running on MySQL 8.0' do
68
- around do |spec|
69
- DeclareSchema.mysql_version = Gem::Version.new('8.0.21')
70
- spec.run
71
- ensure
72
- DeclareSchema.remove_instance_variable('@mysql_version') rescue nil
73
- end
74
-
75
- it 'normalizes charset and collation' do
76
- subject = described_class.new(model, :title, :string, limit: 100, null: true, charset: 'utf8', collation: 'utf8_general', position: 0)
77
-
78
- expect(subject.schema_attributes(col_spec)).to eq(type: :string, limit: 100, null: true, charset: 'utf8mb3', collation: 'utf8mb3_general')
79
- end
80
- end
81
-
82
- it 'raises error when default_string_limit option is nil when not explicitly set in field spec' do
61
+ it 'raises error when default_string_limit option is nil when not explicitly set in field spec' do
62
+ if defined?(Mysql2)
83
63
  expect(::DeclareSchema).to receive(:default_string_limit) { nil }
84
64
  expect do
85
65
  described_class.new(model, :title, :string, null: true, charset: 'utf8mb4', position: 0)