diff --git a/lib/permittable/generator.rb b/lib/permittable/generator.rb index ec28371..c7a407c 100644 --- a/lib/permittable/generator.rb +++ b/lib/permittable/generator.rb @@ -400,6 +400,15 @@ def columns_for(model) # top (draft_column) must not share this rescue: an error there would # otherwise discard EVERY column — the draft silently falls back to a # scan alone, or to nothing — for what is one column's problem. + # + # The rescue is scoped to ActiveRecord::ActiveRecordError, same as + # ColumnGuard.schema_reachable? and for the same reason: a genuinely + # unreachable schema (no database yet, table not migrated) degrades to + # no columns, but a real bug — a broken custom type adapter, a NameError + # from a typo — must keep surfacing instead of quietly emitting an empty + # draft. The defined? guard keeps this gem loadable without + # activerecord, same as schema_reachable? (a host without it duck-types + # `model:` and cannot raise an ActiveRecordError in the first place). def schema_columns(model) return nil unless model.respond_to?(:columns) return nil unless model.table_exists? @@ -408,7 +417,9 @@ def schema_columns(model) # into its column names; a nil primary key becomes []. skipped = SKIPPED_COLUMNS + Array(model.primary_key).map(&:to_s) model.columns.reject { |c| skipped.include?(c.name) } - rescue StandardError + rescue StandardError => e + raise unless defined?(ActiveRecord::ActiveRecordError) && e.is_a?(ActiveRecord::ActiveRecordError) + nil end diff --git a/spec/generator_spec.rb b/spec/generator_spec.rb index c2f41e4..9e40aad 100644 --- a/spec/generator_spec.rb +++ b/spec/generator_spec.rb @@ -588,6 +588,18 @@ def deserialize(_value) = raise("a type deserialize ran while drafting") allow(GenUnreachable).to receive(:columns).and_raise(ActiveRecord::StatementInvalid, "no such table") expect(described_class.draft(model: GenUnreachable)).to be_nil end + + it "lets a real bug from schema access propagate instead of drafting an empty contract" do + # Same rescue scope as ColumnGuard.schema_reachable?: only + # ActiveRecord::ActiveRecordError is swallowed (a genuinely + # unreachable schema). Anything else — a broken custom type + # adapter, a real app bug — must surface loudly rather than + # quietly falling back to "no columns". + stub_const("GenMisconfigured", Class.new(TestModel) { self.table_name = "gen_vehicles" }) + allow(GenMisconfigured).to receive(:columns).and_raise(NoMethodError, "undefined method `type' for nil") + expect { described_class.draft(model: GenMisconfigured) } + .to raise_error(NoMethodError, /undefined method `type'/) + end end context "with STI and optimistic locking" do