Note: This gem is currently in early beta. But try it out (on some non-critical apps)!
Demo available on: https://cg3-media.github.io/alto-site/
Alto is a mountable Rails engine for collecting user feedback with multiple boards, threaded comments, and voting.
β οΈ AI-Generated Code Disclaimer This engine contains code written by AI.
- Multiple feedback boards (Bug Reports, Feature Requests, etc.)
- Customizable item labels per board ("tickets", "posts", "bugs", "requests", etc.)
- Custom fields per board (dropdowns, text inputs, multiselect, etc.)
- Ticket creation with 3-level threaded comments and upvoting
- Image upload support for tickets and comments with automatic cloud storage optimization
- Configurable status sets with custom statuses and colors
- Admin dashboard with status management and analytics
- Full-text search across tickets and comments
- Callback hooks for integration with email notifications, Slack, analytics, and external APIs
Add to your Gemfile:
gem 'alto', github: 'CG3-Media/alto'bundle installAdd to config/routes.rb:
mount Alto::Engine => "/community" # e.g. or /feedback or whateverrails generate alto:installThis creates:
- Database tables (using your app's main database)
- Configuration file at
config/initializers/alto.rb - Default feedback boards
The installer is safe to run multiple times.
Alto uses the standard Rails engine pattern - all tables are stored in your main database with the alto_ prefix to avoid conflicts.
Edit config/initializers/alto.rb:
Alto.configure do |config|
# Restrict access to signed-in users
config.permission :can_access_alto? do
user_signed_in?
end
# Only admins can access admin area
config.permission :can_access_admin? do
current_user&.admin?
end
# Customize user display names
config.user_display_name do |user_id|
User.find(user_id)&.full_name || "User ##{user_id}"
end
end<!-- Link to feedback board -->
<%= link_to "Feedback", alto.alto_home_path %>
<!-- Link to specific board -->
<%= link_to "Bug Reports", alto.board_path("bugs") %>Alto supports image uploads for tickets and comments. Works with Cloudinary, S3, or local storage.
# config/initializers/alto.rb
Alto.configure do |config|
config.image_uploads_enabled = true
endRequires ActiveStorage to be configured in your Rails app. Images are automatically optimized and cleaned up when records are deleted.
One of Alto's most powerful features is its callback hook system. The engine automatically calls methods in your host app when events occur, enabling seamless integration with external services.
π« Ticket Events:
ticket_created(ticket, board, user)- New ticket submittedticket_status_changed(ticket, old_status, new_status, board, user)- Status updated
π¬ Comment Events:
comment_created(comment, ticket, board, user)- New comment or replycomment_deleted(comment, ticket, board, user)- Comment removed
π Voting Events:
upvote_created(upvote, votable, board, user)- Item upvotedupvote_removed(upvote, votable, board, user)- Upvote removed
- β‘ Zero Configuration - Just define the methods you need
- π Rich Context - Every callback receives the object, board, and user
- π‘οΈ Error Isolation - Callback failures don't break the main flow
- π Flexible Integration - Works with any external service or internal logic
ποΈ Use a Concern for organization:
# app/controllers/concerns/alto_callbacks.rb
module AltoCallbacks
extend ActiveSupport::Concern
private
def ticket_created(ticket, board, user)
FeedbackIntegrationService.handle_new_ticket(ticket, board, user)
end
end
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
include AltoCallbacks
end㪠Slack/Discord/Email Notifications:
def ticket_created(ticket, board, user)
SlackWebhook.post(
channel: board.slug == 'bugs' ? '#dev-team' : '#product',
text: "New #{board.name}: #{ticket.title}",
user: user&.email
)
# Send email notification
UserMailer.alto_notification(
to: board.admin_email,
subject: "New ticket: #{ticket.title}",
ticket: ticket,
user: user
).deliver_later
endπ Analytics Tracking:
def ticket_created(ticket, board, user)
Analytics.track(user&.id, 'feedback_submitted', {
board: board.name,
category: board.slug
})
endπ« External Ticketing:
def ticket_created(ticket, board, user)
if board.slug == 'support'
ZendeskAPI.create_ticket(
subject: ticket.title,
description: ticket.description,
requester: user&.email
)
end
endAll configuration happens in config/initializers/alto.rb. The installer creates this file with documented options.
π Authentication-Agnostic - Alto works with ANY authentication system (or none at all):
- β Devise
- β Custom authentication
- β JWT tokens
- β Session-based auth
- β No authentication (public feedback)
Alto.configure do |config|
# User model (default: "User")
config.user_model = "Account"
# User display names
config.user_display_name do |user_id|
User.find(user_id)&.name || "Anonymous"
end
# Permissions (works with any auth system)
config.permission :can_create_tickets? do
current_user.present? # Devise, custom auth, etc.
end
config.permission :can_access_admin? do
current_user&.admin? # Your admin logic here
end
# Image uploads (requires ActiveStorage)
config.image_uploads_enabled = true
# Board configuration
config.allow_board_deletion_with_tickets = false
end# Devise - works automatically
config.permission :can_create_tickets? do
user_signed_in?
end
# Custom auth - define current_user in ApplicationController
config.permission :can_create_tickets? do
current_user.present?
end
# Public feedback (no auth required)
config.permission :can_create_tickets? do
true
endGenerate embeddable widgets for any board:
rails generate alto:widgetThis creates a partial at app/views/shared/alto/_BOARD_widget.html.erb that you can embed anywhere:
<!-- In any view -->
<%= render 'shared/alto/feature_requests_widget' %>The generated widget is a complete feedback form using your board's custom fields. Customize the HTML/CSS as needed!
For simplicity, we're using tailwind via a CDN. π€·πΎββοΈ
Alto integrates seamlessly with your existing Rails application through its callback hook system. Common integration patterns include:
- Slack/Discord Notifications - Notify teams of new tickets and comments
- Email Notifications - Send updates to stakeholders and users
- Analytics Tracking - Track user engagement and feedback patterns
- External Ticketing - Sync with Zendesk, Jira, or other systems
- Custom Business Logic - Trigger workflows based on feedback events
See the Callback Hooks section above for implementation examples.
Alto uses Rails polymorphic associations to support any user model in your host application. This flexible design allows the engine to work with different user model names across different applications.
The engine stores user references using two columns:
user_id- The ID of the user recorduser_type- The class name of your user model (e.g., "User", "Account", "Member")
This is automatically configured based on your initializer:
# config/initializers/alto.rb
Alto.configure do |config|
config.user_model = "User" # β user_type = "User"
# config.user_model = "Account" # β user_type = "Account"
# config.user_model = "Member" # β user_type = "Member"
endCheck your permission configuration in config/initializers/alto.rb. The default permissions allow all access.
rails generate alto:uninstallThis will guide you through removing the engine and optionally cleaning up database tables.
Alto Custom License v1.0 Copyright (c) 2025 CG3 Media, LLC
You are free to use, copy, modify, and deploy this software (βAltoβ) in unlimited personal and commercial projects.
However, the following restrictions apply:
-
You may not resell, sublicense, or redistribute Alto β whether in original or modified form β as a standalone product, hosted service, or software offering.
-
You may not rebrand or present Alto as your own original product. Derivative works must include attribution and must not imply original authorship.
-
The name βAltoβ, associated logos, and brand identity may not be used without prior written permission from the copyright holder.
-
Attribution must be preserved in all copies or substantial portions of the Software, including in open-source forks.
-
Contributions are welcome. Unless otherwise agreed in writing, all contributions are licensed back to the project under the terms of this license and ownership remains with the original author.
THE SOFTWARE IS PROVIDED βAS ISβ, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT.