declare_schema 1.3.6 → 1.4.0.colin.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.
@@ -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)