In Rails 8.1 SQLite column order differs between platforms
If you're upgrading to Rails 8.1 and using SQLite with Ransack, you might encounter mysterious test failures that only happen on CI but never locally.
Here's what we discovered while upgrading AlchemyCMS to Rails 8.1, and how to fix it.
The Symptom
While preparing AlchemyCMS for Rails 8.1 compatibility, our test suite passed locally on macOS but consistently failed on GitHub Actions (using ubuntu-24) for the SQLite + Rails 8.1 combination.
The failures were in search functionality using Ransack:
ActionController::UnpermittedParameters:
found unpermitted parameter: :name_or_hidden_name_or_description_or_location_name_cont
The puzzling part:
The same Rails version, same Ruby version, same SQLite, yet different behavior.
The Investigation
After ruling out Ruby versions, storage adapters, and SQLite versions, we created a Docker container with platform: linux/amd64 to reproduce the CI environment locally. Adding debug logging revealed the issue:
What the client sent:
name_or_hidden_name_or_description_or_location_name_cont
What the server permitted:
description_or_hidden_name_or_name_or_location_name_cont
The field names were alphabetically sorted differently!
The Root Cause
SQLite returns columns from PRAGMA table_info in different orders depending on the platform. AlchemyCMS generates Ransack search field names dynamically by joining column names with _or_:
def searchable_attribute_names
attributes.select { |a| searchable_attribute?(a) }
.collect { |h| h[:name] }
end
def search_field_name
"#{searchable_attribute_names.join("_or_")}_cont"
end
On macOS, columns came back in one order. On Linux, a different order. This created mismatched field names between what the form submitted and what the controller permitted.
The Fix
Sort the attribute names to ensure consistent, deterministic field names across all platforms:
def searchable_attribute_names
attributes.select { |a| searchable_attribute?(a) }
.collect { |h| h[:name] }
.sort # Add this!
end
Bonus: Ransack Association Validation
After sorting, we hit a second issue. Ransack validates associations differently based on attribute order in compound predicates:
name_or_image_file_blob_filename_cont— works ✓image_file_blob_filename_or_name_cont— fails ✗
When the association-traversing attribute comes first, Ransack requires ransackable_associations on the target model.
We added:
ActiveSupport.on_load(:active_storage_blob) do
ActiveStorage::Blob.define_singleton_method(:ransackable_associations) do |_auth_object|
%w[]
end
end
And this fixed it.
Key Takeaways
- SQLite column order is platform-dependent. Don't assume deterministic ordering
- Sort dynamic field names. Any code generating identifiers from database metadata should sort for consistency
- Use Docker Compose with platform:
linux/amd64 - Ransack compound predicates. Attribute order affects validation behavior
Debugging Tips
If you're seeing similar issues:
- Add logging to see exactly what parameters are being sent vs permitted
- Use Docker to match your CI environment exactly
- Check any code that iterates over database columns or attributes
The fix will be included in AlchemyCMS 8.1. If you're building your own Ransack integration with dynamic field names, make sure to sort them!