Instruction file imported from hendripermana/sure (
.cursor/rules/project-conventions.mdc). Copyright stays with the author.
This rule serves as high-level documentation for how you should write code in this codebase.
Project Tech Stack
- Web framework: Ruby on Rails
- Minitest + fixtures for testing
- Propshaft for asset pipeline
- Hotwire Turbo/Stimulus for SPA-like UI/UX
- TailwindCSS for styles
- Lucide Icons for icons
- OpenAI for AI chat
- Database: PostgreSQL
- Jobs: Sidekiq + Redis
- External
- Payments: Stripe
- User bank data syncing: Plaid
Project conventions
These conventions should be used when writing code for the project.
Convention 1: Minimize dependencies, vanilla Rails is plenty
Dependencies are a natural part of building software, but we aim to minimize them when possible to keep this open-source codebase easy to understand, maintain, and contribute to.
- Push Rails to its limits before adding new dependencies
- When a new dependency is added, there must be a strong technical or business reason to add it
- When adding dependencies, you should favor old and reliable over new and flashy
Convention 2: Leverage POROs and concerns over "service objects"
This codebase adopts a "skinny controller, fat models" convention. Furthermore, we put almost everything directly in the app/models/ folder and avoid separate folders for business logic such as app/services/.
- Organize large pieces of business logic into Rails concerns and POROs (Plain ole' Ruby Objects)
- While a Rails concern may offer shared functionality (i.e. "duck types"), it can also be a "one-off" concern that is only included in one place for better organization and readability.
- When concerns are used for code organization, they should be organized around the "traits" of a model; not for simply moving code to another spot in the codebase.
- When possible, models should answer questions about themselves—for example, we might have a method,
account.balance_seriesthat returns a time-series of the account's most recent balances. We prefer this over something more service-like such asAccountSeries.new(account).call.
Convention 3: Leverage Hotwire, write semantic HTML, CSS, and JS, prefer server-side solutions
- Native HTML is always preferred over JS-based components
- Example 1: Use
<dialog>element for modals instead of creating a custom component - Example 2: Use
<details><summary>...</summary></details>for disclosures rather than custom components
- Example 1: Use
- Leverage Turbo frames to break up the page over JS-driven client-side solutions
- Example 1: A good example of turbo frame usage is in application.html.erb where we load chats_controller.rb actions in a turbo frame in the global layout
- Leverage query params in the URL for state over local storage and sessions. If absolutely necessary, utilize the DB for persistent state.
- Use Turbo streams to enhance functionality, but do not solely depend on it
- Format currencies, numbers, dates, and other values server-side, then pass to Stimulus controllers for display only
- Keep client-side code for where it truly shines. For example, @bulk_select_controller.js is a case where server-side solutions would degrade the user experience significantly. When bulk-selecting entries, client-side solutions are the way to go and Stimulus provides the right toolset to achieve this.
- Always use the
iconhelper in application_helper.rb for icons. NEVER uselucide_iconhelper directly.
The Hotwire suite (Turbo/Stimulus) works very well with these native elements and we optimize for this.
Convention 4: Optimize for simplicitly and clarity
All code should maximize readability and simplicity.
- Prioritize good OOP domain design over performance
- Only focus on performance for critical and global areas of the codebase; otherwise, don't sweat the small stuff.
- Example 1: be mindful of loading large data payloads in global layouts
- Example 2: Avoid N+1 queries
Convention 5: Use ActiveRecord for complex validations, DB for simple ones, keep business logic out of DB
- Enforce
nullchecks, unique indexes, and other simple validations in the DB - ActiveRecord validations may mirror the DB level ones, but not 100% necessary. These are for convenience when error handling in forms. Always prefer client-side form validation when possible.
- Complex validations and business logic should remain in ActiveRecord