declare_schema 1.4.0.colin.7 → 1.4.0.colin.9
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 +10 -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 +30 -30
- data/lib/declare_schema/model/index_definition.rb +51 -30
- data/lib/declare_schema/model/table_options_definition.rb +11 -1
- data/lib/declare_schema/model.rb +55 -42
- data/lib/declare_schema/version.rb +1 -1
- data/lib/declare_schema.rb +34 -2
- data/lib/generators/declare_schema/migration/migrator.rb +32 -21
- data/spec/lib/declare_schema/field_spec_spec.rb +22 -2
- data/spec/lib/declare_schema/migration_generator_spec.rb +63 -41
- data/spec/lib/declare_schema/model/foreign_key_definition_spec.rb +28 -27
- data/spec/lib/declare_schema/model/habtm_model_shim_spec.rb +84 -62
- data/spec/lib/declare_schema/model/index_definition_spec.rb +126 -55
- 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: 7e20de5eeec8ea9436d2fd674f5ff33f20888cfb87e17456fd278a38504bbede
|
4
|
+
data.tar.gz: 259d8dd463292836e40d3fe07261743e714d588440d8a988183daa32f0c8a4d9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 18dc843b60c81ace18bc692f1e8d76038d50695081ab846b14f0653aa4d6a755666417f4d7190429aa83e9eb9fffba02d61c71b2a5c44ef7dbf69baf401e7e31
|
7
|
+
data.tar.gz: 94bf1af85ecdc252e08f6453018edc6a2f216e65aa6d7fce0ab7399058097d6a0e1a834e02d350789e8e6fb3485050979dde543e2036e048cc914dd6ffb5cdad
|
data/CHANGELOG.md
CHANGED
@@ -10,6 +10,16 @@ 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.4] - 2024-01-18
|
14
|
+
### Fixed
|
15
|
+
- Add test for migrating `has_and_belongs_to_many` associations and fix them to properly declare their
|
16
|
+
2 foreign keys as the primary key of the join table, rather than just a unique index.
|
17
|
+
|
18
|
+
## [1.3.3] - 2024-01-17
|
19
|
+
### Fixed
|
20
|
+
- Fix a MySQL 8 bug where MySQL 8+ renames charset 'utf8' to 'utf8mb3' and collation 'utf8_general_ci' to
|
21
|
+
'utf8mb3_unicode_ci'.
|
22
|
+
|
13
23
|
## [1.3.2] - 2024-01-12
|
14
24
|
### Fixed
|
15
25
|
- 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
|
@@ -4,28 +4,24 @@ module DeclareSchema
|
|
4
4
|
module Model
|
5
5
|
class HabtmModelShim
|
6
6
|
class << self
|
7
|
-
def from_reflection(
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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)
|
7
|
+
def from_reflection(reflection)
|
8
|
+
new(reflection.join_table,
|
9
|
+
[reflection.foreign_key, reflection.association_foreign_key],
|
10
|
+
[reflection.active_record.table_name, reflection.klass.table_name],
|
11
|
+
connection: reflection.active_record.connection)
|
20
12
|
end
|
21
13
|
end
|
22
14
|
|
23
|
-
attr_reader :join_table, :foreign_keys, :
|
15
|
+
attr_reader :join_table, :foreign_keys, :parent_table_names, :connection
|
24
16
|
|
25
|
-
def initialize(join_table, foreign_keys,
|
17
|
+
def initialize(join_table, foreign_keys, parent_table_names, connection:)
|
18
|
+
foreign_keys.is_a?(Array) && foreign_keys.size == 2 or
|
19
|
+
raise ArgumentError, "foreign_keys must be <Array[2]>; got #{foreign_keys.inspect}"
|
20
|
+
parent_table_names.is_a?(Array) && parent_table_names.size == 2 or
|
21
|
+
raise ArgumentError, "parent_table_names must be <Array[2]>; got #{parent_table_names.inspect}"
|
26
22
|
@join_table = join_table
|
27
|
-
@foreign_keys = foreign_keys
|
28
|
-
@
|
23
|
+
@foreign_keys = foreign_keys.sort # Rails requires these be in alphabetical order
|
24
|
+
@parent_table_names = @foreign_keys == foreign_keys ? parent_table_names : parent_table_names.reverse # match the above sort
|
29
25
|
@connection = connection
|
30
26
|
end
|
31
27
|
|
@@ -38,8 +34,8 @@ module DeclareSchema
|
|
38
34
|
end
|
39
35
|
|
40
36
|
def field_specs
|
41
|
-
foreign_keys.each_with_index.each_with_object({}) do |(
|
42
|
-
result[
|
37
|
+
foreign_keys.each_with_index.each_with_object({}) do |(foreign_key, i), result|
|
38
|
+
result[foreign_key] = ::DeclareSchema::Model::FieldSpec.new(self, foreign_key, :bigint, position: i, null: false)
|
43
39
|
end
|
44
40
|
end
|
45
41
|
|
@@ -48,26 +44,30 @@ module DeclareSchema
|
|
48
44
|
end
|
49
45
|
|
50
46
|
def _declared_primary_key
|
51
|
-
|
47
|
+
foreign_keys
|
52
48
|
end
|
53
49
|
|
54
|
-
def
|
55
|
-
|
56
|
-
IndexDefinition.new(
|
57
|
-
|
58
|
-
])
|
50
|
+
def index_definitions
|
51
|
+
[
|
52
|
+
IndexDefinition.new(foreign_keys.last, table_name: table_name, unique: false) # index for queries where we only have the last foreign key
|
53
|
+
]
|
59
54
|
end
|
60
55
|
|
61
|
-
|
56
|
+
def index_definitions_with_primary_key
|
57
|
+
[
|
58
|
+
*index_definitions,
|
59
|
+
IndexDefinition.new(foreign_keys, table_name: table_name, name: Model::IndexDefinition::PRIMARY_KEY_NAME, unique: true) # creates a primary composite key on both foreign keys
|
60
|
+
]
|
61
|
+
end
|
62
62
|
|
63
63
|
def ignore_indexes
|
64
64
|
@ignore_indexes ||= Set.new
|
65
65
|
end
|
66
66
|
|
67
|
-
def
|
68
|
-
@
|
69
|
-
ForeignKeyDefinition.new(
|
70
|
-
ForeignKeyDefinition.new(
|
67
|
+
def constraint_definitions
|
68
|
+
@constraint_definitions ||= Set.new([
|
69
|
+
ForeignKeyDefinition.new(foreign_keys.first, constraint_name: "#{join_table}_FK1", child_table_name: @join_table, parent_table_name: parent_table_names.first, dependent: :delete),
|
70
|
+
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
71
|
])
|
72
72
|
end
|
73
73
|
end
|
@@ -7,65 +7,66 @@ 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
|
-
def initialize(
|
20
|
-
@
|
21
|
-
@
|
22
|
-
@
|
23
|
-
@explicit_name =
|
24
|
-
|
25
|
-
@
|
26
|
-
|
19
|
+
def initialize(columns, table_name:, name: nil, allow_equivalent: false, unique: false, where: nil, length: nil)
|
20
|
+
@table_name = table_name
|
21
|
+
@name = name || self.class.default_index_name(table_name, columns)
|
22
|
+
@columns = Array.wrap(columns).map(&:to_s)
|
23
|
+
@explicit_name = @name if !allow_equivalent
|
24
|
+
unique.in?([false, true]) or raise ArgumentError, "unique must be true or false: got #{unique.inspect}"
|
25
|
+
if @name == PRIMARY_KEY_NAME
|
26
|
+
unique or raise ArgumentError, "primary key index must be unique"
|
27
|
+
end
|
28
|
+
@unique = unique
|
27
29
|
|
28
30
|
if DeclareSchema.max_index_and_constraint_name_length && @name.length > DeclareSchema.max_index_and_constraint_name_length
|
29
31
|
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
32
|
end
|
31
33
|
|
32
|
-
if
|
34
|
+
if where
|
33
35
|
@where = where.start_with?('(') ? where : "(#{where})"
|
34
36
|
end
|
35
37
|
|
36
|
-
|
38
|
+
@length = self.class.normalize_index_length(length, columns: @columns)
|
37
39
|
end
|
38
40
|
|
39
41
|
class << self
|
40
42
|
# extract IndexSpecs from an existing table
|
41
43
|
# 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}"
|
44
|
+
def for_table(table_name, ignore_indexes, connection)
|
45
|
+
primary_key_columns = Array(connection.primary_key(table_name))
|
46
|
+
primary_key_columns.present? or raise "could not find primary key for table #{table_name} in #{connection.columns(table_name).inspect}"
|
47
47
|
|
48
48
|
primary_key_found = false
|
49
|
-
index_definitions =
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
49
|
+
index_definitions = connection.indexes(table_name).map do |index|
|
50
|
+
next if ignore_indexes.include?(index.name)
|
51
|
+
|
52
|
+
if index.name == PRIMARY_KEY_NAME
|
53
|
+
index.columns == primary_key_columns && index.unique or
|
54
|
+
raise "primary key on #{table_name} was not unique on #{primary_key_columns} (was unique=#{index.unique} on #{index.columns})"
|
54
55
|
primary_key_found = true
|
55
56
|
end
|
56
|
-
new(
|
57
|
+
new(index.columns, name: index.name, table_name: table_name, unique: index.unique, where: index.where, length: index.lengths)
|
57
58
|
end.compact
|
58
59
|
|
59
60
|
if !primary_key_found
|
60
|
-
index_definitions << new(
|
61
|
+
index_definitions << new(primary_key_columns, name: PRIMARY_KEY_NAME, table_name: table_name, unique: true)
|
61
62
|
end
|
62
63
|
index_definitions
|
63
64
|
end
|
64
65
|
|
65
|
-
def default_index_name(
|
66
|
+
def default_index_name(table_name, columns)
|
66
67
|
index_name = nil
|
67
68
|
[:long_index_name, :short_index_name].find do |method_name|
|
68
|
-
index_name = send(method_name,
|
69
|
+
index_name = send(method_name, table_name, columns)
|
69
70
|
if DeclareSchema.max_index_and_constraint_name_length.nil? || index_name.length <= DeclareSchema.max_index_and_constraint_name_length
|
70
71
|
break index_name
|
71
72
|
end
|
@@ -73,6 +74,26 @@ module DeclareSchema
|
|
73
74
|
"Default index name '#{index_name}' exceeds configured limit of #{DeclareSchema.max_index_and_constraint_name_length} characters. Use the `name:` option to give it a shorter name, or adjust DeclareSchema.max_index_and_constraint_name_length if you know your database can accept longer names."
|
74
75
|
end
|
75
76
|
|
77
|
+
# This method normalizes the length option to be either nil or a Hash of Symbol column names to lengths,
|
78
|
+
# so that we can safely compare what the user specified with what we get when querying the database schema.
|
79
|
+
# @return [Hash<Symbol, nil>]
|
80
|
+
def normalize_index_length(length, columns:)
|
81
|
+
case length
|
82
|
+
when nil, {}
|
83
|
+
nil
|
84
|
+
when Integer
|
85
|
+
if columns.size == 1
|
86
|
+
{ columns.first.to_sym => length }
|
87
|
+
else
|
88
|
+
raise ArgumentError, "Index length of Integer only allowed when exactly one column; got #{length.inspect} for #{columns.inspect}"
|
89
|
+
end
|
90
|
+
when Hash
|
91
|
+
length.transform_keys(&:to_sym)
|
92
|
+
else
|
93
|
+
raise ArgumentError, "Index length must be nil or Integer or a Hash of column names to lengths; got #{length.inspect} for #{columns.inspect}"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
76
97
|
private
|
77
98
|
|
78
99
|
SHA_SUFFIX_LENGTH = 4
|
@@ -115,12 +136,12 @@ module DeclareSchema
|
|
115
136
|
|
116
137
|
# Unique key for this object. Used for equality checking.
|
117
138
|
def to_key
|
118
|
-
@
|
139
|
+
@to_key ||= [name, *settings].freeze
|
119
140
|
end
|
120
141
|
|
121
142
|
# The index settings for this object. Used for equivalence checking. Does not include the name.
|
122
143
|
def settings
|
123
|
-
@settings ||= [
|
144
|
+
@settings ||= [columns, options.except(:name)].freeze
|
124
145
|
end
|
125
146
|
|
126
147
|
def hash
|
@@ -136,7 +157,7 @@ module DeclareSchema
|
|
136
157
|
end
|
137
158
|
|
138
159
|
def with_name(new_name)
|
139
|
-
self.class.new(@
|
160
|
+
self.class.new(@columns, name: new_name, table_name: @table_name, unique: @unique, allow_equivalent: @explicit_name.nil?, where: @where, length: @length)
|
140
161
|
end
|
141
162
|
|
142
163
|
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,16 +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
|
-
|
63
|
+
def constraint(foreign_key_column, parent_table_name: nil, constraint_name: nil, parent_class_name: nil, dependent: nil)
|
64
|
+
constraint_definitions << ::DeclareSchema::Model::ForeignKeyDefinition.new(
|
65
|
+
foreign_key_column.to_s,
|
66
|
+
constraint_name: constraint_name,
|
67
|
+
child_table_name: table_name, parent_table_name: parent_table_name, parent_class_name: parent_class_name, dependent: dependent
|
68
|
+
)
|
62
69
|
end
|
63
70
|
|
64
71
|
# tell the migration generator to ignore the named index. Useful for existing indexes, or for indexes
|
@@ -76,7 +83,7 @@ module DeclareSchema
|
|
76
83
|
_add_serialize_for_field(name, type, options)
|
77
84
|
_add_formatting_for_field(name, type)
|
78
85
|
_add_validations_for_field(name, type, args, options)
|
79
|
-
_add_index_for_field(name, args, options)
|
86
|
+
_add_index_for_field(name, args, **options)
|
80
87
|
field_specs[name] = ::DeclareSchema::Model::FieldSpec.new(self, name, type, position: field_specs.size, **options)
|
81
88
|
attr_order << name unless attr_order.include?(name)
|
82
89
|
end
|
@@ -91,8 +98,8 @@ module DeclareSchema
|
|
91
98
|
|
92
99
|
# Extend belongs_to so that it
|
93
100
|
# 1. creates a FieldSpec for the foreign key
|
94
|
-
# 2. declares an index on the foreign key
|
95
|
-
# 3. declares a foreign_key constraint
|
101
|
+
# 2. declares an index on the foreign key (optional)
|
102
|
+
# 3. declares a foreign_key constraint (optional)
|
96
103
|
def belongs_to(name, scope = nil, **options)
|
97
104
|
column_options = {}
|
98
105
|
|
@@ -104,54 +111,52 @@ module DeclareSchema
|
|
104
111
|
column_options[:default] = options.delete(:default) if options.has_key?(:default)
|
105
112
|
if options.has_key?(:limit)
|
106
113
|
options.delete(:limit)
|
107
|
-
ActiveSupport::Deprecation.warn("belongs_to limit: is deprecated since it is now inferred")
|
114
|
+
ActiveSupport::Deprecation.warn("belongs_to #{name.inspect}, limit: is deprecated since it is now inferred")
|
108
115
|
end
|
109
116
|
|
110
117
|
# index: true means create an index on the foreign key
|
111
118
|
# index: false means do not create an index on the foreign key
|
112
119
|
# index: { ... } means create an index on the foreign key with the given options
|
113
120
|
index_value = options.delete(:index)
|
114
|
-
if index_value
|
115
|
-
|
121
|
+
if index_value == false # don't create an index
|
122
|
+
options.delete(:unique)
|
123
|
+
options.delete(:allow_equivalent)
|
124
|
+
else
|
125
|
+
index_options = {} # create an index
|
116
126
|
case index_value
|
117
|
-
when String
|
118
|
-
|
119
|
-
index_options[:name] = index_value
|
127
|
+
when String, Symbol
|
128
|
+
ActiveSupport::Deprecation.warn("belongs_to #{name.inspect}, index: 'name' is deprecated; use index: { name: 'name' } instead (in #{self.name})")
|
129
|
+
index_options[:name] = index_value.to_s
|
120
130
|
when true
|
121
|
-
when false
|
122
|
-
raise ArgumentError, "belongs_to index: false contradicts others options #{options.inspect} (in #{name})"
|
123
131
|
when nil
|
124
132
|
when Hash
|
125
133
|
index_options = index_value
|
126
134
|
else
|
127
|
-
raise ArgumentError, "belongs_to index: must be true or false or a Hash; got #{index_value.inspect} (in #{name})"
|
135
|
+
raise ArgumentError, "belongs_to #{name.inspect}, index: must be true or false or a Hash; got #{index_value.inspect} (in #{self.name})"
|
128
136
|
end
|
129
137
|
|
130
138
|
if options.has_key?(:unique)
|
131
|
-
|
139
|
+
ActiveSupport::Deprecation.warn("belongs_to #{name.inspect}, unique: true|false is deprecated; use index: { unique: true|false } instead (in #{self.name})")
|
132
140
|
index_options[:unique] = options.delete(:unique)
|
133
141
|
end
|
134
142
|
|
135
143
|
index_options[:allow_equivalent] = options.delete(:allow_equivalent) if options.has_key?(:allow_equivalent)
|
136
144
|
end
|
137
145
|
|
138
|
-
|
139
|
-
fk_options[:constraint_name] = options.delete(:constraint) if options.has_key?(:constraint)
|
140
|
-
fk_options[:index_name] = index_options&.[](:name)
|
146
|
+
constraint_name = options.delete(:constraint)
|
141
147
|
|
142
|
-
|
148
|
+
dependent_delete = :delete if options.delete(:far_end_dependent) == :delete
|
143
149
|
|
150
|
+
# infer :optional from :null
|
144
151
|
if !options.has_key?(:optional)
|
145
|
-
options[:optional] = column_options[:null]
|
152
|
+
options[:optional] = column_options[:null]
|
146
153
|
end
|
147
154
|
|
148
|
-
fk_options[:dependent] = options.delete(:far_end_dependent) if options.has_key?(:far_end_dependent)
|
149
|
-
|
150
155
|
super
|
151
156
|
|
152
157
|
reflection = reflections[name.to_s] or raise "Couldn't find reflection #{name} in #{reflections.keys}"
|
153
|
-
|
154
|
-
|
158
|
+
foreign_key_column = reflection.foreign_key or raise "Couldn't find foreign_key for #{name} in #{reflection.inspect}"
|
159
|
+
foreign_key_column_options = column_options.dup
|
155
160
|
|
156
161
|
# Note: the foreign key limit: should match the primary key limit:. (If there is a foreign key constraint,
|
157
162
|
# those limits _must_ match.) We'd like to call _infer_fk_limit and get the limit right from the PK.
|
@@ -162,27 +167,34 @@ module DeclareSchema
|
|
162
167
|
# The one downside of this approach is that application code that asks the field_spec for the declared
|
163
168
|
# foreign key limit: will always get 8 back even if this is a grandfathered foreign key that points to
|
164
169
|
# a limit: 4 primary key. It seems unlikely that any application code would do this.
|
165
|
-
|
166
|
-
if (inferred_limit = _infer_fk_limit(
|
170
|
+
foreign_key_column_options[:pre_migration] = ->(field_spec) do
|
171
|
+
if (inferred_limit = _infer_fk_limit(foreign_key_column, reflection))
|
167
172
|
field_spec.sql_options[:limit] = inferred_limit
|
168
173
|
end
|
169
174
|
end
|
170
175
|
|
171
|
-
declare_field(
|
176
|
+
declare_field(foreign_key_column.to_sym, :bigint, **foreign_key_column_options)
|
172
177
|
|
173
178
|
if reflection.options[:polymorphic]
|
174
179
|
foreign_type = options[:foreign_type] || "#{name}_type"
|
175
180
|
_declare_polymorphic_type_field(foreign_type, column_options)
|
176
|
-
|
181
|
+
if ::DeclareSchema.default_generate_indexing && index_options
|
182
|
+
index([foreign_type, foreign_key_column], **index_options)
|
183
|
+
end
|
177
184
|
else
|
178
|
-
|
179
|
-
|
185
|
+
if ::DeclareSchema.default_generate_indexing && index_options
|
186
|
+
index([foreign_key_column], **index_options)
|
187
|
+
end
|
188
|
+
|
189
|
+
if ::DeclareSchema.default_generate_foreign_keys && constraint_name != false
|
190
|
+
constraint(foreign_key_column, constraint_name: constraint_name || index_options&.[](:name), parent_class_name: reflection.class_name, dependent: dependent_delete)
|
191
|
+
end
|
180
192
|
end
|
181
193
|
end
|
182
194
|
|
183
|
-
def _infer_fk_limit(
|
195
|
+
def _infer_fk_limit(foreign_key_column, reflection)
|
184
196
|
if reflection.options[:polymorphic]
|
185
|
-
if (foreign_key_column = columns_hash[
|
197
|
+
if (foreign_key_column = columns_hash[foreign_key_column.to_s]) && foreign_key_column.type == :integer
|
186
198
|
foreign_key_column.limit
|
187
199
|
end
|
188
200
|
else
|
@@ -224,7 +236,7 @@ module DeclareSchema
|
|
224
236
|
end
|
225
237
|
|
226
238
|
def _rails_default_primary_key
|
227
|
-
::DeclareSchema::Model::IndexDefinition.new(
|
239
|
+
::DeclareSchema::Model::IndexDefinition.new([_declared_primary_key], name: DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME, table_name: table_name, unique: true)
|
228
240
|
end
|
229
241
|
|
230
242
|
# Declares the "foo_type" field that accompanies the "foo_id"
|
@@ -295,15 +307,16 @@ module DeclareSchema
|
|
295
307
|
end
|
296
308
|
end
|
297
309
|
|
298
|
-
def _add_index_for_field(
|
299
|
-
if (
|
310
|
+
def _add_index_for_field(column_name, args, **options)
|
311
|
+
if (index_name = options.delete(:index))
|
300
312
|
index_opts =
|
301
313
|
{
|
302
314
|
unique: args.include?(:unique) || !!options.delete(:unique)
|
303
315
|
}
|
316
|
+
|
304
317
|
# support index: true declaration
|
305
|
-
index_opts[:name] =
|
306
|
-
index(
|
318
|
+
index_opts[:name] = index_name unless index_name == true
|
319
|
+
index([column_name], **index_opts)
|
307
320
|
end
|
308
321
|
end
|
309
322
|
|