search ESC

Searching…

No results for "".

Type at least 2 characters to search.

Docs
You are viewing an older version (1.0.0-alpha.10). Go to the latest.

Eloquent: Getting Started

Introduction

Magic's Eloquent ORM provides a beautiful, simple Active Record implementation for working with your database. Each database table has a corresponding "Model" which is used to interact with that table. Models allow you to query for data, insert new records, and update existing ones.

Unlike traditional ORMs, Magic's Eloquent supports Hybrid Persistence. Your models can persist to a local SQLite database, a remote REST API, or both—giving you offline-first capabilities out of the box.

Generating Models

To generate a new model, use the make:model Magic CLI command:

dart run magic:magic make:model Post

Available Options

Option Shortcut Description
--migration -m Create a database migration
--seeder -s Create a database seeder
--factory -f Create a model factory
--controller -c Create a controller
--policy -p Create a policy
--all -a Create all related files

The --all Flag

The -a flag is the most convenient way to scaffold a complete feature:

dart run magic:magic make:model Product --all

This single command creates:

  • lib/app/models/product.dart
  • lib/database/migrations/m__create_products_table.dart
  • lib/database/seeders/product_seeder.dart
  • lib/database/factories/product_factory.dart
  • lib/app/policies/product_policy.dart
  • lib/app/controllers/product_controller.dart
  • lib/resources/views/product/ (index, show, create, edit)

Defining Models

Models typically live in the lib/app/models directory:

import 'package:magic/magic.dart';

class User extends Model with HasTimestamps, InteractsWithPersistence {
  @override
  String get table => 'users';

  @override
  String get resource => 'users';

  @override
  List get fillable => ['name', 'email', 'avatar'];

  @override
  Map get casts => {
    'email_verified_at': 'datetime',
    'is_active': 'bool',
    'settings': 'json',
  };

  // Typed accessors
  String? get name => getAttribute('name') as String?;
  set name(String? value) => setAttribute('name', value);

  String? get email => getAttribute('email') as String?;
  Carbon? get emailVerifiedAt => getAttribute('email_verified_at') as Carbon?;
  bool get isActive => getAttribute('is_active') as bool? ?? false;

  // Static helpers (required for type-safe queries)
  static Future find(dynamic id) =>
      InteractsWithPersistence.findById(id, User.new);

  static Future> all() =>
      InteractsWithPersistence.allModels(User.new);
}

Table Names

The table property specifies which database table corresponds to your model:

@override
String get table => 'users';

Primary Keys

By default, Eloquent assumes your primary key is id. You may override this:

@override
String get primaryKey => 'user_id';

@override
bool get incrementing => false; // For UUID keys

Fillable Attributes

Define which attributes can be mass-assigned:

@override
List get fillable => ['name', 'email', 'avatar'];

Only these attributes will be set when using fill():

user.fill({
  'name': 'John',
  'email': '[email protected]',
  'is_admin': true, // Ignored - not in fillable
});

Attribute Casting

The casts property converts attributes to common data types:

@override
Map get casts => {
  'created_at': 'datetime',   // Returns Carbon
  'updated_at': 'datetime',
  'is_active': 'bool',        // Returns bool
  'settings': 'json',         // Returns Map
  'age': 'int',               // Returns int
  'price': 'double',          // Returns double
};

Available Cast Types

Cast Returns
datetime Carbon (Magic's date/time wrapper)
bool bool
int int
double double
json Map or List

Accessing Cast Attributes

final user = await User.find(1);

Carbon? createdAt = user.createdAt;       // Carbon instance
bool isActive = user.isActive;             // bool
Map settings = user.settings; // Map

Retrieving Models

Retrieving All Models

final users = await User.all();

for (final user in users) {
  print(user.name);
}

Retrieving A Single Model

final user = await User.find(1);

if (user != null) {
  print(user.name);
}

Refreshing Models

Reload a model's attributes from the database:

await user.refresh();

Inserting & Updating

Inserting Models

To create a new record, instantiate a model, set attributes, and call save():

final user = User()
  ..fill({
    'name': 'John Doe',
    'email': '[email protected]',
  });

await user.save();

print(user.id); // The new auto-generated ID

Updating Models

To update an existing model, retrieve it, modify attributes, and call save():

final user = await User.find(1);

if (user != null) {
  user.name = 'Updated Name';
  await user.save();
}

Dirty Checking

Track which attributes have changed:

final user = await User.find(1);

print(user.isDirty());       // false

user.name = 'New Name';

print(user.isDirty());       // true
print(user.isDirty('name')); // true
print(user.getDirty());      // {'name': 'New Name'}

Deleting Models

final user = await User.find(1);

if (user != null) {
  await user.delete();
  print(user.exists); // false
}

Hybrid Persistence

Magic's unique feature is hybrid persistence—syncing between local SQLite and remote REST API.

Configuration

Control where your model persists:

// Local database only (offline mode)
@override
bool get useLocal => true;
@override
bool get useRemote => false;

// Remote API only (online mode)
@override
bool get useLocal => false;
@override
bool get useRemote => true;

// Both (hybrid mode - default)
@override
bool get useLocal => true;
@override
bool get useRemote => true;

API Resource Endpoint

The resource property maps to your REST API:

@override
String get resource => 'users';

// Maps to:
// GET    /users        -> all()
// GET    /users/{id}   -> find(id)
// POST   /users        -> save() (insert)
// PUT    /users/{id}   -> save() (update)
// DELETE /users/{id}   -> delete()

How Hybrid Mode Works

When using hybrid mode:

  1. Read operations check local database first, then sync from API
  2. Write operations persist to both local and remote
  3. Offline support is automatic—writes queue until online

[!TIP] Use hybrid mode for offline-first apps that sync to a backend API.