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 +4 -4
- data/CHANGELOG.md +5 -0
- data/Gemfile.lock +1 -1
- data/README.md +4 -1
- data/lib/declare_schema/model/field_spec.rb +2 -0
- data/lib/declare_schema/model/foreign_key_definition.rb +39 -43
- data/lib/declare_schema/model/habtm_model_shim.rb +18 -25
- data/lib/declare_schema/model/index_definition.rb +43 -30
- data/lib/declare_schema/model/table_options_definition.rb +11 -1
- data/lib/declare_schema/model.rb +57 -48
- data/lib/declare_schema/version.rb +1 -1
- data/lib/declare_schema.rb +34 -2
- data/lib/generators/declare_schema/migration/migrator.rb +17 -11
- data/spec/lib/declare_schema/field_spec_spec.rb +22 -2
- data/spec/lib/declare_schema/migration_generator_spec.rb +17 -35
- data/spec/lib/declare_schema/model/foreign_key_definition_spec.rb +28 -27
- data/spec/lib/declare_schema/model/habtm_model_shim_spec.rb +54 -57
- data/spec/lib/declare_schema/model/index_definition_spec.rb +60 -53
- data/spec/lib/declare_schema/model/table_options_definition_spec.rb +26 -6
- data/spec/lib/declare_schema_spec.rb +62 -8
- data/spec/lib/generators/declare_schema/migration/migrator_spec.rb +3 -1
- data/spec/spec_helper.rb +4 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ca0ed75221c664e2a5c59bff495a971495922f7f31ea0fb548fb67f0ad0eb639
|
4
|
+
data.tar.gz: fc76669be291fff9da61353f7f040ba14327e1359f712a963e5865ed948034c8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
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
|
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 :
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
@
|
16
|
-
@
|
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
|
19
|
+
case parent_class_name
|
24
20
|
when String, Symbol
|
25
|
-
|
21
|
+
parent_class_name.to_s
|
26
22
|
when Class
|
27
|
-
@parent_class =
|
23
|
+
@parent_class = parent_class_name
|
28
24
|
@parent_class.name
|
29
25
|
when nil
|
30
|
-
@
|
26
|
+
@foreign_key_column.sub(/_id\z/, '').camelize
|
31
27
|
end
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
40
|
-
show_create_table =
|
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
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
9
|
-
|
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, :
|
13
|
+
attr_reader :join_table, :foreign_keys, :parent_table_names
|
24
14
|
|
25
|
-
def initialize(join_table, foreign_keys,
|
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
|
-
@
|
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 |(
|
42
|
-
result[
|
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(
|
57
|
-
IndexDefinition.new(
|
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
|
68
|
-
@
|
69
|
-
ForeignKeyDefinition.new(
|
70
|
-
ForeignKeyDefinition.new(
|
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 :
|
13
|
-
|
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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
@
|
23
|
-
@
|
24
|
-
@name
|
25
|
-
@
|
26
|
-
@
|
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
|
37
|
+
if where
|
33
38
|
@where = where.start_with?('(') ? where : "(#{where})"
|
34
39
|
end
|
35
40
|
|
36
|
-
|
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
|
43
|
-
|
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 =
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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
|
-
|
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(
|
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(
|
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,
|
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
|
-
@
|
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 ||= [
|
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(@
|
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
|
data/lib/declare_schema/model.rb
CHANGED
@@ -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
|
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(
|
53
|
-
index_definitions << ::DeclareSchema::Model::IndexDefinition.new(
|
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(*
|
57
|
-
index(
|
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(
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
-
|
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
|
-
|
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]
|
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
|
-
|
156
|
-
|
157
|
-
|
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
|
-
|
169
|
-
if (inferred_limit = _infer_fk_limit(
|
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(
|
175
|
+
declare_field(foreign_key_column.to_sym, :bigint, **foreign_key_column_options)
|
175
176
|
|
176
|
-
if
|
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
|
-
|
180
|
+
if ::DeclareSchema.default_generate_indexing && index_options
|
181
|
+
index([foreign_type, foreign_key_column], **index_options)
|
182
|
+
end
|
180
183
|
else
|
181
|
-
|
182
|
-
|
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(
|
187
|
-
if
|
188
|
-
if (
|
189
|
-
|
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 =
|
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(
|
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(
|
302
|
-
if (
|
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] =
|
309
|
-
index(
|
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 (
|
324
|
-
if
|
325
|
-
|
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
|
-
|
336
|
+
reflection
|
328
337
|
end
|
329
338
|
end ||
|
330
339
|
if (col = _column(name.to_s))
|