From 0cf66642a21d7e6d368476251c44b84977ab4a40 Mon Sep 17 00:00:00 2001 From: Timur Gordon <31495328+timurgordon@users.noreply.github.com> Date: Sat, 2 Aug 2025 11:19:57 +0200 Subject: [PATCH] add postgres example instructions --- .../examples/postgres_example/README.md | 73 ++++ .../examples/postgres_example/example.rs | 249 +++++++++++++ heromodels/examples/postgres_example/setup.sh | 337 ++++++++++++++++++ 3 files changed, 659 insertions(+) create mode 100644 heromodels/examples/postgres_example/README.md create mode 100644 heromodels/examples/postgres_example/example.rs create mode 100755 heromodels/examples/postgres_example/setup.sh diff --git a/heromodels/examples/postgres_example/README.md b/heromodels/examples/postgres_example/README.md new file mode 100644 index 0000000..9450a4c --- /dev/null +++ b/heromodels/examples/postgres_example/README.md @@ -0,0 +1,73 @@ +# PostgreSQL Model Example + +This example demonstrates the Hero Models framework's PostgreSQL integration capabilities, showcasing how to create, store, retrieve, and manage models in a PostgreSQL database. + +## Quick Setup + +**Automated Setup (Recommended):** +```bash +./setup.sh +``` + +The setup script will automatically: +- Detect your operating system (macOS, Ubuntu/Debian, CentOS/RHEL) +- Install PostgreSQL if not already installed +- Start the PostgreSQL service +- Create the required database user with password +- Test the database connection +- Configure PATH (macOS only) + +**Manual Setup:** +If you prefer to set up PostgreSQL manually, the example expects: +- PostgreSQL server running on `localhost:5432` +- Username: `postgres` +- Password: `test123` + +## What This Example Demonstrates + +### Core Features +1. **Model Creation** - Creating User and Comment models with the Hero Models framework +2. **Database Operations** - Storing, retrieving, updating, and deleting records +3. **Indexing** - Using username and active status indexes for efficient queries +4. **Relationships** - Associating comments with users +5. **Connection Management** - PostgreSQL connection pooling and configuration + +### Specific Operations +- **User Management**: Create users with different attributes (username, email, active status) +- **Index Queries**: Retrieve users by username and filter by active status +- **Data Deletion**: Remove users and see the impact on queries +- **Comment System**: Create comments and associate them with users +- **Model Introspection**: Display model information and database prefixes + +## Running the Example + +From the heromodels root directory: + +```bash +cargo run --example postgres_model_example +``` + +## Expected Output + +The example will: +1. Create 4 sample users with different attributes +2. Display all users retrieved from the database +3. Demonstrate index-based queries (by username and active status) +4. Delete a user and show the updated results +5. Create and associate comments with users +6. Display model metadata information + +## Code Structure + +- **Database Configuration**: Sets up PostgreSQL connection with credentials +- **Model Creation**: Uses fluent builder pattern for creating User and Comment instances +- **Database Operations**: Demonstrates CRUD operations using the Hero Models API +- **Index Usage**: Shows how to query using predefined indexes +- **Error Handling**: Proper error handling for database operations + +## Key Technologies + +- **Hero Models Framework**: Core ORM-like functionality +- **PostgreSQL**: Database backend with connection pooling +- **Rust**: Type-safe model definitions and operations +- **Serde**: JSON serialization/deserialization for database storage diff --git a/heromodels/examples/postgres_example/example.rs b/heromodels/examples/postgres_example/example.rs new file mode 100644 index 0000000..3cc1eb4 --- /dev/null +++ b/heromodels/examples/postgres_example/example.rs @@ -0,0 +1,249 @@ +use heromodels::db::postgres::Config; +use heromodels::db::{Collection, Db}; +use heromodels::models::userexample::user::user_index::{is_active, username}; +use heromodels::models::{Comment, User}; +use heromodels_core::Model; + +// Helper function to print user details +fn print_user_details(user: &User) { + println!("\n--- User Details ---"); + println!("ID: {}", user.get_id()); + println!("Username: {}", user.username); + println!("Email: {}", user.email); + println!("Full Name: {}", user.full_name); + println!("Active: {}", user.is_active); + println!("Created At: {}", user.base_data.created_at); + println!("Modified At: {}", user.base_data.modified_at); + println!("Comments: {:?}", user.base_data.comments); +} + +// Helper function to print comment details +fn print_comment_details(comment: &Comment) { + println!("\n--- Comment Details ---"); + println!("ID: {}", comment.get_id()); + println!("User ID: {}", comment.user_id); + println!("Content: {}", comment.content); + println!("Created At: {}", comment.base_data.created_at); + println!("Modified At: {}", comment.base_data.modified_at); +} + +fn main() { + let db = heromodels::db::postgres::Postgres::new( + Config::new() + .user(Some("postgres".into())) + .password(Some("test123".into())) + .host(Some("localhost".into())) + .port(Some(5432)), + ) + .expect("Can connect to postgress"); + + println!("Hero Models - Basic Usage Example"); + println!("================================"); + + // Clean up any existing data to ensure consistent results + println!("Cleaning up existing data..."); + let user_collection = db.collection::().expect("can open user collection"); + let comment_collection = db.collection::().expect("can open comment collection"); + + // Clear all existing users and comments + if let Ok(existing_users) = user_collection.get_all() { + for user in existing_users { + let _ = user_collection.delete_by_id(user.get_id()); + } + } + if let Ok(existing_comments) = comment_collection.get_all() { + for comment in existing_comments { + let _ = comment_collection.delete_by_id(comment.get_id()); + } + } + println!("Database cleaned.\n"); + + // Create users with auto-generated IDs + + // User 1 + let user1 = User::new() + .username("johndoe") + .email("john.doe@example.com") + .full_name("John Doe") + .is_active(false) + .build(); + + // User 2 + let user2 = User::new() + .username("janesmith") + .email("jane.smith@example.com") + .full_name("Jane Smith") + .is_active(true) + .build(); + + // User 3 + let user3 = User::new() + .username("willism") + .email("willis.masters@example.com") + .full_name("Willis Masters") + .is_active(true) + .build(); + + // User 4 + let user4 = User::new() + .username("carrols") + .email("carrol.smith@example.com") + .full_name("Carrol Smith") + .is_active(false) + .build(); + + // Save all users to database and get their assigned IDs and updated models + let (user1_id, db_user1) = db + .collection() + .expect("can open user collection") + .set(&user1) + .expect("can set user"); + let (user2_id, db_user2) = db + .collection() + .expect("can open user collection") + .set(&user2) + .expect("can set user"); + let (user3_id, db_user3) = db + .collection() + .expect("can open user collection") + .set(&user3) + .expect("can set user"); + let (user4_id, db_user4) = db + .collection() + .expect("can open user collection") + .set(&user4) + .expect("can set user"); + + println!("User 1 assigned ID: {user1_id}"); + println!("User 2 assigned ID: {user2_id}"); + println!("User 3 assigned ID: {user3_id}"); + println!("User 4 assigned ID: {user4_id}"); + + // We already have the updated models from the set method, so we don't need to retrieve them again + + // Print all users retrieved from database + println!("\n--- Users Retrieved from Database ---"); + println!("\n1. First user:"); + print_user_details(&db_user1); + + println!("\n2. Second user:"); + print_user_details(&db_user2); + + println!("\n3. Third user:"); + print_user_details(&db_user3); + + println!("\n4. Fourth user:"); + print_user_details(&db_user4); + + // Demonstrate different ways to retrieve users from the database + + // 1. Retrieve by username index + println!("\n--- Retrieving Users by Different Methods ---"); + println!("\n1. By Username Index:"); + let stored_users = db + .collection::() + .expect("can open user collection") + .get::("johndoe") + .expect("can load stored user"); + + assert_eq!(stored_users.len(), 1); + print_user_details(&stored_users[0]); + + // 2. Retrieve by active status + println!("\n2. By Active Status (Active = true):"); + let active_users = db + .collection::() + .expect("can open user collection") + .get::(&true) + .expect("can load stored users"); + + assert_eq!(active_users.len(), 2); + for active_user in active_users.iter() { + print_user_details(active_user); + } + + // 3. Delete a user and show the updated results + println!("\n3. After Deleting a User:"); + let user_to_delete_id = active_users[0].get_id(); + println!("Deleting user with ID: {user_to_delete_id}"); + db.collection::() + .expect("can open user collection") + .delete_by_id(user_to_delete_id) + .expect("can delete existing user"); + + // Show remaining active users + let active_users = db + .collection::() + .expect("can open user collection") + .get::(&true) + .expect("can load stored users"); + + println!(" a. Remaining Active Users:"); + assert_eq!(active_users.len(), 1); + for active_user in active_users.iter() { + print_user_details(active_user); + } + + // Show inactive users + let inactive_users = db + .collection::() + .expect("can open user collection") + .get::(&false) + .expect("can load stored users"); + + println!(" b. Inactive Users:"); + assert_eq!(inactive_users.len(), 2); + for inactive_user in inactive_users.iter() { + print_user_details(inactive_user); + } + + // Delete a user based on an index for good measure + db.collection::() + .expect("can open user collection") + .delete::("janesmith") + .expect("can delete existing user"); + + println!("\n--- User Model Information ---"); + println!("User DB Prefix: {}", User::db_prefix()); + + // Demonstrate comment creation and association with a user + println!("\n--- Working with Comments ---"); + + // 1. Create and save a comment + println!("\n1. Creating a Comment:"); + let comment = Comment::new() + .user_id(db_user1.get_id()) // commenter's user ID + .content("This is a comment on the user") + .build(); + + // Save the comment and get its assigned ID and updated model + let (comment_id, db_comment) = db + .collection() + .expect("can open comment collection") + .set(&comment) + .expect("can set comment"); + + println!("Comment assigned ID: {comment_id}"); + + println!(" a. Comment Retrieved from Database:"); + print_comment_details(&db_comment); + + // 3. Associate the comment with a user + println!("\n2. Associating Comment with User:"); + let mut updated_user = db_user1.clone(); + updated_user.base_data.add_comment(db_comment.get_id()); + + // Save the updated user and get the new version + let (_, user_with_comment) = db + .collection::() + .expect("can open user collection") + .set(&updated_user) + .expect("can set updated user"); + + println!(" a. User with Associated Comment:"); + print_user_details(&user_with_comment); + + println!("\n--- Model Information ---"); + println!("User DB Prefix: {}", User::db_prefix()); + println!("Comment DB Prefix: {}", Comment::db_prefix()); +} diff --git a/heromodels/examples/postgres_example/setup.sh b/heromodels/examples/postgres_example/setup.sh new file mode 100755 index 0000000..33bfa7f --- /dev/null +++ b/heromodels/examples/postgres_example/setup.sh @@ -0,0 +1,337 @@ +#!/bin/bash + +# PostgreSQL Setup Script for Hero Models Example +# This script checks for PostgreSQL installation and sets up the required database configuration + +set -e # Exit on any error + +echo "🚀 Hero Models PostgreSQL Example Setup" +echo "========================================" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Function to print colored output +print_status() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Detect operating system +detect_os() { + if [[ "$OSTYPE" == "darwin"* ]]; then + echo "macos" + elif [[ "$OSTYPE" == "linux-gnu"* ]]; then + if command -v apt-get &> /dev/null; then + echo "ubuntu" + elif command -v yum &> /dev/null; then + echo "centos" + else + echo "linux" + fi + else + echo "unknown" + fi +} + +# Check if PostgreSQL is installed +check_postgres_installed() { + # Check if PostgreSQL is in current PATH + if command -v postgres &> /dev/null || command -v psql &> /dev/null; then + return 0 + fi + + # Check macOS Homebrew installation location + if [[ $(detect_os) == "macos" ]]; then + if [[ -f "/opt/homebrew/opt/postgresql@15/bin/postgres" ]] || [[ -f "/opt/homebrew/opt/postgresql@15/bin/psql" ]]; then + return 0 + fi + # Also check Intel Mac location + if [[ -f "/usr/local/opt/postgresql@15/bin/postgres" ]] || [[ -f "/usr/local/opt/postgresql@15/bin/psql" ]]; then + return 0 + fi + fi + + return 1 +} + +# Install PostgreSQL based on OS +install_postgres() { + local os=$(detect_os) + + case $os in + "macos") + print_status "Installing PostgreSQL on macOS using Homebrew..." + if ! command -v brew &> /dev/null; then + print_error "Homebrew is not installed. Please install Homebrew first:" + echo " /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"" + exit 1 + fi + brew install postgresql@15 + print_success "PostgreSQL installed successfully" + ;; + "ubuntu") + print_status "Installing PostgreSQL on Ubuntu/Debian..." + sudo apt-get update + sudo apt-get install -y postgresql postgresql-contrib + print_success "PostgreSQL installed successfully" + ;; + "centos") + print_status "Installing PostgreSQL on CentOS/RHEL..." + sudo yum install -y postgresql-server postgresql-contrib + sudo postgresql-setup initdb + print_success "PostgreSQL installed successfully" + ;; + *) + print_error "Unsupported operating system: $os" + print_error "Please install PostgreSQL manually and run this script again" + exit 1 + ;; + esac +} + +# Start PostgreSQL service +start_postgres() { + local os=$(detect_os) + + case $os in + "macos") + print_status "Starting PostgreSQL service on macOS..." + brew services start postgresql@15 + # Add PostgreSQL to PATH for this session + export PATH="/opt/homebrew/opt/postgresql@15/bin:$PATH" + print_success "PostgreSQL service started" + ;; + "ubuntu") + print_status "Starting PostgreSQL service on Ubuntu/Debian..." + sudo systemctl start postgresql + sudo systemctl enable postgresql + print_success "PostgreSQL service started and enabled" + ;; + "centos") + print_status "Starting PostgreSQL service on CentOS/RHEL..." + sudo systemctl start postgresql + sudo systemctl enable postgresql + print_success "PostgreSQL service started and enabled" + ;; + esac +} + +# Check if PostgreSQL is running +check_postgres_running() { + local os=$(detect_os) + + # Ensure PostgreSQL binaries are in PATH for macOS + if [[ $os == "macos" ]]; then + export PATH="/opt/homebrew/opt/postgresql@15/bin:$PATH" + fi + + if pg_isready -h localhost -p 5432 &> /dev/null; then + return 0 + else + return 1 + fi +} + +# Setup database user and password +setup_database() { + print_status "Setting up database user and password..." + + local os=$(detect_os) + + # Ensure PostgreSQL binaries are in PATH for macOS + if [[ $os == "macos" ]]; then + export PATH="/opt/homebrew/opt/postgresql@15/bin:$PATH" + fi + + # Create postgres user if it doesn't exist (mainly for macOS) + if ! psql -U postgres -c '\q' &> /dev/null; then + print_status "Creating postgres user..." + if [[ $os == "macos" ]]; then + createuser -s postgres 2>/dev/null || true + else + sudo -u postgres createuser -s postgres 2>/dev/null || true + fi + fi + + # Set password for postgres user + print_status "Setting password for postgres user..." + if [[ $os == "macos" ]]; then + psql -U postgres -c "ALTER USER postgres PASSWORD 'test123';" 2>/dev/null || { + print_warning "Could not set password directly. Trying alternative method..." + createdb -U postgres postgres 2>/dev/null || true + psql -U postgres -d postgres -c "ALTER USER postgres PASSWORD 'test123';" || { + print_error "Failed to set password. You may need to set it manually:" + echo " psql -U postgres -c \"ALTER USER postgres PASSWORD 'test123';\"" + return 1 + } + } + else + sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'test123';" || { + print_error "Failed to set password. You may need to set it manually:" + echo " sudo -u postgres psql -c \"ALTER USER postgres PASSWORD 'test123';\"" + return 1 + } + fi + + print_success "Database user configured successfully" +} + +# Test database connection +test_connection() { + print_status "Testing database connection..." + + local os=$(detect_os) + if [[ $os == "macos" ]]; then + export PATH="/opt/homebrew/opt/postgresql@15/bin:$PATH" + fi + + if PGPASSWORD=test123 psql -h localhost -p 5432 -U postgres -d postgres -c "SELECT version();" &> /dev/null; then + print_success "Database connection test successful!" + return 0 + else + print_error "Database connection test failed" + return 1 + fi +} + +# Add PATH export to shell profile (macOS only) +setup_path_macos() { + if [[ $(detect_os) == "macos" ]]; then + local shell_profile="" + if [[ $SHELL == *"zsh"* ]]; then + shell_profile="$HOME/.zshrc" + elif [[ $SHELL == *"bash"* ]]; then + shell_profile="$HOME/.bash_profile" + fi + + if [[ -n $shell_profile ]]; then + local path_export='export PATH="/opt/homebrew/opt/postgresql@15/bin:$PATH"' + if ! grep -q "$path_export" "$shell_profile" 2>/dev/null; then + print_status "Adding PostgreSQL to PATH in $shell_profile..." + echo "" >> "$shell_profile" + echo "# PostgreSQL" >> "$shell_profile" + echo "$path_export" >> "$shell_profile" + print_success "PostgreSQL added to PATH. Restart your terminal or run: source $shell_profile" + fi + fi + fi +} + +# Prompt user for installation +prompt_install() { + echo "" + print_warning "PostgreSQL is not installed on your system." + echo "" + print_status "The Hero Models example requires PostgreSQL to be installed and configured." + echo "" + echo "Options:" + echo " 1. Let this script install PostgreSQL automatically" + echo " 2. Install PostgreSQL manually and run this script again" + echo "" + read -p "Would you like this script to install PostgreSQL for you? (y/n): " -n 1 -r + echo "" + + if [[ $REPLY =~ ^[Yy]$ ]]; then + return 0 # User wants automatic installation + else + return 1 # User wants manual installation + fi +} + +# Main setup process +main() { + echo "" + + # Check if PostgreSQL is already installed + if check_postgres_installed; then + print_success "PostgreSQL is already installed" + else + if prompt_install; then + print_status "Installing PostgreSQL..." + install_postgres + else + print_status "Please install PostgreSQL manually and run this script again." + echo "" + print_status "Manual installation instructions:" + local os=$(detect_os) + case $os in + "macos") + echo " brew install postgresql@15" + echo " brew services start postgresql@15" + ;; + "ubuntu") + echo " sudo apt-get update" + echo " sudo apt-get install postgresql postgresql-contrib" + echo " sudo systemctl start postgresql" + ;; + "centos") + echo " sudo yum install postgresql-server postgresql-contrib" + echo " sudo postgresql-setup initdb" + echo " sudo systemctl start postgresql" + ;; + *) + echo " Please install PostgreSQL using your system's package manager" + ;; + esac + echo "" + exit 0 + fi + fi + + # Start PostgreSQL service + print_status "Checking PostgreSQL service status..." + if ! check_postgres_running; then + print_status "PostgreSQL is not running. Starting service..." + start_postgres + sleep 2 # Give service time to start + else + print_success "PostgreSQL service is already running" + fi + + # Setup database user and password + setup_database + + # Test the connection + if test_connection; then + print_success "Setup completed successfully!" + else + print_error "Setup completed but connection test failed" + print_error "You may need to manually configure the database" + exit 1 + fi + + # Setup PATH for macOS + setup_path_macos + + echo "" + print_success "🎉 PostgreSQL setup complete!" + echo "" + print_status "You can now run the example with:" + echo " cargo run --example postgres_model_example" + echo "" + print_status "Database configuration:" + echo " Host: localhost" + echo " Port: 5432" + echo " Username: postgres" + echo " Password: test123" + echo "" +} + +# Run main function +main "$@"