Architecture reference for AI agents. For coding rules and conventions, see CLAUDE.md (single source of truth).
AWBW Portal is a Rails 8.1 application (Ruby 4.0.1) for A Window Between Worlds — a platform where workshop leaders manage workshops, resources, community news, stories, and events.
- Backend: Rails 8.1, Ruby 4.0.1, MySQL (via Trilogy adapter)
- Frontend: Vite, Tailwind CSS v4, Stimulus, Turbo Rails
- Auth: Devise with JWT token support
- Authorization: ActionPolicy (app/policies/)
- Rich text: ActionText with Rhino editor (TipTap-based)
- File uploads: ActiveStorage with DigitalOcean Spaces
- Background jobs: SolidQueue
- Caching: SolidCache
Full setup (bundle, npm, database create/migrate/seed):
bin/setup
If you just need frontend dependencies:
npm ci
This codebase (Rails 8.1)
├── Main app (app/) — Workshops, resources, stories, events, people, organizations
├── Frontend — Stimulus + Turbo + Tailwind CSS v4 (Vite bundler)
├── Background jobs — SolidQueue
├── Caching — SolidCache
├── Auth — Devise (database + JWT)
├── Authorization — ActionPolicy
└── Database — MySQL (Trilogy adapter)
| Directory | Purpose | Count |
|---|---|---|
app/models/ |
ActiveRecord models | ~66 files |
app/services/ |
Service objects for complex logic | ~21 files |
app/jobs/ |
SolidQueue background jobs | 3 files |
app/models/concerns/ |
Shared model modules | 12 concerns |
| Directory | Purpose | Count |
|---|---|---|
app/controllers/ |
Rails controllers (admin/, events/) | ~63 files |
app/views/ |
ERB templates | ~465 files |
app/decorators/ |
Draper decorators for view logic | ~36 files |
app/policies/ |
ActionPolicy authorization rules | ~44 files |
app/presenters/ |
Presentation objects | 1 file |
app/helpers/ |
View helpers | ~19 files |
app/mailers/ |
ActionMailer classes | 6 files |
app/inputs/ |
Custom SimpleForm inputs | 1 file |
| Directory | Purpose |
|---|---|
app/frontend/entrypoints/ |
Vite entry points (application.js, application.css) |
app/frontend/javascript/controllers/ |
Stimulus controllers (34) |
app/frontend/javascript/rhino/ |
Rich text editor customizations (mentions, grid) |
app/frontend/stylesheets/ |
Tailwind CSS and component styles |
| File/Directory | Purpose |
|---|---|
config/routes.rb |
All routes (single file) |
config/database.yml |
MySQL via Trilogy adapter |
config/initializers/ |
~28 initializer files |
.github/workflows/ |
GitHub Actions CI |
Procfile.dev |
Dev services: vite + web |
ai/ |
Shell script shortcuts for common dev tasks (see ai/README.md) |
| Model | Purpose |
|---|---|
User |
Devise authentication, SearchCop search, super_user admin flag |
Workshop |
Core content: rich text fields, categories, sectors, bookmarks, variations |
Event |
Events with registrations, featured/published states |
Story |
Editorial content with facilitators, primary/gallery assets |
Resource |
Handouts, toolkits, templates with downloadable assets |
Person |
Organization affiliates with contacts, addresses, sectors |
Organization |
Groups with affiliations, addresses, logos via ActiveStorage |
Report |
STI base class for MonthlyReport |
WorkshopLog |
Standalone model for workshop log submissions (attendance, form fields) |
- Asset (inheritance column:
type): PrimaryAsset, GalleryAsset, RichTextAsset, DownloadableAsset, ThumbnailAsset - Report: MonthlyReport
- Bookmarks (
bookmarkable): Workshop, Event, Resource, etc. - Assets (
owner): Workshop, Story, Resource, Report, etc. - Comments (
commentable): User, Person, Organization, etc. - Categorizable/Sectorable items: Workshop, Story, Resource, etc.
- Forms (
owner): Resource, Report, etc.
| Concern | Purpose |
|---|---|
AhoyTrackable |
Event tracking integration |
AuthorCreditable |
Author attribution |
Featureable |
featured, publicly_featured scopes |
Mentioner |
ActionText @mention extraction and grouping |
NameFilterable |
Name-based filtering |
Publishable |
published, publicly_visible scopes |
PunctuationStrippable |
Strips punctuation from strings |
RemoteSearchable |
AJAX remote search by column |
RichTextSearchable |
Full-text search on ActionText rich_text fields |
TagFilterable |
Scope-based filtering by tag names |
Trendable |
Trending metrics tracking |
WindowsTypeFilterable |
Filter by WindowsType association |
- Root level (~51 controllers): Workshops, stories, resources, events, people, organizations, etc.
admin/: HomeController, AnalyticsController, AhoyActivitiesControllerevents/: Registrations sub-resource (create/destroy + slug-based show at/registration/:slug)- Devise overrides: Registrations, Confirmations, Passwords
class ApplicationController < ActionController::Base
before_action :authenticate_user!
verify_authorized # ActionPolicy enforcement
# Common helpers:
# authorize! @record — check policy
# authorized_scope(Model.all) — filtered relation
# @record.decorate — Draper decorator
endAhoyTracking— Event tracking integrationDedupable— Data deduplication helpersExternallyRedirectable— External URL redirectionTagAssignable— Tag assignment helpers
Analytics::LifecycleBuffer— Thread-safe event buffer for batch trackingAnalytics::EventBuilder— Constructs analytics event payloadsAnalytics::AhoyTracker— Coordinates ahoy event tracking
WorkshopSearchService— Complex filtering, sorting, pagination with ActionPolicyWorkshopFromIdeaService— Converts WorkshopIdea to Workshop with asset migrationWorkshopVariationFromIdeaService— Variation creation from ideasTaggingSearchService— Search and filter tagging dataPersonFromUserService— Create Person from User accountBulkInviteService— Bulk send welcome instructions and reset created_at for usersModelDeduper— Deduplication logicRichTextMigrator— Rich text migration utilityDisplayImagePresenter— Image display logic
EventRegistrationServices::ProcessConfirmation— Registration confirmation flowEventRegistrationServices::PublicRegistration— Public registration handlingExtendedEventRegistrationFormBuilder— Extended registration form builderShortEventRegistrationFormBuilder— Short registration form builderScholarshipApplicationFormBuilder— Scholarship form builder
NotificationServices::CreateNotification— Notification creationNotificationServices::PersistDeliveredEmail— Email delivery tracking
UserServices::ProcessEmailChange— Email change processingUserServices::ProcessEmailManualConfirm— Manual email confirmation
All inherit from ApplicationDecorator which provides:
delegate_allfor transparent delegationdisplay_image— selects primary/gallery/downloadable asset intelligentlylink_target— polymorphic path generation
Key decorators: WorkshopDecorator, StoryDecorator, ResourceDecorator, PersonDecorator, OrganizationDecorator, UserDecorator, EventDecorator, ReportDecorator.
class ApplicationPolicy < ActionPolicy::Base
authorize :user, optional: true, allow_nil: true
default_rule :manage?
alias_rule :index?, :show?, :new?, :create?, :edit?, :update?, to: :manage?
def manage? = admin?
def destroy? = record.persisted? && manage?
private
def admin? = user&.super_user?
def authenticated? = user.present?
def owner? = record.created_by_id == user.id || record.user_id == user.id
endrelation_scope do |relation|
next relation if admin?
authenticated? ? relation.published : relation.publicly_visible
endApplicationMailer— Base, from:ENV["REPLY_TO_EMAIL"]DeviseMailer— Custom Devise emailsEventMailer— Event registration confirmationsNotificationMailer— Notification deliveryContactUsMailer— Contact form submissions- All use premailer-rails for inline CSS
- Previews live in
test/mailers/previews/(viewable at/rails/mailers/in development)
affiliation_dates— Recalculate affiliation date rangesanchor_highlight— Highlight anchored elementsasset_picker— Asset selection UIautosave— Auto-save form statecarousel— Swiper-based carouselscocoon— Nested form handling (cocoon gem)collection— Filter form auto-submit with debouncecolumn_toggle— Toggle table column visibilitycomment_edit_toggle— Inline comment editing modeconfirm_email— Email confirmation UIdirty_form— Unsaved changes detectiondismiss— Dismissable elementsdropdown— Dropdown menus with keyboard/click-outside handlingfile_preview— File upload previewinactive_toggle— Gray out expired affiliationsoptimistic_bookmark— Instant bookmark UI feedbackorg_toggle— Organization toggle UIpaginated_fields— Client-side pagination of nested fieldspassword_toggle— Show/hide password fieldsprefetch_lazy— Prefetch lazy-loaded contentprint_options— Print options toggle for analyticsremote_select— AJAX-powered select dropdownrhino_source— Rich text editor integrationsearchable_checkbox— TomSelect checkbox-style multi-selectsearchable_select— Tom Select autocompleteshare_url— URL sharing/copyingsortable— Drag-drop sorting (SortableJS)tabs— Tab panel navigationtag_link_loading— Loading state for tag linkstags_combination_highlight— Highlight tags matching selected filterstags_sync_list_heights— Sync tag list column heightstimeframe— Date range filteringtoggle_lock— Lock/unlock toggle UItoggle_user_icon— User icon visibility toggle
| Library | Purpose |
|---|---|
| TipTap + ProseMirror | Rich text editor (Rhino) |
| Tom Select | Advanced select components |
| Chart.js + Chartkick | Analytics charts |
| Swiper | Image carousels |
| SortableJS | Drag-and-drop sorting |
| Tippy.js | Tooltips |
| Font Awesome 7 | Icons |
Custom colors defined in app/frontend/stylesheets/application.tailwind.css:
--color-primary: #063b8d(dark blue)- Standard semantic colors: secondary, danger, warning, info, success
| Directory | Count | Purpose |
|---|---|---|
spec/models/ |
~58 | Model unit tests |
spec/views/ |
~73 | View template tests |
spec/requests/ |
~47 | HTTP request/integration tests |
spec/system/ |
~25 | End-to-end browser tests (Capybara) |
spec/routing/ |
~13 | Route definition tests |
spec/policies/ |
~9 | Authorization policy tests |
spec/decorators/ |
~10 | Decorator tests |
spec/services/ |
~12 | Service object tests |
spec/mailers/ |
~5 | Mailer tests |
spec/helpers/ |
~1 | Helper tests |
spec/factories/ |
~53 | FactoryBot factory definitions |
- rails_helper.rb: Loads RSpec Rails, FactoryBot, Shoulda Matchers, ActionPolicy, Devise test helpers, ActiveStorage validation matchers. Transactional fixtures enabled. ActiveJob uses
:testadapter. - spec_helper.rb: SimpleCov with branch coverage (minimum 20%), random test ordering, profile of 10 slowest examples.
spec/support/capybara.rb— Selenium Chrome headless driverspec/support/devise.rb— Devise integration for request/view/system specsspec/support/eventually_matcher.rb— Custom async assertion matcherspec/support/shared_examples/featureable.rb— Shared tests for featured contentspec/support/shared_examples/mentioner.rb— Shared tests for @mention functionalityspec/support/system_helpers/asset_upload_helpers.rb— Upload/delete helpers for system tests
Common factory traits across models:
:featured,:published,:unpublished:publicly_visible,:publicly_featured:admin(User with super_user=true):with_primary_asset,:with_gallery_assets
bundle exec rubocop # lint
bundle exec rubocop -a # auto-fix
bundle exec brakeman # security scan
bundle exec bundle-audit check --update
- scan_ruby: Brakeman security analysis + bundler-audit
- build-and-test: MySQL 8.0 service, Ruby + Node 22 setup,
npm ci, schema load,bundle exec rspec
RuboCop linting on PRs and pushes to main.
| Need | Library |
|---|---|
| Authentication | Devise (database + confirmable + lockable + trackable) |
| Authorization | ActionPolicy with relation scoping |
| Search | SearchCop (multi-field, rich text joins) |
| Decorators | Draper (with ActionPolicy integration via initializer) |
| Forms | SimpleForm + Cocoon (nested forms) |
| Pagination | WillPaginate with TailwindPaginationRenderer |
| Rich text | ActionText + Rhino editor (TipTap-based) |
| File uploads | ActiveStorage (DigitalOcean Spaces) + legacy Paperclip |
| Feature flags | FeatureFlipper |
| Analytics | Ahoy (events + visits), Chartkick, Groupdate, Blazer |
| Geocoding | Geocoder + MaxMind GeoIP2 |
| Email styling | Premailer-rails (inline CSS) |
| Positioning | Positioning gem for ordered records |
Located in lib/tasks/ (4 files):
dev.rake— Development database seeding from XML/CSVrhino_migrator.rake— Rich text editor migrationattachment_report.rake— Attachment reportingmigrate_internal_id_to_filemaker_code.rake— FileMaker code migration