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.sl
Controller
app/controllers/users_controller.sl
Views
app/views/users/ (index, show, new, edit, _form)
Tests
tests/models/users_test.sl, tests/controllers/users_controller_test.sl
Migration
db/migrations/*create_users_*.sl
Routes
Auto-added to config/routes.sl

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.sl
class Users < Model
    static
        # Fields
        # name (string)
        # email (text)

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

    before_save("normalize_fields")
end

Controller

app/controllers/users_controller.sl
class UsersController < Controller
    static
        this.layout = "application"
    end

    def index
        users = Users.all()
        render("users/index", {
            "title": "Users"
        })
    end

    def show
        user = Users.find(params["id"])
        render("users/show", {
            "title": "View User"
        })
    end

    def create
        permitted = this._permit_params(params)
        user = Users.create(permitted)
        if user["valid"] == true
            return redirect("/users")
        end
        render("users/new", {
            "title": "New User"
        })
    end

    def update
        id = params["id"]
        permitted = this._permit_params(params)
        Users.update(id, permitted)
        return redirect("/users")
    end

    def delete
        id = params["id"]
        Users.delete(id)
        return redirect("/users")
    end

    # Mass assignment protection
    def _permit_params(params)
        {
            "name": params["name"],
            "email": params["email"]
        }
    end
end

Views

Index View

app/views/users/index.html.slv
<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.slv
<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.slv
<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.slv
<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.slv
<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_*.sl
def up(db)
    db.create_collection("users")
end

def down(db)
    db.drop_collection("users")
end

Tests

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

Controller Test

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

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

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

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

Auto-Validations

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

app/models/users_model.sl
class Users < Model
    static
        # Fields
        # name (string)
        # email (email)

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

    before_save("normalize_fields")
end

Mass Assignment Protection

Scaffolded controllers include built-in mass assignment protection to prevent security vulnerabilities. The _permit_params method whitelists only the allowed fields:

Security Pattern
class UsersController < Controller
    # POST /users
    def create
        # Only permit whitelisted fields
        permitted = this._permit_params(params)
        user = Users.create(permitted)
    end

    # Mass assignment protection
    def _permit_params(params)
        {
            "name": params["name"],
            "email": params["email"]
        }
    end
end

Security Best Practice

  • Never pass raw req.params directly to model operations
  • Always use _permit_params() to filter input
  • Add sensitive fields (is_admin, role, permissions) to the blacklist if needed

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

  • Collections are auto-created - no migration needed!
  • Start server: soli serve . --dev
  • Visit: http://localhost:5011/posts
  • Tip: Use migrations to define indexes for better query performance in production.