declare_schema 1.4.0.colin.7 → 1.4.0.colin.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0f8226540260a887af839da5afcb4641217c03ac5a6eb2d7a00da2806188f1ce
4
- data.tar.gz: 7eeb464b5bfc9b1fbf88446696ede88b8ea582060658421cf2772e62a6205797
3
+ metadata.gz: ca0ed75221c664e2a5c59bff495a971495922f7f31ea0fb548fb67f0ad0eb639
4
+ data.tar.gz: fc76669be291fff9da61353f7f040ba14327e1359f712a963e5865ed948034c8
5
5
  SHA512:
6
- metadata.gz: 2294e94f2165ec156807fbf9ba98dea10f191bfda1b4c630f5444ab08ee7f80323e86a9af80e968e20e579e21ba24ddef520312a7f0a146d850b6f8927aa50aa
7
- data.tar.gz: 2742ce98af1fb6ef8eb4ba1fa40563a635abb584cc1860efd373a474c6dce9fc1a27b630141f94ea6a23f36602d8381b08d6a33b13b6fd3d352b40df85f37daf
6
+ metadata.gz: 47b53d7e5306a3b2ada9296494af897738056c1c5e14e35e0b3967daf3176e07cf48c74560e2ccd659d0c1b8bc466e6d29f632f86b25bff568bcc4b6dcf672af
7
+ data.tar.gz: 9af422633cae00b32e94bf64d0e6e9445aee83849e4898633384af679b61c877a22ce14f1b20c225d17f4a1546ee3c3a1867e0661516f186717895a658d4b2eb
data/CHANGELOG.md CHANGED
@@ -10,6 +10,11 @@ Note: this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0
10
10
  ### Changed
11
11
  - Deprecate index: 'name' and unique: true|false in favor of index: { name: 'name', unique: true|false }.
12
12
 
13
+ ## [1.3.3] - 2023-01-17
14
+ ### Fixed
15
+ - Fix a MySQL 8 bug where MySQL 8+ renames charset 'utf8' to 'utf8mb3' and collation 'utf8_general_ci' to
16
+ 'utf8mb3_unicode_ci'.
17
+
13
18
  ## [1.3.2] - 2024-01-12
14
19
  ### Fixed
15
20
  - Fix bug in migrator when table option definitions differ
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- declare_schema (1.4.0.colin.7)
4
+ declare_schema (1.4.0.colin.8)
5
5
  rails (>= 5.0)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -176,7 +176,7 @@ DeclareSchema.clear_default_schema
176
176
  ```
177
177
 
178
178
  ### Global Configuration
179
- Configurations can be set at globally to customize default declaration for the following values:
179
+ Configurations can be set globally to customize default declaration for the following values:
180
180
 
181
181
  #### Text Limit
182
182
  The default text limit can be set using the `DeclareSchema.default_text_limit=` method.
@@ -256,6 +256,9 @@ turn all tables into `utf8mb4` supporting tables:
256
256
  DeclareSchema.default_charset = "utf8mb4"
257
257
  DeclareSchema.default_collation = "utf8mb4_bin"
258
258
  ```
259
+ Note: MySQL 8+ aliases charset 'utf8' to 'utf8mb3', and 'utf8_general_ci' to 'utf8mb3_unicode_ci',
260
+ so when running on MySQL 8+, those aliases will be applied by `DeclareSchema`.
261
+
259
262
  #### db:migrate Command
260
263
  `declare_schema` can run the migration once it is generated, if the `--migrate` option is passed.
261
264
  If not, it will display the command to run later. By default this command is
@@ -107,7 +107,9 @@ module DeclareSchema
107
107
  if @type.in?([:text, :string])
108
108
  if ActiveRecord::Base.connection.class.name.match?(/mysql/i)
109
109
  @options[:charset] ||= model._table_options&.[](:charset) || ::DeclareSchema.default_charset
110
+ @options[:charset] = DeclareSchema.normalize_charset(@options[:charset])
110
111
  @options[:collation] ||= model._table_options&.[](:collation) || ::DeclareSchema.default_collation
112
+ @options[:collation] = DeclareSchema.normalize_collation(@options[:collation])
111
113
  else
112
114
  @options.delete(:charset)
113
115
  @options.delete(:collation)
@@ -7,53 +7,61 @@ module DeclareSchema
7
7
  class ForeignKeyDefinition
8
8
  include Comparable
9
9
 
10
- attr_reader :constraint_name, :model, :foreign_key, :foreign_key_name, :child_table_name, :options, :on_delete_cascade
11
-
12
-
13
- def initialize(model, foreign_key, **options)
14
- @model = model
15
- @foreign_key = foreign_key.to_s.presence or raise ArgumentError "Foreign key must not be empty: #{foreign_key.inspect}"
16
- @options = options
17
-
18
- @child_table_name = model.table_name # unless a table rename, which would happen when a class is renamed??
19
- @parent_table_name = options[:parent_table]&.to_s
20
- @foreign_key_name = options[:foreign_key]&.to_s || @foreign_key
21
-
10
+ attr_reader :foreign_key_column, :constraint_name, :child_table_name, :parent_class_name, :dependent
11
+
12
+ # Caller needs to pass either constraint_name or child_table_name, and
13
+ # either parent_class_name or parent_table_name.
14
+ def initialize(foreign_key_column, constraint_name: nil, child_table_name: nil, parent_class_name: nil, parent_table_name: nil, dependent: nil)
15
+ @foreign_key_column = foreign_key_column&.to_s or raise ArgumentError "foreign key must not be empty: #{foreign_key_column.inspect}"
16
+ @constraint_name = constraint_name&.to_s.presence || ::DeclareSchema::Model::IndexDefinition.default_index_name(child_table_name, [@foreign_key_column])
17
+ @child_table_name = child_table_name&.to_s or raise ArgumentError, "child_table_name must not be nil"
22
18
  @parent_class_name =
23
- case class_name = options[:class_name]
19
+ case parent_class_name
24
20
  when String, Symbol
25
- class_name.to_s
21
+ parent_class_name.to_s
26
22
  when Class
27
- @parent_class = class_name
23
+ @parent_class = parent_class_name
28
24
  @parent_class.name
29
25
  when nil
30
- @foreign_key.sub(/_id\z/, '').camelize
26
+ @foreign_key_column.sub(/_id\z/, '').camelize
31
27
  end
32
-
33
- @constraint_name = options[:constraint_name]&.to_s.presence ||
34
- model.connection.index_name(model.table_name, column: @foreign_key_name)
35
- @on_delete_cascade = options[:dependent] == :delete
28
+ @parent_table_name = parent_table_name
29
+ dependent.in?([nil, :delete]) or raise ArgumentError, "dependent: must be nil or :delete"
30
+ @dependent = dependent
36
31
  end
37
32
 
38
33
  class << self
39
- def for_model(model, old_table_name)
40
- show_create_table = model.connection.select_rows("show create table #{model.connection.quote_table_name(old_table_name)}").first.last
34
+ def for_table(child_table_name, connection, dependent: nil)
35
+ show_create_table = connection.select_rows("show create table #{connection.quote_table_name(child_table_name)}").first.last
41
36
  constraints = show_create_table.split("\n").map { |line| line.strip if line['CONSTRAINT'] }.compact
42
37
 
43
38
  constraints.map do |fkc|
44
- name, foreign_key, parent_table = fkc.match(/CONSTRAINT `([^`]*)` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)`/).captures
45
- options = {
46
- constraint_name: name,
47
- parent_table: parent_table,
48
- foreign_key: foreign_key
49
- }
50
- options[:dependent] = :delete if fkc['ON DELETE CASCADE'] || model.is_a?(DeclareSchema::Model::HabtmModelShim)
51
-
52
- new(model, foreign_key, **options)
39
+ constraint_name, foreign_key_column, parent_table_name = fkc.match(/CONSTRAINT `([^`]*)` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)`/).captures
40
+ dependent_value = :delete if dependent || fkc['ON DELETE CASCADE']
41
+
42
+ new(foreign_key_column,
43
+ constraint_name: constraint_name,
44
+ child_table_name: child_table_name,
45
+ parent_table_name: parent_table_name,
46
+ dependent: dependent_value)
53
47
  end
54
48
  end
55
49
  end
56
50
 
51
+ def key
52
+ @key ||= [@child_table_name, @foreign_key_column, @dependent].freeze
53
+ end
54
+
55
+ def <=>(rhs)
56
+ key <=> rhs.key
57
+ end
58
+
59
+ alias eql? ==
60
+
61
+ def equivalent?(rhs)
62
+ self == rhs
63
+ end
64
+
57
65
  # returns the parent class as a Class object
58
66
  # lazy loaded so that we don't require the parent class until we need it
59
67
  def parent_class
@@ -64,21 +72,9 @@ module DeclareSchema
64
72
  @parent_table_name ||= parent_class.table_name
65
73
  end
66
74
 
67
- def <=>(rhs)
68
- key <=> rhs.send(:key)
69
- end
70
-
71
- alias eql? ==
72
-
73
75
  def hash
74
76
  key.hash
75
77
  end
76
-
77
- private
78
-
79
- def key
80
- @key ||= [@child_table_name, @parent_class_name, @foreign_key_name, @on_delete_cascade].map(&:to_s)
81
- end
82
78
  end
83
79
  end
84
80
  end
@@ -5,28 +5,21 @@ module DeclareSchema
5
5
  class HabtmModelShim
6
6
  class << self
7
7
  def from_reflection(refl)
8
- join_table = refl.join_table
9
- foreign_keys_and_classes = [
10
- [refl.foreign_key.to_s, refl.active_record],
11
- [refl.association_foreign_key.to_s, refl.class_name.constantize]
12
- ].sort { |a, b| a.first <=> b.first }
13
- foreign_keys = foreign_keys_and_classes.map(&:first)
14
- foreign_key_classes = foreign_keys_and_classes.map(&:last)
15
- # this may fail in weird ways if HABTM is running across two DB connections (assuming that's even supported)
16
- # figure that anybody who sets THAT up can deal with their own migrations...
17
- connection = refl.active_record.connection
18
-
19
- new(join_table, foreign_keys, foreign_key_classes, connection)
8
+ new(refl.join_table, [refl.foreign_key, refl.association_foreign_key],
9
+ [refl.active_record.table_name, refl.class_name.constantize.table_name])
20
10
  end
21
11
  end
22
12
 
23
- attr_reader :join_table, :foreign_keys, :foreign_key_classes, :connection
13
+ attr_reader :join_table, :foreign_keys, :parent_table_names
24
14
 
25
- def initialize(join_table, foreign_keys, foreign_key_classes, connection)
15
+ def initialize(join_table, foreign_keys, parent_table_names)
16
+ foreign_keys.is_a?(Array) && foreign_keys.size == 2 or
17
+ raise ArgumentError, "foreign_keys must be <Array[2]>; got #{foreign_keys.inspect}"
18
+ parent_table_names.is_a?(Array) && parent_table_names.size == 2 or
19
+ raise ArgumentError, "parent_table_names must be <Array[2]>; got #{parent_table_names.inspect}"
26
20
  @join_table = join_table
27
- @foreign_keys = foreign_keys
28
- @foreign_key_classes = foreign_key_classes
29
- @connection = connection
21
+ @foreign_keys = foreign_keys.sort # Rails requires these be in alphabetical order
22
+ @parent_table_names = @foreign_keys == foreign_keys ? parent_table_names : parent_table_names.reverse # match the above sort
30
23
  end
31
24
 
32
25
  def _table_options
@@ -38,8 +31,8 @@ module DeclareSchema
38
31
  end
39
32
 
40
33
  def field_specs
41
- foreign_keys.each_with_index.each_with_object({}) do |(v, position), result|
42
- result[v] = ::DeclareSchema::Model::FieldSpec.new(self, v, :bigint, position: position, null: false)
34
+ foreign_keys.each_with_index.each_with_object({}) do |(foreign_key, i), result|
35
+ result[foreign_key] = ::DeclareSchema::Model::FieldSpec.new(self, foreign_key, :bigint, position: i, null: false)
43
36
  end
44
37
  end
45
38
 
@@ -53,8 +46,8 @@ module DeclareSchema
53
46
 
54
47
  def index_definitions_with_primary_key
55
48
  @index_definitions_with_primary_key ||= Set.new([
56
- IndexDefinition.new(self, foreign_keys, unique: true, name: Model::IndexDefinition::PRIMARY_KEY_NAME), # creates a primary composite key on both foreign keys
57
- IndexDefinition.new(self, foreign_keys.last) # not unique by itself; combines with primary key to be unique
49
+ IndexDefinition.new(foreign_keys, name: Model::IndexDefinition::PRIMARY_KEY_NAME, table_name: table_name, unique: true), # creates a primary composite key on both foreign keys
50
+ IndexDefinition.new(foreign_keys.last, table_name: table_name, unique: false) # index for queries where we only have the last foreign key
58
51
  ])
59
52
  end
60
53
 
@@ -64,10 +57,10 @@ module DeclareSchema
64
57
  @ignore_indexes ||= Set.new
65
58
  end
66
59
 
67
- def constraint_specs
68
- @constraint_specs ||= Set.new([
69
- ForeignKeyDefinition.new(self, foreign_keys.first, parent_table: foreign_key_classes.first.table_name, constraint_name: "#{join_table}_FK1", dependent: :delete),
70
- ForeignKeyDefinition.new(self, foreign_keys.last, parent_table: foreign_key_classes.last.table_name, constraint_name: "#{join_table}_FK2", dependent: :delete)
60
+ def constraint_definitions
61
+ @constraint_definitions ||= Set.new([
62
+ ForeignKeyDefinition.new(foreign_keys.first, constraint_name: "#{join_table}_FK1", child_table_name: @join_table, parent_table_name: parent_table_names.first, dependent: :delete),
63
+ ForeignKeyDefinition.new(foreign_keys.last, constraint_name: "#{join_table}_FK2", child_table_name: @join_table, parent_table_name: parent_table_names.last, dependent: :delete)
71
64
  ])
72
65
  end
73
66
  end
@@ -7,65 +7,78 @@ module DeclareSchema
7
7
  class IndexDefinition
8
8
  include Comparable
9
9
 
10
- # TODO: replace `fields` with `columns` and remove alias. -Colin
11
10
  OPTIONS = [:name, :unique, :where, :length].freeze
12
- attr_reader :table, :fields, :explicit_name, *OPTIONS
13
- alias columns fields
11
+ attr_reader :columns, :explicit_name, :table_name, *OPTIONS
12
+
13
+ alias fields columns # TODO: change callers to use columns. -Colin
14
14
 
15
15
  class IndexNameTooLongError < RuntimeError; end
16
16
 
17
17
  PRIMARY_KEY_NAME = "PRIMARY"
18
18
 
19
- def initialize(model, fields, **options)
20
- @model = model
21
- @table = options.delete(:table_name) || model.table_name
22
- @fields = Array.wrap(fields).map(&:to_s)
23
- @explicit_name = options[:name] unless options.delete(:allow_equivalent)
24
- @name = options.delete(:name) || self.class.default_index_name(@table, @fields)
25
- @unique = options.delete(:unique) || name == PRIMARY_KEY_NAME || false
26
- @length = options.delete(:length)
19
+ # Caller needs to pass either name or table_name. The table_name is not remembered; it is just used to compute the
20
+ # default name if no name is given.
21
+ def initialize(columns, table_name:, name: nil, allow_equivalent: false, unique: false, where: nil, length: nil)
22
+ @table_name = table_name
23
+ @name = name || self.class.default_index_name(table_name, columns)
24
+ @name.to_s == 'index_adverts_on_Advert' and binding.pry
25
+ @columns = Array.wrap(columns).map(&:to_s)
26
+ @explicit_name = @name if !allow_equivalent
27
+ unique.in?([false, true]) or raise ArgumentError, "unique must be true or false: got #{unique.inspect}"
28
+ if @name == PRIMARY_KEY_NAME
29
+ unique or raise ArgumentError, "primary key index must be unique"
30
+ end
31
+ @unique = unique
27
32
 
28
33
  if DeclareSchema.max_index_and_constraint_name_length && @name.length > DeclareSchema.max_index_and_constraint_name_length
29
34
  raise IndexNameTooLongError, "Index '#{@name}' exceeds configured limit of #{DeclareSchema.max_index_and_constraint_name_length} characters. Give it a shorter name, or adjust DeclareSchema.max_index_and_constraint_name_length if you know your database can accept longer names."
30
35
  end
31
36
 
32
- if (where = options.delete(:where))
37
+ if where
33
38
  @where = where.start_with?('(') ? where : "(#{where})"
34
39
  end
35
40
 
36
- options.any? and warn("ignoring unrecognized option(s): #{options.inspect} for model #{model}")
41
+ @length = length
37
42
  end
38
43
 
39
44
  class << self
40
45
  # extract IndexSpecs from an existing table
41
46
  # includes the PRIMARY KEY index
42
- def for_model(model, old_table_name = nil)
43
- t = old_table_name || model.table_name
44
-
45
- primary_key_columns = Array(model.connection.primary_key(t)).presence
46
- primary_key_columns or raise "could not find primary key for table #{t} in #{model.connection.columns(t).inspect}"
47
+ def for_table(table_name, ignore_indexes, connection)
48
+ primary_key_columns = Array(connection.primary_key(table_name))
49
+ primary_key_columns.present? or raise "could not find primary key for table #{table_name} in #{connection.columns(table_name).inspect}"
47
50
 
48
51
  primary_key_found = false
49
- index_definitions = model.connection.indexes(t).map do |i|
50
- model.ignore_indexes.include?(i.name) and next
51
- if i.name == PRIMARY_KEY_NAME
52
- i.columns == primary_key_columns && i.unique or
53
- raise "primary key on #{t} was not unique on #{primary_key_columns} (was unique=#{i.unique} on #{i.columns})"
52
+ index_definitions = connection.indexes(table_name).map do |index|
53
+ next if ignore_indexes.include?(index.name)
54
+
55
+ if index.name == PRIMARY_KEY_NAME
56
+ index.columns == primary_key_columns && index.unique or
57
+ raise "primary key on #{table_name} was not unique on #{primary_key_columns} (was unique=#{index.unique} on #{index.columns})"
54
58
  primary_key_found = true
55
59
  end
56
- new(model, i.columns, name: i.name, unique: i.unique, where: i.where, table_name: old_table_name)
60
+ length =
61
+ case lengths = index.lengths
62
+ when {}
63
+ nil
64
+ when Hash
65
+ lengths.size == 1 ? lengths.values.first : lengths
66
+ else
67
+ lengths
68
+ end
69
+ new(index.columns, name: index.name, table_name: table_name, unique: index.unique, where: index.where, length: length)
57
70
  end.compact
58
71
 
59
72
  if !primary_key_found
60
- index_definitions << new(model, primary_key_columns, name: PRIMARY_KEY_NAME, unique: true, where: nil, table_name: old_table_name)
73
+ index_definitions << new(primary_key_columns, name: PRIMARY_KEY_NAME, table_name: table_name, unique: true)
61
74
  end
62
75
  index_definitions
63
76
  end
64
77
 
65
- def default_index_name(table, fields)
78
+ def default_index_name(table_name, columns)
66
79
  index_name = nil
67
80
  [:long_index_name, :short_index_name].find do |method_name|
68
- index_name = send(method_name, table, fields)
81
+ index_name = send(method_name, table_name, columns)
69
82
  if DeclareSchema.max_index_and_constraint_name_length.nil? || index_name.length <= DeclareSchema.max_index_and_constraint_name_length
70
83
  break index_name
71
84
  end
@@ -115,12 +128,12 @@ module DeclareSchema
115
128
 
116
129
  # Unique key for this object. Used for equality checking.
117
130
  def to_key
118
- @key ||= [table, fields, options].freeze
131
+ @to_key ||= [name, *settings].freeze
119
132
  end
120
133
 
121
134
  # The index settings for this object. Used for equivalence checking. Does not include the name.
122
135
  def settings
123
- @settings ||= [table, fields, options.except(:name)].freeze
136
+ @settings ||= [columns, options.except(:name)].freeze
124
137
  end
125
138
 
126
139
  def hash
@@ -136,7 +149,7 @@ module DeclareSchema
136
149
  end
137
150
 
138
151
  def with_name(new_name)
139
- self.class.new(@model, @fields, **{ **options, name: new_name })
152
+ self.class.new(@columns, name: new_name, table_name: @table_name, unique: @unique, allow_equivalent: @explicit_name.nil?, where: @where, length: @length)
140
153
  end
141
154
 
142
155
  alias eql? ==
@@ -50,7 +50,17 @@ module DeclareSchema
50
50
 
51
51
  def initialize(table_name, **table_options)
52
52
  @table_name = table_name
53
- @table_options = table_options
53
+ @table_options = table_options.each_with_object({}) do |(k, v),result|
54
+ result[k] =
55
+ case k
56
+ when :charset
57
+ DeclareSchema.normalize_charset(v)
58
+ when :collation
59
+ DeclareSchema.normalize_collation(v)
60
+ else
61
+ v
62
+ end
63
+ end
54
64
  end
55
65
 
56
66
  def to_key
@@ -28,7 +28,7 @@ module DeclareSchema
28
28
  # index_definitions holds IndexDefinition objects for all the declared indexes.
29
29
  inheriting_cattr_reader index_definitions: Set.new
30
30
  inheriting_cattr_reader ignore_indexes: Set.new
31
- inheriting_cattr_reader constraint_specs: Set.new
31
+ inheriting_cattr_reader constraint_definitions: Set.new
32
32
 
33
33
  # table_options holds optional configuration for the create_table statement
34
34
  # supported options include :charset and :collation
@@ -49,16 +49,23 @@ module DeclareSchema
49
49
  end
50
50
 
51
51
  module ClassMethods
52
- def index(fields, **options)
53
- index_definitions << ::DeclareSchema::Model::IndexDefinition.new(self, fields, **options)
52
+ def index(columns, name: nil, allow_equivalent: false, unique: false, where: nil, length: nil)
53
+ index_definitions << ::DeclareSchema::Model::IndexDefinition.new(
54
+ columns,
55
+ name: name, table_name: table_name, allow_equivalent: allow_equivalent, unique: unique, where: where, length: length
56
+ )
54
57
  end
55
58
 
56
- def primary_key_index(*fields)
57
- index(fields.flatten, unique: true, name: ::DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME)
59
+ def primary_key_index(*columns)
60
+ index(columns.flatten, unique: true, name: ::DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME)
58
61
  end
59
62
 
60
- def constraint(foreign_key, **options)
61
- constraint_specs << DeclareSchema::Model::ForeignKeyDefinition.new(self, foreign_key.to_s, **options)
63
+ def constraint(foreign_key_column, parent_table_name: nil, constraint_name: nil, parent_class_name: nil, dependent: nil)
64
+ constraint_definitions << ::DeclareSchema::Model::ForeignKeyDefinition.new(
65
+ foreign_key_column.to_s,
66
+ constraint_name: constraint_name,
67
+ child_table_name: table_name, parent_table_name: parent_table_name, parent_class_name: parent_class_name, dependent: dependent
68
+ )
62
69
  end
63
70
 
64
71
  # tell the migration generator to ignore the named index. Useful for existing indexes, or for indexes
@@ -76,7 +83,7 @@ module DeclareSchema
76
83
  _add_serialize_for_field(name, type, options)
77
84
  _add_formatting_for_field(name, type)
78
85
  _add_validations_for_field(name, type, args, options)
79
- _add_index_for_field(name, args, options)
86
+ _add_index_for_field(name, args, **options)
80
87
  field_specs[name] = ::DeclareSchema::Model::FieldSpec.new(self, name, type, position: field_specs.size, **options)
81
88
  attr_order << name unless attr_order.include?(name)
82
89
  end
@@ -91,8 +98,8 @@ module DeclareSchema
91
98
 
92
99
  # Extend belongs_to so that it
93
100
  # 1. creates a FieldSpec for the foreign key
94
- # 2. declares an index on the foreign key
95
- # 3. declares a foreign_key constraint
101
+ # 2. declares an index on the foreign key (optional)
102
+ # 3. declares a foreign_key constraint (optional)
96
103
  def belongs_to(name, scope = nil, **options)
97
104
  column_options = {}
98
105
 
@@ -112,14 +119,14 @@ module DeclareSchema
112
119
  # index: { ... } means create an index on the foreign key with the given options
113
120
  index_value = options.delete(:index)
114
121
  if index_value != false || options.has_key?(:unique) || options.has_key?(:allow_equivalent)
115
- index_options = {}
122
+ index_options = {} # truthy iff we want an index
116
123
  case index_value
117
- when String
124
+ when String, Symbol
118
125
  Kernel.warn("belongs_to index: 'name' is deprecated; use index: { name: 'name' } instead (in #{name})")
119
- index_options[:name] = index_value
120
- when true
126
+ index_options[:name] = index_value.to_s
121
127
  when false
122
128
  raise ArgumentError, "belongs_to index: false contradicts others options #{options.inspect} (in #{name})"
129
+ when true
123
130
  when nil
124
131
  when Hash
125
132
  index_options = index_value
@@ -135,23 +142,20 @@ module DeclareSchema
135
142
  index_options[:allow_equivalent] = options.delete(:allow_equivalent) if options.has_key?(:allow_equivalent)
136
143
  end
137
144
 
138
- fk_options = options.dup
139
- fk_options[:constraint_name] = options.delete(:constraint) if options.has_key?(:constraint)
140
- fk_options[:index_name] = index_options&.[](:name)
145
+ constraint_name = options.delete(:constraint)
141
146
 
142
- fk = options[:foreign_key]&.to_s || "#{name}_id"
147
+ dependent_delete = :delete if options.delete(:far_end_dependent) == :delete
143
148
 
149
+ # infer :optional from :null
144
150
  if !options.has_key?(:optional)
145
- options[:optional] = column_options[:null] # infer :optional from :null
151
+ options[:optional] = column_options[:null]
146
152
  end
147
153
 
148
- fk_options[:dependent] = options.delete(:far_end_dependent) if options.has_key?(:far_end_dependent)
149
-
150
154
  super
151
155
 
152
156
  reflection = reflections[name.to_s] or raise "Couldn't find reflection #{name} in #{reflections.keys}"
153
- foreign_key = reflection.foreign_key or raise "Couldn't find foreign_key for #{name} in #{reflection.inspect}"
154
- foreign_key_id_column_options = column_options.dup
157
+ foreign_key_column = reflection.foreign_key or raise "Couldn't find foreign_key for #{name} in #{reflection.inspect}"
158
+ foreign_key_column_options = column_options.dup
155
159
 
156
160
  # Note: the foreign key limit: should match the primary key limit:. (If there is a foreign key constraint,
157
161
  # those limits _must_ match.) We'd like to call _infer_fk_limit and get the limit right from the PK.
@@ -162,27 +166,34 @@ module DeclareSchema
162
166
  # The one downside of this approach is that application code that asks the field_spec for the declared
163
167
  # foreign key limit: will always get 8 back even if this is a grandfathered foreign key that points to
164
168
  # a limit: 4 primary key. It seems unlikely that any application code would do this.
165
- foreign_key_id_column_options[:pre_migration] = ->(field_spec) do
166
- if (inferred_limit = _infer_fk_limit(foreign_key, reflection))
169
+ foreign_key_column_options[:pre_migration] = ->(field_spec) do
170
+ if (inferred_limit = _infer_fk_limit(foreign_key_column, reflection))
167
171
  field_spec.sql_options[:limit] = inferred_limit
168
172
  end
169
173
  end
170
174
 
171
- declare_field(foreign_key.to_sym, :bigint, **foreign_key_id_column_options)
175
+ declare_field(foreign_key_column.to_sym, :bigint, **foreign_key_column_options)
172
176
 
173
177
  if reflection.options[:polymorphic]
174
178
  foreign_type = options[:foreign_type] || "#{name}_type"
175
179
  _declare_polymorphic_type_field(foreign_type, column_options)
176
- index([foreign_type, foreign_key], **index_options) if index_options
180
+ if ::DeclareSchema.default_generate_indexing && index_options
181
+ index([foreign_type, foreign_key_column], **index_options)
182
+ end
177
183
  else
178
- index(foreign_key, **index_options) if index_options
179
- constraint(foreign_key, **fk_options) if fk_options[:constraint_name] != false
184
+ if ::DeclareSchema.default_generate_indexing && index_options
185
+ index([foreign_key_column], **index_options)
186
+ end
187
+
188
+ if ::DeclareSchema.default_generate_foreign_keys && constraint_name != false
189
+ constraint(foreign_key_column, constraint_name: constraint_name || index_options&.[](:name), parent_class_name: reflection.klass, dependent: dependent_delete)
190
+ end
180
191
  end
181
192
  end
182
193
 
183
- def _infer_fk_limit(foreign_key, reflection)
194
+ def _infer_fk_limit(foreign_key_column, reflection)
184
195
  if reflection.options[:polymorphic]
185
- if (foreign_key_column = columns_hash[foreign_key.to_s]) && foreign_key_column.type == :integer
196
+ if (foreign_key_column = columns_hash[foreign_key_column.to_s]) && foreign_key_column.type == :integer
186
197
  foreign_key_column.limit
187
198
  end
188
199
  else
@@ -224,7 +235,7 @@ module DeclareSchema
224
235
  end
225
236
 
226
237
  def _rails_default_primary_key
227
- ::DeclareSchema::Model::IndexDefinition.new(self, [_declared_primary_key.to_sym], unique: true, name: DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME)
238
+ ::DeclareSchema::Model::IndexDefinition.new([_declared_primary_key], name: DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME, table_name: table_name, unique: true)
228
239
  end
229
240
 
230
241
  # Declares the "foo_type" field that accompanies the "foo_id"
@@ -295,15 +306,16 @@ module DeclareSchema
295
306
  end
296
307
  end
297
308
 
298
- def _add_index_for_field(name, args, options)
299
- if (to_name = options.delete(:index))
309
+ def _add_index_for_field(column_name, args, **options)
310
+ if (index_name = options.delete(:index))
300
311
  index_opts =
301
312
  {
302
313
  unique: args.include?(:unique) || !!options.delete(:unique)
303
314
  }
315
+
304
316
  # support index: true declaration
305
- index_opts[:name] = to_name unless to_name == true
306
- index(name, **index_opts)
317
+ index_opts[:name] = index_name unless index_name == true
318
+ index([column_name], **index_opts)
307
319
  end
308
320
  end
309
321
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeclareSchema
4
- VERSION = "1.4.0.colin.7"
4
+ VERSION = "1.4.0.colin.8"
5
5
  end