declare_schema 0.11.1 → 0.12.0.pre.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 +7 -0
- data/Gemfile.lock +1 -1
- data/README.md +5 -5
- data/lib/declare_schema/extensions/active_record/fields_declaration.rb +4 -2
- data/lib/declare_schema/model.rb +56 -10
- data/lib/declare_schema/model/column.rb +1 -1
- data/lib/declare_schema/model/field_spec.rb +4 -4
- data/lib/declare_schema/model/habtm_model_shim.rb +2 -2
- data/lib/declare_schema/model/index_definition.rb +7 -5
- data/lib/declare_schema/version.rb +1 -1
- data/lib/generators/declare_schema/migration/migrator.rb +15 -10
- data/spec/lib/declare_schema/api_spec.rb +2 -0
- data/spec/lib/declare_schema/field_spec_spec.rb +1 -1
- data/spec/lib/declare_schema/interactive_primary_key_spec.rb +6 -8
- data/spec/lib/declare_schema/migration_generator_spec.rb +179 -58
- data/spec/lib/declare_schema/model/habtm_model_shim_spec.rb +4 -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: e1353d236bb050d93155e56eab2bf5e5d31da06338970b13278c07ed86ac3180
|
4
|
+
data.tar.gz: f70054b7f56fd18264abe2e719167a2d83ebaa143279dc784dfcaf16b4cb4514
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7e541c39b62bf7228842f20830ae52d1b49dca66a6a834f38478cc3320aa65dea1c7b85becbcc75edf7382470e7bdf0bc9949a3665e72ff778a26aaae65ae15a
|
7
|
+
data.tar.gz: f3bdcaad7b1aa8341d168c5d10461737b54e7781dfadce2da26e289f2ca3192b6dd9775bdb8c99a040b17ba415be2725fcbd3451c518c96e901227f2572cf95c
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,12 @@ Inspired by [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
4
4
|
|
5
5
|
Note: this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
6
6
|
|
7
|
+
## [0.12.0] - Unreleased
|
8
|
+
### Added
|
9
|
+
- `belongs_to` now always infers the `limit:` of the foreign key to match that of the primary key it points to.
|
10
|
+
Note: this isn't possible for polymorphic foreign keys, so it assumes `limit: 8` there...unless the schema
|
11
|
+
was migrated in the past with `limit: 4`.
|
12
|
+
|
7
13
|
## [0.11.1] - 2021-03-26
|
8
14
|
### Fixed
|
9
15
|
- Fixed a bug where up and down in generated migration would be empty in Rails 4.
|
@@ -168,6 +174,7 @@ using the appropriate Rails configuration attributes.
|
|
168
174
|
### Added
|
169
175
|
- Initial version from https://github.com/Invoca/hobo_fields v4.1.0.
|
170
176
|
|
177
|
+
[0.12.0]: https://github.com/Invoca/declare_schema/compare/v0.11.1...v0.12.0
|
171
178
|
[0.11.1]: https://github.com/Invoca/declare_schema/compare/v0.11.0...v0.11.1
|
172
179
|
[0.11.0]: https://github.com/Invoca/declare_schema/compare/v0.10.1...v0.11.0
|
173
180
|
[0.10.1]: https://github.com/Invoca/declare_schema/compare/v0.10.0...v0.10.1
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -4,10 +4,10 @@ Declare your Rails/active_record model schemas and have database migrations gene
|
|
4
4
|
|
5
5
|
## Example
|
6
6
|
|
7
|
-
Make a model and declare your schema within a `
|
7
|
+
Make a model and declare your schema within a `declare_schema do ... end` block:
|
8
8
|
```ruby
|
9
9
|
class Company < ActiveRecord::Base
|
10
|
-
|
10
|
+
declare_schema do
|
11
11
|
company_name :string, limit: 100
|
12
12
|
ticker_symbol :string, limit: 4, null: true, index: true, unique: true
|
13
13
|
employee_count :integer
|
@@ -187,7 +187,7 @@ at three separate levels
|
|
187
187
|
|
188
188
|
### Table Configuration
|
189
189
|
In order to configure a table's default character set and collation, the `charset` and
|
190
|
-
`collation` arguments can be added to the `
|
190
|
+
`collation` arguments can be added to the `declare_schema` block.
|
191
191
|
|
192
192
|
For example, if you have a comments model that needs `utf8mb4` support, it would look
|
193
193
|
like the following:
|
@@ -197,7 +197,7 @@ like the following:
|
|
197
197
|
# frozen_string_literal: true
|
198
198
|
|
199
199
|
class Comment < ActiveRecord::Base
|
200
|
-
|
200
|
+
declare_schema charset: "utf8mb4", collation: "utf8mb4_bin" do
|
201
201
|
subject :string, limit: 255
|
202
202
|
content :text, limit: 0xffff_ffff
|
203
203
|
end
|
@@ -217,7 +217,7 @@ look like the following:
|
|
217
217
|
# frozen_string_literal: true
|
218
218
|
|
219
219
|
class Comment < ActiveRecord::Base
|
220
|
-
|
220
|
+
declare_schema do
|
221
221
|
subject :string, limit: 255
|
222
222
|
context :text, limit: 0xffff_ffff, charset: "utf8mb4", collation: "utf8mb4_bin"
|
223
223
|
end
|
@@ -7,12 +7,14 @@ require 'declare_schema/field_declaration_dsl'
|
|
7
7
|
|
8
8
|
module DeclareSchema
|
9
9
|
module Macros
|
10
|
+
attr_reader :_table_options
|
11
|
+
|
10
12
|
def fields(table_options = {}, &block)
|
11
13
|
# Any model that calls 'fields' gets DeclareSchema::Model behavior
|
12
14
|
DeclareSchema::Model.mix_in(self)
|
13
15
|
|
14
16
|
@include_in_migration = true
|
15
|
-
@
|
17
|
+
@_table_options = table_options
|
16
18
|
|
17
19
|
if block
|
18
20
|
dsl = DeclareSchema::FieldDeclarationDsl.new(self)
|
@@ -31,7 +33,7 @@ module DeclareSchema
|
|
31
33
|
|
32
34
|
# @include_in_migration = false #||= options.fetch(:include_in_migration, true); options.delete(:include_in_migration)
|
33
35
|
@include_in_migration = true # TODO: Add back or delete the include_in_migration feature
|
34
|
-
@
|
36
|
+
@_table_options = table_options
|
35
37
|
|
36
38
|
if block
|
37
39
|
dsl = DeclareSchema::Dsl.new(self, null: false)
|
data/lib/declare_schema/model.rb
CHANGED
@@ -99,7 +99,10 @@ module DeclareSchema
|
|
99
99
|
end
|
100
100
|
end
|
101
101
|
|
102
|
-
# Extend belongs_to so that it
|
102
|
+
# Extend belongs_to so that it
|
103
|
+
# 1. creates a FieldSpec for the foreign key
|
104
|
+
# 2. declares an index on the foreign key
|
105
|
+
# 3. declares a foreign_key constraint
|
103
106
|
def belongs_to(name, scope = nil, **options)
|
104
107
|
column_options = {}
|
105
108
|
|
@@ -109,7 +112,10 @@ module DeclareSchema
|
|
109
112
|
options[:optional] # infer :null from :optional
|
110
113
|
end || false
|
111
114
|
column_options[:default] = options.delete(:default) if options.has_key?(:default)
|
112
|
-
|
115
|
+
if options.has_key?(:limit)
|
116
|
+
options.delete(:limit)
|
117
|
+
ActiveSupport::Deprecation.warn("belongs_to limit: is deprecated since it is now inferred")
|
118
|
+
end
|
113
119
|
|
114
120
|
index_options = {}
|
115
121
|
index_options[:name] = options.delete(:index) if options.has_key?(:index)
|
@@ -136,7 +142,25 @@ module DeclareSchema
|
|
136
142
|
|
137
143
|
refl = reflections[name.to_s] or raise "Couldn't find reflection #{name} in #{reflections.keys}"
|
138
144
|
fkey = refl.foreign_key or raise "Couldn't find foreign_key for #{name} in #{refl.inspect}"
|
139
|
-
|
145
|
+
fkey_id_column_options = column_options.dup
|
146
|
+
|
147
|
+
# Note: the foreign key limit: should match the primary key limit:. (If there is a foreign key constraint,
|
148
|
+
# those limits _must_ match.) We'd like to call _infer_fk_limit and get the limit right from the PK.
|
149
|
+
# But we can't here, because that will mess up the autoloader to follow every belongs_to association right
|
150
|
+
# when it is declared. So instead we assume :bigint (integer limit: 8) below, while also registering this
|
151
|
+
# pre_migration: callback to double-check that assumption Just In Time--right before we generate a migration.
|
152
|
+
#
|
153
|
+
# The one downside of this approach is that application code that asks the field_spec for the declared
|
154
|
+
# foreign key limit: will always get 8 back even if this is a grandfathered foreign key that points to
|
155
|
+
# a limit: 4 primary key. It seems unlikely that any application code would do this.
|
156
|
+
fkey_id_column_options[:pre_migration] = ->(field_spec) do
|
157
|
+
if (inferred_limit = _infer_fk_limit(fkey, refl))
|
158
|
+
field_spec.sql_options[:limit] = inferred_limit
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
declare_field(fkey.to_sym, :bigint, fkey_id_column_options)
|
163
|
+
|
140
164
|
if refl.options[:polymorphic]
|
141
165
|
foreign_type = options[:foreign_type] || "#{name}_type"
|
142
166
|
_declare_polymorphic_type_field(foreign_type, column_options)
|
@@ -147,6 +171,28 @@ module DeclareSchema
|
|
147
171
|
end
|
148
172
|
end
|
149
173
|
|
174
|
+
def _infer_fk_limit(fkey, refl)
|
175
|
+
if refl.options[:polymorphic]
|
176
|
+
if (fkey_column = columns_hash[fkey.to_s]) && fkey_column.type == :integer
|
177
|
+
fkey_column.limit
|
178
|
+
end
|
179
|
+
else
|
180
|
+
klass = refl.klass or raise "Couldn't find belongs_to klass for #{name} in #{refl.inspect}"
|
181
|
+
if (pk_id_type = klass._table_options&.[](:id))
|
182
|
+
if pk_id_type == :integer
|
183
|
+
4
|
184
|
+
end
|
185
|
+
else
|
186
|
+
if klass.table_exists? && (pk_column = klass.columns_hash[klass._declared_primary_key])
|
187
|
+
pk_id_type = pk_column.type
|
188
|
+
if pk_id_type == :integer
|
189
|
+
pk_column.limit
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
150
196
|
if ::ActiveSupport::VERSION::MAJOR < 5
|
151
197
|
def primary_key
|
152
198
|
super || 'id'
|
@@ -155,27 +201,27 @@ module DeclareSchema
|
|
155
201
|
|
156
202
|
# returns the primary key (String) as declared with primary_key =
|
157
203
|
# unlike the `primary_key` method, DOES NOT query the database to find the actual primary key in use right now
|
158
|
-
# if no explicit primary key set, returns the
|
159
|
-
def
|
204
|
+
# if no explicit primary key set, returns the _default_declared_primary_key
|
205
|
+
def _declared_primary_key
|
160
206
|
if defined?(@primary_key)
|
161
207
|
@primary_key&.to_s
|
162
|
-
end ||
|
208
|
+
end || _default_declared_primary_key
|
163
209
|
end
|
164
210
|
|
165
211
|
private
|
166
212
|
|
167
|
-
# if this is a derived class, returns the base class's
|
213
|
+
# if this is a derived class, returns the base class's _declared_primary_key
|
168
214
|
# otherwise, returns 'id'
|
169
|
-
def
|
215
|
+
def _default_declared_primary_key
|
170
216
|
if self == base_class
|
171
217
|
'id'
|
172
218
|
else
|
173
|
-
base_class.
|
219
|
+
base_class._declared_primary_key
|
174
220
|
end
|
175
221
|
end
|
176
222
|
|
177
223
|
def _rails_default_primary_key
|
178
|
-
::DeclareSchema::Model::IndexDefinition.new(self, [
|
224
|
+
::DeclareSchema::Model::IndexDefinition.new(self, [_declared_primary_key.to_sym], unique: true, name: DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME)
|
179
225
|
end
|
180
226
|
|
181
227
|
# Declares the "foo_type" field that accompanies the "foo_id"
|
@@ -59,7 +59,7 @@ module DeclareSchema
|
|
59
59
|
# might be getting migrated to a new type. We should be using just type as below. -Colin
|
60
60
|
column.type_cast_from_database(default_value)
|
61
61
|
else
|
62
|
-
cast_type = ActiveRecord::Base.connection.send(:lookup_cast_type, type) or
|
62
|
+
cast_type = ActiveRecord::Base.connection.send(:lookup_cast_type, type.to_s) or
|
63
63
|
raise "cast_type not found for #{type}"
|
64
64
|
cast_type.deserialize(default_value)
|
65
65
|
end
|
@@ -47,9 +47,9 @@ module DeclareSchema
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def initialize(model, name, type, position: 0, **options)
|
50
|
-
|
50
|
+
_declared_primary_key = model._declared_primary_key
|
51
51
|
|
52
|
-
name.to_s ==
|
52
|
+
name.to_s == _declared_primary_key and raise ArgumentError, "you may not provide a field spec for the primary key #{name.inspect}"
|
53
53
|
|
54
54
|
@model = model
|
55
55
|
@name = name.to_sym
|
@@ -99,8 +99,8 @@ module DeclareSchema
|
|
99
99
|
|
100
100
|
if @type.in?([:text, :string])
|
101
101
|
if ActiveRecord::Base.connection.class.name.match?(/mysql/i)
|
102
|
-
@options[:charset] ||= model.
|
103
|
-
@options[:collation] ||= model.
|
102
|
+
@options[:charset] ||= model._table_options&.[](:charset) || ::DeclareSchema.default_charset
|
103
|
+
@options[:collation] ||= model._table_options&.[](:collation) || ::DeclareSchema.default_collation
|
104
104
|
else
|
105
105
|
@options.delete(:charset)
|
106
106
|
@options.delete(:collation)
|
@@ -29,7 +29,7 @@ module DeclareSchema
|
|
29
29
|
@connection = connection
|
30
30
|
end
|
31
31
|
|
32
|
-
def
|
32
|
+
def _table_options
|
33
33
|
{}
|
34
34
|
end
|
35
35
|
|
@@ -47,7 +47,7 @@ module DeclareSchema
|
|
47
47
|
false # no single-column primary key in database
|
48
48
|
end
|
49
49
|
|
50
|
-
def
|
50
|
+
def _declared_primary_key
|
51
51
|
false # no single-column primary key declared
|
52
52
|
end
|
53
53
|
|
@@ -37,7 +37,7 @@ module DeclareSchema
|
|
37
37
|
def for_model(model, old_table_name = nil)
|
38
38
|
t = old_table_name || model.table_name
|
39
39
|
|
40
|
-
primary_key_columns = Array(model.connection.primary_key(t)).presence ||
|
40
|
+
primary_key_columns = Array(model.connection.primary_key(t)).presence || fallback_find_primary_key(model, t) or
|
41
41
|
raise "could not find primary key for table #{t} in #{model.connection.columns(t).inspect}"
|
42
42
|
|
43
43
|
primary_key_found = false
|
@@ -67,7 +67,7 @@ module DeclareSchema
|
|
67
67
|
private
|
68
68
|
|
69
69
|
# This is the old approach which is still needed for MySQL in Rails 4 and SQLite
|
70
|
-
def
|
70
|
+
def fallback_find_primary_key(model, table)
|
71
71
|
ActiveRecord::Base.connection.class.name.match?(/SQLite3Adapter/) || ActiveSupport::VERSION::MAJOR < 5 or return nil
|
72
72
|
|
73
73
|
connection = model.connection.dup
|
@@ -83,9 +83,11 @@ module DeclareSchema
|
|
83
83
|
end
|
84
84
|
end
|
85
85
|
|
86
|
-
pk_index = connection.indexes(table).find { |index| index.name.to_s == PRIMARY_KEY_NAME }
|
87
|
-
|
88
|
-
|
86
|
+
if (pk_index = connection.indexes(table).find { |index| index.name.to_s == PRIMARY_KEY_NAME })
|
87
|
+
Array(pk_index.columns)
|
88
|
+
elsif model.connection.columns(table).any? { |col| col.name == 'id' }
|
89
|
+
['id']
|
90
|
+
end
|
89
91
|
end
|
90
92
|
end
|
91
93
|
|
@@ -187,6 +187,12 @@ module Generators
|
|
187
187
|
models, db_tables = models_and_tables
|
188
188
|
models_by_table_name = {}
|
189
189
|
models.each do |m|
|
190
|
+
m.try(:field_specs)&.each do |_name, field_spec|
|
191
|
+
if (pre_migration = field_spec.options.delete(:pre_migration))
|
192
|
+
pre_migration.call(field_spec)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
190
196
|
if !models_by_table_name.has_key?(m.table_name)
|
191
197
|
models_by_table_name[m.table_name] = m
|
192
198
|
elsif m.superclass == models_by_table_name[m.table_name].superclass.superclass
|
@@ -203,8 +209,8 @@ module Generators
|
|
203
209
|
|
204
210
|
to_create = model_table_names - db_tables
|
205
211
|
to_drop = db_tables - model_table_names - self.class.always_ignore_tables
|
206
|
-
to_change = model_table_names
|
207
212
|
to_rename = extract_table_renames!(to_create, to_drop)
|
213
|
+
to_change = model_table_names
|
208
214
|
|
209
215
|
renames = to_rename.map do |old_name, new_name|
|
210
216
|
::DeclareSchema::SchemaChange::TableRename.new(old_name, new_name)
|
@@ -297,14 +303,14 @@ module Generators
|
|
297
303
|
end
|
298
304
|
|
299
305
|
def create_table_options(model, disable_auto_increment)
|
300
|
-
primary_key = model.
|
306
|
+
primary_key = model._declared_primary_key
|
301
307
|
if primary_key.blank? || disable_auto_increment
|
302
308
|
{ id: false }
|
303
309
|
elsif primary_key == "id"
|
304
310
|
{ id: :bigint }
|
305
311
|
else
|
306
312
|
{ primary_key: primary_key.to_sym }
|
307
|
-
end
|
313
|
+
end.merge(model._table_options)
|
308
314
|
end
|
309
315
|
|
310
316
|
def table_options_for_model(model)
|
@@ -312,8 +318,8 @@ module Generators
|
|
312
318
|
{}
|
313
319
|
else
|
314
320
|
{
|
315
|
-
charset: model.
|
316
|
-
collation: model.
|
321
|
+
charset: model._table_options&.[](:charset) || ::DeclareSchema.default_charset,
|
322
|
+
collation: model._table_options&.[](:collation) || ::DeclareSchema.default_collation
|
317
323
|
}
|
318
324
|
end
|
319
325
|
end
|
@@ -336,18 +342,17 @@ module Generators
|
|
336
342
|
new_table_name = model.table_name
|
337
343
|
|
338
344
|
db_columns = model.connection.columns(current_table_name).index_by(&:name)
|
339
|
-
|
340
|
-
|
341
|
-
db_columns.delete(model._defined_primary_key)
|
345
|
+
if (pk = model._declared_primary_key.presence)
|
346
|
+
key_was_in_db_columns = db_columns.delete(pk)
|
342
347
|
end
|
343
348
|
|
344
349
|
model_column_names = model.field_specs.keys.map(&:to_s)
|
345
350
|
db_column_names = db_columns.keys.map(&:to_s)
|
346
351
|
|
347
352
|
to_add = model_column_names - db_column_names
|
348
|
-
to_add += [
|
353
|
+
to_add += [pk] unless key_was_in_db_columns
|
349
354
|
to_remove = db_column_names - model_column_names
|
350
|
-
to_remove -= [
|
355
|
+
to_remove -= [pk.to_sym] if pk # TODO: The .to_sym here means this code is always a no-op, right? -Colin
|
351
356
|
|
352
357
|
to_rename = extract_column_renames!(to_add, to_remove, new_table_name)
|
353
358
|
|
@@ -41,10 +41,12 @@ RSpec.describe 'DeclareSchema API' do
|
|
41
41
|
|
42
42
|
if ActiveSupport::VERSION::MAJOR == 5
|
43
43
|
# TODO: get this to work on Travis for Rails 6
|
44
|
+
# TODO: uncomment since we're not on Travis any more? -Colin
|
44
45
|
generate_migrations '-n', '-m'
|
45
46
|
end
|
46
47
|
|
47
48
|
require 'advert'
|
49
|
+
Advert.reset_primary_key
|
48
50
|
|
49
51
|
## The Basics
|
50
52
|
|
@@ -6,7 +6,7 @@ rescue LoadError
|
|
6
6
|
end
|
7
7
|
|
8
8
|
RSpec.describe DeclareSchema::Model::FieldSpec do
|
9
|
-
let(:model) { double('model',
|
9
|
+
let(:model) { double('model', _table_options: {}, _declared_primary_key: 'id') }
|
10
10
|
let(:col_spec) { double('col_spec', type: :string) }
|
11
11
|
|
12
12
|
before do
|
@@ -19,7 +19,7 @@ RSpec.describe 'DeclareSchema Migration Generator interactive primary key' do
|
|
19
19
|
end
|
20
20
|
|
21
21
|
generate_migrations '-n', '-m'
|
22
|
-
expect(Foo.
|
22
|
+
expect(Foo._declared_primary_key).to eq('foo_id')
|
23
23
|
|
24
24
|
### migrate from
|
25
25
|
# rename from custom primary_key
|
@@ -31,7 +31,7 @@ RSpec.describe 'DeclareSchema Migration Generator interactive primary key' do
|
|
31
31
|
|
32
32
|
allow_any_instance_of(DeclareSchema::Support::ThorShell).to receive(:ask).with(/one of the rename choices or press enter to keep/) { 'id' }
|
33
33
|
generate_migrations '-n', '-m'
|
34
|
-
expect(Foo.
|
34
|
+
expect(Foo._declared_primary_key).to eq('id')
|
35
35
|
|
36
36
|
nuke_model_class(Foo)
|
37
37
|
|
@@ -72,7 +72,7 @@ RSpec.describe 'DeclareSchema Migration Generator interactive primary key' do
|
|
72
72
|
|
73
73
|
allow_any_instance_of(DeclareSchema::Support::ThorShell).to receive(:ask).with(/one of the rename choices or press enter to keep/) { 'drop id' }
|
74
74
|
generate_migrations '-n', '-m'
|
75
|
-
expect(Foo.
|
75
|
+
expect(Foo._declared_primary_key).to eq('foo_id')
|
76
76
|
end
|
77
77
|
end
|
78
78
|
end
|
@@ -80,8 +80,7 @@ RSpec.describe 'DeclareSchema Migration Generator interactive primary key' do
|
|
80
80
|
context 'Using declare_schema' do
|
81
81
|
it "allows alternate primary keys" do
|
82
82
|
class Foo < ActiveRecord::Base
|
83
|
-
declare_schema
|
84
|
-
end
|
83
|
+
declare_schema { }
|
85
84
|
self.primary_key = "foo_id"
|
86
85
|
end
|
87
86
|
|
@@ -91,15 +90,14 @@ RSpec.describe 'DeclareSchema Migration Generator interactive primary key' do
|
|
91
90
|
### migrate from
|
92
91
|
# rename from custom primary_key
|
93
92
|
class Foo < ActiveRecord::Base
|
94
|
-
declare_schema
|
95
|
-
end
|
93
|
+
declare_schema { }
|
96
94
|
self.primary_key = "id"
|
97
95
|
end
|
98
96
|
|
99
97
|
puts "\n\e[45m Please enter 'id' (no quotes) at the next prompt \e[0m"
|
100
98
|
allow_any_instance_of(DeclareSchema::Support::ThorShell).to receive(:ask).with(/one of the rename choices or press enter to keep/) { 'id' }
|
101
99
|
generate_migrations '-n', '-m'
|
102
|
-
expect(Foo.
|
100
|
+
expect(Foo._declared_primary_key).to eq('id')
|
103
101
|
|
104
102
|
nuke_model_class(Foo)
|
105
103
|
|
@@ -441,10 +441,10 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
441
441
|
Advert.field_specs.delete(:category_id)
|
442
442
|
Advert.index_definitions.delete_if { |spec| spec.fields == ["category_id"] }
|
443
443
|
|
444
|
-
### Timestamps and
|
444
|
+
### Timestamps and Optimistic Locking
|
445
445
|
|
446
446
|
# `updated_at` and `created_at` can be declared with the shorthand `timestamps`.
|
447
|
-
# Similarly, `lock_version` can be declared with the "shorthand" `
|
447
|
+
# Similarly, `lock_version` can be declared with the "shorthand" `optimistic_lock`.
|
448
448
|
|
449
449
|
class Advert < ActiveRecord::Base
|
450
450
|
fields do
|
@@ -2039,7 +2039,6 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
2039
2039
|
add_foreign_key :affiliates, :categories, column: :category_id, name: :index_affiliates_on_category_id
|
2040
2040
|
EOS
|
2041
2041
|
)
|
2042
|
-
binding.pry
|
2043
2042
|
migrate
|
2044
2043
|
|
2045
2044
|
nuke_model_class(Advertiser)
|
@@ -2242,80 +2241,202 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
2242
2241
|
let(:optional_flag) { { false => optional_false, true => optional_true } }
|
2243
2242
|
|
2244
2243
|
describe 'belongs_to' do
|
2245
|
-
|
2246
|
-
|
2247
|
-
|
2248
|
-
|
2244
|
+
context 'with AdCategory and Advert in DB' do
|
2245
|
+
before do
|
2246
|
+
unless defined?(AdCategory)
|
2247
|
+
class AdCategory < ActiveRecord::Base
|
2248
|
+
declare_schema { }
|
2249
|
+
end
|
2249
2250
|
end
|
2250
|
-
end
|
2251
2251
|
|
2252
|
-
|
2253
|
-
|
2254
|
-
|
2255
|
-
|
2256
|
-
|
2252
|
+
class Advert < ActiveRecord::Base
|
2253
|
+
declare_schema do
|
2254
|
+
string :name, limit: 250, null: true
|
2255
|
+
integer :category_id, limit: 8
|
2256
|
+
integer :nullable_category_id, limit: 8, null: true
|
2257
|
+
end
|
2257
2258
|
end
|
2259
|
+
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
2260
|
+
ActiveRecord::Migration.class_eval(up)
|
2258
2261
|
end
|
2259
|
-
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
2260
|
-
ActiveRecord::Migration.class_eval(up)
|
2261
|
-
end
|
2262
|
-
|
2263
|
-
it 'passes through optional: when given' do
|
2264
|
-
class AdvertBelongsTo < ActiveRecord::Base
|
2265
|
-
self.table_name = 'adverts'
|
2266
|
-
declare_schema { }
|
2267
|
-
reset_column_information
|
2268
|
-
belongs_to :ad_category, optional: true
|
2269
|
-
end
|
2270
|
-
expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_true)
|
2271
|
-
end
|
2272
2262
|
|
2273
|
-
|
2274
|
-
it 'passes through optional: true, null: false' do
|
2263
|
+
it 'passes through optional: when given' do
|
2275
2264
|
class AdvertBelongsTo < ActiveRecord::Base
|
2276
2265
|
self.table_name = 'adverts'
|
2277
2266
|
declare_schema { }
|
2278
2267
|
reset_column_information
|
2279
|
-
belongs_to :ad_category, optional: true
|
2268
|
+
belongs_to :ad_category, optional: true
|
2280
2269
|
end
|
2281
2270
|
expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_true)
|
2282
|
-
expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(false)
|
2283
2271
|
end
|
2284
2272
|
|
2285
|
-
|
2286
|
-
|
2287
|
-
|
2288
|
-
|
2289
|
-
|
2290
|
-
|
2273
|
+
describe 'contradictory settings' do # contradictory settings are ok--for example, during migration
|
2274
|
+
it 'passes through optional: true, null: false' do
|
2275
|
+
class AdvertBelongsTo < ActiveRecord::Base
|
2276
|
+
self.table_name = 'adverts'
|
2277
|
+
declare_schema { }
|
2278
|
+
reset_column_information
|
2279
|
+
belongs_to :ad_category, optional: true, null: false
|
2280
|
+
end
|
2281
|
+
expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_true)
|
2282
|
+
expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(false)
|
2283
|
+
end
|
2284
|
+
|
2285
|
+
it 'passes through optional: false, null: true' do
|
2286
|
+
class AdvertBelongsTo < ActiveRecord::Base
|
2287
|
+
self.table_name = 'adverts'
|
2288
|
+
declare_schema { }
|
2289
|
+
reset_column_information
|
2290
|
+
belongs_to :ad_category, optional: false, null: true
|
2291
|
+
end
|
2292
|
+
expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_false)
|
2293
|
+
expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(true)
|
2294
|
+
end
|
2295
|
+
end
|
2296
|
+
|
2297
|
+
[false, true].each do |nullable|
|
2298
|
+
context "nullable=#{nullable}" do
|
2299
|
+
it 'infers optional: from null:' do
|
2300
|
+
eval <<~EOS
|
2301
|
+
class AdvertBelongsTo < ActiveRecord::Base
|
2302
|
+
declare_schema { }
|
2303
|
+
belongs_to :ad_category, null: #{nullable}
|
2304
|
+
end
|
2305
|
+
EOS
|
2306
|
+
expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_flag[nullable])
|
2307
|
+
expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(nullable)
|
2308
|
+
end
|
2309
|
+
|
2310
|
+
it 'infers null: from optional:' do
|
2311
|
+
eval <<~EOS
|
2312
|
+
class AdvertBelongsTo < ActiveRecord::Base
|
2313
|
+
declare_schema { }
|
2314
|
+
belongs_to :ad_category, optional: #{nullable}
|
2315
|
+
end
|
2316
|
+
EOS
|
2317
|
+
expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_flag[nullable])
|
2318
|
+
expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(nullable)
|
2319
|
+
end
|
2291
2320
|
end
|
2292
|
-
|
2293
|
-
|
2321
|
+
end
|
2322
|
+
|
2323
|
+
it 'deprecates limit:' do
|
2324
|
+
expect(ActiveSupport::Deprecation).to receive(:warn).with("belongs_to limit: is deprecated since it is now inferred")
|
2325
|
+
eval <<~EOS
|
2326
|
+
class UsingLimit < ActiveRecord::Base
|
2327
|
+
declare_schema { }
|
2328
|
+
belongs_to :ad_category, limit: 4
|
2329
|
+
end
|
2330
|
+
EOS
|
2294
2331
|
end
|
2295
2332
|
end
|
2296
2333
|
|
2297
|
-
|
2298
|
-
|
2299
|
-
|
2300
|
-
|
2301
|
-
|
2302
|
-
|
2303
|
-
|
2304
|
-
|
2305
|
-
|
2306
|
-
|
2307
|
-
|
2334
|
+
context 'when parent object PKs have different limits' do
|
2335
|
+
before do
|
2336
|
+
class IdDefault < ActiveRecord::Base
|
2337
|
+
declare_schema { }
|
2338
|
+
end
|
2339
|
+
class Id4 < ActiveRecord::Base
|
2340
|
+
declare_schema id: :integer do
|
2341
|
+
end
|
2342
|
+
end
|
2343
|
+
class Id8 < ActiveRecord::Base
|
2344
|
+
declare_schema id: :bigint do
|
2345
|
+
end
|
2346
|
+
end
|
2347
|
+
class Fk < ActiveRecord::Base
|
2348
|
+
declare_schema { }
|
2349
|
+
belongs_to :id_default, (ActiveSupport::VERSION::MAJOR < 5 ? { constraint: false } : {})
|
2350
|
+
belongs_to :id4, (ActiveSupport::VERSION::MAJOR < 5 ? { constraint: false } : {})
|
2351
|
+
belongs_to :id8, (ActiveSupport::VERSION::MAJOR < 5 ? { constraint: false } : {})
|
2352
|
+
end
|
2353
|
+
end
|
2354
|
+
|
2355
|
+
it 'creates the proper PKs' do
|
2356
|
+
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
2357
|
+
|
2358
|
+
create_id4_defaults = up.split("\n").grep(/create_table :id_defaults/).first
|
2359
|
+
expect(create_id4_defaults).to be, up
|
2360
|
+
expect(create_id4_defaults).to match(/, id: :bigint/)
|
2361
|
+
|
2362
|
+
create_id4s = up.split("\n").grep(/create_table :id4s/).first
|
2363
|
+
expect(create_id4s).to be, up
|
2364
|
+
expect(create_id4s).to match(/, id: :integer/)
|
2365
|
+
|
2366
|
+
create_id8s = up.split("\n").grep(/create_table :id8s/).first
|
2367
|
+
expect(create_id8s).to be, up
|
2368
|
+
expect(create_id8s).to match(/, id: :bigint/)
|
2369
|
+
end
|
2370
|
+
|
2371
|
+
it 'infers the correct FK type from the create_table id: type' do
|
2372
|
+
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
2373
|
+
|
2374
|
+
create_fks = up.split("\n").grep(/t\.integer /).map { |command| command.gsub(', null: false', '').gsub(/^ +/, '') }
|
2375
|
+
if defined?(SQLite3)
|
2376
|
+
create_fks.map! { |command| command.gsub(/limit: [a-z0-9]+/, 'limit: X') }
|
2377
|
+
expect(create_fks).to eq([
|
2378
|
+
't.integer :id_default_id, limit: X',
|
2379
|
+
't.integer :id4_id, limit: X',
|
2380
|
+
't.integer :id8_id, limit: X'
|
2381
|
+
]), up
|
2382
|
+
else
|
2383
|
+
expect(create_fks).to eq([
|
2384
|
+
't.integer :id_default_id, limit: 8',
|
2385
|
+
't.integer :id4_id, limit: 4',
|
2386
|
+
't.integer :id8_id, limit: 8'
|
2387
|
+
]), up
|
2308
2388
|
end
|
2389
|
+
end
|
2309
2390
|
|
2310
|
-
|
2311
|
-
|
2312
|
-
|
2313
|
-
|
2314
|
-
|
2315
|
-
|
2316
|
-
|
2317
|
-
|
2318
|
-
|
2391
|
+
context "when parent objects were migrated before and later definitions don't have explicit id:" do
|
2392
|
+
before do
|
2393
|
+
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
2394
|
+
ActiveRecord::Migration.class_eval up
|
2395
|
+
nuke_model_class(IdDefault)
|
2396
|
+
nuke_model_class(Id4)
|
2397
|
+
nuke_model_class(Id8)
|
2398
|
+
nuke_model_class(Fk)
|
2399
|
+
ActiveRecord::Base.connection.schema_cache.clear!
|
2400
|
+
|
2401
|
+
|
2402
|
+
class NewIdDefault < ActiveRecord::Base
|
2403
|
+
self.table_name = 'id_defaults'
|
2404
|
+
declare_schema { }
|
2405
|
+
end
|
2406
|
+
class NewId4 < ActiveRecord::Base
|
2407
|
+
self.table_name = 'id4s'
|
2408
|
+
declare_schema { }
|
2409
|
+
end
|
2410
|
+
class NewId8 < ActiveRecord::Base
|
2411
|
+
self.table_name = 'id8s'
|
2412
|
+
declare_schema { }
|
2413
|
+
end
|
2414
|
+
class NewFk < ActiveRecord::Base
|
2415
|
+
declare_schema { }
|
2416
|
+
belongs_to :new_id_default
|
2417
|
+
belongs_to :new_id4
|
2418
|
+
belongs_to :new_id8
|
2419
|
+
end
|
2420
|
+
end
|
2421
|
+
|
2422
|
+
it 'infers the correct FK :integer limit: ' do
|
2423
|
+
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
2424
|
+
|
2425
|
+
create_fks = up.split("\n").grep(/t\.integer /).map { |command| command.gsub(', null: false', '').gsub(/^ +/, '') }
|
2426
|
+
if defined?(SQLite3)
|
2427
|
+
create_fks.map! { |command| command.gsub(/limit: [a-z0-9]+/, 'limit: X') }
|
2428
|
+
expect(create_fks).to eq([
|
2429
|
+
't.integer :new_id_default_id, limit: X',
|
2430
|
+
't.integer :new_id4_id, limit: X',
|
2431
|
+
't.integer :new_id8_id, limit: X'
|
2432
|
+
]), up
|
2433
|
+
else
|
2434
|
+
expect(create_fks).to eq([
|
2435
|
+
't.integer :new_id_default_id, limit: 8',
|
2436
|
+
't.integer :new_id4_id, limit: 4',
|
2437
|
+
't.integer :new_id8_id, limit: 8'
|
2438
|
+
]), up
|
2439
|
+
end
|
2319
2440
|
end
|
2320
2441
|
end
|
2321
2442
|
end
|
@@ -55,7 +55,7 @@ RSpec.describe DeclareSchema::Model::HabtmModelShim do
|
|
55
55
|
|
56
56
|
describe '#table_options' do
|
57
57
|
it 'returns empty hash' do
|
58
|
-
expect(subject.
|
58
|
+
expect(subject._table_options).to eq({})
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
@@ -86,13 +86,13 @@ RSpec.describe DeclareSchema::Model::HabtmModelShim do
|
|
86
86
|
|
87
87
|
describe '#primary_key' do
|
88
88
|
it 'returns false' do
|
89
|
-
expect(subject.
|
89
|
+
expect(subject._declared_primary_key).to eq(false)
|
90
90
|
end
|
91
91
|
end
|
92
92
|
|
93
|
-
describe '#
|
93
|
+
describe '#_declared_primary_key' do
|
94
94
|
it 'returns false' do
|
95
|
-
expect(subject.
|
95
|
+
expect(subject._declared_primary_key).to eq(false)
|
96
96
|
end
|
97
97
|
end
|
98
98
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: declare_schema
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.12.0.pre.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Invoca Development adapted from hobo_fields by Tom Locke
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-04-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|