declare_schema 1.3.2.rp.1 → 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 +7 -1
- data/Gemfile.lock +1 -1
- data/README.md +82 -38
- data/lib/declare_schema/model/index_definition.rb +18 -5
- data/lib/declare_schema/model.rb +29 -7
- data/lib/declare_schema/schema_change/index_add.rb +3 -1
- data/lib/declare_schema/version.rb +1 -1
- data/lib/generators/declare_schema/migration/migrator.rb +1 -1
- data/spec/lib/declare_schema/migration_generator_spec.rb +80 -51
- data/spec/lib/declare_schema/model/index_definition_spec.rb +103 -27
- data/spec/lib/declare_schema/schema_change/index_add_spec.rb +28 -1
- metadata +1 -1
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,7 +4,13 @@ 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.
|
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 }.
|
12
|
+
|
13
|
+
## [1.3.2] - 2024-01-12
|
8
14
|
### Fixed
|
9
15
|
- Fix bug in migrator when table option definitions differ
|
10
16
|
|
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,8 +253,6 @@ 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
|
```
|
@@ -221,7 +264,7 @@ bundle exec rails db:migrate
|
|
221
264
|
```
|
222
265
|
If your repo has a different command to run for migrations, you can configure it like this:
|
223
266
|
```ruby
|
224
|
-
|
267
|
+
DeclareSchema.db_migrate_command = "bundle exec rails db:migrate_immediate"
|
225
268
|
```
|
226
269
|
|
227
270
|
## The `belongs_to` Association
|
@@ -230,24 +273,16 @@ association is outside of the `declare_schema do` block, so `declare_schema` int
|
|
230
273
|
infer the foreign key column.
|
231
274
|
|
232
275
|
By default, `declare_schema` creates an index for `belongs_to` relations. If this default index is not desired,
|
233
|
-
you can use `index: false` in the `belongs_to` expression. This may be the case if for example a different index
|
234
|
-
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.
|
235
278
|
|
236
279
|
## The `has_and_belongs_to_many` Association
|
237
280
|
Like the `belongs_to` association, `has_and_belongs_to_many` is outside of the `declare_schema ` block. `declare_schema` similarly
|
238
281
|
infers foreign keys (and the intersection table).
|
239
282
|
|
240
283
|
## Ignored Tables
|
241
|
-
If a table's schema or metadata are managed elsewhere, `declare_schema`
|
242
|
-
|
243
|
-
|
244
|
-
`declare_schema` by default ignores these tables:
|
245
|
-
- The ActiveRecord `schema_info` table
|
246
|
-
- The ActiveRecord schema migrations table (generally named `schema_migrations`)
|
247
|
-
- The ActiveRecord internal metadata table (generally named `ar_internal_metadata`)
|
248
|
-
- If defined/configured, the CGI ActiveRecordStore session table
|
249
|
-
|
250
|
-
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`.
|
251
286
|
For example:
|
252
287
|
|
253
288
|
```ruby
|
@@ -258,6 +293,12 @@ For example:
|
|
258
293
|
]
|
259
294
|
```
|
260
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
|
+
|
261
302
|
## Maximum Length of Index and Constraint Names
|
262
303
|
|
263
304
|
MySQL limits the length of index and constraint names to 64 characters.
|
@@ -279,7 +320,10 @@ But later, Unicode was extended beyond U+FFFF to make room for emojis, and with
|
|
279
320
|
UTF-8 require 1-4 bytes (`mb4` or "multi-byte 4"). With this addition, there has
|
280
321
|
come a need to dynamically define the character set and collation for individual
|
281
322
|
tables and columns in the database. With `declare_schema` this can be configured
|
282
|
-
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.
|
283
327
|
|
284
328
|
### Table Configuration
|
285
329
|
In order to configure a table's default character set and collation, the `charset` and
|
@@ -8,7 +8,8 @@ module DeclareSchema
|
|
8
8
|
include Comparable
|
9
9
|
|
10
10
|
# TODO: replace `fields` with `columns` and remove alias. -Colin
|
11
|
-
|
11
|
+
OPTIONS = [:name, :unique, :where, :length].freeze
|
12
|
+
attr_reader :table, :fields, :explicit_name, *OPTIONS
|
12
13
|
alias columns fields
|
13
14
|
|
14
15
|
class IndexNameTooLongError < RuntimeError; end
|
@@ -22,14 +23,17 @@ module DeclareSchema
|
|
22
23
|
@explicit_name = options[:name] unless options.delete(:allow_equivalent)
|
23
24
|
@name = options.delete(:name) || self.class.default_index_name(@table, @fields)
|
24
25
|
@unique = options.delete(:unique) || name == PRIMARY_KEY_NAME || false
|
26
|
+
@length = options.delete(:length)
|
25
27
|
|
26
28
|
if DeclareSchema.max_index_and_constraint_name_length && @name.length > DeclareSchema.max_index_and_constraint_name_length
|
27
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."
|
28
30
|
end
|
29
31
|
|
30
|
-
if (where = options
|
32
|
+
if (where = options.delete(:where))
|
31
33
|
@where = where.start_with?('(') ? where : "(#{where})"
|
32
34
|
end
|
35
|
+
|
36
|
+
options.any? and warn("ignoring unrecognized option(s): #{options.inspect} for model #{model}")
|
33
37
|
end
|
34
38
|
|
35
39
|
class << self
|
@@ -102,12 +106,21 @@ module DeclareSchema
|
|
102
106
|
name == PRIMARY_KEY_NAME
|
103
107
|
end
|
104
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.
|
105
117
|
def to_key
|
106
|
-
@key ||= [table, fields,
|
118
|
+
@key ||= [table, fields, options].freeze
|
107
119
|
end
|
108
120
|
|
121
|
+
# The index settings for this object. Used for equivalence checking. Does not include the name.
|
109
122
|
def settings
|
110
|
-
@settings ||= [table, fields,
|
123
|
+
@settings ||= [table, fields, options.except(:name)].freeze
|
111
124
|
end
|
112
125
|
|
113
126
|
def hash
|
@@ -123,7 +136,7 @@ module DeclareSchema
|
|
123
136
|
end
|
124
137
|
|
125
138
|
def with_name(new_name)
|
126
|
-
self.class.new(@model, @fields,
|
139
|
+
self.class.new(@model, @fields, **{ **options, name: new_name })
|
127
140
|
end
|
128
141
|
|
129
142
|
alias eql? ==
|
data/lib/declare_schema/model.rb
CHANGED
@@ -114,14 +114,36 @@ module DeclareSchema
|
|
114
114
|
ActiveSupport::Deprecation.warn("belongs_to limit: is deprecated since it is now inferred")
|
115
115
|
end
|
116
116
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
117
|
+
# index: true means create an index on the foreign key
|
118
|
+
# index: false means do not create an index on the foreign key
|
119
|
+
# index: { ... } means create an index on the foreign key with the given options
|
120
|
+
index_value = options.delete(:index)
|
121
|
+
if index_value != false || options.has_key?(:unique) || options.has_key?(:allow_equivalent)
|
122
|
+
index_options = {}
|
123
|
+
case index_value
|
124
|
+
when String
|
125
|
+
Kernel.warn("belongs_to index: 'name' is deprecated; use index: { name: 'name' } instead")
|
126
|
+
index_options[:name] = index_value
|
127
|
+
# when false -- impossible since we checked that above
|
128
|
+
when true
|
129
|
+
when nil
|
130
|
+
when Hash
|
131
|
+
index_options = index_value
|
132
|
+
else
|
133
|
+
raise ArgumentError, "belongs_to index: must be true or false or a Hash; got #{index_value.inspect}"
|
134
|
+
end
|
135
|
+
|
136
|
+
if options.has_key?(:unique)
|
137
|
+
Kernel.warn("belongs_to unique: true|false is deprecated; use index: { unique: true|false } instead")
|
138
|
+
index_options[:unique] = options.delete(:unique)
|
139
|
+
end
|
140
|
+
|
141
|
+
index_options[:allow_equivalent] = options.delete(:allow_equivalent) if options.has_key?(:allow_equivalent)
|
142
|
+
end
|
121
143
|
|
122
144
|
fk_options = options.dup
|
123
145
|
fk_options[:constraint_name] = options.delete(:constraint) if options.has_key?(:constraint)
|
124
|
-
fk_options[:index_name] = index_options[:name
|
146
|
+
fk_options[:index_name] = index_options&.[](:name)
|
125
147
|
|
126
148
|
fk = options[:foreign_key]&.to_s || "#{name}_id"
|
127
149
|
|
@@ -157,9 +179,9 @@ module DeclareSchema
|
|
157
179
|
if refl.options[:polymorphic]
|
158
180
|
foreign_type = options[:foreign_type] || "#{name}_type"
|
159
181
|
_declare_polymorphic_type_field(foreign_type, column_options)
|
160
|
-
index([foreign_type, fkey], **index_options) if index_options
|
182
|
+
index([foreign_type, fkey], **index_options) if index_options
|
161
183
|
else
|
162
|
-
index(fkey, **index_options) if index_options
|
184
|
+
index(fkey, **index_options) if index_options
|
163
185
|
constraint(fkey, **fk_options) if fk_options[:constraint_name] != false
|
164
186
|
end
|
165
187
|
end
|
@@ -5,12 +5,13 @@ require_relative 'base'
|
|
5
5
|
module DeclareSchema
|
6
6
|
module SchemaChange
|
7
7
|
class IndexAdd < Base
|
8
|
-
def initialize(table_name, column_names, name:, unique:, where: nil)
|
8
|
+
def initialize(table_name, column_names, name:, unique:, where: nil, limit: nil)
|
9
9
|
@table_name = table_name
|
10
10
|
@column_names = column_names
|
11
11
|
@name = name
|
12
12
|
@unique = unique
|
13
13
|
@where = where.presence
|
14
|
+
@limit = limit.presence
|
14
15
|
end
|
15
16
|
|
16
17
|
def up_command
|
@@ -19,6 +20,7 @@ module DeclareSchema
|
|
19
20
|
}
|
20
21
|
options[:unique] = true if @unique
|
21
22
|
options[:where] = @where if @where
|
23
|
+
options[:limit] = @limit if @limit
|
22
24
|
|
23
25
|
"add_index #{[@table_name.to_sym.inspect,
|
24
26
|
@column_names.map(&:to_sym).inspect,
|
@@ -330,7 +330,7 @@ module Generators
|
|
330
330
|
# TODO: TECH-5338: optimize that index doesn't need to be dropped on undo since entire table will be dropped
|
331
331
|
def create_indexes(model)
|
332
332
|
model.index_definitions.map do |i|
|
333
|
-
::DeclareSchema::SchemaChange::IndexAdd.new(model.table_name, i.columns,
|
333
|
+
::DeclareSchema::SchemaChange::IndexAdd.new(model.table_name, i.columns, **i.options)
|
334
334
|
end
|
335
335
|
end
|
336
336
|
|
@@ -108,8 +108,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
108
108
|
add_column :adverts, :published_at, :datetime, null: true
|
109
109
|
EOS
|
110
110
|
.and migrate_down(<<~EOS.strip)
|
111
|
-
|
112
|
-
|
111
|
+
remove_column :adverts, :published_at
|
112
|
+
remove_column :adverts, :body
|
113
113
|
EOS
|
114
114
|
)
|
115
115
|
|
@@ -141,8 +141,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
141
141
|
remove_column :adverts, :name
|
142
142
|
EOS
|
143
143
|
.and migrate_down(<<~EOS.strip)
|
144
|
-
|
145
|
-
|
144
|
+
add_column :adverts, :name, :string, limit: 250, null: true#{charset_and_collation}
|
145
|
+
remove_column :adverts, :title
|
146
146
|
EOS
|
147
147
|
)
|
148
148
|
|
@@ -179,7 +179,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
179
179
|
change_column :adverts, :title, :string, limit: 250, null: true, default: "Untitled"#{charset_and_collation}
|
180
180
|
EOS
|
181
181
|
.and migrate_down(<<~EOS.strip)
|
182
|
-
|
182
|
+
change_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
183
183
|
EOS
|
184
184
|
)
|
185
185
|
|
@@ -209,7 +209,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
209
209
|
change_column :adverts, :price, :integer, limit: 3, null: true
|
210
210
|
EOS
|
211
211
|
.and migrate_down(<<~EOS.strip)
|
212
|
-
|
212
|
+
change_column :adverts, :price, :integer, limit: 2, null: true
|
213
213
|
EOS
|
214
214
|
)
|
215
215
|
|
@@ -306,7 +306,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
306
306
|
change_column :adverts, :description, :text, limit: 4294967295, null: false#{charset_and_collation}
|
307
307
|
EOS
|
308
308
|
.and migrate_down(<<~EOS.strip)
|
309
|
-
|
309
|
+
change_column :adverts, :description, :text#{', limit: 255' if defined?(Mysql2)}, null: true#{charset_and_collation}
|
310
310
|
EOS
|
311
311
|
)
|
312
312
|
|
@@ -323,7 +323,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
323
323
|
change_column :adverts, :description, :text, limit: 4294967295, null: false#{charset_and_collation}
|
324
324
|
EOS
|
325
325
|
.and migrate_down(<<~EOS.strip)
|
326
|
-
|
326
|
+
change_column :adverts, :description, :text#{', limit: 255' if defined?(Mysql2)}, null: true#{charset_and_collation}
|
327
327
|
EOS
|
328
328
|
)
|
329
329
|
end
|
@@ -363,14 +363,14 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
363
363
|
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" if defined?(Mysql2)}
|
364
364
|
EOS
|
365
365
|
.and migrate_down(<<~EOS.strip)
|
366
|
-
|
367
|
-
|
368
|
-
|
366
|
+
#{"remove_foreign_key :adverts, name: :index_adverts_on_category_id" if defined?(Mysql2)}
|
367
|
+
remove_index :adverts, name: :index_adverts_on_category_id
|
368
|
+
remove_column :adverts, :category_id
|
369
369
|
EOS
|
370
370
|
)
|
371
371
|
|
372
372
|
Advert.field_specs.delete(:category_id)
|
373
|
-
Advert.index_definitions.delete_if { |spec| spec.fields==["category_id"] }
|
373
|
+
Advert.index_definitions.delete_if { |spec| spec.fields == ["category_id"] }
|
374
374
|
|
375
375
|
# If you specify a custom foreign key, the migration generator observes that:
|
376
376
|
|
@@ -411,7 +411,9 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
411
411
|
Advert.field_specs.delete(:category_id)
|
412
412
|
Advert.index_definitions.delete_if { |spec| spec.fields == ["category_id"] }
|
413
413
|
|
414
|
-
# You can specify the index name with :
|
414
|
+
# You can specify the index name with index: 'name' [deprecated]
|
415
|
+
|
416
|
+
expect(Kernel).to receive(:warn).with(/belongs_to index: 'name' is deprecated; use index: \{ name: 'name' \} instead/i)
|
415
417
|
|
416
418
|
class Category < ActiveRecord::Base; end
|
417
419
|
class Advert < ActiveRecord::Base
|
@@ -431,6 +433,26 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
431
433
|
Advert.field_specs.delete(:category_id)
|
432
434
|
Advert.index_definitions.delete_if { |spec| spec.fields == ["category_id"] }
|
433
435
|
|
436
|
+
# You can specify the index name with index: { name: }'name', unique: true|false }
|
437
|
+
|
438
|
+
class Category < ActiveRecord::Base; end
|
439
|
+
class Advert < ActiveRecord::Base
|
440
|
+
declare_schema { }
|
441
|
+
belongs_to :category, index: { name: 'my_index', unique: false }
|
442
|
+
end
|
443
|
+
|
444
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
445
|
+
migrate_up(<<~EOS.strip)
|
446
|
+
add_column :adverts, :category_id, :integer, limit: 8, null: false
|
447
|
+
add_index :adverts, [:category_id], name: :my_index
|
448
|
+
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
449
|
+
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
450
|
+
EOS
|
451
|
+
)
|
452
|
+
|
453
|
+
Advert.field_specs.delete(:category_id)
|
454
|
+
Advert.index_definitions.delete_if { |spec| spec.fields == ["category_id"] }
|
455
|
+
|
434
456
|
### Timestamps and Optimimistic Locking
|
435
457
|
|
436
458
|
# `updated_at` and `created_at` can be declared with the shorthand `timestamps`.
|
@@ -452,11 +474,11 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
452
474
|
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
453
475
|
EOS
|
454
476
|
.and migrate_down(<<~EOS.strip)
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
477
|
+
#{"remove_foreign_key :adverts, name: :index_adverts_on_c_id\n" +
|
478
|
+
"remove_foreign_key :adverts, name: :index_adverts_on_category_id" if defined?(Mysql2)}
|
479
|
+
remove_column :adverts, :lock_version
|
480
|
+
remove_column :adverts, :updated_at
|
481
|
+
remove_column :adverts, :created_at
|
460
482
|
EOS
|
461
483
|
)
|
462
484
|
|
@@ -468,22 +490,29 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
468
490
|
|
469
491
|
# You can add an index to a field definition
|
470
492
|
|
493
|
+
expect(Kernel).to receive(:warn).with(/belongs_to index: 'name' is deprecated; use index: \{ name: 'name' \} instead/i)
|
494
|
+
expect(Kernel).to receive(:warn).with(/belongs_to unique: true\|false is deprecated; use index: \{ unique: true\|false \} instead/i)
|
495
|
+
|
471
496
|
class Advert < ActiveRecord::Base
|
472
497
|
declare_schema do
|
473
498
|
string :title, index: true, limit: 250, null: true
|
474
499
|
end
|
500
|
+
belongs_to :category, index: 'my_index', unique: false
|
475
501
|
end
|
476
502
|
|
477
503
|
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
478
504
|
migrate_up(<<~EOS.strip)
|
479
505
|
add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
506
|
+
add_column :adverts, :category_id, :integer, limit: 8, null: false
|
480
507
|
add_index :adverts, [:title], name: :index_adverts_on_title
|
508
|
+
add_index :adverts, [:category_id], name: :my_index
|
481
509
|
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
482
510
|
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
483
511
|
EOS
|
484
512
|
)
|
485
513
|
|
486
|
-
Advert.
|
514
|
+
Advert.field_specs.delete(:category_id)
|
515
|
+
Advert.index_definitions.delete_if { |spec| spec.fields == ["title"] || spec.fields == ["category_id"] }
|
487
516
|
|
488
517
|
# You can ask for a unique index
|
489
518
|
|
@@ -521,7 +550,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
521
550
|
EOS
|
522
551
|
)
|
523
552
|
|
524
|
-
Advert.index_definitions.delete_if { |spec| spec.fields==["title"] }
|
553
|
+
Advert.index_definitions.delete_if { |spec| spec.fields == ["title"] }
|
525
554
|
|
526
555
|
# You can ask for an index outside of the fields block
|
527
556
|
|
@@ -540,16 +569,16 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
540
569
|
|
541
570
|
Advert.index_definitions.delete_if { |spec| spec.fields == ["title"] }
|
542
571
|
|
543
|
-
# The available options for the index function are
|
572
|
+
# The available options for the index function are :unique, :name, :where, and :length.
|
544
573
|
|
545
574
|
class Advert < ActiveRecord::Base
|
546
|
-
index :title, unique:
|
575
|
+
index :title, unique: false, name: 'my_index', length: 10
|
547
576
|
end
|
548
577
|
|
549
578
|
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
550
579
|
migrate_up(<<~EOS.strip)
|
551
580
|
add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
|
552
|
-
add_index :adverts, [:title], name: :my_index,
|
581
|
+
add_index :adverts, [:title], name: :my_index, length: 10
|
553
582
|
#{"add_foreign_key :adverts, :categories, column: :category_id, name: :index_adverts_on_category_id\n" +
|
554
583
|
"add_foreign_key :adverts, :categories, column: :c_id, name: :index_adverts_on_c_id" if defined?(Mysql2)}
|
555
584
|
EOS
|
@@ -572,7 +601,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
572
601
|
EOS
|
573
602
|
)
|
574
603
|
|
575
|
-
Advert.index_definitions.delete_if { |spec| spec.fields==["title", "category_id"] }
|
604
|
+
Advert.index_definitions.delete_if { |spec| spec.fields == ["title", "category_id"] }
|
576
605
|
|
577
606
|
# Finally, you can specify that the migration generator should completely ignore an
|
578
607
|
# index by passing its name to ignore_index in the model.
|
@@ -645,10 +674,10 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
645
674
|
remove_column :advertisements, :name
|
646
675
|
EOS
|
647
676
|
.and migrate_down(<<~EOS.strip)
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
677
|
+
add_column :advertisements, :name, :string, limit: 250, null: true#{charset_and_collation}
|
678
|
+
remove_column :advertisements, :body
|
679
|
+
remove_column :advertisements, :title
|
680
|
+
rename_table :advertisements, :adverts
|
652
681
|
EOS
|
653
682
|
)
|
654
683
|
|
@@ -665,9 +694,9 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
665
694
|
drop_table :adverts
|
666
695
|
EOS
|
667
696
|
.and migrate_down(<<~EOS.strip)
|
668
|
-
|
669
|
-
|
670
|
-
|
697
|
+
create_table "adverts"#{table_options}, force: :cascade do |t|
|
698
|
+
t.string "name", limit: 250#{charset_and_collation}
|
699
|
+
end
|
671
700
|
EOS
|
672
701
|
)
|
673
702
|
|
@@ -700,8 +729,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
700
729
|
add_index :adverts, [:type], name: :on_type
|
701
730
|
EOS
|
702
731
|
.and migrate_down(<<~EOS.strip)
|
703
|
-
|
704
|
-
|
732
|
+
remove_index :adverts, name: :on_type
|
733
|
+
remove_column :adverts, :type
|
705
734
|
EOS
|
706
735
|
)
|
707
736
|
end
|
@@ -709,7 +738,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
709
738
|
Advert.field_specs.delete(:type)
|
710
739
|
nuke_model_class(SuperFancyAdvert)
|
711
740
|
nuke_model_class(FancyAdvert)
|
712
|
-
Advert.index_definitions.delete_if { |spec| spec.fields==["type"] }
|
741
|
+
Advert.index_definitions.delete_if { |spec| spec.fields == ["type"] }
|
713
742
|
|
714
743
|
## Coping with multiple changes
|
715
744
|
|
@@ -744,8 +773,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
744
773
|
change_column :adverts, :name, :string, limit: 250, null: true, default: "No Name"#{charset_and_collation}
|
745
774
|
EOS
|
746
775
|
.and migrate_down(<<~EOS.strip)
|
747
|
-
|
748
|
-
|
776
|
+
change_column :adverts, :name, :string, limit: 250, null: true, default: "Untitled"#{charset_and_collation}
|
777
|
+
rename_column :adverts, :name, :title
|
749
778
|
EOS
|
750
779
|
)
|
751
780
|
|
@@ -855,21 +884,21 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
855
884
|
it 'will genereate unique constraint names' do
|
856
885
|
expect(Generators::DeclareSchema::Migration::Migrator.run).to(
|
857
886
|
migrate_up(<<~EOS.strip)
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
887
|
+
create_table :categories, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
|
888
|
+
t.string :name, limit: 250, null: true, charset: "utf8mb4", collation: "utf8mb4_bin"
|
889
|
+
end
|
890
|
+
create_table :advertisers, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
|
891
|
+
t.string :name, limit: 250, null: true, charset: "utf8mb4", collation: "utf8mb4_bin"
|
892
|
+
t.integer :category_id, limit: 8, null: false
|
893
|
+
end
|
894
|
+
create_table :affiliates, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
|
895
|
+
t.string :name, limit: 250, null: true, charset: "utf8mb4", collation: "utf8mb4_bin"
|
896
|
+
t.integer :category_id, limit: 8, null: false
|
897
|
+
end
|
898
|
+
add_index :advertisers, [:category_id], name: :index_advertisers_on_category_id
|
899
|
+
add_index :affiliates, [:category_id], name: :index_affiliates_on_category_id
|
900
|
+
add_foreign_key :advertisers, :categories, column: :category_id, name: :index_advertisers_on_category_id
|
901
|
+
add_foreign_key :affiliates, :categories, column: :category_id, name: :index_affiliates_on_category_id
|
873
902
|
EOS
|
874
903
|
)
|
875
904
|
migrate
|
@@ -36,26 +36,102 @@ RSpec.describe DeclareSchema::Model::IndexDefinition do
|
|
36
36
|
|
37
37
|
describe 'instance methods' do
|
38
38
|
let(:model) { model_class.new }
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
39
|
+
let(:fields) { ['last_name', 'first_name'] }
|
40
|
+
let(:options) { {} }
|
41
|
+
subject(:instance) { described_class.new(model_class, fields, **options) }
|
42
|
+
|
43
|
+
describe 'attr_readers' do
|
44
|
+
describe '#table' do
|
45
|
+
subject { instance.table }
|
46
|
+
|
47
|
+
it { is_expected.to eq(model_class.table_name) }
|
48
|
+
|
49
|
+
context 'with table_name option' do
|
50
|
+
let(:options) { { table_name: 'auth_users' } }
|
51
|
+
|
52
|
+
it { is_expected.to eq('auth_users') }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '#fields' do
|
57
|
+
subject { instance.fields }
|
58
|
+
|
59
|
+
it { is_expected.to eq(fields) }
|
60
|
+
end
|
61
|
+
|
62
|
+
describe '#explicit_name' do
|
63
|
+
subject { instance.explicit_name }
|
64
|
+
|
65
|
+
it { is_expected.to eq(nil) }
|
66
|
+
|
67
|
+
context 'with name option' do
|
68
|
+
let(:options) { { name: 'index_auth_users_on_last_name_and_first_name' } }
|
69
|
+
|
70
|
+
it { is_expected.to eq('index_auth_users_on_last_name_and_first_name') }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe '#length' do
|
75
|
+
subject { instance.length }
|
76
|
+
let(:options) { { length: length } }
|
77
|
+
|
78
|
+
context 'with integer length' do
|
79
|
+
let(:length) { 2 }
|
80
|
+
|
81
|
+
it { is_expected.to eq(length) }
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'with Hash length' do
|
85
|
+
let(:length) { { name: 2 } }
|
86
|
+
|
87
|
+
it { is_expected.to eq(length) }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe '#options' do
|
92
|
+
subject { instance.options }
|
93
|
+
let(:options) { { name: 'my_index', unique: false, where: "(name like 'a%')", length: 10 } }
|
94
|
+
|
95
|
+
it { is_expected.to eq(options) }
|
96
|
+
end
|
97
|
+
|
98
|
+
describe '#with_name' do
|
99
|
+
subject { instance.with_name('new_name') }
|
100
|
+
|
101
|
+
it { is_expected.to be_kind_of(described_class) }
|
102
|
+
it { expect(instance.name).to eq('index_index_definition_test_models_on_last_name_and_first_name') }
|
103
|
+
it { expect(subject.name).to eq('new_name') }
|
104
|
+
end
|
47
105
|
end
|
106
|
+
end
|
48
107
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
[
|
58
|
-
|
108
|
+
describe 'class methods' do
|
109
|
+
let(:model) { model_class.new }
|
110
|
+
|
111
|
+
describe 'index_definitions' do
|
112
|
+
it do
|
113
|
+
expect(model_class.index_definitions.size).to eq(1)
|
114
|
+
|
115
|
+
expect(model_class.index_definitions[0].name).to eq('index_index_definition_test_models_on_name')
|
116
|
+
expect(model_class.index_definitions[0].fields).to eq(['name'])
|
117
|
+
expect(model_class.index_definitions[0].unique).to eq(false)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe 'has index_definitions_with_primary_key' do
|
122
|
+
it do
|
123
|
+
expect(model_class.index_definitions_with_primary_key).to be_kind_of(Array)
|
124
|
+
result = model_class.index_definitions_with_primary_key.sort_by(&:name)
|
125
|
+
expect(result.size).to eq(2)
|
126
|
+
|
127
|
+
expect(result[0].name).to eq('PRIMARY')
|
128
|
+
expect(result[0].fields).to eq(['id'])
|
129
|
+
expect(result[0].unique).to eq(true)
|
130
|
+
|
131
|
+
expect(result[1].name).to eq('index_index_definition_test_models_on_name')
|
132
|
+
expect(result[1].fields).to eq(['name'])
|
133
|
+
expect(result[1].unique).to eq(false)
|
134
|
+
end
|
59
135
|
end
|
60
136
|
end
|
61
137
|
|
@@ -86,12 +162,12 @@ RSpec.describe DeclareSchema::Model::IndexDefinition do
|
|
86
162
|
context 'with single-column PK' do
|
87
163
|
it 'returns the indexes for the model' do
|
88
164
|
expect(subject.size).to eq(2), subject.inspect
|
89
|
-
expect(
|
90
|
-
|
91
|
-
)
|
92
|
-
expect(
|
93
|
-
|
94
|
-
)
|
165
|
+
expect(subject[0].name).to eq('index_definition_test_models_on_name')
|
166
|
+
expect(subject[0].columns).to eq(['name'])
|
167
|
+
expect(subject[0].unique).to eq(true)
|
168
|
+
expect(subject[1].name).to eq('PRIMARY')
|
169
|
+
expect(subject[1].columns).to eq(['id'])
|
170
|
+
expect(subject[1].unique).to eq(true)
|
95
171
|
end
|
96
172
|
end
|
97
173
|
|
@@ -100,9 +176,9 @@ RSpec.describe DeclareSchema::Model::IndexDefinition do
|
|
100
176
|
|
101
177
|
it 'returns the indexes for the model' do
|
102
178
|
expect(subject.size).to eq(1), subject.inspect
|
103
|
-
expect(
|
104
|
-
|
105
|
-
)
|
179
|
+
expect(subject[0].name).to eq('PRIMARY')
|
180
|
+
expect(subject[0].columns).to eq(['fk1_id', 'fk2_id'])
|
181
|
+
expect(subject[0].unique).to eq(true)
|
106
182
|
end
|
107
183
|
end
|
108
184
|
end
|
@@ -30,7 +30,7 @@ RSpec.describe DeclareSchema::SchemaChange::IndexAdd do
|
|
30
30
|
end
|
31
31
|
|
32
32
|
context 'with where:' do
|
33
|
-
let(:where) { "'last_name like 'A%'"}
|
33
|
+
let(:where) { "'last_name like 'A%'" }
|
34
34
|
subject { described_class.new(table_name, column_names, name: name, unique: unique, where: where) }
|
35
35
|
|
36
36
|
it 'responds with command' do
|
@@ -45,6 +45,33 @@ RSpec.describe DeclareSchema::SchemaChange::IndexAdd do
|
|
45
45
|
expect(subject.up).to eq("add_index :#{table_name}, #{column_names.map(&:to_sym).inspect}, name: #{name.to_sym.inspect}, unique: true\n")
|
46
46
|
end
|
47
47
|
end
|
48
|
+
|
49
|
+
context 'with limit: nil' do
|
50
|
+
let(:limit) { nil }
|
51
|
+
subject { described_class.new(table_name, column_names, name: name, unique: unique, limit: limit) }
|
52
|
+
|
53
|
+
it 'responds with command' do
|
54
|
+
expect(subject.up).to eq("add_index :#{table_name}, #{column_names.map(&:to_sym).inspect}, name: #{name.to_sym.inspect}\n")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'with limit: 2' do
|
59
|
+
let(:limit) { 2 }
|
60
|
+
subject { described_class.new(table_name, column_names, name: name, unique: unique, limit: limit) }
|
61
|
+
|
62
|
+
it 'responds with command' do
|
63
|
+
expect(subject.up).to eq("add_index :#{table_name}, #{column_names.map(&:to_sym).inspect}, name: #{name.to_sym.inspect}, limit: #{limit}\n")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'with limit: hash' do
|
68
|
+
let(:limit) { { last_name: 10, first_name: 1 } }
|
69
|
+
subject { described_class.new(table_name, column_names, name: name, unique: unique, limit: limit) }
|
70
|
+
|
71
|
+
it 'responds with command' do
|
72
|
+
expect(subject.up).to eq("add_index :#{table_name}, #{column_names.map(&:to_sym).inspect}, name: #{name.to_sym.inspect}, limit: { last_name: 10, first_name: 1 }\n")
|
73
|
+
end
|
74
|
+
end
|
48
75
|
end
|
49
76
|
|
50
77
|
describe '#down' do
|