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