declare_schema 1.3.2.rp.1 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 177fdef8d03843627f34a461487ec92d222e5d18efe3a4881bc1b4326a069e99
4
- data.tar.gz: d5dac684d51bfad5e7e92214fcc9a7e1b4020a70aa4cd2fd8a5bbb0c718f1c71
3
+ metadata.gz: fe0a594fea50536d529c2d4e42280d9050726e1017524dbaff2b8a77e17c6b41
4
+ data.tar.gz: 89199ba5b58b936f93a7bfe0409c403b95da8bd771b425b2c61a6afd8a951f2a
5
5
  SHA512:
6
- metadata.gz: 7bdc6408f561f9aa70dad7e2208ae2022f8b30adce1c1350f598cae1c93b4dbd9f65f61a97324282fad9f0d2dd2126e90dda199781040f08564bc6998121c670
7
- data.tar.gz: 1057a1c6202b77f5854b119208227c7a7341f2c3d0cab7eb7a743322ebb60bddaec4eb77671d60035e610789a9d73c7b345868262aebe0e6052ff2c17c8a6d81
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.3.2] - Unreleased
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- declare_schema (1.3.2.rp.1)
4
+ declare_schema (1.4.0.colin.1)
5
5
  rails (>= 5.0)
6
6
 
7
7
  GEM
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: true, unique: true
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 the global level to customize default declaration for the following values:
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
- The default value for generate foreign keys can be set using the `DeclareSchema.default_generate_foreign_keys=` method.
176
- This value defaults to `true` and can only be set at the global level.
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 set
179
- the default `generate foreign keys` value to `false`:
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
- The default value for generate indexing can be set using the `DeclareSchema.default_generate_indexing=` method.
190
- This value defaults to `true` and can only be set at the global level.
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
- set the default `generate indexing` value to `false`:
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 the global level
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
- `DeclareSchema.db_migrate_command = "bundle exec rails db:migrate_immediate"`
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 accounts for it.
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` probably should not alter it. Accordingly,
242
- `declare_schema` can be configured to ignore tables.
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
- attr_reader :table, :fields, :explicit_name, :name, :unique, :where
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[:where])
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, name, unique, where].map(&:to_s)
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, unique].map(&:to_s)
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, table_name: @table_name, index_name: @index_name, unique: @unique, name: new_name)
139
+ self.class.new(@model, @fields, **{ **options, name: new_name })
127
140
  end
128
141
 
129
142
  alias eql? ==
@@ -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
- index_options = {}
118
- index_options[:name] = options.delete(:index) if options.has_key?(:index)
119
- index_options[:unique] = options.delete(:unique) if options.has_key?(:unique)
120
- index_options[:allow_equivalent] = options.delete(:allow_equivalent) if options.has_key?(:allow_equivalent)
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[:name] != false
182
+ index([foreign_type, fkey], **index_options) if index_options
161
183
  else
162
- index(fkey, **index_options) if index_options[:name] != false
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,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeclareSchema
4
- VERSION = "1.3.2.rp.1"
4
+ VERSION = "1.4.0.colin.1"
5
5
  end
@@ -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, unique: i.unique, where: i.where, name: i.name)
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
- remove_column :adverts, :published_at
112
- remove_column :adverts, :body
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
- add_column :adverts, :name, :string, limit: 250, null: true#{charset_and_collation}
145
- remove_column :adverts, :title
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
- change_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
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
- change_column :adverts, :price, :integer, limit: 2, null: true
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
- change_column :adverts, :description, :text#{', limit: 255' if defined?(Mysql2)}, null: true#{charset_and_collation}
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
- change_column :adverts, :description, :text#{', limit: 255' if defined?(Mysql2)}, null: true#{charset_and_collation}
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
- #{"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
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 :index
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
- #{"remove_foreign_key :adverts, name: :index_adverts_on_c_id\n" +
456
- "remove_foreign_key :adverts, name: :index_adverts_on_category_id" if defined?(Mysql2)}
457
- remove_column :adverts, :lock_version
458
- remove_column :adverts, :updated_at
459
- remove_column :adverts, :created_at
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.index_definitions.delete_if { |spec| spec.fields==["title"] }
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 `:unique` and `:name`
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: true, name: 'my_index'
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, unique: true
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
- add_column :advertisements, :name, :string, limit: 250, null: true#{charset_and_collation}
649
- remove_column :advertisements, :body
650
- remove_column :advertisements, :title
651
- rename_table :advertisements, :adverts
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
- create_table "adverts"#{table_options}, force: :cascade do |t|
669
- t.string "name", limit: 250#{charset_and_collation}
670
- end
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
- remove_index :adverts, name: :on_type
704
- remove_column :adverts, :type
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
- change_column :adverts, :name, :string, limit: 250, null: true, default: "Untitled"#{charset_and_collation}
748
- rename_column :adverts, :name, :title
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
- create_table :categories, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
859
- t.string :name, limit: 250, null: true, charset: "utf8mb4", collation: "utf8mb4_bin"
860
- end
861
- create_table :advertisers, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
862
- t.string :name, limit: 250, null: true, charset: "utf8mb4", collation: "utf8mb4_bin"
863
- t.integer :category_id, limit: 8, null: false
864
- end
865
- create_table :affiliates, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
866
- t.string :name, limit: 250, null: true, charset: "utf8mb4", collation: "utf8mb4_bin"
867
- t.integer :category_id, limit: 8, null: false
868
- end
869
- add_index :advertisers, [:category_id], name: :index_advertisers_on_category_id
870
- add_index :affiliates, [:category_id], name: :index_affiliates_on_category_id
871
- add_foreign_key :advertisers, :categories, column: :category_id, name: :index_advertisers_on_category_id
872
- add_foreign_key :affiliates, :categories, column: :category_id, name: :index_affiliates_on_category_id
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
- subject { declared_class.new(model_class) }
40
-
41
- it 'has index_definitions' do
42
- expect(model_class.index_definitions).to be_kind_of(Array)
43
- expect(model_class.index_definitions.map(&:name)).to eq(['index_index_definition_test_models_on_name'])
44
- expect([:name, :fields, :unique].map { |attr| model_class.index_definitions[0].send(attr)}).to eq(
45
- ['index_index_definition_test_models_on_name', ['name'], false]
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
- it 'has index_definitions_with_primary_key' do
50
- expect(model_class.index_definitions_with_primary_key).to be_kind_of(Array)
51
- result = model_class.index_definitions_with_primary_key.sort_by(&:name)
52
- expect(result.map(&:name)).to eq(['PRIMARY', 'index_index_definition_test_models_on_name'])
53
- expect([:name, :fields, :unique].map { |attr| result[0].send(attr)}).to eq(
54
- ['PRIMARY', ['id'], true]
55
- )
56
- expect([:name, :fields, :unique].map { |attr| result[1].send(attr)}).to eq(
57
- ['index_index_definition_test_models_on_name', ['name'], false]
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([:name, :columns, :unique].map { |attr| subject[0].send(attr) }).to eq(
90
- ['index_definition_test_models_on_name', ['name'], true]
91
- )
92
- expect([:name, :columns, :unique].map { |attr| subject[1].send(attr) }).to eq(
93
- ['PRIMARY', ['id'], true]
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([:name, :columns, :unique].map { |attr| subject[0].send(attr) }).to eq(
104
- ['PRIMARY', ['fk1_id', 'fk2_id'], true]
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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: declare_schema
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.2.rp.1
4
+ version: 1.4.0.colin.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Invoca Development adapted from hobo_fields by Tom Locke