Models & ORM
Models manage data and business logic in your MVC application. SoliLang provides a simple OOP-style interface for database operations.
Defining Models
Create model files in app/models/. The collection name is automatically derived from the class name:
User→"users"BlogPost→"blog_posts"UserProfile→"user_profiles"
Automatic Collection Creation: When you call a Model method (like create(), all(), find(), etc.) on a collection that doesn't exist yet, SoliLang will automatically create the collection for you. This means you can start using your models immediately without running migrations first.
class User extends Model end
That's it! No need to manually specify collection names or field definitions.
CRUD Operations
Auto-creation: All Model operations automatically create the collection if it doesn't exist. This only happens on the first call that encounters a missing collection.
CREATE Creating Records
let result = User.create({
"email": "[email protected]",
"name": "Alice",
"age": 30
});
# Returns: { "valid": true, "record": { "id": "...", ... } }
# Or: { "valid": false, "errors": [...] }
READ Finding Records
# Find by ID
let user = User.find("user123");
# Find all
let users = User.all();
# Find with SDBQL filter
# Note: where() returns a QueryBuilder - call .all() to get results
let adults = User.where("doc.age >= @age", { "age": 18 }).all();
# Complex conditions
let results = User.where("doc.age >= @min AND doc.role == @role", {
"min": 21,
"role": "admin"
}).all();
UPDATE Updating Records
User.update("user123", {
"name": "Alice Smith",
"age": 31
});
DELETE Deleting Records
User.delete("user123");
COUNT Counting Records
let total = User.count();
Query Builder Chaining
Chain methods to build complex queries:
let results = User
.where("doc.age >= @age", { "age": 18 })
.where("doc.active == @active", { "active": true })
.order("created_at", "desc")
.limit(10)
.offset(20)
.all();
# Get first result only
let first = User.where("doc.email == @email", { "email": "[email protected]" }).first();
# Count with conditions
let count = User.where("doc.role == @role", { "role": "admin" }).count();
Static Methods Reference
| Method | Description |
|---|---|
Model.create(data) |
Insert a new document |
Model.find(id) |
Get document by ID |
Model.where(filter, bind_vars) |
Query with SDBQL filter |
Model.all() |
Get all documents |
Model.update(id, data) |
Update a document |
Model.delete(id) |
Delete a document |
Model.count() |
Count all documents |
QueryBuilder Methods
| Method | Description |
|---|---|
.where(filter, bind_vars) |
Add filter condition (ANDed) |
.order(field, direction) |
Set sort order ("asc"/"desc") |
.limit(n) |
Limit results to n documents |
.offset(n) |
Skip first n documents |
.all() |
Execute, return all results |
.first() |
Execute, return first result |
.count() |
Execute, return count |
Validations
Define validation rules in your model class:
class User extends Model
def is_admin -> Bool
this.role == "admin"
end
def full_name -> String
this.first_name + " " + this.last_name
end
end
# Usage
let user = User.find("user123");
if user.is_admin()
print("Welcome, admin " + user.full_name());
end
Validation Options
| Option | Description |
|---|---|
presence: true |
Field must be present and not empty |
uniqueness: true |
Value must be unique in collection |
min_length: n |
String must be at least n characters |
max_length: n |
String must be at most n characters |
format: "regex" |
String must match regex pattern |
numericality: true |
Value must be a number |
min: n |
Number must be >= n |
max: n |
Number must be <= n |
custom: "method" |
Call custom validation method |
Validation Results
let result = User.create({ "email": "" });
if result["valid"]
let user = result["record"];
print("Created user: " + user["id"]);
else
for error in result["errors"]
print(error["field"] + ": " + error["message"]);
end
end
Callbacks
Define lifecycle callbacks to run code at specific points:
class User extends Model
before_save("normalize_email")
after_create("send_welcome_email")
before_update("log_changes")
after_delete("cleanup_related")
def normalize_email
this.email = this.email.downcase();
end
def send_welcome_email
# Send email logic
end
end
Available Callbacks
before_save
Before create or update
after_save
After create or update
before_create
Before inserting new record
after_create
After inserting new record
before_update
Before updating record
after_update
After updating record
before_delete
Before deleting record
after_delete
After deleting record
Relationships
Implement relationships using model methods:
class Post extends Model
def author
User.find(this.author_id)
end
end
class User extends Model
# Returns a QueryBuilder for chaining
def posts
Post.where("doc.author_id == @id", { "id": this.id })
end
end
Query Generation (SDBQL)
Under the hood, Model methods generate SDBQL (SoliDB Query Language) queries:
| Method | Generated SDBQL |
|---|---|
User.all() |
FOR doc IN users RETURN doc |
User.where("doc.age >= @age", {"age": 18}) |
FOR doc IN users FILTER doc.age >= @age RETURN doc |
.order("name", "asc") |
... SORT doc.name ASC RETURN doc |
.limit(10).offset(20) |
... LIMIT 20, 10 RETURN doc |
User.count() |
FOR doc IN users COLLECT WITH COUNT INTO count RETURN count |
SDBQL Syntax
FOR doc IN collectioninstead ofSELECT * FROMFILTER expressioninstead ofWHERESORT doc.field ASC/DESCinstead ofORDER BY@variablesyntax for bind parameters
Complete Example
# app/models/user.sl
class User extends Model
validates("email", { "presence": true, "uniqueness": true })
validates("name", { "presence": true, "min_length": 2 })
before_save("normalize_email")
def normalize_email
this.email = this.email.downcase();
end
def posts
Post.where("doc.user_id == @id", { "id": this.id })
end
def is_adult -> Bool
this.age >= 18
end
end
# Usage in controller
class UsersController extends Controller
def index(req: Any) let users = User.all();
render("users/index", { "users": users })
end
def show(req: Any) let id = req["params"]["id"];
let user = User.find(id);
let posts = user.posts().order("created_at", "desc").limit(5).all();
render("users/show", { "user": user, "posts": posts })
end
def create(req: Any) let result = User.create({
"name": req["params"]["name"],
"email": req["params"]["email"]
});
if result["valid"]
return redirect("/users/" + result["record"]["id"]);
else
render("users/new", { "errors": result["errors"] })
end
end
end
Best Practices
- Keep models simple - Just extend
Model, no configuration needed - Use meaningful class names - They become collection names automatically
- Add validations - Validate data before it reaches the database
- Use callbacks wisely - Keep them focused and avoid heavy operations
- Add custom methods - Encapsulate business logic in model methods
- Use relationships - Create methods that return related models
- Use migrations in production - Define indexes and schema for optimal performance