declare_schema 1.3.3.colin.1 → 1.3.4.colin.1
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 +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/field_spec_spec.rb +1 -1
- 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: 67383598f5b2515ce62994cc33931e14b5f7736f84f142cdba28da87a3765698
|
4
|
+
data.tar.gz: 1f11e932b6222419aac070f17878108a05072080469195a5b299bdfe604c26c8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e7547cfc57e2c5a06ba8c45923a8aa6bd4631637757a081868081e9d0b63641c9ec24d07eb9bb0eab43e136638e95f4058b01a3b8fa18ee1159d42c6e67f40d9
|
7
|
+
data.tar.gz: a09ca6210c25cb97171622d6c04e19213a8c9b8afc3c131926697533ffbc92dff7290aa61025373aff2367846a8bca2394efd20a77fe2e63e1c27675794b9d77
|
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 =
|
@@ -66,7 +66,7 @@ RSpec.describe DeclareSchema::Model::FieldSpec do
|
|
66
66
|
if defined?(Mysql2)
|
67
67
|
context 'when running on MySQL 8.0' do
|
68
68
|
around do |spec|
|
69
|
-
DeclareSchema.mysql_version = '8.0.21'
|
69
|
+
DeclareSchema.mysql_version = Gem::Version.new('8.0.21')
|
70
70
|
spec.run
|
71
71
|
ensure
|
72
72
|
DeclareSchema.remove_instance_variable('@mysql_version') rescue nil
|
@@ -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.colin.1
|
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-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|