declare_schema 3.1.0.colin.1 → 3.1.0.colin.2

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: 1c6d94bcff3ece2896188d31c3e5cf4d66474ab4ef5082537037b9c037bfe364
4
- data.tar.gz: fea64238065955c2a3b6a16f5d5f3ee1da5b65d09da8a808b5f11c66b575b602
3
+ metadata.gz: 1b65098860b02787bce136f3b694bcd294cdaaa6980716492bcf66b5ab9c4cc9
4
+ data.tar.gz: 9d8ff3e2e7c2cc06886f59b8052ab0f4b2d4bac88ea46ac4ada831000b6cb0a8
5
5
  SHA512:
6
- metadata.gz: 675ddf024002056463139774a6cdca78f7b157f8de5e07978b945552858d462e3ba59284a42fe169f4395adb793ce4027207e272d721085c638987c49819a6cd
7
- data.tar.gz: 667e85cc287311fa014a0729da207ec763749196b07a0692a746aaa5348e3f48d23fb18860b7ee0ce7a8b50b151a9003f0fb7f460e9b39ce5d14a72838adc0cd
6
+ metadata.gz: 9e7456ba8c763846255997c5fb08a77cb094a199abea888f13c39a9a5dea8e099fe1e09f2a017931eb3b8f1c1e2e7c46b8752dda2510e7f2a892c3f0d2a29a43
7
+ data.tar.gz: 38909be09762f69b40d245385bccf004520142838a95cd27f2c75ef0f82c292a65f301dda444b05e6ce30d76406f712f59c6a9160f8fea9588c1791d3448a8d3
@@ -48,7 +48,7 @@ jobs:
48
48
  - uses: ruby/setup-ruby@v1
49
49
  with:
50
50
  ruby-version: ${{matrix.ruby}}
51
- bundler: 2.2.29
51
+ bundler: latest
52
52
  bundler-cache: true
53
53
  - name: Setup
54
54
  run: |
data/CHANGELOG.md CHANGED
@@ -8,6 +8,9 @@ Note: this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0
8
8
  ### Added
9
9
  - Add HABTM support for arbitrary primary key in the referenced table (rather than just :bigint).
10
10
 
11
+ ### Removed
12
+ - Drop support for Rails 6.x. Minimum supported Rails is now 7.0.
13
+
11
14
  ## [3.0.0] - 2025-04-08
12
15
  ### Changed
13
16
  - The `timestamps` DSL method to create `created_at` and `updated_at` columns now defaults to `null: false` for `datetime` columns
data/Gemfile CHANGED
@@ -15,7 +15,7 @@ gem 'mail'
15
15
  gem 'net-smtp'
16
16
  gem 'pry'
17
17
  gem 'pry-byebug'
18
- gem 'rails', '~> 6.0'
18
+ gem 'rails', '~> 7.0'
19
19
  gem 'responders'
20
20
  gem 'rspec'
21
21
  gem 'rspec-its'
data/Gemfile.lock CHANGED
@@ -1,126 +1,153 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- declare_schema (3.1.0.colin.1)
5
- rails (>= 6.0)
4
+ declare_schema (3.1.0.colin.2)
5
+ rails (>= 7.0)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- actioncable (6.1.7.8)
11
- actionpack (= 6.1.7.8)
12
- activesupport (= 6.1.7.8)
10
+ actioncable (7.2.3.1)
11
+ actionpack (= 7.2.3.1)
12
+ activesupport (= 7.2.3.1)
13
13
  nio4r (~> 2.0)
14
14
  websocket-driver (>= 0.6.1)
15
- actionmailbox (6.1.7.8)
16
- actionpack (= 6.1.7.8)
17
- activejob (= 6.1.7.8)
18
- activerecord (= 6.1.7.8)
19
- activestorage (= 6.1.7.8)
20
- activesupport (= 6.1.7.8)
21
- mail (>= 2.7.1)
22
- actionmailer (6.1.7.8)
23
- actionpack (= 6.1.7.8)
24
- actionview (= 6.1.7.8)
25
- activejob (= 6.1.7.8)
26
- activesupport (= 6.1.7.8)
27
- mail (~> 2.5, >= 2.5.4)
28
- rails-dom-testing (~> 2.0)
29
- actionpack (6.1.7.8)
30
- actionview (= 6.1.7.8)
31
- activesupport (= 6.1.7.8)
32
- rack (~> 2.0, >= 2.0.9)
15
+ zeitwerk (~> 2.6)
16
+ actionmailbox (7.2.3.1)
17
+ actionpack (= 7.2.3.1)
18
+ activejob (= 7.2.3.1)
19
+ activerecord (= 7.2.3.1)
20
+ activestorage (= 7.2.3.1)
21
+ activesupport (= 7.2.3.1)
22
+ mail (>= 2.8.0)
23
+ actionmailer (7.2.3.1)
24
+ actionpack (= 7.2.3.1)
25
+ actionview (= 7.2.3.1)
26
+ activejob (= 7.2.3.1)
27
+ activesupport (= 7.2.3.1)
28
+ mail (>= 2.8.0)
29
+ rails-dom-testing (~> 2.2)
30
+ actionpack (7.2.3.1)
31
+ actionview (= 7.2.3.1)
32
+ activesupport (= 7.2.3.1)
33
+ cgi
34
+ nokogiri (>= 1.8.5)
35
+ racc
36
+ rack (>= 2.2.4, < 3.3)
37
+ rack-session (>= 1.0.1)
33
38
  rack-test (>= 0.6.3)
34
- rails-dom-testing (~> 2.0)
35
- rails-html-sanitizer (~> 1.0, >= 1.2.0)
36
- actiontext (6.1.7.8)
37
- actionpack (= 6.1.7.8)
38
- activerecord (= 6.1.7.8)
39
- activestorage (= 6.1.7.8)
40
- activesupport (= 6.1.7.8)
39
+ rails-dom-testing (~> 2.2)
40
+ rails-html-sanitizer (~> 1.6)
41
+ useragent (~> 0.16)
42
+ actiontext (7.2.3.1)
43
+ actionpack (= 7.2.3.1)
44
+ activerecord (= 7.2.3.1)
45
+ activestorage (= 7.2.3.1)
46
+ activesupport (= 7.2.3.1)
47
+ globalid (>= 0.6.0)
41
48
  nokogiri (>= 1.8.5)
42
- actionview (6.1.7.8)
43
- activesupport (= 6.1.7.8)
49
+ actionview (7.2.3.1)
50
+ activesupport (= 7.2.3.1)
44
51
  builder (~> 3.1)
45
- erubi (~> 1.4)
46
- rails-dom-testing (~> 2.0)
47
- rails-html-sanitizer (~> 1.1, >= 1.2.0)
48
- activejob (6.1.7.8)
49
- activesupport (= 6.1.7.8)
52
+ cgi
53
+ erubi (~> 1.11)
54
+ rails-dom-testing (~> 2.2)
55
+ rails-html-sanitizer (~> 1.6)
56
+ activejob (7.2.3.1)
57
+ activesupport (= 7.2.3.1)
50
58
  globalid (>= 0.3.6)
51
- activemodel (6.1.7.8)
52
- activesupport (= 6.1.7.8)
53
- activerecord (6.1.7.8)
54
- activemodel (= 6.1.7.8)
55
- activesupport (= 6.1.7.8)
56
- activestorage (6.1.7.8)
57
- actionpack (= 6.1.7.8)
58
- activejob (= 6.1.7.8)
59
- activerecord (= 6.1.7.8)
60
- activesupport (= 6.1.7.8)
59
+ activemodel (7.2.3.1)
60
+ activesupport (= 7.2.3.1)
61
+ activerecord (7.2.3.1)
62
+ activemodel (= 7.2.3.1)
63
+ activesupport (= 7.2.3.1)
64
+ timeout (>= 0.4.0)
65
+ activestorage (7.2.3.1)
66
+ actionpack (= 7.2.3.1)
67
+ activejob (= 7.2.3.1)
68
+ activerecord (= 7.2.3.1)
69
+ activesupport (= 7.2.3.1)
61
70
  marcel (~> 1.0)
62
- mini_mime (>= 1.1.0)
63
- activesupport (6.1.7.8)
64
- concurrent-ruby (~> 1.0, >= 1.0.2)
71
+ activesupport (7.2.3.1)
72
+ base64
73
+ benchmark (>= 0.3)
74
+ bigdecimal
75
+ concurrent-ruby (~> 1.0, >= 1.3.1)
76
+ connection_pool (>= 2.2.5)
77
+ drb
65
78
  i18n (>= 1.6, < 2)
66
- minitest (>= 5.1)
67
- tzinfo (~> 2.0)
68
- zeitwerk (~> 2.3)
79
+ logger (>= 1.4.2)
80
+ minitest (>= 5.1, < 6)
81
+ securerandom (>= 0.3)
82
+ tzinfo (~> 2.0, >= 2.0.5)
69
83
  appraisal (2.5.0)
70
84
  bundler
71
85
  rake
72
86
  thor (>= 0.14.0)
73
87
  ast (2.4.2)
74
88
  base64 (0.2.0)
89
+ benchmark (0.5.0)
75
90
  bigdecimal (3.1.9)
76
91
  bootsnap (1.18.4)
77
92
  msgpack (~> 1.2)
78
93
  builder (3.3.0)
79
94
  byebug (11.1.3)
95
+ cgi (0.5.1)
80
96
  climate_control (0.2.0)
81
97
  coderay (1.1.3)
82
98
  concurrent-ruby (1.3.4)
99
+ connection_pool (3.0.2)
83
100
  crass (1.0.6)
84
- date (3.3.4)
101
+ date (3.5.1)
85
102
  diff-lcs (1.5.1)
86
- erubi (1.13.0)
103
+ drb (2.2.3)
104
+ erb (6.0.4)
105
+ erubi (1.13.1)
87
106
  ffi (1.17.0)
88
- globalid (1.2.1)
107
+ globalid (1.3.0)
89
108
  activesupport (>= 6.1)
90
- i18n (1.14.5)
109
+ i18n (1.14.8)
91
110
  concurrent-ruby (~> 1.0)
111
+ io-console (0.8.2)
112
+ irb (1.18.0)
113
+ pp (>= 0.6.0)
114
+ prism (>= 1.3.0)
115
+ rdoc (>= 4.0.0)
116
+ reline (>= 0.4.2)
92
117
  json (2.7.2)
93
118
  language_server-protocol (3.17.0.3)
94
119
  listen (3.9.0)
95
120
  rb-fsevent (~> 0.10, >= 0.10.3)
96
121
  rb-inotify (~> 0.9, >= 0.9.10)
97
- loofah (2.22.0)
122
+ logger (1.7.0)
123
+ loofah (2.25.1)
98
124
  crass (~> 1.0.2)
99
125
  nokogiri (>= 1.12.0)
100
- mail (2.8.1)
126
+ mail (2.9.0)
127
+ logger
101
128
  mini_mime (>= 0.1.1)
102
129
  net-imap
103
130
  net-pop
104
131
  net-smtp
105
- marcel (1.0.4)
132
+ marcel (1.1.0)
106
133
  method_source (1.1.0)
107
134
  mini_mime (1.1.5)
108
- mini_portile2 (2.8.7)
109
- minitest (5.25.0)
135
+ mini_portile2 (2.8.9)
136
+ minitest (5.27.0)
110
137
  msgpack (1.7.2)
111
138
  mutex_m (0.3.0)
112
139
  mysql2 (0.5.6)
113
- net-imap (0.4.14)
140
+ net-imap (0.6.4)
114
141
  date
115
142
  net-protocol
116
143
  net-pop (0.1.2)
117
144
  net-protocol
118
145
  net-protocol (0.2.2)
119
146
  timeout
120
- net-smtp (0.5.0)
147
+ net-smtp (0.5.1)
121
148
  net-protocol
122
- nio4r (2.7.3)
123
- nokogiri (1.16.7)
149
+ nio4r (2.7.5)
150
+ nokogiri (1.19.3)
124
151
  mini_portile2 (~> 2.8.2)
125
152
  racc (~> 1.4)
126
153
  parallel (1.26.2)
@@ -128,54 +155,75 @@ GEM
128
155
  ast (~> 2.4.1)
129
156
  racc
130
157
  pg (1.5.7)
158
+ pp (0.6.3)
159
+ prettyprint
160
+ prettyprint (0.2.0)
161
+ prism (1.9.0)
131
162
  pry (0.14.2)
132
163
  coderay (~> 1.1)
133
164
  method_source (~> 1.0)
134
165
  pry-byebug (3.10.1)
135
166
  byebug (~> 11.0)
136
167
  pry (>= 0.13, < 0.15)
168
+ psych (5.3.1)
169
+ date
170
+ stringio
137
171
  racc (1.8.1)
138
- rack (2.2.9)
139
- rack-test (2.1.0)
172
+ rack (3.2.6)
173
+ rack-session (2.1.2)
174
+ base64 (>= 0.1.0)
175
+ rack (>= 3.0.0)
176
+ rack-test (2.2.0)
140
177
  rack (>= 1.3)
141
- rails (6.1.7.8)
142
- actioncable (= 6.1.7.8)
143
- actionmailbox (= 6.1.7.8)
144
- actionmailer (= 6.1.7.8)
145
- actionpack (= 6.1.7.8)
146
- actiontext (= 6.1.7.8)
147
- actionview (= 6.1.7.8)
148
- activejob (= 6.1.7.8)
149
- activemodel (= 6.1.7.8)
150
- activerecord (= 6.1.7.8)
151
- activestorage (= 6.1.7.8)
152
- activesupport (= 6.1.7.8)
178
+ rackup (2.3.1)
179
+ rack (>= 3)
180
+ rails (7.2.3.1)
181
+ actioncable (= 7.2.3.1)
182
+ actionmailbox (= 7.2.3.1)
183
+ actionmailer (= 7.2.3.1)
184
+ actionpack (= 7.2.3.1)
185
+ actiontext (= 7.2.3.1)
186
+ actionview (= 7.2.3.1)
187
+ activejob (= 7.2.3.1)
188
+ activemodel (= 7.2.3.1)
189
+ activerecord (= 7.2.3.1)
190
+ activestorage (= 7.2.3.1)
191
+ activesupport (= 7.2.3.1)
153
192
  bundler (>= 1.15.0)
154
- railties (= 6.1.7.8)
155
- sprockets-rails (>= 2.0.0)
156
- rails-dom-testing (2.2.0)
193
+ railties (= 7.2.3.1)
194
+ rails-dom-testing (2.3.0)
157
195
  activesupport (>= 5.0.0)
158
196
  minitest
159
197
  nokogiri (>= 1.6)
160
- rails-html-sanitizer (1.6.0)
161
- loofah (~> 2.21)
162
- nokogiri (~> 1.14)
163
- railties (6.1.7.8)
164
- actionpack (= 6.1.7.8)
165
- activesupport (= 6.1.7.8)
166
- method_source
198
+ rails-html-sanitizer (1.7.0)
199
+ loofah (~> 2.25)
200
+ nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
201
+ railties (7.2.3.1)
202
+ actionpack (= 7.2.3.1)
203
+ activesupport (= 7.2.3.1)
204
+ cgi
205
+ irb (~> 1.13)
206
+ rackup (>= 1.0.0)
167
207
  rake (>= 12.2)
168
- thor (~> 1.0)
208
+ thor (~> 1.0, >= 1.2.2)
209
+ tsort (>= 0.2)
210
+ zeitwerk (~> 2.6)
169
211
  rainbow (3.1.1)
170
- rake (13.2.1)
212
+ rake (13.4.2)
171
213
  rb-fsevent (0.11.2)
172
214
  rb-inotify (0.11.1)
173
215
  ffi (~> 1.0)
216
+ rdoc (7.2.0)
217
+ erb
218
+ psych (>= 4.0.0)
219
+ tsort
174
220
  regexp_parser (2.9.2)
221
+ reline (0.6.3)
222
+ io-console (~> 0.5)
175
223
  responders (3.1.1)
176
224
  actionpack (>= 5.2)
177
225
  railties (>= 5.2)
178
- rexml (3.3.8)
226
+ rexml (3.4.4)
179
227
  rspec (3.13.0)
180
228
  rspec-core (~> 3.13.0)
181
229
  rspec-expectations (~> 3.13.0)
@@ -206,25 +254,23 @@ GEM
206
254
  rubocop-ast (1.32.0)
207
255
  parser (>= 3.3.1.0)
208
256
  ruby-progressbar (1.13.0)
209
- sprockets (4.2.1)
210
- concurrent-ruby (~> 1.0)
211
- rack (>= 2.2.4, < 4)
212
- sprockets-rails (3.5.2)
213
- actionpack (>= 6.1)
214
- activesupport (>= 6.1)
215
- sprockets (>= 3.0.0)
257
+ securerandom (0.4.1)
216
258
  sqlite3 (1.7.3)
217
259
  mini_portile2 (~> 2.8.0)
218
- thor (1.3.1)
219
- timeout (0.4.1)
260
+ stringio (3.2.0)
261
+ thor (1.5.0)
262
+ timeout (0.6.1)
263
+ tsort (0.2.0)
220
264
  tzinfo (2.0.6)
221
265
  concurrent-ruby (~> 1.0)
222
266
  unicode-display_width (2.5.0)
223
- websocket-driver (0.7.6)
267
+ useragent (0.16.11)
268
+ websocket-driver (0.8.0)
269
+ base64
224
270
  websocket-extensions (>= 0.1.0)
225
271
  websocket-extensions (0.1.5)
226
272
  yard (0.9.36)
227
- zeitwerk (2.6.17)
273
+ zeitwerk (2.7.5)
228
274
 
229
275
  PLATFORMS
230
276
  ruby
@@ -245,7 +291,7 @@ DEPENDENCIES
245
291
  pg (~> 1.1)
246
292
  pry
247
293
  pry-byebug
248
- rails (~> 6.0)
294
+ rails (~> 7.0)
249
295
  responders
250
296
  rspec
251
297
  rspec-its
@@ -3,11 +3,11 @@
3
3
  {
4
4
  "warning_type": "Command Injection",
5
5
  "warning_code": 14,
6
- "fingerprint": "43f4448f6da2dd7f73909a4456c73c52f8f1d65e83bc188b9b3d624ba9470766",
6
+ "fingerprint": "069879c09668f26db81818bd906f169f4f3be6dc661c9b223fcd6f53b2155369",
7
7
  "check_name": "Execute",
8
8
  "message": "Possible command injection",
9
9
  "file": "lib/declare_schema/command.rb",
10
- "line": 61,
10
+ "line": 55,
11
11
  "link": "https://brakemanscanner.org/docs/warning_types/command_injection/",
12
12
  "code": "system(\"bundle exec rails #{Regexp.last_match(1)} declare_schema:#{(args * \" \")}\")",
13
13
  "render_path": null,
@@ -26,13 +26,13 @@
26
26
  {
27
27
  "warning_type": "Command Injection",
28
28
  "warning_code": 14,
29
- "fingerprint": "6b90f8dd199afbdf79cc8c4d00a0853e0696b067d00c0fe93071e31b69de8628",
29
+ "fingerprint": "9914293cc386897d62f7f645fd2e0bfa51c582f3058ac34357f61b04866a33fe",
30
30
  "check_name": "Execute",
31
31
  "message": "Possible command injection",
32
32
  "file": "lib/declare_schema/command.rb",
33
33
  "line": 45,
34
34
  "link": "https://brakemanscanner.org/docs/warning_types/command_injection/",
35
- "code": "system(\"rails new #{\"new\"} #{(args * \" \")} -m #{File.join(Dir.tmpdir, \"declare_schema_app_template\")}#{begin\n (require(\"mysql2\")\n \" -d mysql\")\nrescue LoadError\n # do nothing\nend}\")",
35
+ "code": "system(\"rails new #{\"new\"} #{(args * \" \")} -m #{File.join(Dir.tmpdir, \"declare_schema_app_template\")}\")",
36
36
  "render_path": null,
37
37
  "location": {
38
38
  "type": "method",
@@ -66,6 +66,6 @@
66
66
  "note": ""
67
67
  }
68
68
  ],
69
- "updated": "2023-07-02 21:29:31 -0700",
69
+ "updated": "2026-05-02 06:56:54 -0700",
70
70
  "brakeman_version": "5.4.1"
71
71
  }
@@ -21,5 +21,5 @@ Gem::Specification.new do |s|
21
21
  s.required_rubygems_version = ">= 1.3.6"
22
22
  s.require_paths = ["lib"]
23
23
 
24
- s.add_dependency 'rails', '>= 6.0'
24
+ s.add_dependency 'rails', '>= 7.0'
25
25
  end
@@ -46,12 +46,13 @@ module DeclareSchema
46
46
  define_method(option) { @options[option] }
47
47
  end
48
48
 
49
- def initialize(model, name, type, position: 0, **options)
49
+ def initialize(model, name, type, position: 0, resolver: nil, **options)
50
50
  @model = model
51
51
  @name = name.to_sym
52
52
  type.is_a?(Symbol) or raise ArgumentError, "type must be a Symbol; got #{type.inspect}"
53
53
  @type = TYPE_SYNONYMS[type] || type
54
54
  @position = position
55
+ @resolver = resolver
55
56
  @options = options.dup
56
57
 
57
58
  @options.has_key?(:null) or @options[:null] = ::DeclareSchema.default_null
@@ -120,7 +121,14 @@ module DeclareSchema
120
121
  @sql_options = @options.slice(*SQL_OPTIONS)
121
122
  end
122
123
 
123
- def foreign_key_field_spec(model, name, position: 0, null: nil)
124
+ # Returns the final FieldSpec, invoking the deferred-resolution callback if one
125
+ # was supplied. Specs without a `resolver:` simply return `self`. Result is
126
+ # memoized so repeated calls (e.g. across migration generation passes) are cheap.
127
+ def resolve
128
+ @resolved ||= @resolver ? @resolver.call(self) : self
129
+ end
130
+
131
+ def foreign_key_field_spec(model, name, position:, null:)
124
132
  self.class.new(model, name, @type, position:, **@options.merge(null.nil? ? {} : { null: }))
125
133
  end
126
134
 
@@ -39,15 +39,42 @@ module DeclareSchema
39
39
  join_table
40
40
  end
41
41
 
42
+ # The migrator (in change_column_back / add_column_back / etc.) wraps column
43
+ # introspection in `with_previous_model_table_name`, which expects a model that
44
+ # exposes `table_name=`. Real AR classes do; this shim doesn't keep AR-style
45
+ # cached metadata keyed on table_name, so we just rewrite @join_table -- the
46
+ # only state our `table_name` reads.
47
+ def table_name=(new_table_name)
48
+ @join_table = new_table_name
49
+ end
50
+
51
+ # Mirror the AR class API for `model.columns_hash` so the migrator's
52
+ # change_column_back / add_column_back paths work uniformly across real AR
53
+ # models and HABTM shims. Backed by `connection.columns(table_name)` rather
54
+ # than AR's class-level cache because the shim has no class-level cache.
55
+ def columns_hash
56
+ connection.columns(table_name).index_by(&:name)
57
+ end
58
+
42
59
  def field_specs
43
60
  foreign_keys.each_with_index.each_with_object({}) do |(foreign_key, i), result|
44
- result[foreign_key] = parent_models[i]._foreign_key_field_spec(self, foreign_key, position: i, null: false)
61
+ parent_model = parent_models[i]
62
+ result[foreign_key] =
63
+ if parent_model.respond_to?(:_foreign_key_field_spec)
64
+ # declare_schema model: mirror the parent's primary key (type, limit, etc.)
65
+ parent_model._foreign_key_field_spec(self, foreign_key, position: i, null: false)
66
+ else
67
+ # Non-declare_schema parent: fall back to the configured default PK type.
68
+ ::DeclareSchema::Model::FieldSpec.new(self, foreign_key, ::DeclareSchema.default_generated_primary_key_type, position: i, null: false)
69
+ end
45
70
  end
46
71
  end
47
72
 
73
+ # The HABTM join table's primary key is the composite of its two foreign keys.
74
+ # (Rails 7.1+ supports composite PKs natively; on 7.0 nothing in AR introspects this
75
+ # shim, so returning an Array is safe for our callers.)
48
76
  def primary_key
49
- # TODO: ActiveRecord now supports composite primary keys, so we could return that here.
50
- false # no single-column primary key in database
77
+ foreign_keys
51
78
  end
52
79
 
53
80
  def _declared_primary_key
@@ -227,19 +227,80 @@ module DeclareSchema
227
227
  end
228
228
 
229
229
  # Returns a FieldSpec for the foreign key column of a belongs_to association.
230
- # If the association is polymorphic, the foreign key column is a bigint, or possibly a 4-byte integer
231
- # if the foreign key column is already defined that way in the database.
232
- # If the association is not polymorphic, the foreign key column matches the primary key type of the associated model.
230
+ # - For a polymorphic association, the FK uses `DeclareSchema.default_generated_primary_key_type`
231
+ # (mirroring `config.generators.primary_key_type`, default :bigint), or :integer with the
232
+ # existing column's limit if the column already exists in the database.
233
+ # - For a non-polymorphic association, the FK should mirror the primary key it points
234
+ # at (same data type, same options like limit:, charset:, etc.). However we cannot
235
+ # load the parent model right now (at `belongs_to` time) without risking dependency
236
+ # cycles between models, so we install a `resolver:` callback. The migration
237
+ # generator calls that resolver at generation time -- after all models are
238
+ # eager-loaded -- and the resolver returns a fully-mirrored FieldSpec that the
239
+ # generator swaps in for this default_spec.
233
240
  def _infer_foreign_key_field_spec(foreign_key_column_name, reflection, column_options)
234
241
  if reflection.options[:polymorphic]
235
242
  if (foreign_key_column = _column(foreign_key_column_name)) && foreign_key_column.type == :integer
236
243
  # grandfather foreign key column to match what's in the database
237
244
  column_options = column_options.merge(limit: foreign_key_column.limit)
238
245
  end
239
- FieldSpec.new(self, foreign_key_column_name, :bigint, position: field_specs.size, **column_options)
246
+ FieldSpec.new(self, foreign_key_column_name, DeclareSchema.default_generated_primary_key_type, position: field_specs.size, **column_options)
240
247
  else
241
- klass = reflection.klass or raise "Couldn't find belongs_to klass for #{name} in #{reflection.inspect}"
242
- klass._foreign_key_field_spec(self, foreign_key_column_name, position: field_specs.size, **column_options)
248
+ # Capture only what we need from `reflection` (no `reflection.klass` here -- that
249
+ # would force the parent model to load, which is exactly the cycle we are avoiding).
250
+ # `reflection.klass` is resolved lazily inside the block below.
251
+ resolver = ->(default_spec) do
252
+ _resolve_belongs_to_foreign_key_field_spec(reflection, default_spec)
253
+ end
254
+ FieldSpec.new(self, foreign_key_column_name, DeclareSchema.default_generated_primary_key_type,
255
+ position: field_specs.size, resolver:, **column_options)
256
+ end
257
+ end
258
+
259
+ # Called at migration generation time to mirror the parent model's primary key.
260
+ # Always returns a FieldSpec: the default_spec unchanged when the parent class is not
261
+ # a declare_schema model (we can't ask for its PK spec, so the configured default PK
262
+ # type is the best we can offer without inspecting the DB), otherwise a fully mirrored
263
+ # FieldSpec.
264
+ #
265
+ # Reconciliation with the live DB: if the parent's PK column already exists in the
266
+ # database with the same Rails type but a different :limit (e.g. a legacy table where
267
+ # `id` is INT(4) but the model now declares the default :bigint), prefer the live
268
+ # column's :limit so the FK matches what's actually on disk. This preserves the
269
+ # behavior of the old DB-column lookup (formerly `fk_field_options`) without
270
+ # overriding intentional type changes.
271
+ def _resolve_belongs_to_foreign_key_field_spec(reflection, default_spec)
272
+ klass = reflection.klass or
273
+ raise "Couldn't find belongs_to klass for #{reflection.name} on #{name} in #{reflection.inspect}"
274
+
275
+ if klass.respond_to?(:_primary_key_field_spec)
276
+ _mirror_parent_primary_key(klass, default_spec)
277
+ else
278
+ default_spec
279
+ end
280
+ end
281
+
282
+ # Build a FieldSpec for the FK by mirroring the parent's declared primary key,
283
+ # then reconciling against the live DB column when it differs only in :limit
284
+ # (see _resolve_belongs_to_foreign_key_field_spec for the full rationale).
285
+ def _mirror_parent_primary_key(klass, default_spec)
286
+ spec = klass._primary_key_field_spec.foreign_key_field_spec(
287
+ default_spec.model, default_spec.name,
288
+ position: default_spec.position, null: default_spec.null
289
+ )
290
+
291
+ # Look up the parent's live PK column directly (not via _column, whose
292
+ # @table_exists memoization can pin to a stale value when the parent table
293
+ # is created after the model class is first defined). The rescue covers
294
+ # the table-doesn't-exist-yet case (greenfield migration).
295
+ live_pk_column = klass.columns_hash[klass._declared_primary_key.to_s] rescue nil
296
+ if live_pk_column && live_pk_column.type == spec.type && live_pk_column.limit && live_pk_column.limit != spec.limit
297
+ FieldSpec.new(
298
+ spec.model, spec.name, spec.type,
299
+ position: spec.position,
300
+ **spec.options.merge(limit: live_pk_column.limit)
301
+ )
302
+ else
303
+ spec
243
304
  end
244
305
  end
245
306
 
@@ -269,14 +330,29 @@ module DeclareSchema
269
330
  end
270
331
 
271
332
  def _primary_key_field_spec_from_table_options(declared_primary_key)
272
- primary_key_options = _table_options[declared_primary_key.to_sym] || _table_options[declared_primary_key]
273
- options = primary_key_options.is_a?(Hash) ? primary_key_options.dup : {}
274
- type = options.delete(:type) || Rails.application.config.generators.options.dig(:active_record, :primary_key_type) || :bigint
333
+ # _table_options is nil on STI subclasses that never call `declare_schema` themselves:
334
+ # they inherit `field_specs` etc. via `inheriting_cattr_reader`, but `@_table_options`
335
+ # is a plain class-instance variable on each class, so the subclass's reader returns
336
+ # nil. Treat that the same as an empty options hash and fall through to
337
+ # `default_generated_primary_key_type` below.
338
+ type, options = _parse_pk_table_options(_table_options&.[](declared_primary_key.to_sym))
339
+ type ||= DeclareSchema.default_generated_primary_key_type
275
340
  FieldSpec.new(self, declared_primary_key, type, **options)
276
341
  end
277
342
 
278
343
  private
279
344
 
345
+ # `declare_schema id: ...` accepts either a Hash (`id: { type: :integer, limit: 4 }`)
346
+ # or a bare type Symbol (`id: :integer`). Returns [type, options_hash], with type == nil
347
+ # when value is neither (caller falls back to a default).
348
+ def _parse_pk_table_options(value)
349
+ case value
350
+ when Hash then [value[:type], value.except(:type)]
351
+ when Symbol then [value, {}]
352
+ else [nil, {}]
353
+ end
354
+ end
355
+
280
356
  # if this is a derived class, returns the base class's _declared_primary_key
281
357
  # otherwise, returns 'id'
282
358
  def _default_declared_primary_key
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeclareSchema
4
- VERSION = "3.1.0.colin.1"
4
+ VERSION = "3.1.0.colin.2"
5
5
  end
@@ -65,7 +65,7 @@ module DeclareSchema
65
65
  if mysql_version && mysql_version >= SEMVER_8 && charset == 'utf8'
66
66
  'utf8mb3'
67
67
  else
68
- charset.downcase
68
+ charset
69
69
  end
70
70
  end
71
71
 
@@ -74,7 +74,7 @@ module DeclareSchema
74
74
  collation.sub(/\Autf8_/, 'utf8mb3_')
75
75
  else
76
76
  collation
77
- end.downcase
77
+ end
78
78
  end
79
79
 
80
80
  def default_charset=(charset)
@@ -162,11 +162,16 @@ module DeclareSchema
162
162
  end
163
163
 
164
164
  def current_adapter(model_class = ActiveRecord::Base)
165
- if Rails::VERSION::MAJOR >= 6.1
166
- model_class.connection_db_config.adapter
167
- else
168
- model_class.connection_config[:adapter]
169
- end
165
+ model_class.connection_db_config.adapter
166
+ end
167
+
168
+ # Default primary key type for generated foreign keys when we have no parent
169
+ # model to mirror (e.g. polymorphic FKs, non-declare_schema parents). Mirrors
170
+ # `config.generators.primary_key_type` so apps that override the Rails default
171
+ # get consistent behavior. Memoized — Rails config is set at boot.
172
+ def default_generated_primary_key_type
173
+ @default_generated_primary_key_type ||=
174
+ Rails.application.config.generators.options.dig(:active_record, :primary_key_type) || :bigint
170
175
  end
171
176
  end
172
177
  end
@@ -187,11 +187,7 @@ 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
190
+ m.try(:field_specs)&.transform_values!(&:resolve)
195
191
 
196
192
  if !models_by_table_name.has_key?(m.table_name)
197
193
  models_by_table_name[m.table_name] = m
@@ -379,9 +375,14 @@ module Generators
379
375
  adds = to_add.map do |col_name_to_add|
380
376
  type, options =
381
377
  if (spec = model.field_specs[col_name_to_add])
382
- [spec.type, spec.sql_options.merge(fk_field_options(model, col_name_to_add)).compact]
378
+ [spec.type, spec.sql_options.compact]
383
379
  else
384
- [:integer, {}]
380
+ # No FieldSpec for this column: it's the declared PK appended at line 366
381
+ # because it isn't in the DB yet. Use the configured default PK type so
382
+ # apps that override config.generators.primary_key_type get consistent
383
+ # behavior; the actual PRIMARY KEY designation is added separately by
384
+ # PrimaryKeyChange.
385
+ [::DeclareSchema.default_generated_primary_key_type, {}]
385
386
  end
386
387
  ::DeclareSchema::SchemaChange::ColumnAdd.new(new_table_name, col_name_to_add, type, **options)
387
388
  end
@@ -400,13 +401,12 @@ module Generators
400
401
  spec_attrs = spec.schema_attributes(column)
401
402
  column_declaration = ::DeclareSchema::Model::Column.new(model, current_table_name, column)
402
403
  col_attrs = column_declaration.schema_attributes
403
- normalized_schema_attrs = spec_attrs.merge(fk_field_options(model, col_name_to_change))
404
404
 
405
- if !::DeclareSchema::Model::Column.equivalent_schema_attributes?(normalized_schema_attrs, col_attrs)
406
- type = normalized_schema_attrs.delete(:type) or raise "no :type found in #{normalized_schema_attrs.inspect}"
405
+ if !::DeclareSchema::Model::Column.equivalent_schema_attributes?(spec_attrs, col_attrs)
406
+ type = spec_attrs.delete(:type) or raise "no :type found in #{spec_attrs.inspect}"
407
407
  old_type, old_options = change_column_back(model, current_table_name, orig_col_name)
408
408
  changes << ::DeclareSchema::SchemaChange::ColumnChange.new(new_table_name, col_name_to_change,
409
- new_type: type, new_options: normalized_schema_attrs,
409
+ new_type: type, new_options: spec_attrs,
410
410
  old_type: old_type, old_options: old_options)
411
411
  end
412
412
  end
@@ -544,25 +544,6 @@ module Generators
544
544
  end
545
545
  end
546
546
 
547
- # TODO: switch this to depend on _infer_foreign_key_field_spec instead
548
- def fk_field_options(model, field_name)
549
- # check if the field_name is a foreign key
550
- if (foreign_key = model.constraint_definitions.find { field_name == _1.foreign_key_column })
551
- # if so, look up the target table's primary key column to get its limit (note: this is looking in the DB, not the spec)
552
- parent_columns = connection.columns(foreign_key.parent_table_name) rescue []
553
- pk_limit =
554
- if (pk_column = parent_columns.find { _1.name.to_s == "id" }) # right now foreign keys assume id is the target
555
- pk_column.limit
556
- else
557
- 8
558
- end
559
-
560
- { limit: pk_limit }
561
- else
562
- {}
563
- end
564
- end
565
-
566
547
  def change_table_options(model, current_table_name)
567
548
  old_options_definition = ::DeclareSchema::Model::TableOptionsDefinition.for_model(model, current_table_name)
568
549
  new_options_definition = ::DeclareSchema::Model::TableOptionsDefinition.new(model.table_name, **table_options_for_model(model))
@@ -25,6 +25,38 @@ RSpec.describe DeclareSchema::Model::FieldSpec do
25
25
  end
26
26
  end
27
27
 
28
+ describe '#resolve' do
29
+ let(:default_spec) { described_class.new(model, :advertiser_id, :integer, limit: 8, null: false, position: 1) }
30
+ let(:mirrored_spec) { described_class.new(model, :advertiser_id, :string, limit: 36, null: false, position: 1) }
31
+
32
+ context 'when no resolver was supplied' do
33
+ it 'returns self' do
34
+ expect(default_spec.resolve).to equal(default_spec)
35
+ end
36
+ end
37
+
38
+ context 'when a resolver was supplied' do
39
+ it 'invokes the resolver with self and returns its result' do
40
+ captured = nil
41
+ spec_under_test = described_class.new(model, :advertiser_id, :integer, limit: 8, null: false, position: 1,
42
+ resolver: ->(spec) { captured = spec; mirrored_spec })
43
+
44
+ expect(spec_under_test.resolve).to equal(mirrored_spec)
45
+ expect(captured).to equal(spec_under_test)
46
+ end
47
+
48
+ it 'memoizes the resolver result' do
49
+ call_count = 0
50
+ spec_under_test = described_class.new(model, :advertiser_id, :integer, limit: 8, null: false, position: 1,
51
+ resolver: ->(_spec) { call_count += 1; mirrored_spec })
52
+
53
+ 3.times { spec_under_test.resolve }
54
+
55
+ expect(call_count).to eq(1)
56
+ end
57
+ end
58
+ end
59
+
28
60
  describe '#schema_attributes' do
29
61
  describe 'integer 4' do
30
62
  subject { described_class.new(model, :price, :integer, limit: 4, null: false, position: 0) }
@@ -988,6 +988,140 @@ RSpec.describe 'DeclareSchema Migration Generator' do
988
988
  nuke_model_class(Creative)
989
989
  end
990
990
 
991
+ context 'foreign keys mirror non-default primary key types' do
992
+ after do
993
+ nuke_model_class(SpecialOrder) if defined?(SpecialOrder)
994
+ nuke_model_class(Order) if defined?(Order)
995
+ nuke_model_class(LineItem) if defined?(LineItem)
996
+ nuke_model_class(Tag) if defined?(Tag)
997
+ nuke_model_class(Product) if defined?(Product)
998
+ nuke_model_class(ProductsTagsClass) if defined?(ProductsTagsClass)
999
+ end
1000
+
1001
+ it 'belongs_to defers parent class lookup to migration time (no dependency cycle)' do
1002
+ # LineItem.belongs_to :order is allowed to mention :order before Order is loaded.
1003
+ # If we eagerly resolved reflection.klass at belongs_to time this would raise
1004
+ # NameError: uninitialized constant LineItem::Order.
1005
+ expect do
1006
+ class LineItem < ActiveRecord::Base # rubocop:disable Lint/ConstantDefinitionInBlock
1007
+ declare_schema { integer :quantity, null: false }
1008
+ belongs_to :order
1009
+ end
1010
+ end.to_not raise_error
1011
+ end
1012
+
1013
+ it 'belongs_to mirrors the parent primary key type and limit (declared via _table_options)' do
1014
+ # Order has a non-default :string primary key; LineItem.belongs_to :order should
1015
+ # produce a :string foreign key with the same limit -- without forcing Order to
1016
+ # load when LineItem is defined.
1017
+ class LineItem < ActiveRecord::Base # rubocop:disable Lint/ConstantDefinitionInBlock
1018
+ declare_schema do
1019
+ integer :quantity, null: false
1020
+ end
1021
+ belongs_to :order
1022
+ end
1023
+
1024
+ class Order < ActiveRecord::Base # rubocop:disable Lint/ConstantDefinitionInBlock
1025
+ self.primary_key = "id"
1026
+ declare_schema id: { type: :string, limit: 36 } do
1027
+ string :customer_email, limit: 250, null: false
1028
+ end
1029
+ end
1030
+
1031
+ up, _ = Generators::DeclareSchema::Migration::Migrator.run
1032
+
1033
+ expect(up).to match(/t\.string\s+:order_id, limit: 36, null: false/)
1034
+ end
1035
+
1036
+ context 'when belongs_to points at an STI subclass' do
1037
+ # An STI subclass inherits field_specs (via inheriting_cattr_reader) and the
1038
+ # `_primary_key_field_spec` class method (mixed into its parent), but it never
1039
+ # calls `declare_schema` on itself, so its own `@_table_options` is nil. When a
1040
+ # belongs_to points at the STI subclass, the migrator's resolver calls
1041
+ # `klass._primary_key_field_spec`, which previously crashed with
1042
+ # `NoMethodError: undefined method '[]' for nil` because
1043
+ # `_primary_key_field_spec_from_table_options` indexed `_table_options` without
1044
+ # a nil guard.
1045
+ before do
1046
+ class Order < ActiveRecord::Base # rubocop:disable Lint/ConstantDefinitionInBlock
1047
+ declare_schema do
1048
+ string :customer_email, limit: 250, null: false
1049
+ end
1050
+ end
1051
+
1052
+ class SpecialOrder < Order # rubocop:disable Lint/ConstantDefinitionInBlock
1053
+ end
1054
+
1055
+ class LineItem < ActiveRecord::Base # rubocop:disable Lint/ConstantDefinitionInBlock
1056
+ declare_schema do
1057
+ integer :quantity, null: false
1058
+ end
1059
+ belongs_to :special_order
1060
+ end
1061
+ end
1062
+
1063
+ let(:up) { Generators::DeclareSchema::Migration::Migrator.run.first }
1064
+
1065
+ it 'mirrors the base class primary key without crashing' do
1066
+ expect(up).to match(/t\.integer\s+:special_order_id, limit: 8, null: false/)
1067
+ end
1068
+ end
1069
+
1070
+ context 'when a HABTM join table has a parent PK type that drifts from the live column' do
1071
+ before do
1072
+ conn = ActiveRecord::Base.connection
1073
+ conn.create_table(:products) { |t| t.string :name, limit: 100, null: false }
1074
+ conn.create_table(:tags) { |t| t.string :label, limit: 50, null: false }
1075
+ conn.create_table(:products_tags, primary_key: [:product_id, :tag_id]) do |t|
1076
+ t.bigint :product_id, null: false
1077
+ t.bigint :tag_id, null: false
1078
+ end
1079
+ conn.schema_cache.clear!
1080
+
1081
+ class Product < ActiveRecord::Base # rubocop:disable Lint/ConstantDefinitionInBlock
1082
+ declare_schema do
1083
+ string :name, limit: 100, null: false
1084
+ end
1085
+ has_and_belongs_to_many :tags
1086
+ end
1087
+
1088
+ class Tag < ActiveRecord::Base # rubocop:disable Lint/ConstantDefinitionInBlock
1089
+ declare_schema id: { type: :string, limit: 36 } do
1090
+ string :label, limit: 50, null: false
1091
+ end
1092
+ has_and_belongs_to_many :products
1093
+ end
1094
+ end
1095
+
1096
+ let(:up) { Generators::DeclareSchema::Migration::Migrator.run.first }
1097
+
1098
+ it 'generates change_column on the join table without crashing' do
1099
+ expect(up).to match(/change_column\s+:products_tags,\s+:tag_id,\s+:string/)
1100
+ end
1101
+ end
1102
+
1103
+ it 'HABTM mirrors both parents primary key types (different non-default types)' do
1104
+ class Product < ActiveRecord::Base # rubocop:disable Lint/ConstantDefinitionInBlock
1105
+ declare_schema id: { type: :string, limit: 36 } do
1106
+ string :name, limit: 100, null: false
1107
+ end
1108
+ has_and_belongs_to_many :tags
1109
+ end
1110
+
1111
+ class Tag < ActiveRecord::Base # rubocop:disable Lint/ConstantDefinitionInBlock
1112
+ declare_schema id: { type: :integer, limit: 4 } do
1113
+ string :label, limit: 50, null: false
1114
+ end
1115
+ has_and_belongs_to_many :products
1116
+ end
1117
+
1118
+ up, _ = Generators::DeclareSchema::Migration::Migrator.run
1119
+
1120
+ expect(up).to match(/t\.string\s+:product_id, limit: 36, null: false/)
1121
+ expect(up).to match(/t\.integer\s+:tag_id, limit: 4, null: false/)
1122
+ end
1123
+ end
1124
+
991
1125
  context 'models with the same parent foreign key relation' do
992
1126
  include_context 'skip if' do
993
1127
  let(:adapter) { 'sqlite3' }
@@ -41,7 +41,12 @@ RSpec.describe DeclareSchema::Model::HabtmModelShim do
41
41
  end
42
42
 
43
43
  describe 'instance methods' do
44
- subject { described_class.new(join_table, foreign_keys, parent_table_names, connection: connection) }
44
+ let(:parent_models) { [User, Customer] }
45
+ let(:parents) { foreign_keys.zip(parent_models) }
46
+
47
+ subject do
48
+ described_class.new(join_table, parents, connection: connection)
49
+ end
45
50
 
46
51
  describe '#initialize' do
47
52
  it 'stores initialization attributes' do
@@ -88,8 +93,8 @@ RSpec.describe DeclareSchema::Model::HabtmModelShim do
88
93
  end
89
94
 
90
95
  describe '#primary_key' do
91
- it 'returns false because there is no single-column PK for ActiveRecord to use' do
92
- expect(subject.primary_key).to eq(false)
96
+ it 'returns the composite of the two foreign keys (alphabetically sorted)' do
97
+ expect(subject.primary_key).to eq(["customer_id", "user_id"])
93
98
  end
94
99
  end
95
100
 
@@ -116,14 +121,15 @@ RSpec.describe DeclareSchema::Model::HabtmModelShim do
116
121
  let(:foreign_keys_and_table_names) { [["advertiser_id", "advertisers"], ["campaign_id", "campaigns"]] }
117
122
  let(:foreign_keys) { foreign_keys_and_table_names.map(&:first) }
118
123
  let(:parent_table_names) { foreign_keys_and_table_names.map(&:last) }
124
+ let(:parent_models) { [Table1, Table2] }
119
125
 
120
126
  before do
121
- class Table1 < ActiveRecord::Base
122
- self.table_name = 'advertiser_campaign'
127
+ class Table1 < ActiveRecord::Base # rubocop:disable Lint/ConstantDefinitionInBlock
128
+ self.table_name = 'advertisers'
123
129
  end
124
130
 
125
- class Table2 < ActiveRecord::Base
126
- self.table_name = 'tracking_pixel'
131
+ class Table2 < ActiveRecord::Base # rubocop:disable Lint/ConstantDefinitionInBlock
132
+ self.table_name = 'campaigns'
127
133
  end
128
134
  end
129
135
 
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'generators/declare_schema/support/thor_shell'
4
+
5
+ # Verifies that when the migrator has to add a declared primary key column
6
+ # that has no corresponding FieldSpec (because it was appended to `to_add`
7
+ # at lib/generators/declare_schema/migration/migrator.rb:366), the column
8
+ # type is derived from `DeclareSchema.default_generated_primary_key_type`
9
+ # rather than a hardcoded `:integer`. This matters for apps that override
10
+ # `config.generators.primary_key_type`.
11
+ RSpec.describe 'DeclareSchema.default_generated_primary_key_type integration with Migrator' do
12
+ include_context 'prepare test app'
13
+
14
+ before do
15
+ if current_adapter == 'mysql2'
16
+ ActiveRecord::Base.connection.execute("CREATE TABLE foos (id int PRIMARY KEY, name varchar(250))")
17
+ else
18
+ ActiveRecord::Base.connection.execute("CREATE TABLE foos (id integer PRIMARY KEY AUTOINCREMENT NOT NULL, name varchar(250))")
19
+ end
20
+ ActiveRecord::Base.connection.schema_cache.clear!
21
+
22
+ allow_any_instance_of(DeclareSchema::Support::ThorShell)
23
+ .to receive(:ask).with(/one of the rename choices or press enter to keep/) { 'drop id' }
24
+ end
25
+
26
+ it 'derives the new PK column type from DeclareSchema.default_generated_primary_key_type rather than a hardcoded :integer' do
27
+ allow(::DeclareSchema).to receive(:default_generated_primary_key_type).and_return(:string)
28
+
29
+ class Foo < ActiveRecord::Base # rubocop:disable Lint/ConstantDefinitionInBlock
30
+ declare_schema do
31
+ string :name, limit: 250
32
+ end
33
+ self.primary_key = "foo_id"
34
+ end
35
+
36
+ up, _down = Generators::DeclareSchema::Migration::Migrator.run
37
+
38
+ expect(up).to include("add_column :foos, :foo_id, :string")
39
+ expect(up).not_to include("add_column :foos, :foo_id, :integer")
40
+ end
41
+
42
+ it 'falls through to the configured Rails default (:bigint) when the helper is not stubbed' do
43
+ class Foo < ActiveRecord::Base # rubocop:disable Lint/ConstantDefinitionInBlock
44
+ declare_schema do
45
+ string :name, limit: 250
46
+ end
47
+ self.primary_key = "foo_id"
48
+ end
49
+
50
+ up, _down = Generators::DeclareSchema::Migration::Migrator.run
51
+
52
+ expect(up).to include("add_column :foos, :foo_id, #{::DeclareSchema.default_generated_primary_key_type.inspect}")
53
+ end
54
+ end
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: 3.1.0.colin.1
4
+ version: 3.1.0.colin.2
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: 2025-08-19 00:00:00.000000000 Z
11
+ date: 2026-05-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '6.0'
19
+ version: '7.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '6.0'
26
+ version: '7.0'
27
27
  description: Declare your Rails/active_record model schemas and have database migrations
28
28
  generated for you!
29
29
  email: development@invoca.com
@@ -131,6 +131,7 @@ files:
131
131
  - spec/lib/declare_schema/schema_change/table_remove_spec.rb
132
132
  - spec/lib/declare_schema/schema_change/table_rename_spec.rb
133
133
  - spec/lib/declare_schema_spec.rb
134
+ - spec/lib/generators/declare_schema/migration/migrator_default_pk_type_spec.rb
134
135
  - spec/lib/generators/declare_schema/migration/migrator_spec.rb
135
136
  - spec/spec_helper.rb
136
137
  - spec/support/acceptance_spec_helpers.rb