# MongoDB Model ORM
[](https://crates.io/crates/mongodb-ro)
[](https://docs.rs/mongodb-ro)
[](LICENSE-MIT)
A high-level, type-safe MongoDB model implementation with support for:
- CRUD operations
- Index management
- Field renaming and visibility control
- Timestamps
- Transactions
- Aggregation pipelines
## Features
- **Type-safe operations** - Works with Rust types that implement `Boot`, `Serialize`, and `DeserializeOwned`
- **Derive macro support** - Use `#[derive(Model)]` for easy model setup
- **Automatic index management** - Syncs indexes with model definitions
- **Field control** - Hide/show fields and rename fields in the database
- **Timestamps** - Automatic `created_at` and `updated_at` timestamps
- **Transaction support** - All operations work with MongoDB transactions
- **Query building** - Chainable methods for building complex queries
## Installation
Add to your `Cargo.toml`:
```toml
[dependencies]
mongodb-ro = "2.1.0"
```
## Usage
### Database Connection Helper
```rust
async fn get_db() -> Arc<Database> {
Arc::new(
Client::with_uri_str("mongodb://localhost:27017")
.await
.expect("failed to connect")
.database("test")
)
}
```
### Defining a Model
```rust
use std::sync::Arc;
use mongodb::bson::{doc, DateTime};
use mongodb::bson::oid::ObjectId;
use mongodb::{Client, Database};
use mongodb_ro::Model;
use serde::{Deserialize, Serialize};
use mongodb_ro::event::Boot;
#[derive(Serialize, Deserialize, Debug, Default, Model)]
#[model(collection="user")]
struct User {
_id: Option<ObjectId>,
name: String,
#[model(asc,unique)] // Creates ascending unique index
phone: String,
#[model(desc)] // Creates descending index
age: u8,
#[model(hidden, name("pswd"))] // Hidden by default and renamed in DB
password: String,
block: bool,
updated_at: Option<DateTime>,
created_at: Option<DateTime>,
}
impl Boot for User {
type Req = bool; // Request context type
}
```
### Defining a Model with custom actix_web::HttpRequest
```rust
use std::sync::Arc;
use mongodb::bson::{doc, DateTime};
use mongodb::bson::oid::ObjectId;
use mongodb::{Client, Database};
use mongodb_ro::Model;
use serde::{Deserialize, Serialize};
use mongodb_ro::event::Boot;
use actix_web::HttpRequest;
#[derive(Serialize, Deserialize, Debug, Default, Model)]
#[model(collection="user")]
struct User {
_id: Option<ObjectId>,
name: String,
#[model(asc,unique)] // Creates ascending unique index
phone: String,
#[model(desc)] // Creates descending index
age: u8,
#[model(hidden, name("pswd"))] // Hidden by default and renamed in DB
password: String,
block: bool,
updated_at: Option<DateTime>,
created_at: Option<DateTime>,
}
impl Boot for User {
type Req = HttpRequest; // Request context type
}
async fn save(req:HttpRequest) {
let db = get_db().await;
let mut user_model = User::new_model(&db).set_request(req);
user_model.name = "Smko".to_string();
user_model.phone = "123456789".to_string();
user_model.password = "1234".to_string();
user_model.create().await.unwrap();
}
```
### Basic Operations
**Create a document:**
```rust
async fn save() {
let db = get_db().await;
let mut user_model = User::new_model(&db);
user_model.name = "Smko".to_string();
user_model.phone = "123456789".to_string();
user_model.password = "1234".to_string();
user_model.create().await.unwrap();
}
```
**Find documents:**
```rust
async fn find_one() {
let db = get_db().await;
let user_model = User::new_model(&db);
// Find with visible password (normally hidden)
let user = user_model
.r#where(doc! {"name": "Smko"})
.visible(vec!["password"])
.first()
.await
.unwrap();
println!("Found user: {:?}", user);
}
```
**Update documents:**
```rust
async fn update() {
let db = get_db().await;
let user_model = User::new_model(&db);
// Simple update
user_model
.r#where(doc! {"name": "Smko"})
.update(doc! {"age": 3})
.await
.unwrap();
// Increment operation
user_model
.reset()
.r#where(doc! {"name": "Smko"})
.update(doc! {"$inc": {"age": 1}})
.await
.unwrap();
}
```
**Delete documents:**
```rust
async fn delete() {
let db = get_db().await;
let user_model = User::new_model(&db);
user_model
.r#where(doc! {"name": "Smko"})
.all()
.delete()
.await
.unwrap();
}
```
### Advanced Usage
**Transactions:**
```rust
async fn transaction_with_session() {
let db = get_db().await;
let mut session = db.client().start_session().await.unwrap();
// Start transaction
session.start_transaction().await.unwrap();
// Create within transaction
let mut user_model = User::new_model(&db);
user_model.name = "TransactionUser".to_string();
user_model.phone = "987654321".to_string();
user_model.password = "txn_pass".to_string();
user_model.create_with_session(&mut session).await.unwrap();
// Verify within transaction
let user = User::new_model(&db)
.r#where(doc! {"name": "TransactionUser"})
.first_with_session(&mut session)
.await
.unwrap();
assert!(user.is_some());
// Commit
session.commit_transaction().await.unwrap();
// Cleanup
User::new_model(&db)
.r#where(doc! {"name": "TransactionUser"})
.delete()
.await
.unwrap();
}
```
**Bulk operations:**
```rust
async fn find_and_collect() {
let db = get_db().await;
let users = User::new_model(&db)
.get()
.await
.unwrap();
println!("All users: {:?}", users);
}
```
## Model Attributes
| `collection` | Sets MongoDB collection name | `#[model(collection="users")]` |
## Field Attributes
| `name` | Renames field in database | `#[model(name="db_name")]` |
| `hidden` | Hides field by default | `#[model(hidden)]` |
| `sphere2d` | Creates sphere2d index | `#[model(sphere2d)]` |
| `text` | Creates text index | `#[model(text="en")]` |
| `asc` | Creates ascending index | `#[model(asc)]` |
| `desc` | Creates descending index | `#[model(desc)]` |
| `unique` | Creates unique index | `#[model(unique)]` |
## Contributing
Contributions are welcome! Please open an issue or submit a PR for:
- New features
- Performance improvements
- Bug fixes
## License
Dual-licensed under [MIT](LICENSE-MIT) or [Apache 2.0](LICENSE-APACHE) at your option.