Fix store_accessor on MariaDB by declaring JSON attribute type#2769
Fix store_accessor on MariaDB by declaring JSON attribute type#2769diogovernier wants to merge 1 commit intobasecamp:mainfrom
Conversation
MariaDB represents JSON columns as LONGTEXT, which causes Rails to map them to Type::Text instead of Type::Json. Since Type::Text lacks the accessor method that store_accessor requires, this raises ActiveRecord::ConfigurationError on MariaDB. Adding explicit `attribute :column, :json` declarations ensures the columns are typed as JSON regardless of what the database adapter reports. On MySQL this is a no-op (already Type::Json); on MariaDB it overrides Type::Text with Type::Json. Fixes basecamp#2402
There was a problem hiding this comment.
Pull request overview
This PR addresses a MariaDB-specific Rails incompatibility where JSON columns are introspected as LONGTEXT (typed as ActiveRecord::Type::Text), causing store_accessor to raise ActiveRecord::ConfigurationError. It fixes this by explicitly declaring the affected columns as JSON attributes in the relevant concerns, preserving native JSON behavior on MySQL while preventing crashes on MariaDB.
Tip
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.
Changes:
- Declare
Filter#fieldsas:jsonbeforestore_accessorviaFilter::Fields. - Declare
Event#particularsas:jsonbeforestore_accessorviaEvent::Particulars.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| app/models/filter/fields.rb | Forces fields to be typed as JSON so store_accessor works on MariaDB. |
| app/models/event/particulars.rb | Forces particulars to be typed as JSON so store_accessor works on MariaDB. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| end | ||
|
|
||
| included do | ||
| attribute :fields, :json |
There was a problem hiding this comment.
Consider adding a regression test that fails when the backing column is treated as text (MariaDB JSON-as-LONGTEXT) to ensure store_accessor :fields continues to work. One approach is a small test model/table with a :text fields column that includes Filter::Fields and asserts reading/writing a store accessor does not raise ActiveRecord::ConfigurationError.
| attribute :fields, :json | |
| if respond_to?(:columns_hash) && columns_hash["fields"]&.type == :json | |
| attribute :fields, :json | |
| end |
| extend ActiveSupport::Concern | ||
|
|
||
| included do | ||
| attribute :particulars, :json |
There was a problem hiding this comment.
Consider adding a regression test for the MariaDB JSON-as-LONGTEXT scenario to ensure store_accessor :particulars keeps working. For example, define a temporary test table/model with a :text particulars column, include Event::Particulars, and assert setting/reading assignee_ids does not raise ActiveRecord::ConfigurationError.
Summary
store_accessorcrash when running Fizzy on MariaDB by adding explicitattribute :column, :jsondeclarations toFilter::FieldsandEvent::ParticularsLONGTEXTinternally, causing Rails to map them toType::Textwhich is not a recognized store typeType::JsonFixes #2402
The problem
MariaDB implements JSON as an alias for
LONGTEXTwith aCHECK (json_valid(...))constraint. When ActiveRecord introspects the column, it seeslongtextand maps it toType::Text. Callingstore_accessorthen fails becauseType::Textis not a recognized store type:Why
attribute :column, :jsonoverstore :column, coder: JSONThe workaround suggested in the issue (
store :fields, coder: JSON) works, but introduces unnecessary complexity on MySQL:Adds a serialization layer that isn't needed.
storewraps the column inType::Serializedwith anIndifferentCoder. On MySQL, where the column is already native JSON, this stacks an extra encoding/decoding layer on top of what the database handles natively.Changes the accessor type.
Type::JsonusesStringKeyedHashAccessor(string keys, matching JSON semantics).Type::Serializedswitches toIndifferentHashAccessor(HashWithIndifferentAccess) — a subtle behavior change for any code accessing the underlying attribute hash directly.attribute :column, :jsonavoids both issues. It simply tells Rails "this column is JSON" regardless of what the database adapter reports. On MySQL it's a no-op; on MariaDB it overridesType::TextwithType::Json. No extra layers, no behavior changes.This is the same approach recommended in rails/rails#44997, where the Rails community converged on
attribute :column, :jsonas the correct fix for MariaDB'sLONGTEXT-backed JSON columns.Reproduction
Standalone reproduction script (requires MariaDB on port 3307)
Setup:
Script (
repro_2402.rb):Expected output: