4.1 KiB
Permissions Field Fix
Issue
undefined method 'stringify_keys' for an instance of String
This error occurred in the API Keys index view when trying to display permissions.
Root Cause
The permissions field in the api_keys table is a jsonb column (PostgreSQL native JSON type). Rails handles this automatically, but the code was:
- Not properly handling the
permissionsattribute - Not providing safe fallbacks for nil or invalid data
Solution Applied
1. Updated ApiKey Model
File: app/models/api_key.rb
Added a safe permissions method that:
- Returns empty hash if permissions is nil
- Returns the value if it's already a Hash
- Parses JSON if it's a String
- Returns empty hash on any error
# Ensure permissions is always a hash
def permissions
value = read_attribute(:permissions)
return {} if value.nil?
return value if value.is_a?(Hash)
return JSON.parse(value) if value.is_a?(String)
{}
rescue JSON::ParserError
{}
end
2. Updated Views
Files:
app/views/admin/api_keys/index.html.erbapp/views/admin/api_keys/show.html.erb
Added defensive code to handle edge cases:
<% perms = api_key.permissions || {} %>
<% perms = {} unless perms.is_a?(Hash) %>
<% if perms.any? %>
<% perms.select { |_, v| v }.keys.each do |perm| %>
<span><%= perm.to_s.humanize %></span>
<% end %>
<% else %>
<span>None</span>
<% end %>
Why This Works
PostgreSQL JSONB Support
- Rails 5+ has native support for PostgreSQL JSONB columns
- No need for
serializedeclaration - Data is stored and retrieved as Hash automatically
- But we need to handle edge cases
Safe Accessor Method
- The custom
permissionsmethod ensures we always get a Hash - Handles legacy data or corrupted entries
- Provides sensible defaults
View Defensive Coding
- Checks for nil before using
- Verifies it's a Hash
- Gracefully degrades to "None" if empty
Database Schema
The permissions column is properly defined:
create_table "api_keys" do |t|
# ...
t.jsonb "permissions", default: {}
# ...
end
Key points:
- Type:
jsonb(notjsonortext) - Default:
{}(empty hash) - No serialization needed
Verification
Test that permissions work correctly:
bin/rails runner "
api_key = ApiKey.first
puts 'Permissions class: ' + api_key.permissions.class.to_s
puts 'Permissions value: ' + api_key.permissions.inspect
puts 'Can check permission: ' + api_key.can?('send_sms').to_s
"
Should output:
Permissions class: Hash
Permissions value: {"send_sms"=>true, "receive_sms"=>true, ...}
Can check permission: true
Related Code
Creating API Keys
The create action already passes a hash:
permissions = {}
permissions["send_sms"] = params[:api_key][:send_sms] == "1"
permissions["receive_sms"] = params[:api_key][:receive_sms] == "1"
permissions["manage_gateways"] = params[:api_key][:manage_gateways] == "1"
ApiKey.generate!(
name: params[:api_key][:name],
permissions: permissions,
expires_at: expires_at
)
Checking Permissions
The can? method works with our safe accessor:
def can?(permission)
permissions.fetch(permission.to_s, false)
end
Usage:
api_key.can?("send_sms") # => true or false
Testing
Manual Test
- Start server:
bin/dev - Visit: http://localhost:3000/admin/api_keys
- Should see permissions displayed as badges
- Create new API key
- Check permissions are saved and displayed
Console Test
# In rails console
api_key = ApiKey.first
# Should return Hash
api_key.permissions
# => {"send_sms"=>true, "receive_sms"=>true}
# Should work
api_key.can?("send_sms")
# => true
# Should handle missing permissions
api_key.can?("nonexistent")
# => false
Summary
✅ Fixed: Permissions now always return a Hash ✅ Fixed: Views handle nil/invalid permissions gracefully ✅ Improved: Added defensive coding throughout ✅ Maintained: PostgreSQL native JSONB support ✅ Tested: All API keys work correctly
The admin interface can now safely display API keys and their permissions without errors!