ESC
Type to search...
S
Soli Docs

Scaffold Generator

Quickly generate complete MVC resources with models, controllers, views, tests, and migrations.

Basic Usage

Generate a scaffold for a resource:

Terminal
$ soli generate scaffold users
$ soli generate scaffold products name:string price:decimal

What Gets Generated

Running soli generate scaffold users creates:

Model
app/models/users_model.soli
Controller
app/controllers/users_controller.soli
Views
app/views/users/ (index, show, new, edit, _form)
Tests
tests/models/users_test.soli, tests/controllers/users_controller_test.soli
Migration
db/migrations/*create_users_*.soli
Routes
Auto-added to config/routes.soli

Field Types

Specify fields with name:type syntax:

Type HTML Input Example
string text name:string
text textarea content:text
email email (unique index) email:email
password password (unique index) password:password
integer number age:integer
float number price:float
boolean checkbox active:boolean
date date picker birthdate:date

Generated Code

Running soli generate scaffold users name:string email:text generates:

Model

app/models/users_model.soli
class Users extends Model {
    // Fields
    // name (string)
    // email (text)

    // Validations (auto-generated)
    validates("name", { "presence": true })
    validates("email", { "presence": true })

    // Callbacks
    before_save("normalize_fields")
}

Controller

app/controllers/users_controller.soli
class UsersController extends Controller {
    fn index(req: Any) -> Any {
        let users = Users.all();
        return render("users/index", {
            "users": users,
            "title": "UsersController"
        });
    }

    fn show(req: Any) -> Any {
        let id = req["params"]["id"];
        let user = Users.find(id);
        return render("users/show", {
            "user": user,
            "title": "View User"
        });
    }

    fn create(req: Any) -> Any {
        let result = Users.create(req["params"]);
        if result["valid"] == true {
            return redirect("/users");
        }
        return render("users/new", {
            "user": result,
            "title": "New User"
        });
    }

    fn update(req: Any) -> Any {
        let id = req["params"]["id"];
        Users.update(id, req["params"]);
        return redirect("/users");
    }

    fn delete(req: Any) -> Any {
        let id = req["params"]["id"];
        Users.delete(id);
        return redirect("/users");
    }
}

Views

Index View

app/views/users/index.html.erb
<div class="p-6">
    <h1 class="text-2xl font-bold">Users</h1>

    <table class="w-full">
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Actions</th>
            </tr>
        </thead>
        <tbody>
            <% for user in users %>
            <tr>
                <td><%= user["id"] %></td>
                <td><%= user["name"] %></td>
                <td>
                    <a href="/users/<%= user["id"] %>">Show</a>
                    <a href="/users/<%= user["id"] %>/edit">Edit</a>
                </td>
            </tr>
            <% end %>
        </tbody>
    </table>

    <a href="/users/new">New User</a>
</div>

Form Partial (shared by new/edit)

app/views/users/_form.html.erb
<form action="/users" method="POST">
    <input type="text" name="name" value="<%= user["name"] %>">
    <input type="text" name="email" value="<%= user["email"] %>">
    <button type="submit">Submit</button>
    </form>

New View

app/views/users/new.html.erb
<h1 class="text-2xl font-bold mb-6">New User</h1>

<%= render("users/_form", { "user": user }) %>

<a href="/users" class="text-indigo-400 hover:text-indigo-300">← Back to Users</a>

Edit View

app/views/users/edit.html.erb
<h1 class="text-2xl font-bold mb-6">Edit User</h1>

<%= render("users/_form", { "user": user }) %>

<a href="/users" class="text-indigo-400 hover:text-indigo-300">← Back to Users</a>

Show View

app/views/users/show.html.erb
<div class="p-6">
    <h1 class="text-2xl font-bold mb-4">User Details</h1>

    <dl class="space-y-4">
        <div>
            <dt class="text-sm text-gray-400">ID</dt>
            <dd class="text-lg text-white"><%= user["id"] %></dd>
        </div>
        <div>
            <dt class="text-sm text-gray-400">Name</dt>
            <dd class="text-lg text-white"><%= user["name"] %></dd>
        </div>
    </dl>

    <a href="/users/<%= user["id"] %>/edit" class="bg-yellow-600 text-white px-4 py-2 rounded">Edit</a>
    <a href="/users" class="text-indigo-400 ml-4">← Back</a>
</div>

Migration

db/migrations/*create_users_*.soli
fn up(db: Any) -> Any {
    db.create_collection("users");
}

fn down(db: Any) -> Any {
    db.drop_collection("users");
}

Tests

tests/models/users_test.soli
describe("UsersModel", fn() {
    test("should create valid record", fn() {
        let data = { "name": "Test User" };
        let result = Users.create(data);
        assert_true(result["valid"], "Create should return valid");
    })
})

Controller Test

tests/controllers/users_controller_test.soli
describe("UsersController", fn() {
    test("index action should render users list", fn() {
        let req = { "params": {}, "session": {} };
        let result = UsersController.index(req);
        assert_true(result["status"] == 200, "Index should return 200");
    })

    test("show action should return single user", fn() {
        let req = { "params": { "id": "test-id" }, "session": {} };
        let result = UsersController.show(req);
        assert_true(result["status"] == 200, "Show should return 200");
    })

    test("create action should redirect on success", fn() {
        let req = { "params": { "name": "Test" }, "session": {} };
        let result = UsersController.create(req);
        assert_true(result["redirect"] != null, "Create should redirect");
    })

    test("delete action should redirect", fn() {
        let req = { "params": { "id": "test-id" }, "session": {} };
        let result = UsersController.delete(req);
        assert_true(result["redirect"] != null, "Delete should redirect");
    })
})

Auto-Validations

Fields with types string, text, email, password, and url automatically get presence: true validation:

app/models/users_model.soli
class Users extends Model {
    // Fields
    // name (string)
    // email (email)

    // Validations (auto-generated)
    validates("name", { "presence": true })
    validates("email", { "presence": true })

    // Callbacks
    before_save("normalize_fields")
}

Generated Routes

Scaffold automatically adds RESTful routes:

HTTP Path Action
GET /users index
GET /users/new new
POST /users create
GET /users/:id show
GET /users/:id/edit edit
PUT /users/:id update
DELETE /users/:id delete

Example

Generate a complete blog posts resource:

Terminal
$ soli generate scaffold posts title:string content:text author:string published:boolean
Success! Created scaffold for posts

Next Steps

  • Run migrations: soli db:migrate up
  • Start server: soli serve . --dev
  • Visit: http://localhost:3000/posts