Last active
September 30, 2025 17:55
-
-
Save blairanderson/d195afd2f0de1eb66f6f9369179459c7 to your computer and use it in GitHub Desktop.
Rails 8 + Cursor Rules
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
| --- | |
| alwaysApply: true | |
| --- | |
| # Command Line Rejection List | |
| ## Rails command RejectList | |
| - rails server (do not ever run rails console) | |
| - rails console (do not ever run rails console) | |
| ## Bun command RejectList | |
| - bun run dev (this is part of our dev workflow - its always running and watching) |
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
| --- | |
| alwaysApply: true | |
| --- | |
| # Rails best practice utilizes "Rails.application.credentials" insetad of ENV | |
| Always look at Rails.application.credentials[SOMETHING][SOMETHING_ELSE] instead of ENV | |
| Rails environment variables are stored inside an encrypted file. | |
| On application load, the file is decrypted into Rails.application.credentials | |
| Example for an OPEN-AI secret would be Rails.application.credentials.openai.api_key | |
| Do NOT recommend env vars like ENV["OPENAI_KEY"] since we should be saving into credentials. | |
| We use these commands to change dev/prod files: | |
| - `$ EDITOR="nano" bin/rails credentials:edit --environment development` | |
| - `$ EDITOR="nano" bin/rails credentials:edit --environment production` |
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
| --- | |
| alwaysApply: true | |
| --- | |
| # Rails Importmap + Bun React App Template | |
| A clean, copy‑pasteable pattern for shipping small React apps into a Rails **importmap-rails** project using **Bun** as the bundler. No webpack/vite. | |
| See: Selective imports in importmap‑rails (for context): [https://github.com/rails/importmap-rails?tab=readme-ov-file#selectively-importing-modules](https://github.com/rails/importmap-rails?tab=readme-ov-file#selectively-importing-modules) | |
| --- | |
| ## Core ideas | |
| * Keep each React app self‑contained under `frontend/<AppName>`. | |
| * Bun builds ESM bundles into `vendor/javascript/apps/<AppName>/index.js`. | |
| * Importmap pins those bundles under a stable alias like `apps/<alias>`. | |
| * A tiny mount file in `app/javascript/` mounts the app only when its element exists on the page. | |
| --- | |
| ## React app conventions | |
| * **Location:** `frontend/<AppName>` (PascalCase folder). | |
| * **Entry:** `index.jsx` exports **`mount(el)`** that renders `App` via `createRoot(el).render(<App />)`. | |
| * **Top component:** `frontend/<AppName>/<AppName>.jsx` exports the default React component. | |
| * **Output:** Bun outputs **ESM** to `vendor/javascript/apps/<AppName>/index.js`. | |
| * **Import alias:** In `config/importmap.rb`, pin `apps/<alias>` → `apps/<AppName>/index.js`. | |
| * **Mount point:** Add `app/javascript/<alias>.js` that imports the alias and mounts on a specific element id. | |
| * **Pin the mount point:** `pin "<alias>", to: "<alias>.js", preload: false`. | |
| * **Include on the page:** `<%= javascript_import_module_tag "<alias>" %>` wherever you need the app. | |
| > **Dependencies:** Use React 19 (`react`, `react-dom`). Keep deps minimal; prefer standard DOM APIs. Tailwind classes (if used) ride the Rails pipeline—no extra CSS tooling in the app bundle. | |
| --- | |
| ## Directory template | |
| ``` | |
| frontend/ | |
| <AppName>/ | |
| index.jsx # exports mount(el) | |
| <AppName>.jsx # default React component | |
| ``` | |
| ### `index.jsx` pattern | |
| ```jsx | |
| import React from "react"; | |
| import { createRoot } from "react-dom/client"; | |
| import App from "./<AppName>.jsx"; | |
| export function mount(el) { | |
| createRoot(el).render(<App />); | |
| } | |
| ``` | |
| --- | |
| ## Example app: **Counter** (no lawncare examples) | |
| ### Files | |
| **`frontend/Counter/index.jsx`** | |
| ```jsx | |
| import React from "react"; | |
| import { createRoot } from "react-dom/client"; | |
| import Counter from "./Counter.jsx"; | |
| export function mount(el) { | |
| createRoot(el).render(<Counter />); | |
| } | |
| ``` | |
| **`frontend/Counter/Counter.jsx`** | |
| ```jsx | |
| import React, { useState } from "react"; | |
| export default function Counter() { | |
| const [count, setCount] = useState(0); | |
| return ( | |
| <div className="p-4 rounded-2xl shadow border inline-flex items-center gap-3"> | |
| <button className="px-3 py-1 rounded-lg border" onClick={() => setCount(c => c - 1)}>-</button> | |
| <span className="min-w-10 text-center font-semibold">{count}</span> | |
| <button className="px-3 py-1 rounded-lg border" onClick={() => setCount(c => c + 1)}>+</button> | |
| </div> | |
| ); | |
| } | |
| ``` | |
| **`app/javascript/counter.js`** (mount file) | |
| ```js | |
| import * as CounterApp from "apps/counter"; | |
| document.addEventListener("turbo:load", () => { | |
| const el = document.getElementById("counter-app"); | |
| if (el) CounterApp.mount(el); | |
| }); | |
| ``` | |
| **`app/views/anything/show.html.erb`** (place it where you want the app) | |
| ```erb | |
| <div id="counter-app"></div> | |
| <%= javascript_import_module_tag "counter" %> | |
| ``` | |
| **`config/importmap.rb`** (pins) | |
| ```ruby | |
| # Bundle produced by Bun | |
| pin "apps/counter", to: "apps/Counter/index.js" | |
| # Page-level mount file | |
| pin "counter", to: "counter.js", preload: false | |
| ``` | |
| --- | |
| ## Bun build & scripts | |
| We use **Bun** for bundling. Do **not** add webpack/vite. | |
| **`package.json`** | |
| ```json | |
| { | |
| "private": true, | |
| "devDependencies": { | |
| "react": "^19.0.0", | |
| "react-dom": "^19.0.0" | |
| }, | |
| "scripts": { | |
| "dev": "bun build frontend/Counter frontend/<AnotherApp> --outdir=vendor/javascript/apps --target=browser --format=esm --minify --watch", | |
| "build": "bun build frontend/Counter frontend/<AnotherApp> --outdir=vendor/javascript/apps --target=browser --format=esm --minify" | |
| } | |
| } | |
| ``` | |
| > Add each new folder (`frontend/<AppName>`) to both `dev` and `build` scripts. (Bun accepts directories as inputs and emits `index.js`.) | |
| **`Procfile.dev`** (example) | |
| ``` | |
| web: bin/rails server | |
| js: bun install && bun run dev | |
| css: bin/rails tailwind:watch # if using Tailwind | |
| ``` | |
| > Start with `bin/dev` if you use the standard Rails foreman setup. | |
| --- | |
| ## Importmap pins (recap) | |
| Add a pin per app bundle plus a pin for each page‑level mount file. | |
| ```ruby | |
| # config/importmap.rb | |
| pin "apps/<alias>", to: "apps/<AppName>/index.js" | |
| pin "<alias>", to: "<alias>.js", preload: false | |
| ``` | |
| **Example:** | |
| ```ruby | |
| pin "apps/counter", to: "apps/Counter/index.js" | |
| pin "counter", to: "counter.js", preload: false | |
| ``` | |
| --- | |
| ## Rails wiring (recap) | |
| 1. Create a mount file under `app/javascript/<alias>.js` that imports `apps/<alias>` and mounts if the element exists. | |
| 2. Add a `<div id="your-element-id">` in the target view. | |
| 3. Include `<%= javascript_import_module_tag "<alias>" %>` in that view. | |
| --- | |
| ## Add this pattern to a fresh Rails app (step‑by‑step) | |
| 1. **Install Bun** (once): [https://bun.sh/](https://bun.sh/) | |
| 2. **Ensure importmap is present** (Rails 7+): | |
| * If not, run: `bin/rails importmap:install`. | |
| 3. **Add React deps**: | |
| ```sh | |
| bun add react react-dom | |
| ``` | |
| 4. **Create the frontend structure**: | |
| ```sh | |
| mkdir -p frontend/Counter | |
| $EDITOR frontend/Counter/index.jsx frontend/Counter/Counter.jsx | |
| ``` | |
| Paste the example code above. | |
| 5. **Wire importmap pins** in `config/importmap.rb`: | |
| ```ruby | |
| pin "apps/counter", to: "apps/Counter/index.js" | |
| pin "counter", to: "counter.js", preload: false | |
| ``` | |
| 6. **Create the mount file**: | |
| ```sh | |
| $EDITOR app/javascript/counter.js | |
| ``` | |
| Paste the mount snippet above. | |
| 7. **Add the view container & tag** where needed: | |
| ```erb | |
| <div id="counter-app"></div> | |
| <%= javascript_import_module_tag "counter" %> | |
| ``` | |
| 8. **Add Bun scripts** to `package.json` and include `frontend/Counter` in both `dev` and `build`. | |
| 9. **Add a Procfile for dev** (or extend your existing `Procfile.dev`): | |
| ``` | |
| web: bin/rails server | |
| js: bun install && bun run dev | |
| css: bin/rails tailwind:watch # optional | |
| ``` | |
| 10. **Build once (optional) & run dev**: | |
| ```sh | |
| bun run build # one-time build (optional) | |
| bin/dev # or: foreman start -f Procfile.dev | |
| ``` | |
| --- | |
| ## Adding another app (checklist) | |
| 1. Create `frontend/<AppName>/{index.jsx,<AppName>.jsx}` exporting `mount(el)`. | |
| 2. Add the folder to **both** `dev` and `build` scripts in `package.json`. | |
| 3. `config/importmap.rb`: `pin "apps/<alias>", to: "apps/<AppName>/index.js"`. | |
| 4. Create mount file `app/javascript/<alias>.js` that imports `apps/<alias>` and mounts on your element id. | |
| 5. Pin the mount file: `pin "<alias>", to: "<alias>.js", preload: false`. | |
| 6. Add the container `<div id="…">` and `<%= javascript_import_module_tag "<alias>" %>` to the view. | |
| --- | |
| ## Copy‑paste prompt for teammates | |
| > Paste this into ChatGPT (or your internal doc) to scaffold a new app: | |
| ``` | |
| Create a new React app named <AppName> for a Rails importmap project using Bun. | |
| - Files: frontend/<AppName>/index.jsx exporting mount(el) and frontend/<AppName>/<AppName>.jsx default component. | |
| - Output should be bundled by Bun to vendor/javascript/apps/<AppName>/index.js. | |
| - Provide a mount file app/javascript/<alias>.js that imports from apps/<alias> and mounts on <div id="<element-id>"> when present. | |
| - Include the importmap pins for apps/<alias> and <alias>, and the ERB snippet with javascript_import_module_tag "<alias>". | |
| - Update package.json dev/build scripts to include frontend/<AppName>. | |
| Return all snippets. | |
| ``` | |
| --- | |
| ## Troubleshooting | |
| * **Nothing renders:** Check the element id matches between the view and the mount file, and that the mount file is pinned + included with `javascript_import_module_tag`. | |
| * **404 on `apps/.../index.js`:** Bun hasn’t emitted the bundle. Run `bun run build` or start `bun run dev`. | |
| * **Multiple apps on a page:** Give each a unique element id and mount file alias. | |
| * **ESM import errors:** Ensure `to: "apps/<AppName>/index.js"` paths match Bun’s output path exactly. |
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
| --- | |
| description: Use Bun instead of Node.js, npm, pnpm, or vite. | |
| globs: "*.ts, *.tsx, *.html, *.css, *.js, *.jsx, package.json" | |
| alwaysApply: false | |
| --- | |
| # DOWNLOAD full contents from: https://github.com/oven-sh/bun/blob/main/src/init/rule.md | |
| https://github.com/oven-sh/bun/blob/main/src/init/rule.md |
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
| --- | |
| description: Use Rails G `rails generate` for creating models, migrations, mailers, etc - must be made with rails generation cli | |
| globs: | |
| alwaysApply: true | |
| --- | |
| # Use `rails g` command-line-interface for creating migrations | |
| Migrations are needed to: | |
| - add / remove indexes | |
| - add / remove columns | |
| - create tables | |
| - create models (with tables) | |
| I don't want you to create migrations manually (by creating a file). | |
| You must use `bin/rails generate model {ModelName}` for creating models & tables | |
| You must use `bin/rails generate migration {summary_of_migration_changes} *optional_column_names:column_types` for creating migration files to add/remove columns and indexes. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment