BetterController 🎮
🚀 A powerful Ruby gem for building standardized, maintainable, and feature-rich Rails controllers
BetterController simplifies the process of building RESTful controllers in your Rails applications. It provides a structured approach to define controllers, services, and serializers, making your development process more maintainable and efficient.
✨ Key Features
- 🏗️ Standardized Controller Structure: Define RESTful controllers with minimal code
- 🛠️ Service Layer: Separate business logic from controllers
- 📦 Serialization: Standardized JSON serialization for your resources
- 📄 Pagination: Built-in pagination support for collections
- 🔍 Parameter Validation: Robust parameter validation and type casting
- 🔄 Response Handling: Consistent JSON responses with standardized structure
- 🚨 Error Handling: Comprehensive error handling with custom error classes
- 📝 Logging: Enhanced logging capabilities
- 🧩 Generators: Rails generators for controllers, services, and serializers
Why BetterController? 🤔
- 🏗️ Standardized Approach: Consistent controller structure across your application
- 🧩 Modular Design:
- Separate controller, service, and serializer components
- Reusable modules
- Configurable behavior
- 🔄 Flexible Implementation:
- Override default behavior when needed
- Customize actions and responses
- Extend with your own functionality
- ✅ Robust Error Handling:
- Standardized error responses
- Detailed error logging
- Custom error classes
- 📊 Enhanced Responses:
- Consistent JSON structure
- Pagination metadata
- Custom response formatting
Installation
Add the gem to your Gemfile:
gem 'better_controller'
Then run:
bundle install
Or install the gem manually:
gem install better_controller
Configuration
In a Rails application, you can create an initializer by running:
rails generate better_controller:install
This command creates the file config/initializers/better_controller.rb
with a default configuration. An example configuration is:
BetterController.configure do |config|
# Pagination configuration
config[:pagination] = {
enabled: true,
per_page: 25
}
# Serialization configuration
config[:serialization] = {
include_root: false,
camelize_keys: true
}
# Error handling configuration
config[:error_handling] = {
log_errors: true,
detailed_errors: true
}
end
Generators
BetterController provides several generators to help you quickly scaffold your application:
Install Generator
Creates an initializer with default configuration:
rails generate better_controller:install
Controller Generator
Generates a controller with optional service and serializer:
rails generate better_controller:controller Users index show create update destroy
Options:
--skip-service
: Skip generating a service class--skip-serializer
: Skip generating a serializer class--model=MODEL_NAME
: Specify a custom model name (defaults to singular of controller name)
This will create:
app/controllers/users_controller.rb
app/services/user_service.rb
(unless--skip-service
is specified)app/serializers/user_serializer.rb
(unless--skip-serializer
is specified)
Basic Usage
Controller Setup
Include BetterController in your ApplicationController:
class ApplicationController < ActionController::Base
include BetterController
end
Creating a ResourcesController
Create a controller that inherits from the ResourcesController:
class UsersController < BetterController::ResourcesController
# Controller-specific configuration
def resource_class
User
end
def resource_params
params.require(:user).permit(:name, :email, :role)
end
def resource_creator
UserService.create(resource_params)
end
def resource_updater
UserService.update(@resource, resource_params)
end
def resource_destroyer
UserService.destroy(@resource)
end
def index_serializer
UserSerializer
end
def show_serializer
UserSerializer
end
def create_serializer
UserSerializer
end
def update_serializer
UserSerializer
end
def destroy_serializer
UserSerializer
end
def
'User created successfully'
end
def
'User updated successfully'
end
def
'User deleted successfully'
end
end
Service Layer
Create a service class to handle business logic:
class UserService
def self.create(params)
user = User.new(params)
user.save
user
end
def self.update(user, params)
user.update(params)
user
end
def self.destroy(user)
user.destroy
user
end
end
Serializer
Create a serializer to format your responses:
class UserSerializer
def self.serialize(user, = {})
{
id: user.id,
name: user.name,
email: user.email,
role: user.role,
created_at: user.created_at,
updated_at: user.updated_at
}
end
end
Core Features
ResourcesController
The ResourcesController
provides a standardized implementation of RESTful actions:
# Available actions
index # GET /resources
show # GET /resources/:id
create # POST /resources
update # PUT/PATCH /resources/:id
destroy # DELETE /resources/:id
Response Handling
BetterController provides standardized methods for handling responses:
# Success response
respond_with_success(data, = {})
# Error response
respond_with_error(errors, = {})
Example response format:
{
"data": { ... },
"message": "Operation completed successfully",
"meta": { ... }
}
Pagination
The Pagination
module provides pagination functionality for ActiveRecord collections:
def index
execute_action do
collection = paginate(resource_collection_resolver)
data = serialize_collection(collection, index_serializer)
respond_with_success(data, options: { meta: })
end
end
Pagination metadata is included in the response:
{
"data": [ ... ],
"meta": {
"pagination": {
"total_count": 100,
"total_pages": 4,
"current_page": 1,
"per_page": 25
}
}
}
Error Handling
BetterController provides comprehensive error handling:
begin
# Your code here
rescue ActiveRecord::RecordNotFound => e
respond_with_error(e., status: :not_found)
rescue ActionController::ParameterMissing => e
respond_with_error(e., status: :bad_request)
rescue StandardError => e
respond_with_error(e., status: :internal_server_error)
end
Error response format:
{
"errors": {
"base": ["Resource not found"]
},
"message": "An error occurred",
"status": 404
}
end
## Advanced Customization
### Overriding Default Behavior
You can override any of the default methods in your controller to customize behavior:
```ruby
class UsersController < BetterController::ResourcesController
# Override the default index action
def index
execute_action do
@users = User.where(active: true)
data = serialize_collection(@users, index_serializer)
respond_with_success(data, options: { meta: { active_count: @users.count } })
end
end
# Override the resource finder method
def resource_finder
User.includes(:posts, :comments).find(params[:id])
end
# Add custom actions
def activate
execute_action do
@resource = resource_finder
@resource.update(active: true)
data = serialize_resource(@resource, show_serializer)
respond_with_success(data, options: { message: 'User activated successfully' })
end
end
end
Custom Serialization
You can implement custom serialization logic:
class UserSerializer
def self.serialize(user, = {})
serialized = {
id: user.id,
name: user.name,
email: user.email
}
# Add additional fields based on options
if [:include_details]
serialized.merge!({
role: user.role,
last_login: user.last_login,
created_at: user.created_at
})
end
serialized
end
def self.serialize_collection(users, = {})
users.map { |user| serialize(user, ) }
end
end
Custom Error Handling
Implement custom error handling in your controllers:
class ApplicationController < ActionController::Base
include BetterController
rescue_from ActiveRecord::RecordNotFound, with: :handle_not_found
rescue_from ActionController::ParameterMissing, with: :handle_parameter_missing
rescue_from CustomError::AuthorizationError, with: :handle_authorization_error
private
def handle_not_found(exception)
respond_with_error(exception., status: :not_found, options: { code: 'NOT_FOUND' })
end
def handle_parameter_missing(exception)
respond_with_error("Required parameter missing: #{exception.param}", status: :bad_request)
end
def (exception)
respond_with_error('You are not authorized to perform this action', status: :forbidden)
end
end
Testing
BetterController includes RSpec tests to ensure functionality. Run the tests with:
bundle exec rspec
Example test for a controller:
RSpec.describe UsersController, type: :controller do
describe '#index' do
it 'returns a collection of users' do
get :index
expect(response).to have_http_status(:ok)
expect(JSON.parse(response.body)['data']).to be_an(Array)
end
end
describe '#create' do
it 'creates a new user' do
post :create, params: { user: { name: 'John Doe', email: '[email protected]' } }
expect(response).to have_http_status(:created)
expect(JSON.parse(response.body)['message']).to eq('User created successfully')
end
end
end
Contributing
- Fork the repository
- Create your feature branch (
git checkout -b feature/my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin feature/my-new-feature
) - Create a new Pull Request
License
This project is licensed under the MIT License - see the LICENSE file for details.
Advanced Features
Pagination
The Pagination
module provides pagination functionality for ActiveRecord collections:
def index
execute_action do
collection = User.all
@users = paginate(collection, page: params[:page], per_page: 20)
respond_with_success(@users)
end
end
Parameter Helpers
The ParamsHelpers
module provides enhanced parameter handling:
# Get a typed parameter
user_id = param(:user_id, type: :integer)
# Get a boolean parameter
active = boolean_param(:active, default: true)
# Get a date parameter
start_date = date_param(:start_date)
# Get a JSON parameter
data = json_param(:data)
Logging
The Logging
module provides enhanced logging capabilities:
# Log at different levels
log_info("Processing request")
log_debug("Debug information")
log_warn("Warning message")
log_error("Error occurred")
# Log with tags
log_info("User created", { user_id: user.id, email: user.email })
# Log exceptions
begin
# Some code that might raise an exception
rescue => e
log_exception(e, { controller: self.class.name, action: action_name })
end
Generators
Controller Generator
Generate a controller with BetterController:
rails generate better_controller:controller Users index show create update destroy
This will create:
- A UsersController with the specified actions
- A UserService for handling business logic
- A UserSerializer for serializing responses
Example Implementation
Controller
class UsersController < ApplicationController
include BetterController::ResourcesController
# GET /users
def index
execute_action do
@resource_collection = resource_collection_resolver
data = serialize_resource(@resource_collection, index_serializer)
respond_with_success(data, options: { meta: })
end
end
# GET /users/:id
def show
execute_action do
@resource = resource_resolver
data = serialize_resource(@resource, show_serializer)
respond_with_success(data)
end
end
# POST /users
def create
execute_action do
@resource = resource_service.create(resource_params)
data = serialize_resource(@resource, create_serializer)
respond_with_success(data, status: :created)
end
end
# PATCH/PUT /users/:id
def update
execute_action do
@resource = resource_resolver
resource_service.update(@resource, resource_params)
data = serialize_resource(@resource, update_serializer)
respond_with_success(data)
end
end
# DELETE /users/:id
def destroy
execute_action do
@resource = resource_resolver
resource_service.destroy(@resource)
respond_with_success(nil, status: :no_content)
end
end
protected
def resource_service_class
UserService
end
def resource_params_root_key
:user
end
def resource_serializer
UserSerializer
end
end
Contributing 🤝
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
License 📄
This project is licensed under the MIT License - see the LICENSE file for details.