coatepec 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7cbcec579a7904ef50c8d8227036bbb0dd45f05bd664ffa2fceb2d210f5171be
4
- data.tar.gz: 672092f99f2bfab2de7ad832572e6f16b5aeea8613860f0456fc81f7abaf25f4
3
+ metadata.gz: 161aafede82c7279701f75a51cc7976a6ef7bbbadff7bc22a7bc04dc050e1780
4
+ data.tar.gz: 2b867df0d495bb573e1e00ad9a94f9aca800fa7045bbf6965fb0825acfc2683f
5
5
  SHA512:
6
- metadata.gz: f065ef0f83fbf5796d8e333326d9cf852a7322de12a12f77ece713df6cf00d29f167ea3ca2001830cb70e1e770a4533e3b111956390da1d0968c682cdeb8335a
7
- data.tar.gz: 62d2d70afe6f5554c4e40fd83572384161f1819b228e65baa5c3a9c048716632e47c126356810e0c5aeb1e2ca1e9a79ed30fe0d10e97e773d6f1b6527a373d38
6
+ metadata.gz: d93777d12dac89c55450377259fc7d206bd6ef077766395f1397045372a58a7203bf986bbb9a525fc89b8510159a7fd9d0c3a057c321219edd95d6f23caa7acf
7
+ data.tar.gz: 3e580288a7c4ef9b0e707909967c28293ce8bc8b749e8c11d03903faef69ef80587c4a8307e13e769633d521b6245a05f806859d124a40346a9a0120b56e3e9b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.1
4
+
5
+ - Fix `rails_model` crashing outright for a model with a `has_one`/
6
+ `has_many :through` association that goes through a polymorphic
7
+ `belongs_to` (e.g. `has_one :x, through: :notable, source: :y` where
8
+ `belongs_to :notable, polymorphic: true`). `foreign_key` on that
9
+ reflection needs a single fixed class to resolve, which a polymorphic
10
+ association can't provide, and that was previously an unrescued
11
+ `ArgumentError` that took down the whole response. That specific case now
12
+ reports `foreign_key: nil`/`class_name: nil` for the affected association,
13
+ the same way an already-handled plain polymorphic `belongs_to` does.
14
+ - Add a general safety net around each association's metadata: if a single
15
+ association still fails for some other, not-yet-anticipated
16
+ `ActiveRecord` reflection quirk, only that association's entry degrades
17
+ (gaining an `error` field describing what went wrong) instead of the
18
+ entire `rails_model` call crashing for the whole model.
19
+
3
20
  ## 0.5.0
4
21
 
5
22
  - **Breaking:** `mcp` is no longer a runtime dependency of the `coatepec`
data/README.md CHANGED
@@ -130,6 +130,31 @@ a no-op.
130
130
  | `rails_routes` | `query?`, `limit?` (1..200, default 50), `offset?` | Case-insensitive filter across name/verb/path/controller/action |
131
131
  | `rails_model` | `name` (constant path, e.g. `Widget` or `Admin::Widget`) | ActiveRecord models only; columns, associations, validators, enums -- no row data |
132
132
 
133
+ ### Example queries
134
+
135
+ `rails_routes`:
136
+
137
+ - "What are all the routes in this app?" -- `rails_routes()`
138
+ - "What's the URL for widgets?" -- `rails_routes(query: "widget")`. `query` is a
139
+ case-insensitive substring match across name, verb, path, controller, *and*
140
+ action -- not just the path -- so a resource name alone typically returns
141
+ every route for that resource (index/create/new/...); narrow further with
142
+ something like `query: "new_widget"` to hit one route by name.
143
+ - "Which routes accept POST?" -- `rails_routes(query: "POST")`, the same
144
+ substring match applied to the verb column.
145
+
146
+ `rails_model`:
147
+
148
+ - "What columns does Widget have, and which are nullable?" --
149
+ `rails_model(name: "Widget")` -- see `columns[].null`, `columns[].sql_type`,
150
+ `columns[].default`.
151
+ - "What validations and associations does Widget enforce?" -- same call --
152
+ see `validators` and `associations`.
153
+ - "What happens if I ask about a non-model class, like a controller?" --
154
+ `rails_model(name: "ApplicationController")` raises `not_active_record_model`
155
+ rather than introspecting it (a nonexistent constant raises `model_not_found`
156
+ instead) -- the tool only ever reflects on `ActiveRecord::Base` descendants.
157
+
133
158
  The warm test worker forces Rails' reload-checking on for its own boot,
134
159
  regardless of the target app's own `test.rb` setting (which disables it by
135
160
  default) -- so editing a model file takes effect on the next tool call
@@ -93,7 +93,32 @@ module Coatepec
93
93
  end
94
94
 
95
95
  def associations_for(klass)
96
- klass.reflect_on_all_associations.first(MAX_ITEMS).map { |assoc| build_association_data(assoc) }
96
+ klass.reflect_on_all_associations.first(MAX_ITEMS).map { |assoc| safe_association_data(assoc) }
97
+ end
98
+
99
+ # build_association_data's own field-level rescues (association_class_name,
100
+ # association_foreign_key) cover every failure mode seen in practice so
101
+ # far, but ActiveRecord's reflection internals are large enough that
102
+ # betting the whole rails_model call on having anticipated all of them
103
+ # is optimistic -- a has_one/has_many :through a polymorphic belongs_to
104
+ # is exactly the kind of case that wasn't anticipated until it crashed
105
+ # this method outright (see association_foreign_key). This is the
106
+ # boundary of last resort: one association's introspection failing
107
+ # degrades just that entry instead of the whole model. It deliberately
108
+ # does not rescue StandardError -- a NoMethodError here is a genuine
109
+ # Coatepec bug (e.g. a typo), and letting that crash loudly beats
110
+ # silently reporting it as "this association is fine, no data".
111
+ def safe_association_data(assoc)
112
+ build_association_data(assoc)
113
+ rescue NameError, ArgumentError, ::ActiveRecord::ActiveRecordError => e
114
+ degraded_association_data(assoc, e)
115
+ end
116
+
117
+ def degraded_association_data(assoc, error)
118
+ {
119
+ name: assoc.name.to_s, macro: assoc.macro.to_s, class_name: nil, foreign_key: nil, through: nil,
120
+ polymorphic: nil, error: "#{error.class}: #{error.message}"
121
+ }
97
122
  end
98
123
 
99
124
  def build_association_data(assoc)
@@ -101,7 +126,7 @@ module Coatepec
101
126
  name: assoc.name.to_s,
102
127
  macro: assoc.macro.to_s,
103
128
  class_name: association_class_name(assoc),
104
- foreign_key: assoc.foreign_key.to_s,
129
+ foreign_key: association_foreign_key(assoc),
105
130
  through: assoc.through_reflection&.name&.to_s,
106
131
  polymorphic: assoc.polymorphic? || false
107
132
  }
@@ -120,6 +145,20 @@ module Coatepec
120
145
  nil
121
146
  end
122
147
 
148
+ # A has_one/has_many :through reflection whose `through:` target is
149
+ # itself a polymorphic belongs_to has no single fixed class either --
150
+ # ThroughReflection#foreign_key needs through_reflection.klass to find
151
+ # the source reflection, and .klass on a polymorphic reflection always
152
+ # raises ArgumentError (see association_class_name above). A dangling
153
+ # class_name on the reflection itself still raises NameError the same
154
+ # way. Report foreign_key: nil rather than letting either crash the
155
+ # call.
156
+ def association_foreign_key(assoc)
157
+ assoc.foreign_key.to_s
158
+ rescue NameError, ArgumentError
159
+ nil
160
+ end
161
+
123
162
  def validators_for(klass)
124
163
  klass.validators.first(MAX_ITEMS).map do |validator|
125
164
  {
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Coatepec
4
- VERSION = "0.5.0"
4
+ VERSION = "0.5.1"
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: coatepec
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Enrique Mogollan
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-08-05 00:00:00.000000000 Z
10
+ date: 2026-08-07 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: railties