declare_schema 0.11.1 → 0.13.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 +18 -0
- data/Gemfile.lock +1 -1
- data/README.md +26 -6
- data/lib/declare_schema.rb +6 -1
- data/lib/declare_schema/dsl.rb +6 -3
- data/lib/declare_schema/extensions/active_record/fields_declaration.rb +8 -7
- data/lib/declare_schema/model.rb +64 -21
- data/lib/declare_schema/model/column.rb +1 -1
- data/lib/declare_schema/model/field_spec.rb +12 -5
- 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/schema_change/column_add.rb +3 -3
- data/lib/declare_schema/version.rb +1 -1
- data/lib/generators/declare_schema/migration/migrator.rb +14 -10
- data/spec/lib/declare_schema/api_spec.rb +1 -3
- data/spec/lib/declare_schema/field_spec_spec.rb +43 -1
- data/spec/lib/declare_schema/interactive_primary_key_spec.rb +6 -8
- data/spec/lib/declare_schema/migration_generator_spec.rb +254 -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: fef97a705f29e37659ade688c2c64b30f0df1eabc76ab47ceb1272f2eec10845
|
|
4
|
+
data.tar.gz: 3602d932b5bae67c6cfd367750e399d44ab65afa4fd19c4923ff5e447b79ca40
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 770d10d283e399d3ca37537e7ed4c68d0dde1a6ba28e033148ed7ed86d0e0833256d0b2446688538ef34f39cac9c1fe535914824d4bf7e6de17d11554b876ec2
|
|
7
|
+
data.tar.gz: 134add2558ab3374d741e04839913257eb48e8b98d4b80a3bc3c35927369032fd9549d9a1dd2768c702386f44b4265c0379475cd01f8097288cad53b479a4ee9
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,21 @@ 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.13.0] - Unreleased
|
|
8
|
+
### Added
|
|
9
|
+
- Added support for `default_schema =` to apply a default schema to every model, unless disabld for that model with `declare_schema: false`.
|
|
10
|
+
|
|
11
|
+
## [0.12.1] - 2021-05-10
|
|
12
|
+
### Fixed
|
|
13
|
+
- When an `enum` type field is declared, there is now enforcement that its `limit:` must be an array of 1 or more Symbols,
|
|
14
|
+
and its `default:`--if given--must be a Symbol or `nil`.
|
|
15
|
+
|
|
16
|
+
## [0.12.0] - 2021-04-28
|
|
17
|
+
### Added
|
|
18
|
+
- `belongs_to` now always infers the `limit:` of the foreign key to match that of the primary key it points to.
|
|
19
|
+
Note: this isn't possible for polymorphic foreign keys, so it assumes `limit: 8` there...unless the schema
|
|
20
|
+
was migrated in the past with `limit: 4`.
|
|
21
|
+
|
|
7
22
|
## [0.11.1] - 2021-03-26
|
|
8
23
|
### Fixed
|
|
9
24
|
- Fixed a bug where up and down in generated migration would be empty in Rails 4.
|
|
@@ -168,6 +183,9 @@ using the appropriate Rails configuration attributes.
|
|
|
168
183
|
### Added
|
|
169
184
|
- Initial version from https://github.com/Invoca/hobo_fields v4.1.0.
|
|
170
185
|
|
|
186
|
+
[0.13.0]: https://github.com/Invoca/declare_schema/compare/v0.12.1...v0.13.0
|
|
187
|
+
[0.12.1]: https://github.com/Invoca/declare_schema/compare/v0.12.0...v0.12.1
|
|
188
|
+
[0.12.0]: https://github.com/Invoca/declare_schema/compare/v0.11.1...v0.12.0
|
|
171
189
|
[0.11.1]: https://github.com/Invoca/declare_schema/compare/v0.11.0...v0.11.1
|
|
172
190
|
[0.11.0]: https://github.com/Invoca/declare_schema/compare/v0.10.1...v0.11.0
|
|
173
191
|
[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
|
|
@@ -62,7 +62,7 @@ trigger the `eager_load!` on the `Rails` application and all `Rails::Engine`s lo
|
|
|
62
62
|
into scope. If you need to generate migrations for models that aren't automatically loaded by `eager_load!`,
|
|
63
63
|
load them in the `before_generating_migration` block.
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
For example:
|
|
66
66
|
|
|
67
67
|
```ruby
|
|
68
68
|
DeclareSchema::Migration::Migrator.before_generating_migration do
|
|
@@ -70,6 +70,26 @@ DeclareSchema::Migration::Migrator.before_generating_migration do
|
|
|
70
70
|
end
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
+
### default_schema
|
|
74
|
+
If there are default columns you would like in the schema for every model, you can define them in a block that is registered with
|
|
75
|
+
`DeclareSchema.default_schema =`. For example:
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
DeclareSchema.default_schema = -> do
|
|
79
|
+
timestamps
|
|
80
|
+
optimistic_lock
|
|
81
|
+
end
|
|
82
|
+
```
|
|
83
|
+
This will add these fields to the schema of each model (if not already there).
|
|
84
|
+
If you have a model where you don't want the defaults applied, that can be set with the `default_schema:` boolean option to `declare_schema` (the default value is true). For example:
|
|
85
|
+
```ruby
|
|
86
|
+
class User < ActiveRecord::Base
|
|
87
|
+
declare_schema default_schema: false do
|
|
88
|
+
...
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
```
|
|
92
|
+
|
|
73
93
|
### Global Configuration
|
|
74
94
|
Configurations can be set at the global level to customize default declaration for the following values:
|
|
75
95
|
|
|
@@ -187,7 +207,7 @@ at three separate levels
|
|
|
187
207
|
|
|
188
208
|
### Table Configuration
|
|
189
209
|
In order to configure a table's default character set and collation, the `charset` and
|
|
190
|
-
`collation` arguments can be added to the `
|
|
210
|
+
`collation` arguments can be added to the `declare_schema` block.
|
|
191
211
|
|
|
192
212
|
For example, if you have a comments model that needs `utf8mb4` support, it would look
|
|
193
213
|
like the following:
|
|
@@ -197,7 +217,7 @@ like the following:
|
|
|
197
217
|
# frozen_string_literal: true
|
|
198
218
|
|
|
199
219
|
class Comment < ActiveRecord::Base
|
|
200
|
-
|
|
220
|
+
declare_schema charset: "utf8mb4", collation: "utf8mb4_bin" do
|
|
201
221
|
subject :string, limit: 255
|
|
202
222
|
content :text, limit: 0xffff_ffff
|
|
203
223
|
end
|
|
@@ -217,7 +237,7 @@ look like the following:
|
|
|
217
237
|
# frozen_string_literal: true
|
|
218
238
|
|
|
219
239
|
class Comment < ActiveRecord::Base
|
|
220
|
-
|
|
240
|
+
declare_schema do
|
|
221
241
|
subject :string, limit: 255
|
|
222
242
|
context :text, limit: 0xffff_ffff, charset: "utf8mb4", collation: "utf8mb4_bin"
|
|
223
243
|
end
|
data/lib/declare_schema.rb
CHANGED
|
@@ -37,7 +37,7 @@ module DeclareSchema
|
|
|
37
37
|
|
|
38
38
|
class << self
|
|
39
39
|
attr_reader :default_charset, :default_collation, :default_text_limit, :default_string_limit, :default_null,
|
|
40
|
-
:default_generate_foreign_keys, :default_generate_indexing, :db_migrate_command
|
|
40
|
+
:default_generate_foreign_keys, :default_generate_indexing, :default_schema, :db_migrate_command
|
|
41
41
|
|
|
42
42
|
def to_class(type)
|
|
43
43
|
case type
|
|
@@ -85,6 +85,11 @@ module DeclareSchema
|
|
|
85
85
|
@default_generate_indexing = generate_indexing
|
|
86
86
|
end
|
|
87
87
|
|
|
88
|
+
def default_schema=(default_schema)
|
|
89
|
+
default_schema.nil? || default_schema.respond_to?(:call) or raise "default_schema must be nil or a block that responds to call"
|
|
90
|
+
@default_schema = default_schema
|
|
91
|
+
end
|
|
92
|
+
|
|
88
93
|
def db_migrate_command=(db_migrate_command)
|
|
89
94
|
db_migrate_command.is_a?(String) or raise ArgumentError, "db_migrate_command must be a string (got #{db_migrate_command.inspect})"
|
|
90
95
|
@db_migrate_command = db_migrate_command
|
data/lib/declare_schema/dsl.rb
CHANGED
|
@@ -7,7 +7,7 @@ module DeclareSchema
|
|
|
7
7
|
include ::Kernel # but we need the basic class methods
|
|
8
8
|
|
|
9
9
|
instance_methods.each do |m|
|
|
10
|
-
unless m.to_s.starts_with?('__') || m.in?([:object_id, :instance_eval])
|
|
10
|
+
unless m.to_s.starts_with?('__') || m.in?([:object_id, :instance_eval, :instance_exec])
|
|
11
11
|
undef_method(m)
|
|
12
12
|
end
|
|
13
13
|
end
|
|
@@ -32,8 +32,11 @@ module DeclareSchema
|
|
|
32
32
|
@model.declare_field(name, type, *(args + [@options.merge(options)]))
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
# TODO: make [:required] just another option. Either 'required: true] or 'optional: false'?
|
|
36
|
+
def method_missing(*args, **options)
|
|
37
|
+
args.count(&:itself) >= 2 or raise ::ArgumentError, "fields in declare_schema block must be declared as: type name, [:required], options (got #{args.inspect}, #{options.inspect})"
|
|
38
|
+
type, name, *required = args
|
|
39
|
+
field(name, type, *required, options)
|
|
37
40
|
end
|
|
38
41
|
end
|
|
39
42
|
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)
|
|
@@ -25,20 +27,19 @@ module DeclareSchema
|
|
|
25
27
|
end
|
|
26
28
|
deprecate :fields, deprecator: ActiveSupport::Deprecation.new('1.0', 'DeclareSchema')
|
|
27
29
|
|
|
28
|
-
def declare_schema(
|
|
30
|
+
def declare_schema(default_schema: true, **table_options, &block)
|
|
29
31
|
# Any model that calls 'fields' gets DeclareSchema::Model behavior
|
|
30
32
|
DeclareSchema::Model.mix_in(self)
|
|
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)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
dsl.instance_eval(&block)
|
|
40
|
+
dsl.instance_eval(&block)
|
|
41
|
+
if default_schema && DeclareSchema.default_schema
|
|
42
|
+
dsl.instance_exec(&DeclareSchema.default_schema)
|
|
42
43
|
end
|
|
43
44
|
end
|
|
44
45
|
end
|
data/lib/declare_schema/model.rb
CHANGED
|
@@ -34,19 +34,16 @@ module DeclareSchema
|
|
|
34
34
|
# supported options include :charset and :collation
|
|
35
35
|
inheriting_cattr_reader table_options: HashWithIndifferentAccess.new
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
declare_schema do |f|
|
|
43
|
-
f.field(inheritance_column, :string, limit: 255, null: true)
|
|
44
|
-
end
|
|
45
|
-
index(inheritance_column)
|
|
37
|
+
def self.inherited(klass)
|
|
38
|
+
unless klass.field_specs.has_key?(inheritance_column)
|
|
39
|
+
ic = inheritance_column
|
|
40
|
+
declare_schema do
|
|
41
|
+
field(ic, :string, limit: 255, null: true)
|
|
46
42
|
end
|
|
47
|
-
|
|
43
|
+
index(ic)
|
|
48
44
|
end
|
|
49
|
-
|
|
45
|
+
super
|
|
46
|
+
end
|
|
50
47
|
end
|
|
51
48
|
end
|
|
52
49
|
end
|
|
@@ -99,7 +96,10 @@ module DeclareSchema
|
|
|
99
96
|
end
|
|
100
97
|
end
|
|
101
98
|
|
|
102
|
-
# Extend belongs_to so that it
|
|
99
|
+
# Extend belongs_to so that it
|
|
100
|
+
# 1. creates a FieldSpec for the foreign key
|
|
101
|
+
# 2. declares an index on the foreign key
|
|
102
|
+
# 3. declares a foreign_key constraint
|
|
103
103
|
def belongs_to(name, scope = nil, **options)
|
|
104
104
|
column_options = {}
|
|
105
105
|
|
|
@@ -109,7 +109,10 @@ module DeclareSchema
|
|
|
109
109
|
options[:optional] # infer :null from :optional
|
|
110
110
|
end || false
|
|
111
111
|
column_options[:default] = options.delete(:default) if options.has_key?(:default)
|
|
112
|
-
|
|
112
|
+
if options.has_key?(:limit)
|
|
113
|
+
options.delete(:limit)
|
|
114
|
+
ActiveSupport::Deprecation.warn("belongs_to limit: is deprecated since it is now inferred")
|
|
115
|
+
end
|
|
113
116
|
|
|
114
117
|
index_options = {}
|
|
115
118
|
index_options[:name] = options.delete(:index) if options.has_key?(:index)
|
|
@@ -136,7 +139,25 @@ module DeclareSchema
|
|
|
136
139
|
|
|
137
140
|
refl = reflections[name.to_s] or raise "Couldn't find reflection #{name} in #{reflections.keys}"
|
|
138
141
|
fkey = refl.foreign_key or raise "Couldn't find foreign_key for #{name} in #{refl.inspect}"
|
|
139
|
-
|
|
142
|
+
fkey_id_column_options = column_options.dup
|
|
143
|
+
|
|
144
|
+
# Note: the foreign key limit: should match the primary key limit:. (If there is a foreign key constraint,
|
|
145
|
+
# those limits _must_ match.) We'd like to call _infer_fk_limit and get the limit right from the PK.
|
|
146
|
+
# But we can't here, because that will mess up the autoloader to follow every belongs_to association right
|
|
147
|
+
# when it is declared. So instead we assume :bigint (integer limit: 8) below, while also registering this
|
|
148
|
+
# pre_migration: callback to double-check that assumption Just In Time--right before we generate a migration.
|
|
149
|
+
#
|
|
150
|
+
# The one downside of this approach is that application code that asks the field_spec for the declared
|
|
151
|
+
# foreign key limit: will always get 8 back even if this is a grandfathered foreign key that points to
|
|
152
|
+
# a limit: 4 primary key. It seems unlikely that any application code would do this.
|
|
153
|
+
fkey_id_column_options[:pre_migration] = ->(field_spec) do
|
|
154
|
+
if (inferred_limit = _infer_fk_limit(fkey, refl))
|
|
155
|
+
field_spec.sql_options[:limit] = inferred_limit
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
declare_field(fkey.to_sym, :bigint, fkey_id_column_options)
|
|
160
|
+
|
|
140
161
|
if refl.options[:polymorphic]
|
|
141
162
|
foreign_type = options[:foreign_type] || "#{name}_type"
|
|
142
163
|
_declare_polymorphic_type_field(foreign_type, column_options)
|
|
@@ -147,6 +168,28 @@ module DeclareSchema
|
|
|
147
168
|
end
|
|
148
169
|
end
|
|
149
170
|
|
|
171
|
+
def _infer_fk_limit(fkey, refl)
|
|
172
|
+
if refl.options[:polymorphic]
|
|
173
|
+
if (fkey_column = columns_hash[fkey.to_s]) && fkey_column.type == :integer
|
|
174
|
+
fkey_column.limit
|
|
175
|
+
end
|
|
176
|
+
else
|
|
177
|
+
klass = refl.klass or raise "Couldn't find belongs_to klass for #{name} in #{refl.inspect}"
|
|
178
|
+
if (pk_id_type = klass._table_options&.[](:id))
|
|
179
|
+
if pk_id_type == :integer
|
|
180
|
+
4
|
|
181
|
+
end
|
|
182
|
+
else
|
|
183
|
+
if klass.table_exists? && (pk_column = klass.columns_hash[klass._declared_primary_key])
|
|
184
|
+
pk_id_type = pk_column.type
|
|
185
|
+
if pk_id_type == :integer
|
|
186
|
+
pk_column.limit
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
150
193
|
if ::ActiveSupport::VERSION::MAJOR < 5
|
|
151
194
|
def primary_key
|
|
152
195
|
super || 'id'
|
|
@@ -155,27 +198,27 @@ module DeclareSchema
|
|
|
155
198
|
|
|
156
199
|
# returns the primary key (String) as declared with primary_key =
|
|
157
200
|
# 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
|
|
201
|
+
# if no explicit primary key set, returns the _default_declared_primary_key
|
|
202
|
+
def _declared_primary_key
|
|
160
203
|
if defined?(@primary_key)
|
|
161
204
|
@primary_key&.to_s
|
|
162
|
-
end ||
|
|
205
|
+
end || _default_declared_primary_key
|
|
163
206
|
end
|
|
164
207
|
|
|
165
208
|
private
|
|
166
209
|
|
|
167
|
-
# if this is a derived class, returns the base class's
|
|
210
|
+
# if this is a derived class, returns the base class's _declared_primary_key
|
|
168
211
|
# otherwise, returns 'id'
|
|
169
|
-
def
|
|
212
|
+
def _default_declared_primary_key
|
|
170
213
|
if self == base_class
|
|
171
214
|
'id'
|
|
172
215
|
else
|
|
173
|
-
base_class.
|
|
216
|
+
base_class._declared_primary_key
|
|
174
217
|
end
|
|
175
218
|
end
|
|
176
219
|
|
|
177
220
|
def _rails_default_primary_key
|
|
178
|
-
::DeclareSchema::Model::IndexDefinition.new(self, [
|
|
221
|
+
::DeclareSchema::Model::IndexDefinition.new(self, [_declared_primary_key.to_sym], unique: true, name: DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME)
|
|
179
222
|
end
|
|
180
223
|
|
|
181
224
|
# 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
|
|
@@ -76,12 +76,19 @@ module DeclareSchema
|
|
|
76
76
|
when :bigint
|
|
77
77
|
@type = :integer
|
|
78
78
|
@options[:limit] = 8
|
|
79
|
+
when :enum
|
|
80
|
+
@options[:default].nil? || @options[:default].is_a?(Symbol) or
|
|
81
|
+
raise ArgumentError, "enum default: must be nil or a Symbol; got #{@options[:default].inspect}"
|
|
82
|
+
@options[:limit].is_a?(Array) && @options[:limit].size >= 1 && @options[:limit].all? { |value| value.is_a?(Symbol) } or
|
|
83
|
+
raise ArgumentError, "enum limit: must be an array of 1 or more Symbols; got #{@options[:limit].inspect}"
|
|
79
84
|
end
|
|
80
85
|
|
|
81
86
|
Column.native_type?(@type) or raise UnknownTypeError, "#{@type.inspect} not found in #{Column.native_types.inspect} for adapter #{ActiveRecord::Base.connection.class.name}"
|
|
82
87
|
|
|
83
|
-
if @type.in?([:string, :text, :binary, :varbinary, :integer
|
|
88
|
+
if @type.in?([:string, :text, :binary, :varbinary, :integer])
|
|
84
89
|
@options[:limit] ||= Column.native_types.dig(@type, :limit)
|
|
90
|
+
elsif @type.in?([:enum])
|
|
91
|
+
# nothing to do
|
|
85
92
|
else
|
|
86
93
|
@type != :decimal && @options.has_key?(:limit) and warn("unsupported limit: for SQL type #{@type} in field #{model}##{@name}")
|
|
87
94
|
@options.delete(:limit)
|
|
@@ -99,8 +106,8 @@ module DeclareSchema
|
|
|
99
106
|
|
|
100
107
|
if @type.in?([:text, :string])
|
|
101
108
|
if ActiveRecord::Base.connection.class.name.match?(/mysql/i)
|
|
102
|
-
@options[:charset] ||= model.
|
|
103
|
-
@options[:collation] ||= model.
|
|
109
|
+
@options[:charset] ||= model._table_options&.[](:charset) || ::DeclareSchema.default_charset
|
|
110
|
+
@options[:collation] ||= model._table_options&.[](:collation) || ::DeclareSchema.default_collation
|
|
104
111
|
else
|
|
105
112
|
@options.delete(:charset)
|
|
106
113
|
@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
|
|
|
@@ -6,9 +6,9 @@ module DeclareSchema
|
|
|
6
6
|
module SchemaChange
|
|
7
7
|
class ColumnAdd < Base
|
|
8
8
|
def initialize(table_name, column_name, column_type, **column_options)
|
|
9
|
-
@table_name = table_name
|
|
10
|
-
@column_name = column_name
|
|
11
|
-
@column_type = column_type
|
|
9
|
+
@table_name = table_name or raise ArgumentError, "must provide table_name"
|
|
10
|
+
@column_name = column_name or raise ArgumentError, "must provide column_name"
|
|
11
|
+
@column_type = column_type or raise ArgumentError, "must provide column_type"
|
|
12
12
|
@column_options = column_options
|
|
13
13
|
end
|
|
14
14
|
|
|
@@ -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,16 @@ 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
|
+
pk_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 if pk && !pk_was_in_db_columns
|
|
349
354
|
to_remove = db_column_names - model_column_names
|
|
350
|
-
to_remove -= [model._defined_primary_key.to_sym] if model._defined_primary_key.present?
|
|
351
355
|
|
|
352
356
|
to_rename = extract_column_renames!(to_add, to_remove, new_table_name)
|
|
353
357
|
|
|
@@ -40,16 +40,14 @@ RSpec.describe 'DeclareSchema API' do
|
|
|
40
40
|
load_models
|
|
41
41
|
|
|
42
42
|
if ActiveSupport::VERSION::MAJOR == 5
|
|
43
|
-
# TODO: get this to work on Travis for Rails 6
|
|
44
43
|
generate_migrations '-n', '-m'
|
|
45
44
|
end
|
|
46
45
|
|
|
47
46
|
require 'advert'
|
|
47
|
+
Advert.reset_primary_key
|
|
48
48
|
|
|
49
49
|
## The Basics
|
|
50
50
|
|
|
51
|
-
# The main feature of DeclareSchema, aside from the migration generator, is the ability to declare rich types for your fields. For example, you can declare that a field is an email address, and the field will be automatically validated for correct email address syntax.
|
|
52
|
-
|
|
53
51
|
### Field Types
|
|
54
52
|
|
|
55
53
|
# Field values are returned as the type you specify.
|
|
@@ -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
|
|
@@ -115,6 +115,48 @@ RSpec.describe DeclareSchema::Model::FieldSpec do
|
|
|
115
115
|
end
|
|
116
116
|
end
|
|
117
117
|
|
|
118
|
+
describe 'enum' do
|
|
119
|
+
before do
|
|
120
|
+
allow(::DeclareSchema::Model::Column).to receive(:native_types).and_wrap_original do |m, *_args|
|
|
121
|
+
result = m.call
|
|
122
|
+
if result.has_key?(:enum)
|
|
123
|
+
result
|
|
124
|
+
else
|
|
125
|
+
result.merge(enum: { name: "enum" })
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
describe 'default' do
|
|
131
|
+
it 'allows default of nil or a Symbol' do
|
|
132
|
+
[nil, :first].each do |default|
|
|
133
|
+
expect do
|
|
134
|
+
subject = described_class.new(model, :status, :enum, limit: [:first, :second, :third], default: default, null: false, position: 2)
|
|
135
|
+
expect(subject.default).to eq(default)
|
|
136
|
+
end.to_not raise_exception
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it 'raises ArgumentError if default is not nil or a Symbol' do
|
|
141
|
+
["first", 1].each do |default|
|
|
142
|
+
expect do
|
|
143
|
+
described_class.new(model, :status, :enum, limit: [:first, :second, :third], default: default, null: false, position: 2)
|
|
144
|
+
end.to raise_exception(ArgumentError, /enum default: must be nil or a Symbol; got #{Regexp.escape(default.inspect)}/)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
describe 'limit' do
|
|
150
|
+
it 'raises ArgumentError if any of the limit values are not Symbols' do
|
|
151
|
+
[['first', 'second', 'third'], [1, 2, 3], nil, []].each do |limit|
|
|
152
|
+
expect do
|
|
153
|
+
described_class.new(model, :status, :enum, limit: limit, null: false, position: 2)
|
|
154
|
+
end.to raise_exception(ArgumentError, /enum limit: must be an array of 1 or more Symbols; got #{Regexp.escape(limit.inspect)}/)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
118
160
|
if defined?(Mysql2)
|
|
119
161
|
describe 'varbinary' do # TODO: :varbinary is an Invoca addition to Rails; make it a configurable option
|
|
120
162
|
it 'is supported' 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
|
|
@@ -1860,6 +1860,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
|
1860
1860
|
class SuperFancyAdvert < FancyAdvert
|
|
1861
1861
|
end
|
|
1862
1862
|
|
|
1863
|
+
expect(Generators::DeclareSchema::Migration::Migrator.run.first).to be_present
|
|
1864
|
+
|
|
1863
1865
|
up, _ = Generators::DeclareSchema::Migration::Migrator.run do |migrations|
|
|
1864
1866
|
expect(migrations).to(
|
|
1865
1867
|
migrate_up(<<~EOS.strip)
|
|
@@ -2039,7 +2041,6 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
|
2039
2041
|
add_foreign_key :affiliates, :categories, column: :category_id, name: :index_affiliates_on_category_id
|
|
2040
2042
|
EOS
|
|
2041
2043
|
)
|
|
2042
|
-
binding.pry
|
|
2043
2044
|
migrate
|
|
2044
2045
|
|
|
2045
2046
|
nuke_model_class(Advertiser)
|
|
@@ -2242,80 +2243,202 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
|
2242
2243
|
let(:optional_flag) { { false => optional_false, true => optional_true } }
|
|
2243
2244
|
|
|
2244
2245
|
describe 'belongs_to' do
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2246
|
+
context 'with AdCategory and Advert in DB' do
|
|
2247
|
+
before do
|
|
2248
|
+
unless defined?(AdCategory)
|
|
2249
|
+
class AdCategory < ActiveRecord::Base
|
|
2250
|
+
declare_schema { }
|
|
2251
|
+
end
|
|
2249
2252
|
end
|
|
2250
|
-
end
|
|
2251
2253
|
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2254
|
+
class Advert < ActiveRecord::Base
|
|
2255
|
+
declare_schema do
|
|
2256
|
+
string :name, limit: 250, null: true
|
|
2257
|
+
integer :category_id, limit: 8
|
|
2258
|
+
integer :nullable_category_id, limit: 8, null: true
|
|
2259
|
+
end
|
|
2257
2260
|
end
|
|
2261
|
+
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
|
2262
|
+
ActiveRecord::Migration.class_eval(up)
|
|
2258
2263
|
end
|
|
2259
|
-
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
|
2260
|
-
ActiveRecord::Migration.class_eval(up)
|
|
2261
|
-
end
|
|
2262
2264
|
|
|
2263
|
-
|
|
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
|
-
|
|
2273
|
-
describe 'contradictory settings' do # contradictory settings are ok--for example, during migration
|
|
2274
|
-
it 'passes through optional: true, null: false' do
|
|
2265
|
+
it 'passes through optional: when given' do
|
|
2275
2266
|
class AdvertBelongsTo < ActiveRecord::Base
|
|
2276
2267
|
self.table_name = 'adverts'
|
|
2277
2268
|
declare_schema { }
|
|
2278
2269
|
reset_column_information
|
|
2279
|
-
belongs_to :ad_category, optional: true
|
|
2270
|
+
belongs_to :ad_category, optional: true
|
|
2280
2271
|
end
|
|
2281
2272
|
expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_true)
|
|
2282
|
-
expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(false)
|
|
2283
2273
|
end
|
|
2284
2274
|
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2275
|
+
describe 'contradictory settings' do # contradictory settings are ok--for example, during migration
|
|
2276
|
+
it 'passes through optional: true, null: false' do
|
|
2277
|
+
class AdvertBelongsTo < ActiveRecord::Base
|
|
2278
|
+
self.table_name = 'adverts'
|
|
2279
|
+
declare_schema { }
|
|
2280
|
+
reset_column_information
|
|
2281
|
+
belongs_to :ad_category, optional: true, null: false
|
|
2282
|
+
end
|
|
2283
|
+
expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_true)
|
|
2284
|
+
expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(false)
|
|
2285
|
+
end
|
|
2286
|
+
|
|
2287
|
+
it 'passes through optional: false, null: true' do
|
|
2288
|
+
class AdvertBelongsTo < ActiveRecord::Base
|
|
2289
|
+
self.table_name = 'adverts'
|
|
2290
|
+
declare_schema { }
|
|
2291
|
+
reset_column_information
|
|
2292
|
+
belongs_to :ad_category, optional: false, null: true
|
|
2293
|
+
end
|
|
2294
|
+
expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_false)
|
|
2295
|
+
expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(true)
|
|
2291
2296
|
end
|
|
2292
|
-
|
|
2293
|
-
|
|
2297
|
+
end
|
|
2298
|
+
|
|
2299
|
+
[false, true].each do |nullable|
|
|
2300
|
+
context "nullable=#{nullable}" do
|
|
2301
|
+
it 'infers optional: from null:' do
|
|
2302
|
+
eval <<~EOS
|
|
2303
|
+
class AdvertBelongsTo < ActiveRecord::Base
|
|
2304
|
+
declare_schema { }
|
|
2305
|
+
belongs_to :ad_category, null: #{nullable}
|
|
2306
|
+
end
|
|
2307
|
+
EOS
|
|
2308
|
+
expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_flag[nullable])
|
|
2309
|
+
expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(nullable)
|
|
2310
|
+
end
|
|
2311
|
+
|
|
2312
|
+
it 'infers null: from optional:' do
|
|
2313
|
+
eval <<~EOS
|
|
2314
|
+
class AdvertBelongsTo < ActiveRecord::Base
|
|
2315
|
+
declare_schema { }
|
|
2316
|
+
belongs_to :ad_category, optional: #{nullable}
|
|
2317
|
+
end
|
|
2318
|
+
EOS
|
|
2319
|
+
expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_flag[nullable])
|
|
2320
|
+
expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(nullable)
|
|
2321
|
+
end
|
|
2322
|
+
end
|
|
2323
|
+
end
|
|
2324
|
+
|
|
2325
|
+
it 'deprecates limit:' do
|
|
2326
|
+
expect(ActiveSupport::Deprecation).to receive(:warn).with("belongs_to limit: is deprecated since it is now inferred")
|
|
2327
|
+
eval <<~EOS
|
|
2328
|
+
class UsingLimit < ActiveRecord::Base
|
|
2329
|
+
declare_schema { }
|
|
2330
|
+
belongs_to :ad_category, limit: 4
|
|
2331
|
+
end
|
|
2332
|
+
EOS
|
|
2294
2333
|
end
|
|
2295
2334
|
end
|
|
2296
2335
|
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2336
|
+
context 'when parent object PKs have different limits' do
|
|
2337
|
+
before do
|
|
2338
|
+
class IdDefault < ActiveRecord::Base
|
|
2339
|
+
declare_schema { }
|
|
2340
|
+
end
|
|
2341
|
+
class Id4 < ActiveRecord::Base
|
|
2342
|
+
declare_schema id: :integer do
|
|
2343
|
+
end
|
|
2344
|
+
end
|
|
2345
|
+
class Id8 < ActiveRecord::Base
|
|
2346
|
+
declare_schema id: :bigint do
|
|
2347
|
+
end
|
|
2348
|
+
end
|
|
2349
|
+
class Fk < ActiveRecord::Base
|
|
2350
|
+
declare_schema { }
|
|
2351
|
+
belongs_to :id_default, (ActiveSupport::VERSION::MAJOR < 5 ? { constraint: false } : {})
|
|
2352
|
+
belongs_to :id4, (ActiveSupport::VERSION::MAJOR < 5 ? { constraint: false } : {})
|
|
2353
|
+
belongs_to :id8, (ActiveSupport::VERSION::MAJOR < 5 ? { constraint: false } : {})
|
|
2308
2354
|
end
|
|
2355
|
+
end
|
|
2309
2356
|
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2357
|
+
it 'creates the proper PKs' do
|
|
2358
|
+
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
|
2359
|
+
|
|
2360
|
+
create_id4_defaults = up.split("\n").grep(/create_table :id_defaults/).first
|
|
2361
|
+
expect(create_id4_defaults).to be, up
|
|
2362
|
+
expect(create_id4_defaults).to match(/, id: :bigint/)
|
|
2363
|
+
|
|
2364
|
+
create_id4s = up.split("\n").grep(/create_table :id4s/).first
|
|
2365
|
+
expect(create_id4s).to be, up
|
|
2366
|
+
expect(create_id4s).to match(/, id: :integer/)
|
|
2367
|
+
|
|
2368
|
+
create_id8s = up.split("\n").grep(/create_table :id8s/).first
|
|
2369
|
+
expect(create_id8s).to be, up
|
|
2370
|
+
expect(create_id8s).to match(/, id: :bigint/)
|
|
2371
|
+
end
|
|
2372
|
+
|
|
2373
|
+
it 'infers the correct FK type from the create_table id: type' do
|
|
2374
|
+
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
|
2375
|
+
|
|
2376
|
+
create_fks = up.split("\n").grep(/t\.integer /).map { |command| command.gsub(', null: false', '').gsub(/^ +/, '') }
|
|
2377
|
+
if defined?(SQLite3)
|
|
2378
|
+
create_fks.map! { |command| command.gsub(/limit: [a-z0-9]+/, 'limit: X') }
|
|
2379
|
+
expect(create_fks).to eq([
|
|
2380
|
+
't.integer :id_default_id, limit: X',
|
|
2381
|
+
't.integer :id4_id, limit: X',
|
|
2382
|
+
't.integer :id8_id, limit: X'
|
|
2383
|
+
]), up
|
|
2384
|
+
else
|
|
2385
|
+
expect(create_fks).to eq([
|
|
2386
|
+
't.integer :id_default_id, limit: 8',
|
|
2387
|
+
't.integer :id4_id, limit: 4',
|
|
2388
|
+
't.integer :id8_id, limit: 8'
|
|
2389
|
+
]), up
|
|
2390
|
+
end
|
|
2391
|
+
end
|
|
2392
|
+
|
|
2393
|
+
context "when parent objects were migrated before and later definitions don't have explicit id:" do
|
|
2394
|
+
before do
|
|
2395
|
+
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
|
2396
|
+
ActiveRecord::Migration.class_eval up
|
|
2397
|
+
nuke_model_class(IdDefault)
|
|
2398
|
+
nuke_model_class(Id4)
|
|
2399
|
+
nuke_model_class(Id8)
|
|
2400
|
+
nuke_model_class(Fk)
|
|
2401
|
+
ActiveRecord::Base.connection.schema_cache.clear!
|
|
2402
|
+
|
|
2403
|
+
|
|
2404
|
+
class NewIdDefault < ActiveRecord::Base
|
|
2405
|
+
self.table_name = 'id_defaults'
|
|
2406
|
+
declare_schema { }
|
|
2407
|
+
end
|
|
2408
|
+
class NewId4 < ActiveRecord::Base
|
|
2409
|
+
self.table_name = 'id4s'
|
|
2410
|
+
declare_schema { }
|
|
2411
|
+
end
|
|
2412
|
+
class NewId8 < ActiveRecord::Base
|
|
2413
|
+
self.table_name = 'id8s'
|
|
2414
|
+
declare_schema { }
|
|
2415
|
+
end
|
|
2416
|
+
class NewFk < ActiveRecord::Base
|
|
2417
|
+
declare_schema { }
|
|
2418
|
+
belongs_to :new_id_default
|
|
2419
|
+
belongs_to :new_id4
|
|
2420
|
+
belongs_to :new_id8
|
|
2421
|
+
end
|
|
2422
|
+
end
|
|
2423
|
+
|
|
2424
|
+
it 'infers the correct FK :integer limit: ' do
|
|
2425
|
+
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
|
2426
|
+
|
|
2427
|
+
create_fks = up.split("\n").grep(/t\.integer /).map { |command| command.gsub(', null: false', '').gsub(/^ +/, '') }
|
|
2428
|
+
if defined?(SQLite3)
|
|
2429
|
+
create_fks.map! { |command| command.gsub(/limit: [a-z0-9]+/, 'limit: X') }
|
|
2430
|
+
expect(create_fks).to eq([
|
|
2431
|
+
't.integer :new_id_default_id, limit: X',
|
|
2432
|
+
't.integer :new_id4_id, limit: X',
|
|
2433
|
+
't.integer :new_id8_id, limit: X'
|
|
2434
|
+
]), up
|
|
2435
|
+
else
|
|
2436
|
+
expect(create_fks).to eq([
|
|
2437
|
+
't.integer :new_id_default_id, limit: 8',
|
|
2438
|
+
't.integer :new_id4_id, limit: 4',
|
|
2439
|
+
't.integer :new_id8_id, limit: 8'
|
|
2440
|
+
]), up
|
|
2441
|
+
end
|
|
2319
2442
|
end
|
|
2320
2443
|
end
|
|
2321
2444
|
end
|
|
@@ -2370,5 +2493,78 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
|
2370
2493
|
end
|
|
2371
2494
|
end
|
|
2372
2495
|
end
|
|
2496
|
+
|
|
2497
|
+
context 'default_schema' do
|
|
2498
|
+
let(:default_schema_block) { nil }
|
|
2499
|
+
let(:declare_model) do
|
|
2500
|
+
-> do
|
|
2501
|
+
class Advert < active_record_base_class.constantize
|
|
2502
|
+
declare_schema do
|
|
2503
|
+
integer :price, limit: 8
|
|
2504
|
+
end
|
|
2505
|
+
end
|
|
2506
|
+
end
|
|
2507
|
+
end
|
|
2508
|
+
|
|
2509
|
+
before do
|
|
2510
|
+
DeclareSchema.default_schema = default_schema_block
|
|
2511
|
+
end
|
|
2512
|
+
|
|
2513
|
+
after do
|
|
2514
|
+
DeclareSchema.default_schema = nil
|
|
2515
|
+
end
|
|
2516
|
+
|
|
2517
|
+
context 'when unset' do
|
|
2518
|
+
it 'adds nothing' do
|
|
2519
|
+
declare_model.call
|
|
2520
|
+
|
|
2521
|
+
expect(Advert.field_specs.keys).to eq(['price'])
|
|
2522
|
+
end
|
|
2523
|
+
end
|
|
2524
|
+
|
|
2525
|
+
context 'when set to a block' do
|
|
2526
|
+
let(:default_schema_block) do
|
|
2527
|
+
-> do
|
|
2528
|
+
timestamps
|
|
2529
|
+
field :lock_version, :integer, default: 1
|
|
2530
|
+
end
|
|
2531
|
+
end
|
|
2532
|
+
|
|
2533
|
+
it 'adds the fields in that block' do
|
|
2534
|
+
declare_model.call
|
|
2535
|
+
|
|
2536
|
+
expect(Advert.field_specs.keys).to eq(['price', 'created_at', 'updated_at', 'lock_version'])
|
|
2537
|
+
end
|
|
2538
|
+
|
|
2539
|
+
context 'and the model sets default_schema: false' do
|
|
2540
|
+
before do
|
|
2541
|
+
class Advert < active_record_base_class.constantize
|
|
2542
|
+
declare_schema default_schema: false do
|
|
2543
|
+
integer :price, limit: 8
|
|
2544
|
+
end
|
|
2545
|
+
end
|
|
2546
|
+
end
|
|
2547
|
+
|
|
2548
|
+
it 'does not add the default schema fields' do
|
|
2549
|
+
expect(Advert.field_specs.keys).to eq(['price'])
|
|
2550
|
+
end
|
|
2551
|
+
end
|
|
2552
|
+
|
|
2553
|
+
context 'and the block has redundant fields' do
|
|
2554
|
+
before do
|
|
2555
|
+
class Advert < active_record_base_class.constantize
|
|
2556
|
+
declare_schema do
|
|
2557
|
+
integer :price, limit: 8
|
|
2558
|
+
timestamps
|
|
2559
|
+
end
|
|
2560
|
+
end
|
|
2561
|
+
end
|
|
2562
|
+
|
|
2563
|
+
it 'is a no-op' do
|
|
2564
|
+
expect(Advert.field_specs.keys).to eq(['price', 'created_at', 'updated_at', 'lock_version'])
|
|
2565
|
+
end
|
|
2566
|
+
end
|
|
2567
|
+
end
|
|
2568
|
+
end
|
|
2373
2569
|
end
|
|
2374
2570
|
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.13.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-06-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|