declare_schema 1.4.0.colin.6 → 1.4.0.colin.8

Sign up to get free protection for your applications and to get access to all the features.
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