declare_schema 3.0.0 → 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: d9bc6d7a94a1064cdeca883fbd1f40063771b12aff133925c1b83fcb83affebf
4
- data.tar.gz: 6cc522f1d5b5bf855451e5985e8f8083ac610489625c71fdb9e1cb9d713ed751
3
+ metadata.gz: 1b65098860b02787bce136f3b694bcd294cdaaa6980716492bcf66b5ab9c4cc9
4
+ data.tar.gz: 9d8ff3e2e7c2cc06886f59b8052ab0f4b2d4bac88ea46ac4ada831000b6cb0a8
5
5
  SHA512:
6
- metadata.gz: f66aa50f211b7252ca146b72af859b90b5802025c99f2395379f0bdc7511b32b01918d56ed752edce00fdcfab17c47b2c218816581bd71ed148a026961e8fe54
7
- data.tar.gz: d8b7b93ab90a18b3dda1ed6b19083b3f087aa8c35143e901247f9a44b67fac81f1c40be2e4d980fc4b38fcd29605912ee53a868451f3648ac9d210ae879c696f
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
@@ -4,6 +4,13 @@ Inspired by [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
4
4
 
5
5
  Note: this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [3.1.0] - Unreleased
8
+ ### Added
9
+ - Add HABTM support for arbitrary primary key in the referenced table (rather than just :bigint).
10
+
11
+ ### Removed
12
+ - Drop support for Rails 6.x. Minimum supported Rails is now 7.0.
13
+
7
14
  ## [3.0.0] - 2025-04-08
8
15
  ### Changed
9
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.0.0)
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
@@ -3,7 +3,6 @@
3
3
  require 'active_record'
4
4
  require 'declare_schema/dsl'
5
5
  require 'declare_schema/model'
6
- require 'declare_schema/field_declaration_dsl'
7
6
 
8
7
  module DeclareSchema
9
8
  module Macros
@@ -46,16 +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)
50
- _declared_primary_key = model._declared_primary_key
51
-
52
- name.to_s == _declared_primary_key and raise ArgumentError, "you may not provide a field spec for the primary key #{name.inspect}"
53
-
49
+ def initialize(model, name, type, position: 0, resolver: nil, **options)
54
50
  @model = model
55
51
  @name = name.to_sym
56
52
  type.is_a?(Symbol) or raise ArgumentError, "type must be a Symbol; got #{type.inspect}"
57
53
  @type = TYPE_SYNONYMS[type] || type
58
54
  @position = position
55
+ @resolver = resolver
59
56
  @options = options.dup
60
57
 
61
58
  @options.has_key?(:null) or @options[:null] = ::DeclareSchema.default_null
@@ -124,6 +121,17 @@ module DeclareSchema
124
121
  @sql_options = @options.slice(*SQL_OPTIONS)
125
122
  end
126
123
 
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:)
132
+ self.class.new(model, name, @type, position:, **@options.merge(null.nil? ? {} : { null: }))
133
+ end
134
+
127
135
  # returns the attributes for schema migrations as a Hash
128
136
  # omits name and position since those are meta-data above the schema
129
137
  # omits keys with nil values
@@ -6,22 +6,28 @@ module DeclareSchema
6
6
  class << self
7
7
  def from_reflection(reflection)
8
8
  new(reflection.join_table,
9
- [reflection.foreign_key, reflection.association_foreign_key],
10
- [reflection.active_record.table_name, reflection.klass.table_name],
9
+ [
10
+ [reflection.foreign_key, reflection.active_record],
11
+ [reflection.association_foreign_key, reflection.klass]
12
+ ],
11
13
  connection: reflection.active_record.connection)
12
14
  end
13
15
  end
14
16
 
15
- attr_reader :join_table, :foreign_keys, :parent_table_names, :connection
17
+ attr_reader :join_table, :foreign_keys, :parent_models, :parent_table_names, :connection
16
18
 
17
- def initialize(join_table, foreign_keys, parent_table_names, connection:)
18
- foreign_keys.is_a?(Array) && foreign_keys.size == 2 or
19
- raise ArgumentError, "foreign_keys must be <Array[2]>; got #{foreign_keys.inspect}"
20
- parent_table_names.is_a?(Array) && parent_table_names.size == 2 or
21
- raise ArgumentError, "parent_table_names must be <Array[2]>; got #{parent_table_names.inspect}"
19
+ def initialize(join_table, parents, connection:)
22
20
  @join_table = join_table
23
- @foreign_keys = foreign_keys.sort # Rails requires these be in alphabetical order
24
- @parent_table_names = @foreign_keys == foreign_keys ? parent_table_names : parent_table_names.reverse # match the above sort
21
+
22
+ parents.is_a?(Array) && parents.size == 2 or
23
+ raise ArgumentError, "parents must be <Array[2]>; got #{parents.inspect}"
24
+
25
+ # Rails requires HABTM foreign keys to be in alphabetical order, so we start by sorting by those
26
+ parents.sort_by!(&:first)
27
+ @foreign_keys = parents.map(&:first)
28
+ @parent_models = parents.map(&:last)
29
+ @parent_table_names = parent_models.map(&:table_name)
30
+
25
31
  @connection = connection
26
32
  end
27
33
 
@@ -33,14 +39,42 @@ module DeclareSchema
33
39
  join_table
34
40
  end
35
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
+
36
59
  def field_specs
37
60
  foreign_keys.each_with_index.each_with_object({}) do |(foreign_key, i), result|
38
- result[foreign_key] = ::DeclareSchema::Model::FieldSpec.new(self, foreign_key, :bigint, 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
39
70
  end
40
71
  end
41
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.)
42
76
  def primary_key
43
- false # no single-column primary key in database
77
+ foreign_keys
44
78
  end
45
79
 
46
80
  def _declared_primary_key