declare_schema 1.3.6 → 1.4.0.colin.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -18
- data/Gemfile.lock +1 -1
- data/README.md +82 -41
- data/lib/declare_schema/model/field_spec.rb +0 -2
- data/lib/declare_schema/model/foreign_key_definition.rb +43 -40
- data/lib/declare_schema/model/habtm_model_shim.rb +26 -26
- data/lib/declare_schema/model/index_definition.rb +41 -30
- data/lib/declare_schema/model/table_options_definition.rb +1 -11
- data/lib/declare_schema/model.rb +66 -60
- data/lib/declare_schema/schema_change/column_add.rb +2 -4
- data/lib/declare_schema/schema_change/index_add.rb +3 -1
- data/lib/declare_schema/version.rb +1 -1
- data/lib/declare_schema.rb +3 -45
- data/lib/generators/declare_schema/migration/migrator.rb +19 -29
- data/spec/lib/declare_schema/field_spec_spec.rb +2 -22
- data/spec/lib/declare_schema/migration_generator_spec.rb +81 -92
- data/spec/lib/declare_schema/model/foreign_key_definition_spec.rb +23 -22
- data/spec/lib/declare_schema/model/habtm_model_shim_spec.rb +61 -76
- data/spec/lib/declare_schema/model/index_definition_spec.rb +106 -37
- data/spec/lib/declare_schema/model/table_options_definition_spec.rb +6 -26
- data/spec/lib/declare_schema/schema_change/index_add_spec.rb +28 -1
- data/spec/lib/declare_schema_spec.rb +8 -102
- data/spec/lib/generators/declare_schema/migration/migrator_spec.rb +1 -3
- data/spec/spec_helper.rb +3 -4
- 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: fe0a594fea50536d529c2d4e42280d9050726e1017524dbaff2b8a77e17c6b41
|
4
|
+
data.tar.gz: 89199ba5b58b936f93a7bfe0409c403b95da8bd771b425b2c61a6afd8a951f2a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ff4858ac26953e22a9dbf37ce5b2391295d05ec5a8401361434987e6e90c7fc01543f2bbb1cebaf91a0ce16dadb8ac5c8600d01130c81f361c3728bcd7c23f9b
|
7
|
+
data.tar.gz: 1fabf741327417f5beb76ba694de17e0176db5f63e95922a03d195eb4dcff60d71434cd6aeb4e8ff8212bc0b1fc7cc5a8cb400307efeb87a83593887621d366b
|
data/CHANGELOG.md
CHANGED
@@ -4,24 +4,11 @@ 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.
|
8
|
-
###
|
9
|
-
-
|
10
|
-
|
11
|
-
|
12
|
-
### Fixed
|
13
|
-
- Make `default_charset=` and `default_collation=` lazy so they don't use the database connection to check the
|
14
|
-
MySQL version. Instead, that is checked the first time `default_charset` or `default_collation` is called.
|
15
|
-
|
16
|
-
## [1.3.4] - 2024-01-18
|
17
|
-
### Fixed
|
18
|
-
- Add test for migrating `has_and_belongs_to_many` associations and fix them to properly declare their
|
19
|
-
2 foreign keys as the primary key of the join table, rather than just a unique index.
|
20
|
-
|
21
|
-
## [1.3.3] - 2024-01-17
|
22
|
-
### Fixed
|
23
|
-
- Fix a MySQL 8 bug where MySQL 8+ renames charset 'utf8' to 'utf8mb3' and collation 'utf8_general_ci' to
|
24
|
-
'utf8mb3_unicode_ci'.
|
7
|
+
## [1.4] - Unreleased
|
8
|
+
### Added
|
9
|
+
- Added support for partial indexes with `length:` option.
|
10
|
+
### Changed
|
11
|
+
- Deprecate index: 'name' and unique: true|false in favor of index: { name: 'name', unique: true|false }.
|
25
12
|
|
26
13
|
## [1.3.2] - 2024-01-12
|
27
14
|
### Fixed
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -9,7 +9,7 @@ Make a model and declare your schema within a `declare_schema do ... end` block:
|
|
9
9
|
class Company < ActiveRecord::Base
|
10
10
|
declare_schema do
|
11
11
|
string :company_name, limit: 100
|
12
|
-
string :ticker_symbol, limit: 4, null: true, index:
|
12
|
+
string :ticker_symbol, limit: 4, null: true, index: { unique: true }
|
13
13
|
integer :employee_count
|
14
14
|
text :comments
|
15
15
|
|
@@ -60,6 +60,61 @@ declare_schema id: :bigint do
|
|
60
60
|
end
|
61
61
|
```
|
62
62
|
|
63
|
+
## declare_schema DSL field (column) declaration
|
64
|
+
The `declare_schema` DSL is yielded to the block as shown with block variable `t` (for table).
|
65
|
+
Each field (column) is declared with the syntax `t.<type> :<column_name>, <options>` as shown here for the `string` column `company_name`:
|
66
|
+
```ruby
|
67
|
+
create_table :companies, id: :bigint do |t|
|
68
|
+
t.string :company_name, null: false, limit: 100
|
69
|
+
...
|
70
|
+
end
|
71
|
+
```
|
72
|
+
### Field (Column) Types
|
73
|
+
All of the ActiveRecord field types are supported, as returned by the database driver in use at the time.
|
74
|
+
These typically include:
|
75
|
+
- `binary` (blob)
|
76
|
+
- `text`
|
77
|
+
- `integer`
|
78
|
+
- `bigint`
|
79
|
+
- `float`
|
80
|
+
- `decimal`
|
81
|
+
- `date`
|
82
|
+
- `time`
|
83
|
+
- `datetime`
|
84
|
+
- `timestamp`
|
85
|
+
- `string` (varchar)
|
86
|
+
- `boolean` (tinyint 0 or 1)
|
87
|
+
- `json`
|
88
|
+
- `array`
|
89
|
+
- `enum` (if using the `activerecord-mysql-enum` gem) (MySQL enum)
|
90
|
+
|
91
|
+
### Field (Column) Options
|
92
|
+
The following field options are:
|
93
|
+
- `limit` (integer) - The maximum length of the field. For `text` and `binary` fields, this is the maximum number of bytes.
|
94
|
+
For `string` fields, this is the maximum number of characters, and defaults to `DeclareSchema.default_string_limit`; for `text`, defaults to `DeclareSchema.default_text_limit`.
|
95
|
+
For `enum`
|
96
|
+
- `null` (boolean) - Whether the field is nullable. Defaults to `DeclareSchema.default_null`.
|
97
|
+
- `default` (any) - The default value for the field.
|
98
|
+
- `ruby_default` (Proc) - A callable Ruby Proc that returns the default value for the field. This is useful for default values that require Ruby computation.
|
99
|
+
(Provided by the `attr_default` gem.)
|
100
|
+
- `index` (boolean [deprecated] or hash) - Whether to create an index for the field. If `true`, defaults to `{ unique: false }` [deprecated]. See below for supported `index` options.
|
101
|
+
- `unique` [deprecated] (boolean) - Whether to create a unique index for the field. Defaults to `false`. Deprecated in favor of `index: { unique: <boolean> }`.
|
102
|
+
- `charset` (string) - The character set for the field. Defaults to `default_charset` (see below).
|
103
|
+
- `collation` (string) - The collation for the field. Defaults to `default_collation` (see below).
|
104
|
+
- `precision` (integer) - The precision for the numeric field.
|
105
|
+
- `scale` (integer) - The scale for the numeric field.
|
106
|
+
|
107
|
+
### Index Options
|
108
|
+
The following `index` options are supported:
|
109
|
+
- `name` (string) - The name of the index. Defaults the longest format that will fit within `DeclareSchema.max_index_and_constraint_name_length`. They are tried in this order:
|
110
|
+
1. `index_<table>_on_<col1>[_and_<col2>...]>`.
|
111
|
+
2. `__<col1>[_<col2>...]>`
|
112
|
+
3. `<table_prefix><sha256_of_columns_prefix>`
|
113
|
+
- `unique` (boolean) - Whether the index is unique. Defaults to `false`.
|
114
|
+
- `order` (synbol or hash) - The index order. If `:asc` or `:desc` is provided, it is used as the order for all columns. If hash is provided, it is used to specify the order of individual columns, where the column names are given as `Symbol` hash keys with values of `:asc` or `:desc` indicating the sort order of that column.
|
115
|
+
- `length` (integer or hash) - The partial index length(s). If an integer is provided, it is used as the length for all columns. If a hash is provided, it is used to specify the length for individual columns, where the column names are given as `Symbol` hash keys.
|
116
|
+
- `where` (string) - The subset index predicate.
|
117
|
+
|
63
118
|
## Usage without Rails
|
64
119
|
|
65
120
|
When using `DeclareSchema` without Rails, you can use the `declare_schema/rake` task to generate the migration file.
|
@@ -115,13 +170,13 @@ end
|
|
115
170
|
```
|
116
171
|
|
117
172
|
### clear_default_schema
|
118
|
-
This method clears out any previously declared `default_schema`.
|
173
|
+
This method clears out any previously declared `default_schema`. This can be useful for tests.
|
119
174
|
```ruby
|
120
175
|
DeclareSchema.clear_default_schema
|
121
176
|
```
|
122
177
|
|
123
178
|
### Global Configuration
|
124
|
-
Configurations can be set at
|
179
|
+
Configurations can be set at globally to customize default declaration for the following values:
|
125
180
|
|
126
181
|
#### Text Limit
|
127
182
|
The default text limit can be set using the `DeclareSchema.default_text_limit=` method.
|
@@ -134,8 +189,6 @@ set the default `text limit` value to `0xffff`:
|
|
134
189
|
|
135
190
|
**declare_schema.rb**
|
136
191
|
```ruby
|
137
|
-
# frozen_string_literal: true
|
138
|
-
|
139
192
|
DeclareSchema.default_text_limit = 0xffff
|
140
193
|
```
|
141
194
|
|
@@ -150,8 +203,6 @@ set the default `string limit` value to `255`:
|
|
150
203
|
|
151
204
|
**declare_schema.rb**
|
152
205
|
```ruby
|
153
|
-
# frozen_string_literal: true
|
154
|
-
|
155
206
|
DeclareSchema.default_string_limit = 255
|
156
207
|
```
|
157
208
|
|
@@ -166,40 +217,34 @@ set the default `null` value to `true`:
|
|
166
217
|
|
167
218
|
**declare_schema.rb**
|
168
219
|
```ruby
|
169
|
-
# frozen_string_literal: true
|
170
|
-
|
171
220
|
DeclareSchema.default_null = true
|
172
221
|
```
|
173
222
|
|
174
223
|
#### Generate Foreign Keys
|
175
|
-
|
176
|
-
This
|
224
|
+
You can choose whether to generate foreign keys by using the `DeclareSchema.default_generate_foreign_keys=` method.
|
225
|
+
This defaults to `true` and can only be set globally.
|
177
226
|
|
178
|
-
For example, adding the following to your `config/initializers` directory will
|
179
|
-
|
227
|
+
For example, adding the following to your `config/initializers` directory will cause
|
228
|
+
foreign keys not to be generated:
|
180
229
|
|
181
230
|
**declare_schema.rb**
|
182
231
|
```ruby
|
183
|
-
# frozen_string_literal: true
|
184
|
-
|
185
232
|
DeclareSchema.default_generate_foreign_keys = false
|
186
233
|
```
|
187
234
|
|
188
235
|
#### Generate Indexing
|
189
|
-
|
190
|
-
This
|
236
|
+
You can choose whether to generate indexes automatically by using the `DeclareSchema.default_generate_indexing=` method.
|
237
|
+
This defaults to `true` and can only be set globally.
|
191
238
|
|
192
|
-
For example, adding the following to your `config/initializers` directory will
|
193
|
-
|
239
|
+
For example, adding the following to your `config/initializers` directory will cause
|
240
|
+
indexes not to be generated by `declare_schema`:
|
194
241
|
|
195
242
|
**declare_schema.rb**
|
196
243
|
```ruby
|
197
|
-
# frozen_string_literal: true
|
198
|
-
|
199
244
|
DeclareSchema.default_generate_indexing = false
|
200
245
|
```
|
201
246
|
#### Character Set and Collation
|
202
|
-
The character set and collation for all tables and fields can be set at
|
247
|
+
The character set and collation for all tables and fields can be set at globally
|
203
248
|
using the `Generators::DeclareSchema::Migrator.default_charset=` and
|
204
249
|
`Generators::DeclareSchema::Migrator.default_collation=` configuration methods.
|
205
250
|
|
@@ -208,14 +253,9 @@ turn all tables into `utf8mb4` supporting tables:
|
|
208
253
|
|
209
254
|
**declare_schema.rb**
|
210
255
|
```ruby
|
211
|
-
# frozen_string_literal: true
|
212
|
-
|
213
256
|
DeclareSchema.default_charset = "utf8mb4"
|
214
257
|
DeclareSchema.default_collation = "utf8mb4_bin"
|
215
258
|
```
|
216
|
-
Note: MySQL 8+ aliases charset 'utf8' to 'utf8mb3', and 'utf8_general_ci' to 'utf8mb3_unicode_ci',
|
217
|
-
so when running on MySQL 8+, those aliases will be applied by `DeclareSchema`.
|
218
|
-
|
219
259
|
#### db:migrate Command
|
220
260
|
`declare_schema` can run the migration once it is generated, if the `--migrate` option is passed.
|
221
261
|
If not, it will display the command to run later. By default this command is
|
@@ -224,7 +264,7 @@ bundle exec rails db:migrate
|
|
224
264
|
```
|
225
265
|
If your repo has a different command to run for migrations, you can configure it like this:
|
226
266
|
```ruby
|
227
|
-
|
267
|
+
DeclareSchema.db_migrate_command = "bundle exec rails db:migrate_immediate"
|
228
268
|
```
|
229
269
|
|
230
270
|
## The `belongs_to` Association
|
@@ -233,24 +273,16 @@ association is outside of the `declare_schema do` block, so `declare_schema` int
|
|
233
273
|
infer the foreign key column.
|
234
274
|
|
235
275
|
By default, `declare_schema` creates an index for `belongs_to` relations. If this default index is not desired,
|
236
|
-
you can use `index: false` in the `belongs_to` expression. This may be the case if for example a different index
|
237
|
-
already
|
276
|
+
you can use `index: false` in the `belongs_to` expression. This may be the case if, for example, a different index
|
277
|
+
already covers those columns at the front.
|
238
278
|
|
239
279
|
## The `has_and_belongs_to_many` Association
|
240
280
|
Like the `belongs_to` association, `has_and_belongs_to_many` is outside of the `declare_schema ` block. `declare_schema` similarly
|
241
281
|
infers foreign keys (and the intersection table).
|
242
282
|
|
243
283
|
## Ignored Tables
|
244
|
-
If a table's schema or metadata are managed elsewhere, `declare_schema`
|
245
|
-
|
246
|
-
|
247
|
-
`declare_schema` by default ignores these tables:
|
248
|
-
- The ActiveRecord `schema_info` table
|
249
|
-
- The ActiveRecord schema migrations table (generally named `schema_migrations`)
|
250
|
-
- The ActiveRecord internal metadata table (generally named `ar_internal_metadata`)
|
251
|
-
- If defined/configured, the CGI ActiveRecordStore session table
|
252
|
-
|
253
|
-
Additional tables can be ignored by configuring `Generators::DeclareSchema::Migration::Migrator.ignore_tables`.
|
284
|
+
If a table's schema or metadata are managed elsewhere, `declare_schema` can be instructed to ignore it
|
285
|
+
by adding those table names to the array assigned to `Generators::DeclareSchema::Migration::Migrator.ignore_tables`.
|
254
286
|
For example:
|
255
287
|
|
256
288
|
```ruby
|
@@ -261,6 +293,12 @@ For example:
|
|
261
293
|
]
|
262
294
|
```
|
263
295
|
|
296
|
+
Note: `declare_schema` always ignores these tables:
|
297
|
+
- The ActiveRecord `schema_info` table
|
298
|
+
- The ActiveRecord schema migrations table (generally named `schema_migrations`)
|
299
|
+
- The ActiveRecord internal metadata table (generally named `ar_internal_metadata`)
|
300
|
+
- If defined/configured, the CGI ActiveRecordStore session table
|
301
|
+
|
264
302
|
## Maximum Length of Index and Constraint Names
|
265
303
|
|
266
304
|
MySQL limits the length of index and constraint names to 64 characters.
|
@@ -282,7 +320,10 @@ But later, Unicode was extended beyond U+FFFF to make room for emojis, and with
|
|
282
320
|
UTF-8 require 1-4 bytes (`mb4` or "multi-byte 4"). With this addition, there has
|
283
321
|
come a need to dynamically define the character set and collation for individual
|
284
322
|
tables and columns in the database. With `declare_schema` this can be configured
|
285
|
-
at three separate levels
|
323
|
+
at three separate levels.
|
324
|
+
|
325
|
+
### Global Configuration
|
326
|
+
The global configuration option is explained above in the [Character Set and Collation](#Character-Set-and-Collation) section.
|
286
327
|
|
287
328
|
### Table Configuration
|
288
329
|
In order to configure a table's default character set and collation, the `charset` and
|
@@ -107,9 +107,7 @@ module DeclareSchema
|
|
107
107
|
if @type.in?([:text, :string])
|
108
108
|
if ActiveRecord::Base.connection.class.name.match?(/mysql/i)
|
109
109
|
@options[:charset] ||= model._table_options&.[](:charset) || ::DeclareSchema.default_charset
|
110
|
-
@options[:charset] = DeclareSchema.normalize_charset(@options[:charset])
|
111
110
|
@options[:collation] ||= model._table_options&.[](:collation) || ::DeclareSchema.default_collation
|
112
|
-
@options[:collation] = DeclareSchema.normalize_collation(@options[:collation])
|
113
111
|
else
|
114
112
|
@options.delete(:charset)
|
115
113
|
@options.delete(:collation)
|
@@ -7,53 +7,46 @@ 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
|
-
@
|
10
|
+
attr_reader :constraint_name, :model, :foreign_key, :foreign_key_name, :parent_table_name, :child_table_name, :options, :on_delete_cascade
|
11
|
+
|
12
|
+
|
13
|
+
def initialize(model, foreign_key, **options)
|
14
|
+
@model = model
|
15
|
+
@foreign_key = foreign_key.to_s.presence or raise ArgumentError "Foreign key must not be empty: #{foreign_key.inspect}"
|
16
|
+
@options = options
|
17
|
+
|
18
|
+
@child_table_name = model.table_name # unless a table rename, which would happen when a class is renamed??
|
19
|
+
@parent_table_name = options[:parent_table]&.to_s
|
20
|
+
@foreign_key_name = options[:foreign_key]&.to_s || @foreign_key
|
21
|
+
|
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
|
21
25
|
end
|
22
26
|
|
23
27
|
class << self
|
24
|
-
def
|
25
|
-
show_create_table = connection.select_rows("show create table #{connection.quote_table_name(
|
28
|
+
def for_model(model, old_table_name)
|
29
|
+
show_create_table = model.connection.select_rows("show create table #{model.connection.quote_table_name(old_table_name)}").first.last
|
26
30
|
constraints = show_create_table.split("\n").map { |line| line.strip if line['CONSTRAINT'] }.compact
|
27
31
|
|
28
32
|
constraints.map do |fkc|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
+
name, foreign_key, parent_table = fkc.match(/CONSTRAINT `([^`]*)` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)`/).captures
|
34
|
+
options = {
|
35
|
+
constraint_name: name,
|
36
|
+
parent_table: parent_table,
|
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)
|
33
42
|
end
|
34
43
|
end
|
35
44
|
end
|
36
45
|
|
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
|
-
|
53
46
|
# returns the parent class as a Class object
|
54
|
-
# or nil if no
|
55
|
-
def parent_class
|
56
|
-
if class_name
|
47
|
+
# or nil if no :class_name option given
|
48
|
+
def parent_class
|
49
|
+
if (class_name = options[:class_name])
|
57
50
|
if class_name.is_a?(Class)
|
58
51
|
class_name
|
59
52
|
else
|
@@ -62,12 +55,22 @@ module DeclareSchema
|
|
62
55
|
end
|
63
56
|
end
|
64
57
|
|
65
|
-
def
|
66
|
-
|
58
|
+
def parent_table_name
|
59
|
+
@parent_table_name ||=
|
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)
|
67
66
|
end
|
68
67
|
|
69
|
-
|
70
|
-
|
68
|
+
alias eql? ==
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def key
|
73
|
+
@key ||= [@child_table_name, parent_table_name, @foreign_key_name, @on_delete_cascade].map(&:to_s)
|
71
74
|
end
|
72
75
|
|
73
76
|
def hash
|
@@ -4,24 +4,28 @@ module DeclareSchema
|
|
4
4
|
module Model
|
5
5
|
class HabtmModelShim
|
6
6
|
class << self
|
7
|
-
def from_reflection(
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
def from_reflection(refl)
|
8
|
+
join_table = refl.join_table
|
9
|
+
foreign_keys_and_classes = [
|
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)
|
12
20
|
end
|
13
21
|
end
|
14
22
|
|
15
|
-
attr_reader :join_table, :foreign_keys, :
|
23
|
+
attr_reader :join_table, :foreign_keys, :foreign_key_classes, :connection
|
16
24
|
|
17
|
-
def initialize(join_table, foreign_keys,
|
18
|
-
foreign_keys.is_a?(Array) && foreign_keys.size == 2 or
|
19
|
-
raise ArgumentError, "foreign_keys must be <Array[2]>; got #{foreign_keys.inspect}"
|
20
|
-
parent_table_names.is_a?(Array) && parent_table_names.size == 2 or
|
21
|
-
raise ArgumentError, "parent_table_names must be <Array[2]>; got #{parent_table_names.inspect}"
|
25
|
+
def initialize(join_table, foreign_keys, foreign_key_classes, connection)
|
22
26
|
@join_table = join_table
|
23
|
-
@foreign_keys = foreign_keys
|
24
|
-
@
|
27
|
+
@foreign_keys = foreign_keys
|
28
|
+
@foreign_key_classes = foreign_key_classes
|
25
29
|
@connection = connection
|
26
30
|
end
|
27
31
|
|
@@ -34,8 +38,8 @@ module DeclareSchema
|
|
34
38
|
end
|
35
39
|
|
36
40
|
def field_specs
|
37
|
-
foreign_keys.each_with_index.each_with_object({}) do |(
|
38
|
-
result[
|
41
|
+
foreign_keys.each_with_index.each_with_object({}) do |(v, position), result|
|
42
|
+
result[v] = ::DeclareSchema::Model::FieldSpec.new(self, v, :bigint, position: position, null: false)
|
39
43
|
end
|
40
44
|
end
|
41
45
|
|
@@ -44,30 +48,26 @@ module DeclareSchema
|
|
44
48
|
end
|
45
49
|
|
46
50
|
def _declared_primary_key
|
47
|
-
|
48
|
-
end
|
49
|
-
|
50
|
-
def index_definitions
|
51
|
-
[
|
52
|
-
IndexDefinition.new(foreign_keys.last, table_name: table_name, unique: false) # index for queries where we only have the last foreign key
|
53
|
-
]
|
51
|
+
false # no single-column primary key declared
|
54
52
|
end
|
55
53
|
|
56
54
|
def index_definitions_with_primary_key
|
57
55
|
[
|
58
|
-
|
59
|
-
IndexDefinition.new(
|
56
|
+
IndexDefinition.new(self, foreign_keys, unique: true, name: Model::IndexDefinition::PRIMARY_KEY_NAME), # creates a primary composite key on both foreign keys
|
57
|
+
IndexDefinition.new(self, foreign_keys.last) # not unique by itself; combines with primary key to be unique
|
60
58
|
]
|
61
59
|
end
|
62
60
|
|
61
|
+
alias_method :index_definitions, :index_definitions_with_primary_key
|
62
|
+
|
63
63
|
def ignore_indexes
|
64
64
|
[]
|
65
65
|
end
|
66
66
|
|
67
67
|
def constraint_specs
|
68
68
|
[
|
69
|
-
ForeignKeyDefinition.new(foreign_keys.first,
|
70
|
-
ForeignKeyDefinition.new(foreign_keys.last,
|
69
|
+
ForeignKeyDefinition.new(self, foreign_keys.first, parent_table: foreign_key_classes.first.table_name, constraint_name: "#{join_table}_FK1", dependent: :delete),
|
70
|
+
ForeignKeyDefinition.new(self, foreign_keys.last, parent_table: foreign_key_classes.last.table_name, constraint_name: "#{join_table}_FK2", dependent: :delete)
|
71
71
|
]
|
72
72
|
end
|
73
73
|
end
|
@@ -7,63 +7,65 @@ module DeclareSchema
|
|
7
7
|
class IndexDefinition
|
8
8
|
include Comparable
|
9
9
|
|
10
|
-
|
11
|
-
|
10
|
+
# TODO: replace `fields` with `columns` and remove alias. -Colin
|
11
|
+
OPTIONS = [:name, :unique, :where, :length].freeze
|
12
|
+
attr_reader :table, :fields, :explicit_name, *OPTIONS
|
13
|
+
alias columns fields
|
12
14
|
|
13
15
|
class IndexNameTooLongError < RuntimeError; end
|
14
16
|
|
15
17
|
PRIMARY_KEY_NAME = "PRIMARY"
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
@
|
21
|
-
@
|
22
|
-
@
|
23
|
-
unique.
|
24
|
-
|
25
|
-
unique or raise ArgumentError, "primary key index must be unique"
|
26
|
-
end
|
27
|
-
@unique = unique
|
19
|
+
def initialize(model, fields, **options)
|
20
|
+
@model = model
|
21
|
+
@table = options.delete(:table_name) || model.table_name
|
22
|
+
@fields = Array.wrap(fields).map(&:to_s)
|
23
|
+
@explicit_name = options[:name] unless options.delete(:allow_equivalent)
|
24
|
+
@name = options.delete(:name) || self.class.default_index_name(@table, @fields)
|
25
|
+
@unique = options.delete(:unique) || name == PRIMARY_KEY_NAME || false
|
26
|
+
@length = options.delete(:length)
|
28
27
|
|
29
28
|
if DeclareSchema.max_index_and_constraint_name_length && @name.length > DeclareSchema.max_index_and_constraint_name_length
|
30
29
|
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."
|
31
30
|
end
|
32
31
|
|
33
|
-
if where
|
32
|
+
if (where = options.delete(:where))
|
34
33
|
@where = where.start_with?('(') ? where : "(#{where})"
|
35
34
|
end
|
35
|
+
|
36
|
+
options.any? and warn("ignoring unrecognized option(s): #{options.inspect} for model #{model}")
|
36
37
|
end
|
37
38
|
|
38
39
|
class << self
|
39
40
|
# extract IndexSpecs from an existing table
|
40
41
|
# includes the PRIMARY KEY index
|
41
|
-
def
|
42
|
-
|
43
|
-
primary_key_columns.present? or raise "could not find primary key for table #{table_name} in #{connection.columns(table_name).inspect}"
|
42
|
+
def for_model(model, old_table_name = nil)
|
43
|
+
t = old_table_name || model.table_name
|
44
44
|
|
45
|
-
|
46
|
-
|
47
|
-
next if ignore_indexes.include?(index.name)
|
45
|
+
primary_key_columns = Array(model.connection.primary_key(t)).presence
|
46
|
+
primary_key_columns or raise "could not find primary key for table #{t} in #{model.connection.columns(t).inspect}"
|
48
47
|
|
49
|
-
|
50
|
-
|
51
|
-
|
48
|
+
primary_key_found = false
|
49
|
+
index_definitions = model.connection.indexes(t).map do |i|
|
50
|
+
model.ignore_indexes.include?(i.name) and next
|
51
|
+
if i.name == PRIMARY_KEY_NAME
|
52
|
+
i.columns == primary_key_columns && i.unique or
|
53
|
+
raise "primary key on #{t} was not unique on #{primary_key_columns} (was unique=#{i.unique} on #{i.columns})"
|
52
54
|
primary_key_found = true
|
53
55
|
end
|
54
|
-
new(
|
56
|
+
new(model, i.columns, name: i.name, unique: i.unique, where: i.where, table_name: old_table_name)
|
55
57
|
end.compact
|
56
58
|
|
57
59
|
if !primary_key_found
|
58
|
-
index_definitions << new(primary_key_columns, name: PRIMARY_KEY_NAME, unique: true)
|
60
|
+
index_definitions << new(model, primary_key_columns, name: PRIMARY_KEY_NAME, unique: true, where: nil, table_name: old_table_name)
|
59
61
|
end
|
60
62
|
index_definitions
|
61
63
|
end
|
62
64
|
|
63
|
-
def default_index_name(
|
65
|
+
def default_index_name(table, fields)
|
64
66
|
index_name = nil
|
65
67
|
[:long_index_name, :short_index_name].find do |method_name|
|
66
|
-
index_name = send(method_name,
|
68
|
+
index_name = send(method_name, table, fields)
|
67
69
|
if DeclareSchema.max_index_and_constraint_name_length.nil? || index_name.length <= DeclareSchema.max_index_and_constraint_name_length
|
68
70
|
break index_name
|
69
71
|
end
|
@@ -104,12 +106,21 @@ module DeclareSchema
|
|
104
106
|
name == PRIMARY_KEY_NAME
|
105
107
|
end
|
106
108
|
|
109
|
+
def options
|
110
|
+
@options ||=
|
111
|
+
OPTIONS.each_with_object({}) do |option, result|
|
112
|
+
result[option] = send(option)
|
113
|
+
end.freeze
|
114
|
+
end
|
115
|
+
|
116
|
+
# Unique key for this object. Used for equality checking.
|
107
117
|
def to_key
|
108
|
-
@
|
118
|
+
@key ||= [table, fields, options].freeze
|
109
119
|
end
|
110
120
|
|
121
|
+
# The index settings for this object. Used for equivalence checking. Does not include the name.
|
111
122
|
def settings
|
112
|
-
@settings ||= [
|
123
|
+
@settings ||= [table, fields, options.except(:name)].freeze
|
113
124
|
end
|
114
125
|
|
115
126
|
def hash
|
@@ -125,7 +136,7 @@ module DeclareSchema
|
|
125
136
|
end
|
126
137
|
|
127
138
|
def with_name(new_name)
|
128
|
-
self.class.new(@
|
139
|
+
self.class.new(@model, @fields, **{ **options, name: new_name })
|
129
140
|
end
|
130
141
|
|
131
142
|
alias eql? ==
|
@@ -50,17 +50,7 @@ module DeclareSchema
|
|
50
50
|
|
51
51
|
def initialize(table_name, **table_options)
|
52
52
|
@table_name = table_name
|
53
|
-
@table_options = table_options
|
54
|
-
result[k] =
|
55
|
-
case k
|
56
|
-
when :charset
|
57
|
-
DeclareSchema.normalize_charset(v)
|
58
|
-
when :collation
|
59
|
-
DeclareSchema.normalize_collation(v)
|
60
|
-
else
|
61
|
-
v
|
62
|
-
end
|
63
|
-
end
|
53
|
+
@table_options = table_options
|
64
54
|
end
|
65
55
|
|
66
56
|
def to_key
|