Add MySQL support and multi-database CI testing#28
Conversation
Fixes #9 - MySQL JSON column default value error ## Problem MySQL 8+ doesn't allow default values on JSON columns. Users running migrations on MySQL would get: Mysql2::Error: BLOB, TEXT, GEOMETRY or JSON column 'metadata' can't have a default value ## Solution 1. **Migration fix**: Added `json_column_default` helper that returns `nil` for MySQL and `{}` for SQLite/PostgreSQL. The models handle nil gracefully. 2. **Model metadata handling**: Added proper nil handling and caching fixes to Wallet, Transaction, and Fulfillment models: - `metadata` getter returns `super || {}` (handles MySQL nil) - `reload(*)` clears the metadata cache (fixes stale data bug) - `before_save :sync_metadata_cache` persists in-place modifications 3. **CI multi-database testing**: Added separate jobs for PostgreSQL and MySQL testing following Pay gem best practices: - Uses `db:migrate:reset` instead of `db:test:prepare` to avoid loading schema.rb which has SQLite-specific defaults - Proper Docker service configuration with health checks ## Files changed - `lib/generators/.../create_usage_credits_tables.rb.erb` - MySQL-safe defaults - `lib/usage_credits/models/{wallet,transaction,fulfillment}.rb` - Metadata fixes - `.github/workflows/test.yml` - Multi-database CI (SQLite, PostgreSQL, MySQL) - `test/dummy/config/database.yml` - Simplified to standard Rails pattern - `test/dummy/db/migrate/...` - Uses json_column_default helper - `Gemfile` - Added mysql2 gem for testing All 700 tests pass across Pay versions 8.3, 9.0, 10.0, and 11.0.
Change ActiveRecord::Schema version from 8.0 to 7.2 so the schema can be loaded by both Rails 7.2 and Rails 8.x. Rails 8.x can load older schema versions, but Rails 7.2 cannot load 8.0 format.
The sync_metadata_cache callback now ensures metadata is set to {}
before saving if it was never accessed. This prevents MySQL from
rejecting inserts with NULL values on the metadata column, which
has null: false but can't have a default value in MySQL.
Fixes the remaining MySQL test failures caused by records created
without explicitly setting metadata.
MySQL's JSON_EXTRACT returns JSON values (not SQL values), so comparing
to Ruby's `true` (which becomes `1`) doesn't work. Use CAST(? AS JSON)
and CAST('true' AS JSON) for proper JSON-to-JSON comparison in MySQL.
Also fixed SQLite to use `1` instead of `true` since json_extract
returns SQL values where booleans become integers.
This fixes the refund tracking tests that were failing on MySQL.
PR Review: MySQL/PostgreSQL Compatibility and Multi-Database CI TestingI've thoroughly reviewed this PR and I'm impressed with the comprehensive approach to fixing the MySQL compatibility issue and adding multi-database testing. Overall, this is high-quality work that addresses a critical bug while improving the gem's robustness. β Strengths1. Excellent Problem AnalysisThe PR description clearly documents the root cause (MySQL 8+ JSON column default value restriction) and the cascading issues discovered during the fix. The metadata caching bug discovery shows thorough testing. 2. Smart Migration HelperThe def json_column_default
return nil if connection.adapter_name.downcase.include?('mysql')
{}
endThis follows the Rails convention of handling database differences in migrations rather than application code. 3. Comprehensive Metadata Caching FixThe three-pronged approach to metadata handling is solid:
This pattern is consistently applied across Wallet, Transaction, and Fulfillment models. 4. Robust CI SetupFollowing the Pay gem's best practices with separate jobs for SQLite, PostgreSQL, and MySQL is the right approach. The use of 5. Database-Specific JSON Query HandlingThe PayChargeExtension fixes for JSON queries show good understanding of database differences:
π Observations & Suggestions1. Test Coverage for Metadata Caching
|
Summary
This PR fixes GitHub issue #9 and adds comprehensive multi-database testing to ensure the gem works reliably with SQLite, PostgreSQL, and MySQL.
What triggered this
A user reported that running migrations on MySQL failed with:
This is because MySQL 8+ doesn't allow default values on JSON columns, but our migration used
default: {}.What we did
1. Fixed MySQL JSON column compatibility
Added a
json_column_defaulthelper in migrations that:nilfor MySQL (where JSON defaults aren't allowed){}for SQLite and PostgreSQL (where they work fine)The models now handle
nilmetadata gracefully by defaulting to{}in their accessors.2. Fixed metadata caching bug (19 test failures)
While implementing the MySQL fix, we discovered a caching bug where
reload()didn't clear the cached metadata, causing stale data issues. Fixed by:reload(*)override to clear@indifferent_metadatacachebefore_save :sync_metadata_cacheto persist in-place modifications3. Added multi-database CI testing
Following the Pay gem's best practices, we added:
Key CI decisions:
db:migrate:resetinstead ofdb:test:prepareto avoid loadingschema.rbwhich has SQLite-specific defaultsPitfalls we avoided
schema.rb incompatibility: The auto-generated
schema.rbhasdefault: {}which fails on MySQL. We usedb:migrate:resetin CI to run migrations instead of loading schema.Metadata cache staleness: The
@indifferent_metadatacache wasn't cleared onreload(), causing tests to see stale data after database updates.In-place modification not persisting: Changes like
metadata["key"] = "value"weren't being saved because they didn't trigger dirty tracking. Fixed withbefore_savecallback.Files changed
lib/generators/.../create_usage_credits_tables.rb.erbjson_column_defaulthelperlib/usage_credits/models/wallet.rblib/usage_credits/models/transaction.rblib/usage_credits/models/fulfillment.rb.github/workflows/test.ymltest/dummy/config/database.ymltest/dummy/db/migrate/...json_column_defaulthelperGemfilemysql2gem for testingTest results
All 700 tests pass across:
Test plan
Closes
Closes #9