declare_schema 1.3.3 → 1.3.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -1
- data/Gemfile.lock +1 -1
- data/lib/declare_schema/model/foreign_key_definition.rb +40 -43
- data/lib/declare_schema/model/habtm_model_shim.rb +23 -26
- data/lib/declare_schema/model/index_definition.rb +30 -28
- data/lib/declare_schema/model.rb +56 -40
- data/lib/declare_schema/version.rb +1 -1
- data/lib/generators/declare_schema/migration/migrator.rb +25 -15
- data/spec/lib/declare_schema/migration_generator_spec.rb +41 -1
- data/spec/lib/declare_schema/model/foreign_key_definition_spec.rb +22 -23
- data/spec/lib/declare_schema/model/habtm_model_shim_spec.rb +74 -59
- data/spec/lib/declare_schema/model/index_definition_spec.rb +40 -33
- 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: 1f5ae7cf20a27eeced3466430509637a9df81ff7456678b129333392bb317985
|
4
|
+
data.tar.gz: cb1b3ddef5eefac4eb1f8bacbe62751900f869ea8577932ab85cd1c2b2cbc59e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 11150b8dc1fd0a21c5b6ea2570a4bc12ce87eb4b48fa9926f67cb61537c26dc1156061b6308f796029bd9d2caeea632b71b0f28198b7a20e0959ccb1c7369f7c
|
7
|
+
data.tar.gz: 30d9e498f22dbb47c2f10f7d107d257ccae6b507bb6a5cd82d17c18e0074723e7551223f75bd4335f5a7453017016774fb1a10739484fdd713fa2aae91175f81
|
data/CHANGELOG.md
CHANGED
@@ -4,7 +4,12 @@ Inspired by [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
4
4
|
|
5
5
|
Note: this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
6
6
|
|
7
|
-
## [1.3.
|
7
|
+
## [1.3.4] - 2024-01-18
|
8
|
+
### Fixed
|
9
|
+
- Add test for migrating `has_and_belongs_to_many` associations and fix them to properly declare their
|
10
|
+
2 foreign keys as the primary key of the join table, rather than just a unique index.
|
11
|
+
|
12
|
+
## [1.3.3] - 2024-01-17
|
8
13
|
### Fixed
|
9
14
|
- Fix a MySQL 8 bug where MySQL 8+ renames charset 'utf8' to 'utf8mb3' and collation 'utf8_general_ci' to
|
10
15
|
'utf8mb3_unicode_ci'.
|
data/Gemfile.lock
CHANGED
@@ -7,46 +7,53 @@ module DeclareSchema
|
|
7
7
|
class ForeignKeyDefinition
|
8
8
|
include Comparable
|
9
9
|
|
10
|
-
attr_reader :
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
@
|
16
|
-
@
|
17
|
-
|
18
|
-
@
|
19
|
-
|
20
|
-
@
|
21
|
-
|
22
|
-
@constraint_name = options[:constraint_name]&.to_s.presence ||
|
23
|
-
model.connection.index_name(model.table_name, column: @foreign_key_name)
|
24
|
-
@on_delete_cascade = options[:dependent] == :delete
|
10
|
+
attr_reader :foreign_key_column, :constraint_name, :child_table_name, :parent_table_name, :dependent
|
11
|
+
|
12
|
+
# Caller needs to pass either constraint_name or child_table. The child_table is remembered, but it is not part of the key;
|
13
|
+
# it is just used to compute the default constraint_name if no constraint_name is given.
|
14
|
+
def initialize(foreign_key_column, constraint_name: nil, child_table: nil, parent_table: nil, class_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, [@foreign_key_column])
|
17
|
+
@child_table_name = child_table&.to_s or raise ArgumentError, "child_table must not be nil"
|
18
|
+
@parent_table_name = parent_table&.to_s || infer_parent_table_name_from_class(class_name) || infer_parent_table_name_from_foreign_key_column(@foreign_key_column)
|
19
|
+
dependent.in?([nil, :delete]) or raise ArgumentError, "dependent: must be nil or :delete"
|
20
|
+
@dependent = dependent
|
25
21
|
end
|
26
22
|
|
27
23
|
class << self
|
28
|
-
def
|
29
|
-
show_create_table =
|
24
|
+
def for_table(table_name, connection, dependent: nil)
|
25
|
+
show_create_table = connection.select_rows("show create table #{connection.quote_table_name(table_name)}").first.last
|
30
26
|
constraints = show_create_table.split("\n").map { |line| line.strip if line['CONSTRAINT'] }.compact
|
31
27
|
|
32
28
|
constraints.map do |fkc|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
foreign_key: foreign_key
|
38
|
-
}
|
39
|
-
options[:dependent] = :delete if fkc['ON DELETE CASCADE'] || model.is_a?(DeclareSchema::Model::HabtmModelShim)
|
40
|
-
|
41
|
-
new(model, foreign_key, **options)
|
29
|
+
constraint_name, foreign_key_column, parent_table = fkc.match(/CONSTRAINT `([^`]*)` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)`/).captures
|
30
|
+
dependent_value = :delete if dependent || fkc['ON DELETE CASCADE']
|
31
|
+
|
32
|
+
new(foreign_key_column, constraint_name: constraint_name, child_table: table_name, parent_table: parent_table, dependent: dependent_value)
|
42
33
|
end
|
43
34
|
end
|
44
35
|
end
|
45
36
|
|
37
|
+
def key
|
38
|
+
@key ||= [@parent_table_name, @foreign_key_column, @dependent].freeze
|
39
|
+
end
|
40
|
+
|
41
|
+
def <=>(rhs)
|
42
|
+
key <=> rhs.key
|
43
|
+
end
|
44
|
+
|
45
|
+
alias eql? ==
|
46
|
+
|
47
|
+
def equivalent?(rhs)
|
48
|
+
self == rhs
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
46
53
|
# returns the parent class as a Class object
|
47
|
-
# or nil if no
|
48
|
-
def parent_class
|
49
|
-
if
|
54
|
+
# or nil if no @class_name option given
|
55
|
+
def parent_class(class_name)
|
56
|
+
if class_name
|
50
57
|
if class_name.is_a?(Class)
|
51
58
|
class_name
|
52
59
|
else
|
@@ -55,22 +62,12 @@ module DeclareSchema
|
|
55
62
|
end
|
56
63
|
end
|
57
64
|
|
58
|
-
def
|
59
|
-
|
60
|
-
parent_class&.try(:table_name) ||
|
61
|
-
foreign_key.sub(/_id\z/, '').camelize.constantize.table_name
|
62
|
-
end
|
63
|
-
|
64
|
-
def <=>(rhs)
|
65
|
-
key <=> rhs.send(:key)
|
65
|
+
def infer_parent_table_name_from_class(class_name)
|
66
|
+
parent_class(class_name)&.try(:table_name)
|
66
67
|
end
|
67
68
|
|
68
|
-
|
69
|
-
|
70
|
-
private
|
71
|
-
|
72
|
-
def key
|
73
|
-
@key ||= [@child_table_name, parent_table_name, @foreign_key_name, @on_delete_cascade].map(&:to_s)
|
69
|
+
def infer_parent_table_name_from_foreign_key_column(foreign_key_column)
|
70
|
+
foreign_key_column.sub(/_id\z/, '').camelize.constantize.table_name
|
74
71
|
end
|
75
72
|
|
76
73
|
def hash
|
@@ -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
|
|
@@ -48,17 +41,21 @@ module DeclareSchema
|
|
48
41
|
end
|
49
42
|
|
50
43
|
def _declared_primary_key
|
51
|
-
|
44
|
+
foreign_keys
|
52
45
|
end
|
53
46
|
|
54
|
-
def
|
47
|
+
def index_definitions
|
55
48
|
[
|
56
|
-
IndexDefinition.new(
|
57
|
-
IndexDefinition.new(self, foreign_keys.last) # not unique by itself; combines with primary key to be unique
|
49
|
+
IndexDefinition.new(foreign_keys.last, table_name: table_name, unique: false) # index for queries where we only have the last foreign key
|
58
50
|
]
|
59
51
|
end
|
60
52
|
|
61
|
-
|
53
|
+
def index_definitions_with_primary_key
|
54
|
+
[
|
55
|
+
*index_definitions,
|
56
|
+
IndexDefinition.new(foreign_keys, name: Model::IndexDefinition::PRIMARY_KEY_NAME, unique: true) # creates a primary composite key on both foreign keys
|
57
|
+
]
|
58
|
+
end
|
62
59
|
|
63
60
|
def ignore_indexes
|
64
61
|
[]
|
@@ -66,8 +63,8 @@ module DeclareSchema
|
|
66
63
|
|
67
64
|
def constraint_specs
|
68
65
|
[
|
69
|
-
ForeignKeyDefinition.new(
|
70
|
-
ForeignKeyDefinition.new(
|
66
|
+
ForeignKeyDefinition.new(foreign_keys.first, child_table: @join_table, parent_table: parent_table_names.first, constraint_name: "#{join_table}_FK1", dependent: :delete),
|
67
|
+
ForeignKeyDefinition.new(foreign_keys.last, child_table: @join_table, parent_table: parent_table_names.last, constraint_name: "#{join_table}_FK2", dependent: :delete)
|
71
68
|
]
|
72
69
|
end
|
73
70
|
end
|
@@ -7,27 +7,30 @@ module DeclareSchema
|
|
7
7
|
class IndexDefinition
|
8
8
|
include Comparable
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
alias columns fields
|
10
|
+
attr_reader :columns, :explicit_name, :name, :unique, :where
|
11
|
+
alias fields columns # TODO: change callers to use columns. -Colin
|
13
12
|
|
14
13
|
class IndexNameTooLongError < RuntimeError; end
|
15
14
|
|
16
15
|
PRIMARY_KEY_NAME = "PRIMARY"
|
17
16
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
@
|
22
|
-
@
|
23
|
-
@
|
24
|
-
|
17
|
+
# Caller needs to pass either name or table_name. The table_name is not remembered; it is just used to compute the
|
18
|
+
# default name if no name is given.
|
19
|
+
def initialize(columns, name: nil, table_name: nil, allow_equivalent: false, unique: false, where: nil)
|
20
|
+
@name = name || self.class.default_index_name(table_name, columns)
|
21
|
+
@columns = Array.wrap(columns).map(&:to_s)
|
22
|
+
@explicit_name = @name unless allow_equivalent
|
23
|
+
unique.in?([false, true]) or raise ArgumentError, "unique must be true or false: got #{unique.inspect}"
|
24
|
+
if @name == PRIMARY_KEY_NAME
|
25
|
+
unique or raise ArgumentError, "primary key index must be unique"
|
26
|
+
end
|
27
|
+
@unique = unique
|
25
28
|
|
26
29
|
if DeclareSchema.max_index_and_constraint_name_length && @name.length > DeclareSchema.max_index_and_constraint_name_length
|
27
30
|
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."
|
28
31
|
end
|
29
32
|
|
30
|
-
if
|
33
|
+
if where
|
31
34
|
@where = where.start_with?('(') ? where : "(#{where})"
|
32
35
|
end
|
33
36
|
end
|
@@ -35,33 +38,32 @@ module DeclareSchema
|
|
35
38
|
class << self
|
36
39
|
# extract IndexSpecs from an existing table
|
37
40
|
# includes the PRIMARY KEY index
|
38
|
-
def
|
39
|
-
|
40
|
-
|
41
|
-
primary_key_columns = Array(model.connection.primary_key(t)).presence
|
42
|
-
primary_key_columns or raise "could not find primary key for table #{t} in #{model.connection.columns(t).inspect}"
|
41
|
+
def for_table(table_name, ignore_indexes, connection)
|
42
|
+
primary_key_columns = Array(connection.primary_key(table_name))
|
43
|
+
primary_key_columns.present? or raise "could not find primary key for table #{table_name} in #{connection.columns(table_name).inspect}"
|
43
44
|
|
44
45
|
primary_key_found = false
|
45
|
-
index_definitions =
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
46
|
+
index_definitions = connection.indexes(table_name).map do |index|
|
47
|
+
next if ignore_indexes.include?(index.name)
|
48
|
+
|
49
|
+
if index.name == PRIMARY_KEY_NAME
|
50
|
+
index.columns == primary_key_columns && index.unique or
|
51
|
+
raise "primary key on #{table_name} was not unique on #{primary_key_columns} (was unique=#{index.unique} on #{index.columns})"
|
50
52
|
primary_key_found = true
|
51
53
|
end
|
52
|
-
new(
|
54
|
+
new(index.columns, name: index.name, unique: index.unique, where: index.where)
|
53
55
|
end.compact
|
54
56
|
|
55
57
|
if !primary_key_found
|
56
|
-
index_definitions << new(
|
58
|
+
index_definitions << new(primary_key_columns, name: PRIMARY_KEY_NAME, unique: true)
|
57
59
|
end
|
58
60
|
index_definitions
|
59
61
|
end
|
60
62
|
|
61
|
-
def default_index_name(
|
63
|
+
def default_index_name(table_name, columns)
|
62
64
|
index_name = nil
|
63
65
|
[:long_index_name, :short_index_name].find do |method_name|
|
64
|
-
index_name = send(method_name,
|
66
|
+
index_name = send(method_name, table_name, columns)
|
65
67
|
if DeclareSchema.max_index_and_constraint_name_length.nil? || index_name.length <= DeclareSchema.max_index_and_constraint_name_length
|
66
68
|
break index_name
|
67
69
|
end
|
@@ -103,11 +105,11 @@ module DeclareSchema
|
|
103
105
|
end
|
104
106
|
|
105
107
|
def to_key
|
106
|
-
@
|
108
|
+
@to_key ||= [name, *settings].freeze
|
107
109
|
end
|
108
110
|
|
109
111
|
def settings
|
110
|
-
@settings ||= [
|
112
|
+
@settings ||= [columns, unique, where].freeze
|
111
113
|
end
|
112
114
|
|
113
115
|
def hash
|
@@ -123,7 +125,7 @@ module DeclareSchema
|
|
123
125
|
end
|
124
126
|
|
125
127
|
def with_name(new_name)
|
126
|
-
self.class.new(@
|
128
|
+
self.class.new(@columns, name: new_name, unique: @unique, allow_equivalent: @explicit_name.nil?, where: @where)
|
127
129
|
end
|
128
130
|
|
129
131
|
alias eql? ==
|
data/lib/declare_schema/model.rb
CHANGED
@@ -49,22 +49,34 @@ module DeclareSchema
|
|
49
49
|
end
|
50
50
|
|
51
51
|
module ClassMethods
|
52
|
-
def index(
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
52
|
+
def index(columns, name: nil, allow_equivalent: false, unique: false, where: nil)
|
53
|
+
index_definition =
|
54
|
+
::DeclareSchema::Model::IndexDefinition.new(
|
55
|
+
columns,
|
56
|
+
name: name, table_name: table_name, allow_equivalent: allow_equivalent, unique: unique, where: where
|
57
|
+
)
|
58
|
+
|
59
|
+
# add idempotently
|
60
|
+
unless index_definitions.any? { |index_def| index_def.equivalent?(index_definition) }
|
61
|
+
index_definitions << index_definition
|
57
62
|
end
|
58
63
|
end
|
59
64
|
|
60
|
-
def primary_key_index(*
|
61
|
-
index(
|
65
|
+
def primary_key_index(*columns)
|
66
|
+
index(columns.flatten, unique: true, name: ::DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME)
|
62
67
|
end
|
63
68
|
|
64
|
-
def constraint(
|
65
|
-
|
66
|
-
|
67
|
-
|
69
|
+
def constraint(foreign_key_column, parent_table: nil, constraint_name: nil, class_name: nil, dependent: nil)
|
70
|
+
foreign_key_column = foreign_key_column.to_s
|
71
|
+
constraint_definition =
|
72
|
+
::DeclareSchema::Model::ForeignKeyDefinition.new(
|
73
|
+
foreign_key_column,
|
74
|
+
constraint_name: constraint_name,
|
75
|
+
child_table: table_name, parent_table: parent_table, class_name: class_name, dependent: dependent
|
76
|
+
)
|
77
|
+
|
78
|
+
unless constraint_specs.any? { |constraint_def| constraint_def.equivalent?(constraint_definition) }
|
79
|
+
constraint_specs << constraint_definition
|
68
80
|
end
|
69
81
|
end
|
70
82
|
|
@@ -98,8 +110,8 @@ module DeclareSchema
|
|
98
110
|
|
99
111
|
# Extend belongs_to so that it
|
100
112
|
# 1. creates a FieldSpec for the foreign key
|
101
|
-
# 2. declares an index on the foreign key
|
102
|
-
# 3. declares a foreign_key constraint
|
113
|
+
# 2. declares an index on the foreign key (optional)
|
114
|
+
# 3. declares a foreign_key constraint (optional)
|
103
115
|
def belongs_to(name, scope = nil, **options)
|
104
116
|
column_options = {}
|
105
117
|
|
@@ -119,23 +131,21 @@ module DeclareSchema
|
|
119
131
|
index_options[:unique] = options.delete(:unique) if options.has_key?(:unique)
|
120
132
|
index_options[:allow_equivalent] = options.delete(:allow_equivalent) if options.has_key?(:allow_equivalent)
|
121
133
|
|
122
|
-
|
123
|
-
|
124
|
-
fk_options[:index_name] = index_options[:name]
|
134
|
+
constraint_name = options.delete(:constraint)
|
135
|
+
index_name = index_options[:name]
|
125
136
|
|
126
|
-
|
137
|
+
dependent_delete = :delete if options.delete(:far_end_dependent) == :delete
|
127
138
|
|
139
|
+
# infer :optional from :null
|
128
140
|
if !options.has_key?(:optional)
|
129
|
-
options[:optional] = column_options[:null]
|
141
|
+
options[:optional] = column_options[:null]
|
130
142
|
end
|
131
143
|
|
132
|
-
fk_options[:dependent] = options.delete(:far_end_dependent) if options.has_key?(:far_end_dependent)
|
133
|
-
|
134
144
|
super
|
135
145
|
|
136
146
|
refl = reflections[name.to_s] or raise "Couldn't find reflection #{name} in #{reflections.keys}"
|
137
|
-
|
138
|
-
|
147
|
+
foreign_key_column = refl.foreign_key or raise "Couldn't find foreign_key for #{name} in #{refl.inspect}"
|
148
|
+
foreign_key_column_options = column_options.dup
|
139
149
|
|
140
150
|
# Note: the foreign key limit: should match the primary key limit:. (If there is a foreign key constraint,
|
141
151
|
# those limits _must_ match.) We'd like to call _infer_fk_limit and get the limit right from the PK.
|
@@ -146,27 +156,34 @@ module DeclareSchema
|
|
146
156
|
# The one downside of this approach is that application code that asks the field_spec for the declared
|
147
157
|
# foreign key limit: will always get 8 back even if this is a grandfathered foreign key that points to
|
148
158
|
# a limit: 4 primary key. It seems unlikely that any application code would do this.
|
149
|
-
|
150
|
-
if (inferred_limit = _infer_fk_limit(
|
159
|
+
foreign_key_column_options[:pre_migration] = ->(field_spec) do
|
160
|
+
if (inferred_limit = _infer_fk_limit(foreign_key_column, refl))
|
151
161
|
field_spec.sql_options[:limit] = inferred_limit
|
152
162
|
end
|
153
163
|
end
|
154
164
|
|
155
|
-
declare_field(
|
165
|
+
declare_field(foreign_key_column.to_sym, :bigint, **foreign_key_column_options)
|
156
166
|
|
157
167
|
if refl.options[:polymorphic]
|
158
168
|
foreign_type = options[:foreign_type] || "#{name}_type"
|
159
169
|
_declare_polymorphic_type_field(foreign_type, column_options)
|
160
|
-
|
170
|
+
if ::DeclareSchema.default_generate_indexing && index_name != false
|
171
|
+
index([foreign_type, foreign_key_column], **index_options)
|
172
|
+
end
|
161
173
|
else
|
162
|
-
|
163
|
-
|
174
|
+
if ::DeclareSchema.default_generate_indexing && index_name != false
|
175
|
+
index(foreign_key_column, **index_options)
|
176
|
+
end
|
177
|
+
|
178
|
+
if ::DeclareSchema.default_generate_foreign_keys && constraint_name != false
|
179
|
+
constraint(foreign_key_column, parent_table: nil, constraint_name: constraint_name || index_name, class_name: refl.klass, dependent: dependent_delete)
|
180
|
+
end
|
164
181
|
end
|
165
182
|
end
|
166
183
|
|
167
|
-
def _infer_fk_limit(
|
184
|
+
def _infer_fk_limit(foreign_key_column, refl)
|
168
185
|
if refl.options[:polymorphic]
|
169
|
-
if (fkey_column = columns_hash[
|
186
|
+
if (fkey_column = columns_hash[foreign_key_column.to_s]) && fkey_column.type == :integer
|
170
187
|
fkey_column.limit
|
171
188
|
end
|
172
189
|
else
|
@@ -208,7 +225,7 @@ module DeclareSchema
|
|
208
225
|
end
|
209
226
|
|
210
227
|
def _rails_default_primary_key
|
211
|
-
::DeclareSchema::Model::IndexDefinition.new(
|
228
|
+
::DeclareSchema::Model::IndexDefinition.new([_declared_primary_key], name: DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME, unique: true)
|
212
229
|
end
|
213
230
|
|
214
231
|
# Declares the "foo_type" field that accompanies the "foo_id"
|
@@ -279,15 +296,14 @@ module DeclareSchema
|
|
279
296
|
end
|
280
297
|
end
|
281
298
|
|
282
|
-
def _add_index_for_field(
|
283
|
-
if (
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
index(name, **index_opts)
|
299
|
+
def _add_index_for_field(column_name, args, options)
|
300
|
+
if (index_name = options.delete(:index))
|
301
|
+
if index_name == true
|
302
|
+
index_name = nil # index: true means generate default name
|
303
|
+
end
|
304
|
+
unique = args.include?(:unique) || options.delete(:unique) || false
|
305
|
+
|
306
|
+
index([column_name], unique: unique, name: index_name)
|
291
307
|
end
|
292
308
|
end
|
293
309
|
|
@@ -3,6 +3,7 @@
|
|
3
3
|
require 'active_record'
|
4
4
|
require 'active_record/connection_adapters/abstract_adapter'
|
5
5
|
require 'declare_schema/schema_change/all'
|
6
|
+
require_relative '../../../declare_schema/model/habtm_model_shim'
|
6
7
|
|
7
8
|
module Generators
|
8
9
|
module DeclareSchema
|
@@ -221,8 +222,8 @@ module Generators
|
|
221
222
|
::DeclareSchema::SchemaChange::TableRemove.new(t, add_table_back(t))
|
222
223
|
end
|
223
224
|
|
224
|
-
creates = to_create.map do |
|
225
|
-
model = models_by_table_name[
|
225
|
+
creates = to_create.map do |table_name|
|
226
|
+
model = models_by_table_name[table_name]
|
226
227
|
disable_auto_increment = model.try(:disable_auto_increment)
|
227
228
|
|
228
229
|
primary_key_definition =
|
@@ -239,10 +240,13 @@ module Generators
|
|
239
240
|
table_options_definition = ::DeclareSchema::Model::TableOptionsDefinition.new(model.table_name, **table_options_for_model(model))
|
240
241
|
table_options = create_table_options(model, disable_auto_increment)
|
241
242
|
|
242
|
-
table_add = ::DeclareSchema::SchemaChange::TableAdd.new(
|
243
|
-
|
244
|
-
|
245
|
-
|
243
|
+
table_add = ::DeclareSchema::SchemaChange::TableAdd.new(
|
244
|
+
table_name,
|
245
|
+
primary_key_definition + field_definitions,
|
246
|
+
table_options,
|
247
|
+
sql_options: table_options_definition.settings
|
248
|
+
)
|
249
|
+
|
246
250
|
[
|
247
251
|
table_add,
|
248
252
|
*Array((create_indexes(model) if ::DeclareSchema.default_generate_indexing)),
|
@@ -255,9 +259,9 @@ module Generators
|
|
255
259
|
fk_changes = []
|
256
260
|
table_options_changes = []
|
257
261
|
|
258
|
-
to_change.each do |
|
259
|
-
model = models_by_table_name[
|
260
|
-
table = to_rename.key(
|
262
|
+
to_change.each do |table_name|
|
263
|
+
model = models_by_table_name[table_name]
|
264
|
+
table = to_rename.key(table_name) || model.table_name
|
261
265
|
if table.in?(db_tables)
|
262
266
|
change, index_change, fk_change, table_options_change = change_table(model, table)
|
263
267
|
changes << change
|
@@ -311,6 +315,8 @@ module Generators
|
|
311
315
|
{ id: false }
|
312
316
|
elsif primary_key == "id"
|
313
317
|
{ id: :bigint }
|
318
|
+
elsif primary_key.is_a?(Array)
|
319
|
+
{ primary_key: primary_key.map(&:to_sym) }
|
314
320
|
else
|
315
321
|
{ primary_key: primary_key.to_sym }
|
316
322
|
end.merge(model._table_options)
|
@@ -337,7 +343,7 @@ module Generators
|
|
337
343
|
def create_constraints(model)
|
338
344
|
model.constraint_specs.map do |fk|
|
339
345
|
::DeclareSchema::SchemaChange::ForeignKeyAdd.new(fk.child_table_name, fk.parent_table_name,
|
340
|
-
column_name: fk.
|
346
|
+
column_name: fk.foreign_key_column, name: fk.constraint_name)
|
341
347
|
end
|
342
348
|
end
|
343
349
|
|
@@ -425,7 +431,7 @@ module Generators
|
|
425
431
|
::DeclareSchema.default_generate_indexing or return []
|
426
432
|
|
427
433
|
new_table_name = model.table_name
|
428
|
-
existing_indexes = ::DeclareSchema::Model::IndexDefinition.
|
434
|
+
existing_indexes = ::DeclareSchema::Model::IndexDefinition.for_table(old_table_name || new_table_name, model.ignore_indexes, model.connection)
|
429
435
|
model_indexes_with_equivalents = model.index_definitions_with_primary_key
|
430
436
|
model_indexes = model_indexes_with_equivalents.map do |i|
|
431
437
|
if i.explicit_name.nil?
|
@@ -485,7 +491,11 @@ module Generators
|
|
485
491
|
ActiveRecord::Base.connection.class.name.match?(/SQLite3Adapter/) and raise ArgumentError, 'SQLite does not support foreign keys'
|
486
492
|
::DeclareSchema.default_generate_foreign_keys or return []
|
487
493
|
|
488
|
-
|
494
|
+
if model.is_a?(::DeclareSchema::Model::HabtmModelShim)
|
495
|
+
force_dependent_delete = :delete
|
496
|
+
end
|
497
|
+
|
498
|
+
existing_fks = ::DeclareSchema::Model::ForeignKeyDefinition.for_table(old_table_name || model.table_name, model.connection, dependent: force_dependent_delete)
|
489
499
|
model_fks = model.constraint_specs
|
490
500
|
|
491
501
|
fks_to_drop = existing_fks - model_fks
|
@@ -495,13 +505,13 @@ module Generators
|
|
495
505
|
|
496
506
|
drop_fks = (fks_to_drop - renamed_fks_to_drop).map do |fk|
|
497
507
|
::DeclareSchema::SchemaChange::ForeignKeyRemove.new(fk.child_table_name, fk.parent_table_name,
|
498
|
-
column_name: fk.
|
508
|
+
column_name: fk.foreign_key_column, name: fk.constraint_name)
|
499
509
|
end
|
500
510
|
|
501
511
|
add_fks = (fks_to_add - renamed_fks_to_add).map do |fk|
|
502
512
|
# next if fk.parent.constantize.abstract_class || fk.parent == fk.model.class_name
|
503
513
|
::DeclareSchema::SchemaChange::ForeignKeyAdd.new(fk.child_table_name, fk.parent_table_name,
|
504
|
-
column_name: fk.
|
514
|
+
column_name: fk.foreign_key_column, name: fk.constraint_name)
|
505
515
|
end
|
506
516
|
|
507
517
|
[drop_fks + add_fks]
|
@@ -523,7 +533,7 @@ module Generators
|
|
523
533
|
end
|
524
534
|
|
525
535
|
def fk_field_options(model, field_name)
|
526
|
-
foreign_key = model.constraint_specs.find { |fk| field_name == fk.
|
536
|
+
foreign_key = model.constraint_specs.find { |fk| field_name == fk.foreign_key_column }
|
527
537
|
if foreign_key && (parent_table = foreign_key.parent_table_name)
|
528
538
|
parent_columns = connection.columns(parent_table) rescue []
|
529
539
|
pk_limit =
|
@@ -809,6 +809,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
809
809
|
expect(User.field_specs.keys).to eq(['company'])
|
810
810
|
expect(User.field_specs['company'].options[:ruby_default]&.call).to eq("BigCorp")
|
811
811
|
|
812
|
+
nuke_model_class(User)
|
813
|
+
|
812
814
|
## validates
|
813
815
|
|
814
816
|
# DeclareSchema can accept a validates hash in the field options.
|
@@ -829,6 +831,44 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
829
831
|
up, _down = Generators::DeclareSchema::Migration::Migrator.run
|
830
832
|
ActiveRecord::Migration.class_eval(up)
|
831
833
|
expect(Ad.field_specs['company'].options[:validates].inspect).to eq("{:presence=>true, :uniqueness=>{:case_sensitive=>false}}")
|
834
|
+
|
835
|
+
# DeclareSchema supports has_and_belongs_to_many relationships and generates the intersection ("join") table
|
836
|
+
# with appropriate primary key, indexes, and foreign keys.
|
837
|
+
|
838
|
+
class Advertiser < ActiveRecord::Base
|
839
|
+
declare_schema do
|
840
|
+
string :name, limit: 250
|
841
|
+
end
|
842
|
+
has_and_belongs_to_many :creatives
|
843
|
+
end
|
844
|
+
class Creative < ActiveRecord::Base
|
845
|
+
declare_schema do
|
846
|
+
string :url, limit: 500
|
847
|
+
end
|
848
|
+
has_and_belongs_to_many :advertisers
|
849
|
+
end
|
850
|
+
|
851
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
852
|
+
migrate_up(<<~EOS.strip)
|
853
|
+
create_table :advertisers, id: :bigint#{create_table_charset_and_collation} do |t|
|
854
|
+
t.string :name, limit: 250, null: false#{charset_and_collation}
|
855
|
+
end
|
856
|
+
create_table :advertisers_creatives, primary_key: [:advertiser_id, :creative_id]#{create_table_charset_and_collation} do |t|
|
857
|
+
t.integer :advertiser_id, limit: 8, null: false
|
858
|
+
t.integer :creative_id, limit: 8, null: false
|
859
|
+
end
|
860
|
+
create_table :creatives, id: :bigint#{create_table_charset_and_collation} do |t|
|
861
|
+
t.string :url, limit: 500, null: false#{charset_and_collation}
|
862
|
+
end
|
863
|
+
add_index :advertisers_creatives, [:creative_id], name: :index_advertisers_creatives_on_creative_id
|
864
|
+
add_foreign_key :advertisers_creatives, :advertisers, column: :advertiser_id, name: :advertisers_creatives_FK1
|
865
|
+
add_foreign_key :advertisers_creatives, :creatives, column: :creative_id, name: :advertisers_creatives_FK2
|
866
|
+
EOS
|
867
|
+
)
|
868
|
+
|
869
|
+
nuke_model_class(Ad)
|
870
|
+
nuke_model_class(Advertiser)
|
871
|
+
nuke_model_class(Creative)
|
832
872
|
end
|
833
873
|
|
834
874
|
context 'models with the same parent foreign key relation' do
|
@@ -852,7 +892,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
852
892
|
end
|
853
893
|
end
|
854
894
|
|
855
|
-
it 'will
|
895
|
+
it 'will generate unique constraint names' do
|
856
896
|
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
857
897
|
migrate_up(<<~EOS.strip)
|
858
898
|
create_table :categories, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
|
@@ -21,9 +21,9 @@ RSpec.describe DeclareSchema::Model::ForeignKeyDefinition do
|
|
21
21
|
describe 'instance methods' do
|
22
22
|
let(:connection) { instance_double(ActiveRecord::Base.connection.class) }
|
23
23
|
let(:model) { instance_double('Model', table_name: 'models', connection: connection) }
|
24
|
-
let(:
|
25
|
-
let(:options) { {} }
|
26
|
-
subject { described_class.new(
|
24
|
+
let(:foreign_key_column) { :network_id }
|
25
|
+
let(:options) { { child_table: 'networks' } }
|
26
|
+
subject { described_class.new(foreign_key_column, **options)}
|
27
27
|
|
28
28
|
before do
|
29
29
|
allow(model.connection).to receive(:index_name).with(any_args) { 'index_on_network_id' }
|
@@ -31,45 +31,44 @@ RSpec.describe DeclareSchema::Model::ForeignKeyDefinition do
|
|
31
31
|
|
32
32
|
describe '#initialize' do
|
33
33
|
it 'normalizes symbols to strings' do
|
34
|
-
expect(subject.
|
34
|
+
expect(subject.foreign_key_column).to eq('network_id')
|
35
35
|
expect(subject.parent_table_name).to eq('networks')
|
36
36
|
end
|
37
37
|
|
38
38
|
context 'when most options passed' do
|
39
|
-
let(:options) { {
|
39
|
+
let(:options) { { child_table: 'networks', parent_table: :networks } }
|
40
40
|
|
41
41
|
it 'normalizes symbols to strings' do
|
42
|
-
expect(subject.
|
43
|
-
expect(subject.foreign_key_name).to eq('the_network_id')
|
42
|
+
expect(subject.foreign_key_column).to eq('network_id')
|
44
43
|
expect(subject.parent_table_name).to eq('networks')
|
45
|
-
expect(subject.
|
46
|
-
expect(subject.constraint_name).to eq('
|
47
|
-
expect(subject.
|
44
|
+
expect(subject.foreign_key_column).to eq('network_id')
|
45
|
+
expect(subject.constraint_name).to eq('index_networks_on_network_id')
|
46
|
+
expect(subject.dependent).to be_nil
|
48
47
|
end
|
49
48
|
end
|
50
49
|
|
51
50
|
context 'when all options passed' do
|
52
|
-
let(:options) { {
|
51
|
+
let(:options) { { child_table: 'networks', parent_table: :networks, constraint_name: :constraint_1, dependent: :delete } }
|
53
52
|
|
54
53
|
it 'normalizes symbols to strings' do
|
55
|
-
expect(subject.
|
56
|
-
expect(subject.foreign_key_name).to eq('the_network_id')
|
54
|
+
expect(subject.foreign_key_column).to eq('network_id')
|
57
55
|
expect(subject.parent_table_name).to eq('networks')
|
58
56
|
expect(subject.constraint_name).to eq('constraint_1')
|
59
|
-
expect(subject.
|
57
|
+
expect(subject.dependent).to eq(:delete)
|
60
58
|
end
|
61
59
|
end
|
62
60
|
|
63
61
|
context 'when constraint name passed as empty string' do
|
64
|
-
let(:options) { { constraint_name: "" } }
|
62
|
+
let(:options) { { child_table: 'networks', constraint_name: "" } }
|
63
|
+
|
65
64
|
it 'defaults to rails constraint name' do
|
66
|
-
expect(subject.constraint_name).to eq("
|
65
|
+
expect(subject.constraint_name).to eq("index_networks_on_network_id")
|
67
66
|
end
|
68
67
|
end
|
69
68
|
|
70
69
|
context 'when no constraint name passed' do
|
71
70
|
it 'defaults to rails constraint name' do
|
72
|
-
expect(subject.constraint_name).to eq("
|
71
|
+
expect(subject.constraint_name).to eq("index_networks_on_network_id")
|
73
72
|
end
|
74
73
|
end
|
75
74
|
end
|
@@ -85,13 +84,13 @@ RSpec.describe DeclareSchema::Model::ForeignKeyDefinition do
|
|
85
84
|
allow(connection).to receive(:index_name).with('models', column: 'network_id') { }
|
86
85
|
end
|
87
86
|
|
88
|
-
describe '.
|
89
|
-
subject { described_class.
|
87
|
+
describe '.for_table' do
|
88
|
+
subject { described_class.for_table(old_table_name, model.connection) }
|
90
89
|
|
91
|
-
it 'returns
|
92
|
-
expect(subject.
|
93
|
-
|
94
|
-
|
90
|
+
it 'returns definitions' do
|
91
|
+
expect(subject.map(&:key)).to eq([
|
92
|
+
["networks", "network_id", nil]
|
93
|
+
])
|
95
94
|
end
|
96
95
|
end
|
97
96
|
end
|
@@ -8,19 +8,19 @@ end
|
|
8
8
|
require_relative '../../../../lib/declare_schema/model/habtm_model_shim'
|
9
9
|
|
10
10
|
RSpec.describe DeclareSchema::Model::HabtmModelShim do
|
11
|
-
let(:join_table) { "
|
12
|
-
let(:foreign_keys) { ["
|
13
|
-
let(:
|
11
|
+
let(:join_table) { "customers_users" }
|
12
|
+
let(:foreign_keys) { ["user_id", "customer_id"] }
|
13
|
+
let(:parent_table_names) { ["users", "customers"] }
|
14
14
|
|
15
15
|
before do
|
16
16
|
load File.expand_path('../prepare_testapp.rb', __dir__)
|
17
17
|
|
18
|
-
class
|
19
|
-
self.table_name = "
|
18
|
+
class User < ActiveRecord::Base
|
19
|
+
self.table_name = "users"
|
20
20
|
end
|
21
21
|
|
22
|
-
class
|
23
|
-
self.table_name = "
|
22
|
+
class Customer < ActiveRecord::Base
|
23
|
+
self.table_name = "customers"
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
@@ -29,12 +29,14 @@ RSpec.describe DeclareSchema::Model::HabtmModelShim do
|
|
29
29
|
let(:reflection) { double("reflection", join_table: join_table,
|
30
30
|
foreign_key: foreign_keys.first,
|
31
31
|
association_foreign_key: foreign_keys.last,
|
32
|
-
active_record:
|
33
|
-
class_name: '
|
32
|
+
active_record: User,
|
33
|
+
class_name: 'Customer') }
|
34
34
|
it 'returns a new object' do
|
35
35
|
result = described_class.from_reflection(reflection)
|
36
36
|
|
37
37
|
expect(result).to be_a(described_class)
|
38
|
+
expect(result.foreign_keys).to eq(foreign_keys.reverse)
|
39
|
+
expect(result.parent_table_names).to eq(parent_table_names.reverse)
|
38
40
|
end
|
39
41
|
end
|
40
42
|
end
|
@@ -42,14 +44,12 @@ RSpec.describe DeclareSchema::Model::HabtmModelShim do
|
|
42
44
|
describe 'instance methods' do
|
43
45
|
let(:connection) { instance_double(ActiveRecord::Base.connection.class, "connection") }
|
44
46
|
|
45
|
-
subject { described_class.new(join_table, foreign_keys,
|
47
|
+
subject { described_class.new(join_table, foreign_keys, parent_table_names) }
|
46
48
|
|
47
49
|
describe '#initialize' do
|
48
50
|
it 'stores initialization attributes' do
|
49
51
|
expect(subject.join_table).to eq(join_table)
|
50
|
-
expect(subject.foreign_keys).to eq(foreign_keys)
|
51
|
-
expect(subject.foreign_key_classes).to be(foreign_key_classes)
|
52
|
-
expect(subject.connection).to be(connection)
|
52
|
+
expect(subject.foreign_keys).to eq(foreign_keys.reverse)
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
@@ -67,51 +67,52 @@ RSpec.describe DeclareSchema::Model::HabtmModelShim do
|
|
67
67
|
|
68
68
|
describe '#field_specs' do
|
69
69
|
it 'returns 2 field specs' do
|
70
|
-
|
71
|
-
expect(
|
72
|
-
|
73
|
-
expect(
|
74
|
-
expect(
|
75
|
-
expect(
|
76
|
-
expect(
|
77
|
-
expect(
|
78
|
-
|
79
|
-
expect(
|
80
|
-
expect(
|
81
|
-
expect(
|
82
|
-
expect(
|
83
|
-
expect(
|
70
|
+
field_specs = subject.field_specs
|
71
|
+
expect(field_specs.size).to eq(2), field_specs.inspect
|
72
|
+
|
73
|
+
expect(field_specs[foreign_keys.first]).to be_a(::DeclareSchema::Model::FieldSpec)
|
74
|
+
expect(field_specs[foreign_keys.first].model).to eq(subject)
|
75
|
+
expect(field_specs[foreign_keys.first].name.to_s).to eq(foreign_keys.first)
|
76
|
+
expect(field_specs[foreign_keys.first].type).to eq(:integer)
|
77
|
+
expect(field_specs[foreign_keys.first].position).to eq(1)
|
78
|
+
|
79
|
+
expect(field_specs[foreign_keys.last]).to be_a(::DeclareSchema::Model::FieldSpec)
|
80
|
+
expect(field_specs[foreign_keys.last].model).to eq(subject)
|
81
|
+
expect(field_specs[foreign_keys.last].name.to_s).to eq(foreign_keys.last)
|
82
|
+
expect(field_specs[foreign_keys.last].type).to eq(:integer)
|
83
|
+
expect(field_specs[foreign_keys.last].position).to eq(0)
|
84
84
|
end
|
85
85
|
end
|
86
86
|
|
87
87
|
describe '#primary_key' do
|
88
|
-
it 'returns false' do
|
89
|
-
expect(subject.
|
88
|
+
it 'returns false because there is no single-column PK for ActiveRecord to use' do
|
89
|
+
expect(subject.primary_key).to eq(false)
|
90
90
|
end
|
91
91
|
end
|
92
92
|
|
93
93
|
describe '#_declared_primary_key' do
|
94
|
-
it 'returns
|
95
|
-
expect(subject._declared_primary_key).to eq(
|
94
|
+
it 'returns the foreign key pair that are used as the primary key in the database' do
|
95
|
+
expect(subject._declared_primary_key).to eq(["customer_id", "user_id"])
|
96
96
|
end
|
97
97
|
end
|
98
98
|
|
99
99
|
describe '#index_definitions_with_primary_key' do
|
100
100
|
it 'returns 2 index definitions' do
|
101
|
-
|
102
|
-
expect(
|
101
|
+
index_definitions = subject.index_definitions_with_primary_key
|
102
|
+
expect(index_definitions.size).to eq(2), index_definitions.inspect
|
103
103
|
|
104
|
-
expect(
|
105
|
-
expect(
|
106
|
-
expect(
|
107
|
-
expect(
|
104
|
+
expect(index_definitions.last).to be_a(::DeclareSchema::Model::IndexDefinition)
|
105
|
+
expect(index_definitions.last.name).to eq('PRIMARY')
|
106
|
+
expect(index_definitions.last.fields).to eq(foreign_keys.reverse)
|
107
|
+
expect(index_definitions.last.unique).to be_truthy
|
108
108
|
end
|
109
109
|
end
|
110
110
|
|
111
111
|
context 'when table and foreign key names are long' do
|
112
112
|
let(:join_table) { "advertiser_campaigns_tracking_pixels" }
|
113
|
-
let(:
|
114
|
-
let(:
|
113
|
+
let(:foreign_keys_and_table_names) { [["advertiser_id", "advertisers"], ["campaign_id", "campaigns"]] }
|
114
|
+
let(:foreign_keys) { foreign_keys_and_table_names.map(&:first) }
|
115
|
+
let(:parent_table_names) { foreign_keys_and_table_names.map(&:last) }
|
115
116
|
|
116
117
|
before do
|
117
118
|
class Table1 < ActiveRecord::Base
|
@@ -124,19 +125,40 @@ RSpec.describe DeclareSchema::Model::HabtmModelShim do
|
|
124
125
|
end
|
125
126
|
|
126
127
|
it 'returns two index definitions and does not raise a IndexNameTooLongError' do
|
127
|
-
|
128
|
-
expect(
|
129
|
-
expect(
|
130
|
-
expect(
|
131
|
-
expect(
|
132
|
-
expect(
|
128
|
+
indexes = subject.index_definitions_with_primary_key
|
129
|
+
expect(indexes.size).to eq(2), indexes.inspect
|
130
|
+
expect(indexes.last).to be_a(::DeclareSchema::Model::IndexDefinition)
|
131
|
+
expect(indexes.last.name).to eq('PRIMARY')
|
132
|
+
expect(indexes.last.fields).to eq(foreign_keys)
|
133
|
+
expect(indexes.last.unique).to be_truthy
|
134
|
+
expect(indexes.first).to be_a(::DeclareSchema::Model::IndexDefinition)
|
135
|
+
expect(indexes.first.name).to eq('index_advertiser_campaigns_tracking_pixels_on_campaign_id')
|
136
|
+
expect(indexes.first.fields).to eq([foreign_keys.last])
|
137
|
+
expect(indexes.first.unique).to be_falsey
|
133
138
|
end
|
134
139
|
end
|
135
140
|
|
136
141
|
describe '#index_definitions' do
|
142
|
+
it 'returns index_definitions' do
|
143
|
+
indexes = subject.index_definitions
|
144
|
+
expect(indexes.size).to eq(1), indexes.inspect
|
145
|
+
expect(indexes.first.columns).to eq(["user_id"])
|
146
|
+
options = [:name, :unique, :where].map { |k| [k, indexes.first.send(k)] }.to_h
|
147
|
+
expect(options).to eq(name: "index_customers_users_on_user_id",
|
148
|
+
unique: false,
|
149
|
+
where: nil)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
describe '#index_definitions_with_primary_key' do
|
137
154
|
it 'returns index_definitions_with_primary_key' do
|
138
|
-
|
139
|
-
expect(
|
155
|
+
indexes = subject.index_definitions_with_primary_key
|
156
|
+
expect(indexes.size).to eq(2), indexes.inspect
|
157
|
+
expect(indexes.last.columns).to eq(["customer_id", "user_id"])
|
158
|
+
options = [:name, :unique, :where].map { |k| [k, indexes.last.send(k)] }.to_h
|
159
|
+
expect(options).to eq(name: "PRIMARY",
|
160
|
+
unique: true,
|
161
|
+
where: nil)
|
140
162
|
end
|
141
163
|
end
|
142
164
|
|
@@ -148,18 +170,11 @@ RSpec.describe DeclareSchema::Model::HabtmModelShim do
|
|
148
170
|
|
149
171
|
describe '#constraint_specs' do
|
150
172
|
it 'returns 2 foreign keys' do
|
151
|
-
|
152
|
-
expect(
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
expect(result.first.parent_table_name).to be(Parent1.table_name)
|
157
|
-
expect(result.first.on_delete_cascade).to be_truthy
|
158
|
-
|
159
|
-
expect(result.last).to be_a(::DeclareSchema::Model::ForeignKeyDefinition)
|
160
|
-
expect(result.last.foreign_key).to eq(foreign_keys.last)
|
161
|
-
expect(result.last.parent_table_name).to be(Parent2.table_name)
|
162
|
-
expect(result.last.on_delete_cascade).to be_truthy
|
173
|
+
constraints = subject.constraint_specs
|
174
|
+
expect(constraints.map(&:key)).to eq([
|
175
|
+
["customers", "customer_id", :delete],
|
176
|
+
["users", "user_id", :delete]
|
177
|
+
])
|
163
178
|
end
|
164
179
|
end
|
165
180
|
end
|
@@ -11,6 +11,7 @@ require_relative '../../../../lib/declare_schema/model/index_definition'
|
|
11
11
|
|
12
12
|
RSpec.describe DeclareSchema::Model::IndexDefinition do
|
13
13
|
let(:model_class) { IndexDefinitionTestModel }
|
14
|
+
let(:table_name) { model_class.table_name }
|
14
15
|
|
15
16
|
context 'Using declare_schema' do
|
16
17
|
before do
|
@@ -34,28 +35,27 @@ RSpec.describe DeclareSchema::Model::IndexDefinition do
|
|
34
35
|
end
|
35
36
|
end
|
36
37
|
|
37
|
-
|
38
|
-
|
39
|
-
|
38
|
+
# TODO: create model_spec.rb and move the Model specs below into it. -Colin
|
39
|
+
context 'Model class methods' do
|
40
|
+
describe '.has index_definitions' do
|
41
|
+
subject { model_class.index_definitions }
|
40
42
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
)
|
43
|
+
it 'returns indexes without primary key' do
|
44
|
+
expect(subject.map(&:to_key)).to eq([
|
45
|
+
['index_index_definition_test_models_on_name', ['name'], false, nil],
|
46
|
+
])
|
47
|
+
end
|
47
48
|
end
|
48
49
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
)
|
50
|
+
describe '.has index_definitions_with_primary_key' do
|
51
|
+
subject { model_class.index_definitions_with_primary_key }
|
52
|
+
|
53
|
+
it 'returns indexes with primary key' do
|
54
|
+
expect(subject.map(&:to_key)).to eq([
|
55
|
+
['index_index_definition_test_models_on_name', ['name'], false, nil],
|
56
|
+
['PRIMARY', ['id'], true, nil],
|
57
|
+
])
|
58
|
+
end
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
@@ -80,29 +80,36 @@ RSpec.describe DeclareSchema::Model::IndexDefinition do
|
|
80
80
|
ActiveRecord::Base.connection.schema_cache.clear!
|
81
81
|
end
|
82
82
|
|
83
|
-
describe '
|
84
|
-
|
83
|
+
describe 'for_table' do
|
84
|
+
let(:ignore_indexes) { model_class.ignore_indexes }
|
85
|
+
subject { described_class.for_table(model_class.table_name, ignore_indexes, model_class.connection) }
|
85
86
|
|
86
87
|
context 'with single-column PK' do
|
87
88
|
it 'returns the indexes for the model' do
|
88
|
-
expect(subject.
|
89
|
-
|
90
|
-
[
|
91
|
-
)
|
92
|
-
expect([:name, :columns, :unique].map { |attr| subject[1].send(attr) }).to eq(
|
93
|
-
['PRIMARY', ['id'], true]
|
94
|
-
)
|
89
|
+
expect(subject.map(&:to_key)).to eq([
|
90
|
+
["index_definition_test_models_on_name", ["name"], true, nil],
|
91
|
+
["PRIMARY", ["id"], true, nil]
|
92
|
+
])
|
95
93
|
end
|
96
94
|
end
|
97
95
|
|
98
|
-
context 'with
|
96
|
+
context 'with composite (multi-column) PK' do
|
99
97
|
let(:model_class) { IndexDefinitionCompoundIndexModel }
|
100
98
|
|
101
99
|
it 'returns the indexes for the model' do
|
102
|
-
expect(subject.
|
103
|
-
|
104
|
-
|
105
|
-
|
100
|
+
expect(subject.map(&:to_key)).to eq([
|
101
|
+
["PRIMARY", ["fk1_id", "fk2_id"], true, nil]
|
102
|
+
])
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context 'with ignored_indexes' do
|
107
|
+
let(:ignore_indexes) { ['index_definition_test_models_on_name'] }
|
108
|
+
|
109
|
+
it 'skips the ignored index' do
|
110
|
+
expect(subject.map(&:to_key)).to eq([
|
111
|
+
["PRIMARY", ["id"], true, nil]
|
112
|
+
])
|
106
113
|
end
|
107
114
|
end
|
108
115
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: declare_schema
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.3.
|
4
|
+
version: 1.3.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Invoca Development adapted from hobo_fields by Tom Locke
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-01-
|
11
|
+
date: 2024-01-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|