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 +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))
|