184 lines
4.1 KiB
Markdown
184 lines
4.1 KiB
Markdown
# 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:
|
|
1. Not properly handling the `permissions` attribute
|
|
2. 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
|
|
|
|
```ruby
|
|
# 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.erb`
|
|
- `app/views/admin/api_keys/show.html.erb`
|
|
|
|
Added defensive code to handle edge cases:
|
|
|
|
```erb
|
|
<% 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 `serialize` declaration
|
|
- Data is stored and retrieved as Hash automatically
|
|
- But we need to handle edge cases
|
|
|
|
### Safe Accessor Method
|
|
- The custom `permissions` method 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:
|
|
|
|
```ruby
|
|
create_table "api_keys" do |t|
|
|
# ...
|
|
t.jsonb "permissions", default: {}
|
|
# ...
|
|
end
|
|
```
|
|
|
|
**Key points:**
|
|
- Type: `jsonb` (not `json` or `text`)
|
|
- Default: `{}` (empty hash)
|
|
- No serialization needed
|
|
|
|
## Verification
|
|
|
|
Test that permissions work correctly:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```ruby
|
|
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:
|
|
|
|
```ruby
|
|
def can?(permission)
|
|
permissions.fetch(permission.to_s, false)
|
|
end
|
|
```
|
|
|
|
Usage:
|
|
```ruby
|
|
api_key.can?("send_sms") # => true or false
|
|
```
|
|
|
|
## Testing
|
|
|
|
### Manual Test
|
|
1. Start server: `bin/dev`
|
|
2. Visit: http://localhost:3000/admin/api_keys
|
|
3. Should see permissions displayed as badges
|
|
4. Create new API key
|
|
5. Check permissions are saved and displayed
|
|
|
|
### Console Test
|
|
```ruby
|
|
# 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!
|