RuboCop Integration
Axn provides custom RuboCop cops to help enforce best practices and maintain code quality in your Action-based codebase.
Overview
The Axn/UncheckedResult
cop enforces proper result handling when calling Actions. It can detect when Action results are ignored and help ensure consistent error handling patterns.
Installation
1. Add to Your .rubocop.yml
require:
- axn/rubocop
# Enable Axn's custom cop
Axn/UncheckedResult:
Enabled: true
CheckNested: true # Check nested Action calls
CheckNonNested: true # Check non-nested Action calls
Severity: warning # or error
2. Verify Installation
Run RuboCop to ensure the cop is loaded:
bundle exec rubocop --show-cops | grep Axn
You should see:
Axn/UncheckedResult
Configuration Options
CheckNested
Controls whether the cop checks Action calls that are inside other Action classes.
Axn/UncheckedResult:
CheckNested: true # Check nested calls (default)
CheckNested: false # Skip nested calls
When to use CheckNested: false
:
- You're gradually adopting the rule and want to focus on top-level calls first
- Your team has different standards for nested vs. non-nested calls
- You're using a different pattern for nested Action handling
CheckNonNested
Controls whether the cop checks Action calls that are outside Action classes.
Axn/UncheckedResult:
CheckNonNested: true # Check non-nested calls (default)
CheckNonNested: false # Skip non-nested calls
When to use CheckNonNested: false
:
- You're only concerned about nested Action calls
- Top-level Action calls are handled by other tools or processes
- You want to focus on the most critical use case first
Severity
Controls how violations are reported.
Axn/UncheckedResult:
Severity: warning # Show as warnings (default)
Severity: error # Show as errors (fails CI)
Common Configuration Patterns
Full Enforcement (Recommended for New Projects)
Axn/UncheckedResult:
Enabled: true
CheckNested: true
CheckNonNested: true
Severity: error
Gradual Adoption (Recommended for Existing Projects)
Axn/UncheckedResult:
Enabled: true
CheckNested: true # Start with nested calls
CheckNonNested: false # Add this later
Severity: warning # Start with warnings
Nested-Only Focus
Axn/UncheckedResult:
Enabled: true
CheckNested: true
CheckNonNested: false
Severity: warning
What the Cop Checks
The cop analyzes your code to determine if you're:
- Inside an Action class - Classes that
include Action
- Inside the
call
method - Only the main execution method - Calling another Action - Using
.call
on Action classes - Properly handling the result - One of the acceptable patterns
What the Cop Ignores
The cop will NOT report offenses for:
- Action calls outside of Action classes (if
CheckNonNested: false
) - Action calls in methods other than
call
- Action calls that use
call!
(bang method) - Action calls where the result is properly handled
Proper Result Handling Patterns
✅ Using call!
class OuterAction
include Action
def call
InnerAction.call!(param: "value") # Exceptions bubble up
end
end
✅ Checking result.ok?
class OuterAction
include Action
def call
result = InnerAction.call(param: "value")
return result unless result.ok?
# Process successful result...
end
end
✅ Checking result.failed?
class OuterAction
include Action
def call
result = InnerAction.call(param: "value")
if result.failed?
return result
end
# Process successful result...
end
end
✅ Accessing result.error
class OuterAction
include Action
def call
result = InnerAction.call(param: "value")
if result.error
return result
end
# Process successful result...
end
end
✅ Returning the result
class OuterAction
include Action
def call
result = InnerAction.call(param: "value")
result # Result is returned, so it's properly handled
end
end
✅ Using result in expose
class OuterAction
include Action
exposes :nested_result
def call
result = InnerAction.call(param: "value")
expose nested_result: result # Result is used, so it's properly handled
end
end
✅ Passing result to another method
class OuterAction
include Action
def call
result = InnerAction.call(param: "value")
process_result(result) # Result is used, so it's properly handled
end
end
Common Anti-Patterns
❌ Ignoring the result
class OuterAction
include Action
def call
InnerAction.call(param: "value") # Result ignored - will trigger offense
# This continues even if InnerAction fails
end
end
❌ Assigning but not using
class OuterAction
include Action
def call
result = InnerAction.call(param: "value") # Assigned but never used
# Will trigger offense unless result is properly handled
end
end
❌ Using unrelated attributes
class OuterAction
include Action
def call
result = InnerAction.call(param: "value")
some_other_method(result.some_other_attribute) # Not checking success/failure
# Will trigger offense - need to check result.ok? first
end
end
Migration Strategies
For New Projects
- Enable the cop with full enforcement from the start
- Use
Severity: error
to catch violations early - Train your team on the proper patterns
For Existing Projects
- Phase 1: Enable with
CheckNested: true, CheckNonNested: false, Severity: warning
- Phase 2: Fix all nested Action violations
- Phase 3: Enable
CheckNonNested: true
- Phase 4: Fix all non-nested Action violations
- Phase 5: Set
Severity: error
Using RuboCop Disable Comments
For intentional violations, you can disable the cop:
class OuterAction
include Action
def call
# rubocop:disable Axn/UncheckedResult
InnerAction.call(param: "value") # Intentionally ignored
# rubocop:enable Axn/UncheckedResult
end
end
Troubleshooting
Cop Not Loading
If you see "uninitialized constant" errors:
- Ensure the gem is properly installed:
bundle list | grep axn
- Check your
.rubocop.yml
syntax - Verify the require path:
require: - axn/rubocop
False Positives
If the cop reports violations for properly handled results:
- Check that you're using the exact patterns shown above
- Ensure the result variable name matches exactly
- Verify the result is being used in an acceptable way
Performance Issues
The cop analyzes AST nodes, so it's generally fast. If you experience slowdowns:
- Ensure you're not running RuboCop on very large files
- Consider using RuboCop's
--parallel
option - Use
.rubocop_todo.yml
for gradual adoption
Best Practices
- Start Small: Begin with warnings and nested calls only
- Be Consistent: Choose one pattern and stick with it
- Train Your Team: Make sure everyone understands the rules
- Review Regularly: Use the cop in your CI/CD pipeline
- Document Exceptions: Use disable comments sparingly and document why
Integration with CI/CD
Add RuboCop to your CI pipeline to catch violations early:
# .github/workflows/rubocop.yml
name: RuboCop
on: [push, pull_request]
jobs:
rubocop:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ruby/setup-ruby@v1
with:
ruby-version: 3.2
- run: bundle install
- run: bundle exec rubocop