Legacy App Modernization: A Case Study in Rails 8 and Rapid Prototyping
Reading time: ~ 7 minutes

Our client, a leading roofing contractor, faced significant challenges with their aging Microsoft Access database application. While once effective, the legacy system had become a bottleneck for their growing business operations. Key pain points included:
- An outdated technology stack that limited scalability and modern feature implementation
- Slow and inefficient workflows that impacted daily operations
- Limited ability to integrate with modern business tools and services
- Growing concerns about data security and system reliability
Recognizing these challenges, we proposed a comprehensive modernization strategy. The approach was to build a rapid prototype, validating the technical solution before committing to full-scale development. This strategy allowed us to work through quick feedback and ensured the solution met the client's needs.
The engineering team chose a modern, robust technology stack:
- Ruby on Rails 8.0.1: The latest version of Rails, providing a mature, battle-tested framework with modern features
- Ruby 3.4.1: Taking advantage of the latest Ruby performance improvements and features
- Hotwire (Turbo + Stimulus): For building dynamic, SPA-like experiences without the complexity of a JavaScript framework
- Bootstrap 5.3.2: For a responsive, professional UI that works across all devices
- PostgreSQL: A powerful, reliable database system
- Kamal: For streamlined, containerized deployment
- AWS S3: For secure, scalable file storage
This technology stack was chosen specifically for its ability to deliver rapid development cycles, maintainable code, and a modern user experience while ensuring long-term scalability and reliability. Combining Rails 8's latest features with Hotwire's modern approach to frontend development allowed the team to build a sophisticated application without the complexity of a separate JavaScript framework.
The solution was designed to be deployed using modern DevOps practices, with containerization through Docker and deployment automation via Kamal, ensuring consistent and reliable deployments across environments. This approach modernized the application and set up our client for future growth and technological advancement.
Building a Prototype Quickly
When we began building the prototype, our primary goal was to create a functional application that validated our technical approach while providing immediate value to the client. We focused on delivering the most critical features first, knowing we could expand the functionality in the full development phase.
The prototype included several key features that were essential for daily operations. Service reports were a top priority, allowing the team to document their work and generate professional PDF reports. We implemented a comprehensive image management system that supported uploading, rotating, and organizing photos of roof conditions and repairs. A particularly important feature was the ability to mark specific points on roof images, which helped the team identify and document areas that needed attention or had been previously repaired.
For the administrative side, we built a role-based system that controlled access to different features. This included permissions for creating and editing forms, exporting PDFs, and managing user accounts. The system was designed to be flexible, allowing various access levels for administrators, office staff, and field employees.
Before writing any code, we made several critical technical decisions. We chose PostgreSQL as our database, which would provide the reliability and performance needed for the application. We implemented AWS S3 integration for image storage, ensuring photos would be securely stored and easily accessible. The PDF generation system was built using Prawn, allowing us to create professional-looking reports with the company's branding.
The development process started with setting up the basic Rails 8 application and configuring the development environment. We then built the core models and controllers to handle the different forms and images. The user interface was built using Bootstrap 5, providing a clean and responsive design that worked well on desktop and mobile devices.
We deliberately left some features for the full development phase. These included Progressive Web App (PWA) capabilities, Google Maps integration for roof overviews, and digital signature functionality. This approach allowed us to deliver a working prototype quickly while gathering feedback from the client.
The prototype demonstrated that our technical approach was sound and that the application would meet the client's needs. It provided a solid foundation for the full development phase while giving the client a clear picture of how the final application would work.
Rails 8: A Modern Foundation
One of the most exciting aspects of this project was the opportunity to work with Rails 8, the latest version of the framework. Rails 8 significantly improves performance, security, and the developer experience, making it an excellent choice for modernizing applications.
ActiveStorage: Robust File Management
ActiveStorage played a crucial role in our prototype, particularly for handling the extensive image management requirements. We implemented a sophisticated image handling system that includes:
- Flexible Storage Backend: Using AWS S3 for production and local storage for development, ActiveStorage provided a seamless way to manage file uploads across different environments.
- Image Variants: We leveraged ActiveStorage's variant system to create optimized versions of images for various use cases, including thumbnails and previews.
- Custom Image Rotation: We implemented a custom image rotation system that allows users to rotate images in 90-degree increments, with the changes persisted to the storage backend.
- Content Type Validation: Added validation to ensure only JPEG and PNG files are accepted, maintaining data quality and security.
The implementation was particularly elegant, with a concern-based approach that made the image handling functionality reusable across different models:
Stimulus: Enhancing User Interactions
Stimulus proved an excellent choice for adding JavaScript behavior to our application. We used it to create several interactive features:
- Form Handling: Created a form controller that manages dynamic form behavior, including image previews and validation feedback.
- Image Marking System: Implemented a sophisticated image marker controller that handles the placement and management of markers on roof diagrams.
- Role-Based UI: Used Stimulus to dynamically show/hide form elements based on user roles, improving the user experience.
The Stimulus controllers were lightweight and focused, following the framework's philosophy of enhancing HTML rather than replacing it. We kept the HTML structure in our views and used JavaScript to add behavior to it, rather than generating the entire UI from JavaScript. For example, our image marker controller:
Hotwire: Seamless Updates
Hotwire, particularly Turbo, was instrumental in creating a smooth, app-like experience without the complexity of a full single-page application (SPA). We used it in several key areas:
- Form Submissions: Implemented Turbo Streams for form submissions, providing instant feedback without full page reloads.
- Status Updates: Used Turbo Frames to update form statuses in real-time.
- Image Previews: Created dynamic image previews that update immediately after file selection.
The combination of Turbo and Stimulus allowed us to create a responsive application that feels modern and fast, while maintaining the simplicity and reliability of server-rendered HTML. For example, our form status updates:
This modern stack, comprising Rails 8, ActiveStorage, Stimulus, and Hotwire, provided a powerful foundation for building a robust and user-friendly application. The combination of these technologies allowed us to deliver a prototype that met the client's requirements and provided a solid foundation for future development.
Kamal - Simplifying Deployment
When it came time to deploy our prototype to a production environment, we needed a solution that would be quick to set up, reliable, and easy to maintain. Enter Kamal, a deployment tool that perfectly suited our needs for getting the application up and running on an EC2 instance.
Why Kamal?
For our prototype phase, Kamal offered several key advantages:
- Quick Setup: With just a single configuration file, we could define our entire deployment stack, including the application, database, and environment variables.
- Docker-Based: Kamal uses Docker containers, ensuring consistent environments between development and production.
- Zero-Downtime Deployments: Built-in support for zero-downtime deployments, crucial for a production application.
- Simple Rollbacks: Easy rollback capability if we needed to revert to a previous version.
Our Kamal Configuration
Here's a simplified version of our config/deploy.yml that shows how we configured the deployment:
Perfect for Prototyping
Kamal was particularly well-suited for our prototype phase for 4 big reasons:
- Rapid Deployment: We could deploy changes quickly with a single command: bin/kamal deploy
- Built-in Monitoring: Easy access to logs and application status
- Database Management: Automatic PostgreSQL setup and management
- Environment Variables: Secure handling of sensitive credentials
What's Next?
As we transition from prototype to full development, we're continuing to use Kamal but with some enhancements:
- Multiple Environments: We're setting up staging and production environments
- Automated Deployments: Integrating with our CI/CD pipeline
- Enhanced Monitoring: Adding more comprehensive logging and monitoring
- Backup Strategy: Implementing regular database backups
The beauty of Kamal is that it grows with your application. What started as a simple deployment solution for our prototype has become a robust deployment platform that will serve us well throughout the project's lifecycle.
Conclusion: From Prototype to Full Development
The prototype phase of the Olsson Roofing application rewrite has been a resounding success. The client was particularly impressed with how quickly we delivered a functional application that addressed their core needs. The combination of Rails 8, Hotwire, and Kamal allowed us to move at an unprecedented pace while maintaining high quality and reliability.
Client Success
The client's response to the prototype has been overwhelmingly positive. They were able to:
- Test the core functionality in a real-world environment
- Provide valuable feedback on the user interface and workflow
- Validate the technical approach and architecture
- See immediate value in the modernized system
Moving Forward with Full Development
We're now in full development mode, building on the success of the prototype. The prototype has served as an excellent foundation, allowing us to:
- Validate Technical Decisions: Our choice of Rails 8, Hotwire, and Kamal has proven to be the right stack for this project.
- Identify Priority Features: The prototype helped us understand which features would deliver the most value first.
- Establish Development Patterns: We've established efficient image handling, form management, and deployment patterns.
We Think You Might Also Enjoy Reading…
- Deploying a Ruby on Rails app to DigitalOcean Using Kamal
- When Should You Upgrade Your Rails Application?
FAQ’s
Q. Can this same approach work for other legacy systems?
Absolutely. Whether it's an old desktop app or an outdated web system, this prototype-first, feedback-driven strategy is designed to work across a wide range of technologies and business types.
Q. How is the new system more secure than the old Access database?
The modern stack includes encrypted credentials, secure file storage with AWS S3, and role-based access controls. These features provide a much higher level of security, reliability, and data integrity.
Q. What’s the next step after a successful prototype?
Once the prototype is validated, we transition into full development by expanding features, scaling infrastructure, and layering on long-term enhancements, like PWA functionality, integrations, and automation.
Q. Why did you choose to build a prototype first instead of jumping into full development?
Starting with a prototype allowed us to validate our technical approach and deliver immediate value without the overhead of a full build. It helped align everyone early, gather feedback, and reduce risk before moving into full development.
Q. How long did it take to build the prototype?
We were able to deliver a functional prototype in just a few weeks. By focusing on the most critical features first, we kept momentum high while laying the groundwork for future development.