# Ruby on Rails

> How midcode starts a Rails app, edits the HTML written in its ERB views on the canvas, reads its routes as pages, and what helpers like link_to leave out of reach.

- Page: https://midcode.app/docs/frameworks/rails
- From the midcode docs. Every page as Markdown: https://midcode.app/llms.txt
- Status: This ships with the next release of midcode. The version you can download today (1.1.2) doesn't have it yet.

midcode opens a Rails app, starts its server, and edits the HTML written in its ERB views, layouts and partials: text, classes, attributes, new elements and their order. What ERB works out (`<%= post.title %>`) and the tags that helpers build (`link_to`, `image_tag`, `form_with`) aren't editable on the canvas.

Rails renders the page, so midcode finds each element in your views after the page loads. [Sites a server renders](https://midcode.app/docs/frameworks/server-templates.md) explains how that works and what it means for editing.

## At a glance

| | Ruby on Rails |
| --- | --- |
| Detected by | `bin/rails` or `config/application.rb` |
| Runs with | `bin/rails server -b 127.0.0.1 -p $PORT` |
| Elements are marked by | midcode, after each page loads, by matching it against the app's `.erb` files |
| Editing | Text, classes, attributes and tag; insert, move, duplicate and remove |
| Components | A partial is edited in its own file |
| Pages | `config/routes.rb`: `root`, `get` and `resources` |
| Styles | Tailwind 4 classes, or `mid:` utilities with `public/midcode.css` |
| Tried with | Rails 8: 15 of 16 elements found on a page with a layout, a view, a partial in a loop and an `if` / `else` |

## How midcode runs it

When the folder has `bin/rails` or `config/application.rb`, midcode runs this in your login shell, in the project's folder:

```bash
bin/rails server -b 127.0.0.1 -p $PORT
```

`$PORT` is a free port midcode picks. The command finds Ruby the way your terminal does, so a version manager set up in your shell applies. What Rails prints is in the **Dev server log** in the top bar. Nothing in the app is changed to run it.

You need Ruby, the app's gems installed (`bundle install`) and its database prepared. midcode doesn't run either on a project it opens.

midcode runs the server, not `bin/dev`. If your app counts on `Procfile.dev` to build its CSS or JavaScript, run those watchers in a terminal, or set the command yourself with **Change how it runs** (below).

## What you can edit

Anything written as HTML in an `.erb` file. Code between `<%` and `%>` is never touched.

```erb title="app/views/posts/index.html.erb"
<section class="posts">
  <h1>Latest posts</h1>
  <% @posts.each do |post| %>
    <article class="post <%= 'featured' if post.featured? %>">
      <h2><%= post.title %></h2>
      <%= link_to "Read more", post, class: "more" %>
    </article>
  <% end %>
</section>
```

- "Latest posts" is written here: double-click it on the canvas.
- The `<article>` is one line shown once per post. The panel changes `post` and what you add next to it, and leaves the ERB inside the attribute alone. A change applies to every post.
- The `<h2>` is found. Its text is `post.title`, which isn't in the file.
- The link has no mark: `link_to` builds the `<a>`, and no file writes that tag. On the page that was tried, the one element of 16 that wasn't found was a `link_to`.

To make that link editable, write the tag yourself. Its class and its words can then be changed on the canvas, and the address stays computed:

```diff title="app/views/posts/index.html.erb"
-      <%= link_to "Read more", post, class: "more" %>
+      <a class="more" href="<%= post_path(post) %>">Read more</a>
```

A partial rendered in a loop (`_post.html.erb`) works like the `<article>` above: every copy on the page points at the same line of the partial.

Pages that Turbo swaps in without a full load are matched again as they arrive.

After an edit midcode reloads the page in every breakpoint.

## Pages

**Pages** in the top bar is filled from `config/routes.rb`, read as text:

| In `routes.rb` | Page | View it opens |
| --- | --- | --- |
| `root "pages#home"` | Home | `app/views/pages/home.html.erb` |
| `get "about", to: "pages#about"` | `/about` | `app/views/pages/about.html.erb` |
| `get "posts/:id", to: "posts#show"` | `/posts/[id]` | `app/views/posts/show.html.erb` |
| `resources :posts` | `/posts` and `/posts/[id]` | `app/views/posts/index.html.erb` and `show.html.erb` |

`only:` on a `resources` line is respected. A dynamic page finds its real addresses from the running site's sitemap and links ([Pages and navigation](https://midcode.app/docs/editor/pages.md)). For any page that isn't listed, type its path in **Pages** and press `Enter`.

midcode doesn't add pages to a Rails app: a page is a route, a controller action and a view, yours or your agent's to write.

## Site settings

The globe in the top bar opens [Site settings](https://midcode.app/docs/editor/site-settings.md). The title, the description, the language, search engines, the favicon and the social image are written as tags in the `<head>` of a layout: the first template that closes a `<head>`, in alphabetical order of its path, which is `app/views/layouts/application.html.erb` in a new app. Images are copied into `public/`.

What ERB computes there (`<title><%= content_for(:title) || "Bakery" %></title>`) shows as code and isn't written. This wasn't tried on a Rails app.

## Styles

With Tailwind 4 in the app, midcode writes Tailwind's classes ([Tailwind CSS](https://midcode.app/docs/styling/tailwind.md)). Something has to compile them: see the note about `bin/dev` above.

Without it, midcode writes `mid:` utilities and keeps their CSS in `public/midcode.css`, which Rails serves as it is. On your first style edit, each layout that closes a `<head>` gets one line:

```diff title="app/views/layouts/application.html.erb"
     <%= stylesheet_link_tag "application" %>
+    <link rel="stylesheet" href="/midcode.css">
   </head>
```

It's one undoable step. [Without Tailwind](https://midcode.app/docs/styling/without-tailwind.md) explains what's in the file.

## Limits

- What helpers build isn't written as HTML, so it has no mark: `link_to`, `image_tag`, `button_to`, `form_with` and its fields, `content_tag`.
- Haml and Slim views aren't read. Their pages are listed, and nothing in them can be edited.
- Routes inside a `namespace` or a `scope` are listed without its prefix. Routes made by an engine or in a loop aren't listed.
- `public/` is never searched for templates: its error pages have a `<body>` of their own.
- If the app's `package.json` lists Vite (Vite Ruby), midcode opens the folder as a Vite project and its views aren't matched.
- A table change made in midcode's Database isn't run on a Rails app: midcode hands your agent the steps for a Rails migration ([Changing tables](https://midcode.app/docs/data/schema.md)).
- The Rails that was tried ran with a Ruby that isn't on the PATH, so its command was set in `.midcode/server.json`. The automatic start runs the same command.
- [The free canvas](https://midcode.app/docs/editor/free-canvas.md) is for Next.js projects.

## Troubleshooting

**The server didn't start, and the log says `ruby` or `bundle` wasn't found.** Your login shell doesn't have the Ruby the app needs. Use **Change how it runs** on the error card and put it in the command. It's saved in `.midcode/server.json`:

```json title=".midcode/server.json"
{
  "command": "PATH=\"/opt/homebrew/opt/ruby/bin:$PATH\" bin/rails server -b 127.0.0.1 -p $PORT",
  "url": "http://127.0.0.1:$PORT"
}
```

**"A server is already running".** That's Rails: it keeps one `tmp/pids/server.pid` per app, so a server you started in a terminal blocks midcode's. Stop the other one, or give midcode's its own pid file by adding `-P tmp/pids/midcode.pid` to the command.

**Classes you add from the panel have no effect.** The app uses Tailwind and nothing is compiling it. Start your CSS watcher, or use a command that starts everything: midcode also sets `PORT` in the command's environment.

**A Rails error page.** The app is running and something in it failed: pending migrations, a missing credential. Fix it as you would in a browser, then press **Reload breakpoints**.
