declare_schema 1.4.0.colin.6 → 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: 1bdf7e72cc4b08040aaaa9036e520edf7c2be76a0a2466fc08215c8d222ec966
4
- data.tar.gz: 63a8e595acec695a3a88ead0daeb46e871353645747ec2c03e6175d7d7c35161
3
+ metadata.gz: ca0ed75221c664e2a5c59bff495a971495922f7f31ea0fb548fb67f0ad0eb639
4
+ data.tar.gz: fc76669be291fff9da61353f7f040ba14327e1359f712a963e5865ed948034c8
5
5
  SHA512:
6
- metadata.gz: eca71720644bbf4783dc90bab55604b154f2feb6ab5e05c72822675c9dc2993368041074f2f3a719188298577820e460ec44815a3b7a3ee19b46f00f5448e19d
7
- data.tar.gz: 35851323ffd8cd93b0f634d03f3164a83087cee588a45da74f4499e5ebd1d48dcea50fb21dfe42eb2ed807ce3cf307a40de79b4f5a8fbb99c0017268d20932d6
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.6)
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,19 +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(fkey, **options)
61
- fkey_s = fkey.to_s
62
- unless constraint_specs.any? { |constraint_spec| constraint_spec.foreign_key == fkey_s }
63
- constraint_specs << DeclareSchema::Model::ForeignKeyDefinition.new(self, fkey, **options)
64
- end
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
+ )
65
69
  end
66
70
 
67
71
  # tell the migration generator to ignore the named index. Useful for existing indexes, or for indexes
@@ -79,7 +83,7 @@ module DeclareSchema
79
83
  _add_serialize_for_field(name, type, options)
80
84
  _add_formatting_for_field(name, type)
81
85
  _add_validations_for_field(name, type, args, options)
82
- _add_index_for_field(name, args, options)
86
+ _add_index_for_field(name, args, **options)
83
87
  field_specs[name] = ::DeclareSchema::Model::FieldSpec.new(self, name, type, position: field_specs.size, **options)
84
88
  attr_order << name unless attr_order.include?(name)
85
89
  end
@@ -94,8 +98,8 @@ module DeclareSchema
94
98
 
95
99
  # Extend belongs_to so that it
96
100
  # 1. creates a FieldSpec for the foreign key
97
- # 2. declares an index on the foreign key
98
- # 3. declares a foreign_key constraint
101
+ # 2. declares an index on the foreign key (optional)
102
+ # 3. declares a foreign_key constraint (optional)
99
103
  def belongs_to(name, scope = nil, **options)
100
104
  column_options = {}
101
105
 
@@ -115,14 +119,14 @@ module DeclareSchema
115
119
  # index: { ... } means create an index on the foreign key with the given options
116
120
  index_value = options.delete(:index)
117
121
  if index_value != false || options.has_key?(:unique) || options.has_key?(:allow_equivalent)
118
- index_options = {}
122
+ index_options = {} # truthy iff we want an index
119
123
  case index_value
120
- when String
124
+ when String, Symbol
121
125
  Kernel.warn("belongs_to index: 'name' is deprecated; use index: { name: 'name' } instead (in #{name})")
122
- index_options[:name] = index_value
123
- when true
126
+ index_options[:name] = index_value.to_s
124
127
  when false
125
128
  raise ArgumentError, "belongs_to index: false contradicts others options #{options.inspect} (in #{name})"
129
+ when true
126
130
  when nil
127
131
  when Hash
128
132
  index_options = index_value
@@ -138,23 +142,20 @@ module DeclareSchema
138
142
  index_options[:allow_equivalent] = options.delete(:allow_equivalent) if options.has_key?(:allow_equivalent)
139
143
  end
140
144
 
141
- fk_options = options.dup
142
- fk_options[:constraint_name] = options.delete(:constraint) if options.has_key?(:constraint)
143
- fk_options[:index_name] = index_options&.[](:name)
145
+ constraint_name = options.delete(:constraint)
144
146
 
145
- fk = options[:foreign_key]&.to_s || "#{name}_id"
147
+ dependent_delete = :delete if options.delete(:far_end_dependent) == :delete
146
148
 
149
+ # infer :optional from :null
147
150
  if !options.has_key?(:optional)
148
- options[:optional] = column_options[:null] # infer :optional from :null
151
+ options[:optional] = column_options[:null]
149
152
  end
150
153
 
151
- fk_options[:dependent] = options.delete(:far_end_dependent) if options.has_key?(:far_end_dependent)
152
-
153
154
  super
154
155
 
155
- refl = reflections[name.to_s] or raise "Couldn't find reflection #{name} in #{reflections.keys}"
156
- fkey = refl.foreign_key or raise "Couldn't find foreign_key for #{name} in #{refl.inspect}"
157
- fkey_id_column_options = column_options.dup
156
+ reflection = reflections[name.to_s] or raise "Couldn't find reflection #{name} in #{reflections.keys}"
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
158
159
 
159
160
  # Note: the foreign key limit: should match the primary key limit:. (If there is a foreign key constraint,
160
161
  # those limits _must_ match.) We'd like to call _infer_fk_limit and get the limit right from the PK.
@@ -165,31 +166,38 @@ module DeclareSchema
165
166
  # The one downside of this approach is that application code that asks the field_spec for the declared
166
167
  # foreign key limit: will always get 8 back even if this is a grandfathered foreign key that points to
167
168
  # a limit: 4 primary key. It seems unlikely that any application code would do this.
168
- fkey_id_column_options[:pre_migration] = ->(field_spec) do
169
- if (inferred_limit = _infer_fk_limit(fkey, refl))
169
+ foreign_key_column_options[:pre_migration] = ->(field_spec) do
170
+ if (inferred_limit = _infer_fk_limit(foreign_key_column, reflection))
170
171
  field_spec.sql_options[:limit] = inferred_limit
171
172
  end
172
173
  end
173
174
 
174
- declare_field(fkey.to_sym, :bigint, **fkey_id_column_options)
175
+ declare_field(foreign_key_column.to_sym, :bigint, **foreign_key_column_options)
175
176
 
176
- if refl.options[:polymorphic]
177
+ if reflection.options[:polymorphic]
177
178
  foreign_type = options[:foreign_type] || "#{name}_type"
178
179
  _declare_polymorphic_type_field(foreign_type, column_options)
179
- index([foreign_type, fkey], **index_options) if index_options
180
+ if ::DeclareSchema.default_generate_indexing && index_options
181
+ index([foreign_type, foreign_key_column], **index_options)
182
+ end
180
183
  else
181
- index(fkey, **index_options) if index_options
182
- constraint(fkey, **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
183
191
  end
184
192
  end
185
193
 
186
- def _infer_fk_limit(fkey, refl)
187
- if refl.options[:polymorphic]
188
- if (fkey_column = columns_hash[fkey.to_s]) && fkey_column.type == :integer
189
- fkey_column.limit
194
+ def _infer_fk_limit(foreign_key_column, reflection)
195
+ if reflection.options[:polymorphic]
196
+ if (foreign_key_column = columns_hash[foreign_key_column.to_s]) && foreign_key_column.type == :integer
197
+ foreign_key_column.limit
190
198
  end
191
199
  else
192
- klass = refl.klass or raise "Couldn't find belongs_to klass for #{name} in #{refl.inspect}"
200
+ klass = reflection.klass or raise "Couldn't find belongs_to klass for #{name} in #{reflection.inspect}"
193
201
  if (pk_id_type = klass._table_options&.[](:id))
194
202
  if pk_id_type == :integer
195
203
  4
@@ -227,7 +235,7 @@ module DeclareSchema
227
235
  end
228
236
 
229
237
  def _rails_default_primary_key
230
- ::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)
231
239
  end
232
240
 
233
241
  # Declares the "foo_type" field that accompanies the "foo_id"
@@ -298,15 +306,16 @@ module DeclareSchema
298
306
  end
299
307
  end
300
308
 
301
- def _add_index_for_field(name, args, options)
302
- if (to_name = options.delete(:index))
309
+ def _add_index_for_field(column_name, args, **options)
310
+ if (index_name = options.delete(:index))
303
311
  index_opts =
304
312
  {
305
313
  unique: args.include?(:unique) || !!options.delete(:unique)
306
314
  }
315
+
307
316
  # support index: true declaration
308
- index_opts[:name] = to_name unless to_name == true
309
- index(name, **index_opts)
317
+ index_opts[:name] = index_name unless index_name == true
318
+ index([column_name], **index_opts)
310
319
  end
311
320
  end
312
321
 
@@ -320,11 +329,11 @@ module DeclareSchema
320
329
  end
321
330
 
322
331
  attr_types[name] ||
323
- if (refl = reflections[name.to_s])
324
- if refl.macro.in?([:has_one, :belongs_to]) && !refl.options[:polymorphic]
325
- refl.klass
332
+ if (reflection = reflections[name.to_s])
333
+ if reflection.macro.in?([:has_one, :belongs_to]) && !reflection.options[:polymorphic]
334
+ reflection.klass
326
335
  else
327
- refl
336
+ reflection
328
337
  end
329
338
  end ||
330
339
  if (col = _column(name.to_s))
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeclareSchema
4
- VERSION = "1.4.0.colin.6"
4
+ VERSION = "1.4.0.colin.8"
5
5
  end