declare_schema 1.3.6 → 1.4.0.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 +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
|