Conversation
Phase 0 of Rails 8.1 upgrade: Migrate frontend build system from Webpacker to jsbundling-rails with esbuild before upgrading Rails. Frontend Changes: - Add jsbundling-rails and cssbundling-rails gems - Replace Webpacker with esbuild for JavaScript bundling - Upgrade Tailwind CSS from v2.1.1 to v3.x - Create new app/javascript/ structure with explicit Stimulus imports - Remove require.context() dynamic loading (not compatible with esbuild) New Stimulus Controllers: - flash_controller.js - Auto-dismiss flash messages (replaces flash_timeout.js) - mobile_menu_controller.js - Toggle sidebar (replaces mobile_menu.js) - feedback_controller.js - Jira issue collector (replaces feedback.js) Migrated Controllers (updated to @hotwired/stimulus): - autosubmit, capacity_slider, confirmation, infopanel - pannellum, updatebuilding, updateroom, visibility View Updates: - Replace stylesheet_pack_tag/javascript_pack_tag with standard asset helpers - Update views to use new Stimulus controllers via data attributes - Remove legacy javascript_include_tag references Removed: - config/webpacker.yml and config/webpack/ directory - babel.config.js - Legacy JS files in app/assets/javascripts/ - yarn.lock (replaced with package-lock.json via npm) Build commands: - npm run build (JavaScript via esbuild) - npm run build:css (Tailwind CSS) - bin/dev (runs both with foreman)
Phase 1 of Rails upgrade (6.1 → 7.0): - Update rails gem to ~> 7.0.0 (installs 7.0.10) - Update config.load_defaults to 7.0 - Replace hotwire-rails with turbo-rails and stimulus-rails - Migrate Devise from fork to official gem (~> 4.9) - Add Devise Turbo settings (error_status, redirect_status) - Update Pundit include to Pundit::Authorization - Pin Pagy to ~> 6.0 and simplify initializer - Add Ruby 3.4 compatibility gems (mutex_m, drb, observer) - Update puma (~> 6.0), rspec-rails (~> 6.0), responders (~> 3.1) - Add new_framework_defaults_7_0.rb initializer All tests pass. Next: Phase 2 (Rails 7.1)
Phase 2 of Rails upgrade (7.0 → 7.1): - Update rails gem to ~> 7.1.0 (installs 7.1.6) - Update config.load_defaults to 7.1 - Remove new_framework_defaults_6_1.rb (settings now default) - Remove new_framework_defaults_7_0.rb (settings now default) All tests pass. Next: Phase 3 (Rails 7.2)
Summary: - Rails: 7.1.6 → 7.2.3 - Updated config.load_defaults to 7.2 All tests pass. Draft commit message: Upgrade to Rails 7.2.3 Phase 3 of Rails upgrade (7.1 → 7.2): - Update rails gem to ~> 7.2.0 (installs 7.2.3) - Update config.load_defaults to 7.2 All tests pass. Next: Phase 4 (Rails 8.0)
Summary: - Rails: 7.2.3 → 8.0.4 - Updated config.load_defaults to 8.0 - Commented out annotate gem (not compatible with Rails 8.0 yet) All tests pass. Draft commit message: Upgrade to Rails 8.0.4 Phase 4 of Rails upgrade (7.2 → 8.0): - Update rails gem to ~> 8.0.0 (installs 8.0.4) - Update config.load_defaults to 8.0 - Comment out annotate gem (not yet compatible with Rails 8) All tests pass. Next: Phase 5 (Rails 8.1)
Phase 5 of Rails upgrade (8.0 → 8.1) - FINAL: - Update rails gem to ~> 8.1.0 (installs 8.1.1) - Update config.load_defaults to 8.1 Complete upgrade path: Rails 6.1.7.9 → 8.1.1
- Add comprehensive tests for Room, Building, RoomCharacteristic, Note, and Floor models following behavior-over-implementation pattern - Create new factories: users, notes, floors - Fix existing factories with valid Faker methods (replace deprecated Faker::Company.department, Faker::String.random, unbounded numbers) - Configure ActiveJob test adapter to prevent Redis dependency in tests - Fix fixture_paths deprecation (singular to plural array) - Add test fixtures for image uploads
Rails 8.1 deprecated the built-in sidekiq adapter and requires Sidekiq 7.3.3+ to use the gem's native adapter instead.
- Update render paths to use standard partial syntax without leading './' prefix (layouts/_headness.html.erb) - Add explicit require for 'benchmark' which is being removed from Ruby stdlib in 3.4+ (api_update_database.rake)
Problem: - View used Building.find_by() instead of eager-loaded room.building - Room thumbnail images were not eager-loaded - Caused excessive database queries per page load Changes: - Update includes to: :room_contact, :room_image_attachment, :building - Replace Building.find_by(bldrecnbr: room.building_bldrecnbr) with room.building in _room_row.html.erb (4 occurrences) Note: Notes associations (.alert scope) intentionally not eager-loaded as the scope triggers separate queries regardless of preloading. Performance improvement: - Eliminated redundant Building.find_by queries (was 1 per room) - Added eager loading for room thumbnails - Reduced database round-trips significantly
Major upgrade to Tailwind CSS v4 with CSS-first configuration approach. Changes: - Migrate from tailwind.config.js to CSS @theme directive - Replace @tailwind directives with @import 'tailwindcss' - Update PostCSS config to use @tailwindcss/postcss - Add @tailwindcss/cli for build scripts - Normalize breakpoints to Tailwind defaults (sm=640px, md=768px, lg=1024px) - Fix deprecated ring-opacity utilities (ring-black/5 syntax) - Remove autoprefixer (now built into v4) Template updates: - Convert ring-black ring-opacity-5 → ring-black/5 - Convert ring-red-400 ring-opacity-30 → ring-red-400/30 Build performance improved from ~400ms to ~80ms.
Replace esbuild JavaScript bundling with browser-native ES modules via importmap-rails, eliminating the JS build step and reducing complexity. ## JavaScript Migration - Add importmap-rails gem, remove jsbundling-rails - Configure importmap.rb with pins for all packages - Vendor third-party packages to vendor/javascript/: - tailwindcss-stimulus-components - stimulus-lightbox - nouislider - lightgallery - pannellum - Remove unused dependencies (flatpickr, stimulus-flatpickr) - Update layout to use javascript_importmap_tags - Remove JS build from Procfile.dev (CSS-only now) ## Tailwind CSS v4 Upgrade - Update package.json to Tailwind CSS v4.1 with @tailwindcss/cli - Add @source directive to scan vendor/javascript for classes ## Turbo Compatibility Fixes - Replace Rails UJS method: with data: { turbo_method: } - Replace confirm: with data: { turbo_confirm: } ## Other Changes - Enable CSP nonce generation for importmap inline scripts - Add Trix CSS from CDN for ActionText rich text editor - Simplify announcements controller update action - Change announcements cancel route from POST to GET
Add eager loading for building_image_attachment to the @buildings query in RoomsController#index to prevent N+1 queries when rendering building images in the view. ## Performance Impact - Before: 43 separate queries (1 per building) for image attachments - After: 1 batch query for all building image attachments - Estimated savings: ~8-10ms in ActiveRecord time per request
The capacity slider feature was replaced with simple number input fields but the JavaScript dependencies were never removed. ## Removed - nouislider.js (96KB vendored file) - capacity_slider_controller.js (unused Stimulus controller) - Related imports and registrations from controllers/index.js - Pin from importmap.rb ## Impact - ~98KB reduction in JavaScript payload - No functionality change (capacity filtering uses number fields)
Migrate from lightgallery + stimulus-lightbox (116KB) to Bigger Picture (77KB), reducing JavaScript bundle size by 39KB (34%). Benefits: - Native ES module compatible with importmap-rails (no bundling required) - Improved accessibility: keyboard navigation (arrow keys, Escape), focus trapping, and ARIA attributes for screen readers - Smaller footprint with simpler, more maintainable API Changes: - Create custom lightbox_controller.js Stimulus controller - Add bigger-picture.js to vendor/javascript/ - Load bigger-picture.css from CDN in layout - Update importmap.rb with new pin - Simplify rooms/show.html.erb lightbox markup - Remove lightgallery.js and stimulus-lightbox.js
The script was loaded in both _header.html.erb and _footer.html.erb, causing a JavaScript error: "trigger_51ecac03 is not a function". Keeping the script in the footer only resolves the duplicate loading issue.
Changes: - Load bigger-picture and tailwindcss-stimulus-components from jspm.io CDN - Remove vendored bigger-picture.js and tailwindcss-stimulus-components.js - Only pannellum.js remains vendored (no ESM available on CDN) - Fix pannellum controller to reference window.pannellum for global access - Add pannellum CSS from unpkg CDN - Fix image variant resize syntax for Rails 7+ (resize_to_limit) - Convert panorama styles to Tailwind CSS v4 syntax - Add semi-transparent load button styling for pannellum Vendored JS reduced from 149KB to 68KB (pannellum only).
Spring is no longer included in Rails 7.1+ by default. Modern hardware and improved Rails boot times make it unnecessary, and it can cause subtle caching bugs.
Replace Sidekiq with SolidQueue for background job processing and Redis with SolidCable for ActionCable. Uses PostgreSQL-backed queues instead of Redis, simplifying infrastructure requirements. - Add solid_queue, solid_cable, mission_control-jobs gems - Remove redis and sidekiq gems - Configure multi-database setup for queue and cable - Replace Sidekiq::Web with MissionControl::Jobs at /jobs - Update Procfiles to use bin/jobs
Replace npm-based Tailwind CSS build with the standalone tailwindcss-rails gem (v4), eliminating Node.js as a dependency. - Replace cssbundling-rails with tailwindcss-rails ~> 4.0 - Move CSS from app/assets/stylesheets/ to app/assets/tailwind/ - Remove package.json, yarn.lock, and node_modules - Update Procfile.dev to use bin/rails tailwindcss:watch - Add tailwind stylesheet link to layout
Clean up leftover files and references from the migration to importmap-rails and tailwindcss-rails: - Remove bin/yarn, bin/webpack, bin/webpack-dev-server, bin/spring - Remove Spring loading from config/boot.rb - Remove yarn call from bin/setup - Remove node_modules path from assets.rb - Remove nodejs/yarn from .asdf-plugins - Remove webpack/yarn entries from .gitignore - Update README with Ruby 3.3.4, Rails 8.1, SolidQueue
- Update skip link to target #main (the actual main content area) - Remove redundant empty div#maincontent elements from views - Fix duplicate IDs on index page buttons and clean up unnecessary attrs
- Remove aria-hidden="true" and focusable="false" that blocked accessibility - Add role="application" to indicate interactive widget - Add descriptive aria-label with keyboard instructions - Add tabindex="0" to enable keyboard focus Pannellum already supports keyboard navigation (arrow keys to pan, +/- to zoom) - this enables users to actually reach and use it.
- Add keyboard support to Pannellum load button (Enter/Space to activate) - Make load button focusable with proper ARIA attributes - Auto-focus panorama container after loading for arrow key navigation - Fix HTML attribute quoting in panorama partial - Escape room display name in aria-label to prevent XSS
There was a problem hiding this comment.
Pull request overview
This PR upgrades the MiClassrooms application from Rails 6.1 to Rails 8.1, eliminating Node.js dependencies by migrating from Webpacker to importmap-rails, upgrading Tailwind CSS from v3 to v4, and replacing Redis/Sidekiq with PostgreSQL-backed SolidQueue for background jobs. The changes significantly simplify the build system, reduce dependencies, and improve performance through N+1 query optimizations.
Key changes:
- Rails framework upgraded from 6.1.7 to 8.1.1 with Ruby 3.3.4
- JavaScript bundling migrated from Webpacker to importmap-rails, eliminating Node.js dependency
- Background jobs migrated from Sidekiq/Redis to SolidQueue/SolidCable (PostgreSQL-backed)
- Tailwind CSS upgraded from v3 to v4 with native Rails gem support
- N+1 query optimizations implemented on rooms index page
- Comprehensive test coverage added for Room, RoomCharacteristic, Note, Floor, and Building models
Reviewed changes
Copilot reviewed 94 out of 111 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
Gemfile |
Updated Rails to 8.1, added Ruby 3.4 stdlib gems, replaced Sidekiq with SolidQueue, removed Webpacker |
config/database.yml |
Added multi-database support for queue and cable databases |
config/importmap.rb |
New importmap configuration for JavaScript dependencies |
app/javascript/application.js |
New importmap entry point replacing Webpacker |
app/javascript/controllers/index.js |
Manual Stimulus controller registration for importmap |
app/assets/tailwind/application.css |
New Tailwind CSS v4 configuration with custom theme variables |
config/routes.rb |
Replaced Sidekiq web UI with Mission Control, changed announcement cancel route to GET |
app/controllers/rooms_controller.rb |
Added eager loading to fix N+1 queries on rooms index |
app/views/**/*.erb |
Updated Tailwind classes for v4 compatibility, fixed accessibility issues |
spec/**/*_spec.rb |
Added comprehensive model test coverage |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
36cbc37 to
2db779c
Compare
Configure the test queue adapter in config/environments/test.rb instead of setting it in every RSpec example. This follows Rails conventions for environment-specific configuration and improves test performance by avoiding redundant setup on each test.
- Add new, create, and destroy actions to AnnouncementsController - Remove non-RESTful cancel route and session-based return tracking - Add LOCATIONS constant and validation to Announcement model - Update Pundit policy with create? and destroy? permissions - Reorganize index view with sections grouped by location type - Add descriptions for each announcement location - Support pre-selecting location when creating from empty section - Create new.html.erb view with location selector
Replace string interpolation with Rails.root.join() for cleaner, more idiomatic path handling in RSpec configuration.
Use build instead of create in after(:build) callback so that build(:note) no longer creates database records for building and room. Add after(:create) callback to persist noteable only when the note itself is being persisted.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
MiClassrooms Upgrade Summary
Overview
This document summarizes all changes made during the extended upgrade session, migrating MiClassrooms from Rails 6.1 with Webpacker to a modern Rails 8.1 stack with importmap-rails, Tailwind CSS v4, and SolidQueue.
Impact Metrics
Build System Improvements
node_modules/Sizepackage.jsonDependenciesAsset Sizes
Database Query Improvements
N+1 Fix #1: Room Thumbnails & Building Association (
rooms#index)Problem: View used
Building.find_by()instead of eager-loadedroom.building, and room thumbnails were not eager-loaded.Solution: Updated to
includes(:room_contact, :room_image_attachment, :building)N+1 Fix #2: Building Images on Rooms Index
Problem: Building images were loaded individually when rendering the building filter sidebar.
Solution: Added
includes(:building_image_attachment)to the@buildingsquery.Infrastructure Simplification
Dependency Reduction
1. Rails Version Upgrades
Rails 6.1 → 7.0
Rails 7.0 → 7.1
Rails 7.1 → 7.2
Rails 7.2 → 8.0 → 8.1
2. JavaScript Build System Migration
Webpacker → jsbundling-rails
jsbundling-rails → importmap-rails
config/importmap.rbfor all JavaScript dependenciesapp/javascript/controllers/index.jsfor manual controller registrationFiles Changed
package.json,yarn.lock,node_modules/bin/yarn,bin/webpack,bin/webpack-dev-serverconfig/importmap.rbapp/javascript/application.js,app/javascript/controllers/index.js3. CSS/Tailwind Migration
Tailwind CSS v3.4 → v4.1
cssbundling-rails → tailwindcss-rails
tailwindcss-rails ~> 4.0gemapp/assets/stylesheets/application.tailwind.csstoapp/assets/tailwind/application.cssProcfile.devto usebin/rails tailwindcss:watchCSS File Location
4. Background Jobs Migration
Redis/Sidekiq → SolidQueue/SolidCable
solid_queue ~> 1.2solid_cable ~> 3.0mission_control-jobs ~> 1.1Database Configuration
Updated
config/database.ymlfor multi-database setup:Queue Adapter
Updated
config/application.rb:Monitoring
/jobsRemoved
redisgemsidekiqgemconfig/sidekiq.yml5. Third-Party Library Updates
Lightbox: lightgallery → Bigger Picture
lightbox_controller.jsPanorama Viewer: Pannellum
vendor/javascript/pannellum.js)Removed Unused Dependencies
nouislider(unused)capacity-slider(unused)6. Spring Removal
Changes
springandspring-watcher-listengemsconfig/spring.rbbin/springbin/railsandbin/raketo remove Spring loadingRationale
Spring is deprecated in Rails 7.1+ and no longer provides significant benefits with modern hardware.
7. Accessibility Improvements
Skip-to-Content Link (Site-wide)
#main(the actual main content)<div id="maincontent">from 7 viewsPanorama Keyboard Accessibility
aria-hidden="true"andfocusable="false"from panoramarole="application"and descriptivearia-labeltabindex="0")Files Modified
app/views/layouts/application.html.erbapp/views/rooms/_room_panorama.html.erbapp/javascript/controllers/pannellum_controller.js8. Performance Fixes
N+1 Query Fixes
rooms#indexpageBug Fixes
9. Announcements CRUD Refactor
Problem
The announcements controller had a non-RESTful pattern:
cancelaction using GET request that modified session statesession[:return_to])new,create, anddestroyactionsSolution
Refactored to standard Rails CRUD pattern:
Controller Changes
cancelaction and session-based return trackingnew,create, anddestroyactionseditandupdateto redirect toannouncements_pathRoute Changes
Model Changes
Added location validation to
Announcementmodel:Policy Changes
Updated
AnnouncementPolicyfor Pundit authorization:create?anddestroy?permissionscancel?permissionView Changes
announcements_path, added location dropdown for new announcementsnew.html.erbtemplateModified Files
config/routes.rbapp/controllers/announcements_controller.rbapp/models/announcement.rbapp/policies/announcement_policy.rbapp/views/announcements/index.html.erbapp/views/announcements/_form.html.erbapp/views/announcements/new.html.erb(created)Test Configuration Fix
Moved
ActiveJob::Base.queue_adapter = :testfromspec/rails_helper.rb(where it ran on every test) toconfig/environments/test.rb(where it runs once at boot). This follows Rails conventions for environment-specific configuration.10. Files Removed (Cleanup)
Build Tools
bin/yarnbin/webpackbin/webpack-dev-serverbin/springpackage.jsonyarn.locknode_modules/Configuration
config/spring.rbconfig/sidekiq.yml.asdf-pluginsentries for nodejs/yarnUpdated .gitignore
11. Current Tech Stack
11. Development Workflow
Starting Development Server
This runs (via
Procfile.dev):web: Rails server on port 3000css: Tailwind CSS watcherjobs: SolidQueue workerMonitoring Background Jobs
Visit
/jobs(requires admin authentication)12. Deployment Considerations
bin/rails assets:precompilenow handles Tailwind