From 2caa81fa16074043bdb1d69a41643b1c126304c9 Mon Sep 17 00:00:00 2001 From: + <+> Date: Mon, 27 Jul 2026 10:41:35 -0400 Subject: [PATCH 01/37] add MY_README.md, add project proposal, and user stories --- MY_README.md | 31 +++++++++++++++++++++++++++++++ db/schema.rb | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 MY_README.md create mode 100644 db/schema.rb diff --git a/MY_README.md b/MY_README.md new file mode 100644 index 0000000..02a05e2 --- /dev/null +++ b/MY_README.md @@ -0,0 +1,31 @@ +# Meal Prep Tracker + +## Description: + +### Meal Prep Tracker is a Ruby backend application built with ActiveRecord that supports creating, updating, and deleting weekly meal plans, organizing meals within those plans, and managing meal data for individual users. + +## Models: + + - User + - MealPlan + - Meal + +## Associations: + +- A User has many MealPlans. +- A User has many Meals through MealPlans. +- A MealPlan belongs to a User. +- A MealPlan has many Meals. +- A Meal belongs to a MealPlan. + +## User Stories + +- View existing users. +- View a user's meal plans. +- Create a new meal plan for a user. +- Update an existing meal plan. +- Delete a meal plan. +- Add meals to a meal plan. +- Update meal information. +- Remove meals from a meal plan. +- View all meals within a meal plan. \ No newline at end of file diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 0000000..b4d1e57 --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,37 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema[7.2].define(version: 2025_07_22_152222) do + create_table "owners", force: :cascade do |t| + t.string "name", null: false + t.string "email", null: false + t.string "phone", null: false + t.text "address" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["email"], name: "index_owners_on_email", unique: true + end + + create_table "pets", force: :cascade do |t| + t.string "name", null: false + t.string "species", null: false + t.string "breed", null: false + t.integer "age", null: false + t.text "notes" + t.integer "owner_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["owner_id"], name: "index_pets_on_owner_id" + end + + add_foreign_key "pets", "owners" +end From 1c497d0adcf425d095688412e5d8be4fe78dadbc Mon Sep 17 00:00:00 2001 From: + <+> Date: Mon, 27 Jul 2026 11:33:57 -0400 Subject: [PATCH 02/37] Replace README boilerplate with MealTracker app proposal --- MY_README.md | 31 --------- README.md | 182 +++++++-------------------------------------------- 2 files changed, 24 insertions(+), 189 deletions(-) delete mode 100644 MY_README.md diff --git a/MY_README.md b/MY_README.md deleted file mode 100644 index 02a05e2..0000000 --- a/MY_README.md +++ /dev/null @@ -1,31 +0,0 @@ -# Meal Prep Tracker - -## Description: - -### Meal Prep Tracker is a Ruby backend application built with ActiveRecord that supports creating, updating, and deleting weekly meal plans, organizing meals within those plans, and managing meal data for individual users. - -## Models: - - - User - - MealPlan - - Meal - -## Associations: - -- A User has many MealPlans. -- A User has many Meals through MealPlans. -- A MealPlan belongs to a User. -- A MealPlan has many Meals. -- A Meal belongs to a MealPlan. - -## User Stories - -- View existing users. -- View a user's meal plans. -- Create a new meal plan for a user. -- Update an existing meal plan. -- Delete a meal plan. -- Add meals to a meal plan. -- Update meal information. -- Remove meals from a meal plan. -- View all meals within a meal plan. \ No newline at end of file diff --git a/README.md b/README.md index d6d0893..02a05e2 100644 --- a/README.md +++ b/README.md @@ -1,165 +1,31 @@ -# Phase 3 Project — Single-Script Active Record CLI +# Meal Prep Tracker -## Learning Goals +## Description: -- Build a database-backed Ruby application using Active Record -- Design and interact with data models using object-oriented Ruby -- Implement a multi-class CLI frontend -- Practice working with migrations, associations, and validations +### Meal Prep Tracker is a Ruby backend application built with ActiveRecord that supports creating, updating, and deleting weekly meal plans, organizing meals within those plans, and managing meal data for individual users. -## Introduction +## Models: + + - User + - MealPlan + - Meal -Congrats on getting through all the material for Phase 3! You've learned how to work with databases, design models with Active Record, and write object-oriented Ruby. Now it's time to bring those skills together into a full project. +## Associations: -This project will focus on building a Ruby command-line application that reads from and writes to a local SQLite3 database using Active Record — no web server required. +- A User has many MealPlans. +- A User has many Meals through MealPlans. +- A MealPlan belongs to a User. +- A MealPlan has many Meals. +- A Meal belongs to a MealPlan. -By the end of the project, you'll have a functioning CLI that lets users interact with your data by creating, viewing, updating, and deleting records from the terminal. +## User Stories -## Requirements - -### Models - -- At least two model classes using `ActiveRecord::Base` -- A one-to-many relationship (`has_many` / `belongs_to`) -- At least one model with validations -- Display associated data where appropriate (e.g. listing a parent record's associated children) - -### CLI - -- At least two Ruby classes (e.g. a `Menu` class and a model-specific helper) -- A loop or menu interface -- Ability to create, view, update, and delete records -- Update prompts should display the current value before asking for a new one - -## Planning - -- Plan out your features -- Develop user stories - - "As [ a user ], I want [ to perform this action ] so that [ I can accomplish this goal ]." - - Features should not need you there to explain them to users - - Create a `user-stories.md` file and add your user stories there - -## Project Pitches - -Before you start working on your project, you'll pitch your project idea to your instructors for approval and feedback. - -For your project pitch, you should include: - -- The basic story of your application -- The core features of your MVP -- The data you plan to persist and how you will structure it -- Challenges you expect to face -- How you are meeting the requirements of the project - -**MVP ASAP** — Focus on getting your minimum viable product working first! - -## Example Project Domains - -You could build a **Book Tracker** app: - -- `Author` has many `Books` -- Users can: - - Create a new book - - List all books - - Update book details - - Delete a book - - View books by a specific author - -Or a **Workout Log**: - -- `WorkoutSession` has many `Exercises` -- Users can: - - Log a new workout - - Add exercises - - Update reps/weights - - View or delete past workouts - -## Getting Started - -**Fork and clone** this repository to get started. - -Install dependencies: - -```bash -bundle install -``` - -Create and migrate the database: - -```bash -bundle exec rake db:create -bundle exec rake db:migrate -``` - -Optionally seed the database with starter data: - -```bash -bundle exec rake seed -``` - -Run your CLI application: - -```bash -ruby cli/main.rb -``` - -## Other Useful Commands - -Open a Pry console with your models loaded: - -```bash -bundle exec rake console -``` - -Generate a new migration: - -```bash -bundle exec rake db:create_migration NAME=create_books -``` - -## Project Structure - -``` -├── app/ -│ └── models/ # Your Active Record model classes go here -├── cli/ -│ └── main.rb # Entry point — your CLI menu lives here -├── config/ -│ └── environment.rb # Loads gems, DB connection, and models -├── db/ -│ ├── config.yml # Database connection settings -│ ├── migrate/ # Migration files -│ └── seeds.rb # Seed data -└── spec/ # RSpec tests (optional) -``` - -## Project Tips - -- Sketch your domain model first using [dbdiagram.io](https://dbdiagram.io/) -- Use `bundle exec rake console` to test your models before building the CLI -- Use `binding.pry` for debugging -- Use `puts` and `pp` or gems like `tty-table` for formatted CLI output - -## Sample Project - -A complete implementation is available on the `sample-project` branch. It demonstrates: - -- **Pet Tracker** domain with Owners and Pets -- Full CRUD with Active Record and a clean menu-driven CLI -- Object-oriented design with user-friendly output -- All required features including current value prompts for updates - -To view the sample: - -```bash -git checkout sample-project -``` - -See `SAMPLE_PROJECT_README.md` for detailed documentation. - -## Resources - -- [dbdiagram.io](https://dbdiagram.io/) -- [Active Record Basics](https://guides.rubyonrails.org/active_record_basics.html) -- [Active Record Associations](https://guides.rubyonrails.org/association_basics.html) -- [Active Record Validations](https://guides.rubyonrails.org/active_record_validations.html) +- View existing users. +- View a user's meal plans. +- Create a new meal plan for a user. +- Update an existing meal plan. +- Delete a meal plan. +- Add meals to a meal plan. +- Update meal information. +- Remove meals from a meal plan. +- View all meals within a meal plan. \ No newline at end of file From 86bb366ad0acdcbec72f985a039db3d58d734404 Mon Sep 17 00:00:00 2001 From: + <+> Date: Mon, 27 Jul 2026 12:31:28 -0400 Subject: [PATCH 03/37] Add Faker gem for seed data --- Gemfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Gemfile b/Gemfile index e682c9b..6caa461 100644 --- a/Gemfile +++ b/Gemfile @@ -18,6 +18,9 @@ gem "require_all", "~> 3.0" gem "standalone_migrations" +# Generate mock data for database seeds +gem "faker" + # These gems will only be used when we are running the application locally group :development do gem "pry", "~> 0.14.1" From 16bebe189f7bc2b0d5a521c41e11be12f27a3619 Mon Sep 17 00:00:00 2001 From: + <+> Date: Mon, 27 Jul 2026 12:52:07 -0400 Subject: [PATCH 04/37] Add models, associations, and migrations --- Gemfile.lock | 3 ++ app/models/meal.rb | 3 ++ app/models/meal_plan.rb | 4 +++ app/models/user.rb | 4 +++ db/migrate/20260727163608_create_users.rb | 9 +++++ .../20260727163633_create_meal_plans.rb | 13 ++++++++ db/migrate/20260727163639_create_meals.rb | 16 +++++++++ db/schema.rb | 33 +++++++++++-------- 8 files changed, 71 insertions(+), 14 deletions(-) create mode 100644 app/models/meal.rb create mode 100644 app/models/meal_plan.rb create mode 100644 app/models/user.rb create mode 100644 db/migrate/20260727163608_create_users.rb create mode 100644 db/migrate/20260727163633_create_meal_plans.rb create mode 100644 db/migrate/20260727163639_create_meals.rb diff --git a/Gemfile.lock b/Gemfile.lock index bbe6a3d..f4c2ec9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -58,6 +58,8 @@ GEM drb (2.2.3) erb (6.0.6) erubi (1.13.1) + faker (3.8.0) + i18n (>= 1.8.11, < 2) i18n (1.15.2) concurrent-ruby (~> 1.0) io-console (0.8.2) @@ -206,6 +208,7 @@ PLATFORMS DEPENDENCIES activerecord (~> 7.1) database_cleaner (~> 2.0) + faker pry (~> 0.14.1) rake (~> 13.0) require_all (~> 3.0) diff --git a/app/models/meal.rb b/app/models/meal.rb new file mode 100644 index 0000000..0e9c586 --- /dev/null +++ b/app/models/meal.rb @@ -0,0 +1,3 @@ +class Meal < ActiveRecord:: Base + belongs_to :meal_plan +end \ No newline at end of file diff --git a/app/models/meal_plan.rb b/app/models/meal_plan.rb new file mode 100644 index 0000000..ecf9184 --- /dev/null +++ b/app/models/meal_plan.rb @@ -0,0 +1,4 @@ +class MealPlan < ActiveRecord:: Base + belongs_to :user + has_many :meals +end \ No newline at end of file diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 0000000..7ff64a6 --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,4 @@ +class User < ActiveRecord:: Base + has_many :meal_plans + has_many :meals, through: :meal_plans +end \ No newline at end of file diff --git a/db/migrate/20260727163608_create_users.rb b/db/migrate/20260727163608_create_users.rb new file mode 100644 index 0000000..15032b2 --- /dev/null +++ b/db/migrate/20260727163608_create_users.rb @@ -0,0 +1,9 @@ +class CreateUsers < ActiveRecord::Migration[7.1] + def change + create_table :users do |t| + t.string :name, null: false + + t.timestamps + end + end +end diff --git a/db/migrate/20260727163633_create_meal_plans.rb b/db/migrate/20260727163633_create_meal_plans.rb new file mode 100644 index 0000000..f63c6e7 --- /dev/null +++ b/db/migrate/20260727163633_create_meal_plans.rb @@ -0,0 +1,13 @@ +class CreateMealPlans < ActiveRecord::Migration[7.1] + def change + create_table :meal_plans do |t| + t.string :name, null: false + t.date :week_start + t.string :goal + t.float :budget + t.bigint :user_id, null: false + + t.timestamps + end + end +end diff --git a/db/migrate/20260727163639_create_meals.rb b/db/migrate/20260727163639_create_meals.rb new file mode 100644 index 0000000..d12a410 --- /dev/null +++ b/db/migrate/20260727163639_create_meals.rb @@ -0,0 +1,16 @@ +class CreateMeals < ActiveRecord::Migration[7.1] + def change + create_table :meals do |t| + t.string :name, null: false + t.string :meal_type + t.integer :prep_time + t.float :estimated_cost + t.integer :calories + t.boolean :prepared, default: false, null: false + t.boolean :favorite, default: false, null: false + t.bigint :meal_plan_id, null: false + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index b4d1e57..6c56e69 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,28 +10,33 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2025_07_22_152222) do - create_table "owners", force: :cascade do |t| +ActiveRecord::Schema[7.2].define(version: 2026_07_27_163639) do + create_table "meal_plans", force: :cascade do |t| t.string "name", null: false - t.string "email", null: false - t.string "phone", null: false - t.text "address" + t.date "week_start" + t.string "goal" + t.float "budget" + t.bigint "user_id", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.index ["email"], name: "index_owners_on_email", unique: true end - create_table "pets", force: :cascade do |t| + create_table "meals", force: :cascade do |t| t.string "name", null: false - t.string "species", null: false - t.string "breed", null: false - t.integer "age", null: false - t.text "notes" - t.integer "owner_id", null: false + t.string "meal_type" + t.integer "prep_time" + t.float "estimated_cost" + t.integer "calories" + t.boolean "prepared", default: false, null: false + t.boolean "favorite", default: false, null: false + t.bigint "meal_plan_id", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.index ["owner_id"], name: "index_pets_on_owner_id" end - add_foreign_key "pets", "owners" + create_table "users", force: :cascade do |t| + t.string "name", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end end From ef5c2ffe8d2064c503a56aa5183bfee2adff6e91 Mon Sep 17 00:00:00 2001 From: + <+> Date: Mon, 27 Jul 2026 13:21:39 -0400 Subject: [PATCH 05/37] Use Faker to create mock seed data --- db/seeds.rb | 45 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/db/seeds.rb b/db/seeds.rb index 437ef89..6323683 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,7 +1,44 @@ -# frozen_string_literal: true +require "faker" -puts "🌱 Seeding spices..." +puts "Cleaning up database..." +Meal.destroy_all +MealPlan.destroy_all +User.destroy_all -# Seed your database here +puts "Seeding 5 users..." +users = 5.times.map do + User.create!( + name: Faker::Name.name + ) +end -puts "✅ Done seeding!" +puts "Seeding meal plans..." +meal_plans = users.flat_map do |user| + 2.times.map do + MealPlan.create!( + name: "#{Faker::Food.ethnic_category} Meal Plan", + week_start: Faker::Date.forward(days: 30), + goal: ["Meal Prep", "High Protein", "Budget Friendly", "Quick Meals", "Balanced Eating"].sample, + budget: Faker::Number.decimal(l_digits: 2, r_digits: 2), + user: user + ) + end +end + +puts "Seeding meals..." +meal_plans.each do |meal_plan| + 5.times do + Meal.create!( + name: Faker::Food.dish, + meal_type: ["Breakfast", "Lunch", "Dinner", "Snack"].sample, + prep_time: Faker::Number.between(from: 10, to: 90), + estimated_cost: Faker::Number.decimal(l_digits: 2, r_digits: 2), + calories: Faker::Number.between(from: 250, to: 900), + prepared: [true, false].sample, + favorite: [true, false].sample, + meal_plan: meal_plan + ) + end +end + +puts "Database seeded successfully!" \ No newline at end of file From b26ab78f5ba9985e81656e1ddc335bf403747a0d Mon Sep 17 00:00:00 2001 From: + <+> Date: Mon, 27 Jul 2026 14:54:35 -0400 Subject: [PATCH 06/37] Add User meal plan and meal helper methods --- app/models/user.rb | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 7ff64a6..6a73d20 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,4 +1,30 @@ -class User < ActiveRecord:: Base +class User < ActiveRecord::Base has_many :meal_plans has_many :meals, through: :meal_plans -end \ No newline at end of file + + def create_meal_plan(attributes) + meal_plans.create(attributes) + end + + def users_budget + meal_plans.sum(:budget) + end + + def favorite_meal + meals.where(favorite: true) + end + + def select_individual_meal + meals.pluck(:id, :name) + end + + def find_meal(input) + meals.find_by(id: input) || meals.find_by(name: input) + end + + def add_favorite_meal(input) + meal = find_meal(input) + meal.update(favorite: true) + meal + end +end From fa0ba5b1c561015f4cf5323344ab5513ad762066 Mon Sep 17 00:00:00 2001 From: + <+> Date: Mon, 27 Jul 2026 15:05:06 -0400 Subject: [PATCH 07/37] Add User meal plan and meal helper methods --- app/models/user.rb | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 7ff64a6..6a73d20 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,4 +1,30 @@ -class User < ActiveRecord:: Base +class User < ActiveRecord::Base has_many :meal_plans has_many :meals, through: :meal_plans -end \ No newline at end of file + + def create_meal_plan(attributes) + meal_plans.create(attributes) + end + + def users_budget + meal_plans.sum(:budget) + end + + def favorite_meal + meals.where(favorite: true) + end + + def select_individual_meal + meals.pluck(:id, :name) + end + + def find_meal(input) + meals.find_by(id: input) || meals.find_by(name: input) + end + + def add_favorite_meal(input) + meal = find_meal(input) + meal.update(favorite: true) + meal + end +end From 934b3908b4151ba33c24e4f3e78fd7aefa8114d0 Mon Sep 17 00:00:00 2001 From: + <+> Date: Mon, 27 Jul 2026 15:31:08 -0400 Subject: [PATCH 08/37] Clean Up User model methods --- app/models/user.rb | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 6a73d20..814d009 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -2,29 +2,16 @@ class User < ActiveRecord::Base has_many :meal_plans has_many :meals, through: :meal_plans - def create_meal_plan(attributes) - meal_plans.create(attributes) - end - def users_budget + def total_budget meal_plans.sum(:budget) end - def favorite_meal + def favorite_meals meals.where(favorite: true) end - def select_individual_meal + def meal_options meals.pluck(:id, :name) end - - def find_meal(input) - meals.find_by(id: input) || meals.find_by(name: input) - end - - def add_favorite_meal(input) - meal = find_meal(input) - meal.update(favorite: true) - meal - end end From a7e9b5b069a36ee5c3ede97a8c67688dbb45a860 Mon Sep 17 00:00:00 2001 From: + <+> Date: Mon, 27 Jul 2026 15:52:42 -0400 Subject: [PATCH 09/37] Add CLI main menu --- cli/cli.rb | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 cli/cli.rb diff --git a/cli/cli.rb b/cli/cli.rb new file mode 100644 index 0000000..9166b11 --- /dev/null +++ b/cli/cli.rb @@ -0,0 +1,48 @@ +class CLI + def run + puts "Welcome to Meal Prep Tracker!" + main_menu + end + + def main_menu + loop do + puts "\n1. View users" + puts "2. Select user" + puts "3. Exit" + print "Choose an option: " + + choice = gets.chomp.downcase + + case choice + when "1", "users" + list_users + when "2", "select" + select_user + when "3", "exit" + puts "Goodbye!" + break + else + puts "Invalid choice, try again." + end + end + end + + def list_users + User.all.each do |user| + puts "#{user.id}. #{user.name}" + end + end + + def select_user + print "Enter a user ID: " + user_id = gets.chomp + + user = User.find_by(id: user_id) + + if user + puts "Selected user: #{user.name}" + else + puts "User not found." + end + end +end \ No newline at end of file From 22eed25b25a49d3310406008db17c4e74c96810d Mon Sep 17 00:00:00 2001 From: + <+> Date: Mon, 27 Jul 2026 18:09:01 -0400 Subject: [PATCH 10/37] Add CLI main menu and view users feature --- cli/cli.rb | 22 +++------------------- cli/main.rb | 3 +++ 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/cli/cli.rb b/cli/cli.rb index 9166b11..172f4e3 100644 --- a/cli/cli.rb +++ b/cli/cli.rb @@ -7,8 +7,7 @@ def run def main_menu loop do puts "\n1. View users" - puts "2. Select user" - puts "3. Exit" + puts "2. Exit" print "Choose an option: " choice = gets.chomp.downcase @@ -16,9 +15,7 @@ def main_menu case choice when "1", "users" list_users - when "2", "select" - select_user - when "3", "exit" + when "2", "exit" puts "Goodbye!" break else @@ -32,17 +29,4 @@ def list_users puts "#{user.id}. #{user.name}" end end - - def select_user - print "Enter a user ID: " - user_id = gets.chomp - - user = User.find_by(id: user_id) - - if user - puts "Selected user: #{user.name}" - else - puts "User not found." - end - end -end \ No newline at end of file +end diff --git a/cli/main.rb b/cli/main.rb index 33e117f..71608af 100755 --- a/cli/main.rb +++ b/cli/main.rb @@ -1,6 +1,9 @@ #!/usr/bin/env ruby require_relative "../config/environment" +require_relative "cli" + +CLI.new.run # TODO: Build your CLI application here! # From dd8e5a735899ccb46e0034fc47d1319263f679ec Mon Sep 17 00:00:00 2001 From: + <+> Date: Mon, 27 Jul 2026 18:22:26 -0400 Subject: [PATCH 11/37] Add select user feature and remove unused User model methods --- app/models/user.rb | 13 ------------- cli/cli.rb | 20 ++++++++++++++++++-- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 814d009..a77e487 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,17 +1,4 @@ class User < ActiveRecord::Base has_many :meal_plans has_many :meals, through: :meal_plans - - - def total_budget - meal_plans.sum(:budget) - end - - def favorite_meals - meals.where(favorite: true) - end - - def meal_options - meals.pluck(:id, :name) - end end diff --git a/cli/cli.rb b/cli/cli.rb index 172f4e3..6bacd40 100644 --- a/cli/cli.rb +++ b/cli/cli.rb @@ -7,7 +7,8 @@ def run def main_menu loop do puts "\n1. View users" - puts "2. Exit" + puts "2. Select user" + puts "3 Exit" print "Choose an option: " choice = gets.chomp.downcase @@ -15,7 +16,9 @@ def main_menu case choice when "1", "users" list_users - when "2", "exit" + when "2", "select" + select_user + when "3", "exit" puts "Goodbye!" break else @@ -29,4 +32,17 @@ def list_users puts "#{user.id}. #{user.name}" end end + + def select_user + print "Enter a user ID: " + user_id = gets.chomp + + user = User.find_by(id: user_id) + + if user + puts "Selected user: #{user.name}" + else + puts "User not found." + end + end end From 73fec49eec8c55f0350fc21ebff49706a0f4a88b Mon Sep 17 00:00:00 2001 From: + <+> Date: Tue, 28 Jul 2026 08:51:25 -0400 Subject: [PATCH 12/37] Add menu options for features --- cli/cli.rb | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/cli/cli.rb b/cli/cli.rb index 6bacd40..9eb673e 100644 --- a/cli/cli.rb +++ b/cli/cli.rb @@ -8,7 +8,12 @@ def main_menu loop do puts "\n1. View users" puts "2. Select user" - puts "3 Exit" + puts "3 Update mealplan" + puts "4. Delete mealplan" + puts "5. Add meal" + puts "6. Update meal" + puts "7. Delete meal" + puts "8 Exit" print "Choose an option: " choice = gets.chomp.downcase @@ -18,7 +23,17 @@ def main_menu list_users when "2", "select" select_user - when "3", "exit" + when "3", "update mealplan" + update_mealplan + when "4", "delete mealplan" + delete_mealplan + when "5", "add meal" + add_meal + when "6", "update meal" + update_meal + when "7", "delete meal" + delete_meal + when "8", "exit" puts "Goodbye!" break else From f558dd71df465f52f74bebc85fe6d906d761ceb1 Mon Sep 17 00:00:00 2001 From: + <+> Date: Tue, 28 Jul 2026 09:17:32 -0400 Subject: [PATCH 13/37] Add user mealplans when user selects user --- cli/cli.rb | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/cli/cli.rb b/cli/cli.rb index 9eb673e..a2cc411 100644 --- a/cli/cli.rb +++ b/cli/cli.rb @@ -52,12 +52,24 @@ def select_user print "Enter a user ID: " user_id = gets.chomp - user = User.find_by(id: user_id) + user = User.includes(:meal_plans).find_by(id: user_id) if user - puts "Selected user: #{user.name}" + puts "\nSelected user: #{user.name} (ID: #{user.id})" + + if user.meal_plans.any? + user.meal_plans.each do |plan| + puts " -> Meal Plan: #{plan.name} (ID: #{plan.id})" + end + else + puts " -> No meal plans found." + end + puts "-" * 40 + + user else puts "User not found." + nil end end end From 62f711662415ffb2be37aa19dc6ea8f18949d1b3 Mon Sep 17 00:00:00 2001 From: + <+> Date: Tue, 28 Jul 2026 09:37:44 -0400 Subject: [PATCH 14/37] Add option to list individual meals in user mealplans --- cli/cli.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/cli/cli.rb b/cli/cli.rb index a2cc411..129383f 100644 --- a/cli/cli.rb +++ b/cli/cli.rb @@ -52,7 +52,7 @@ def select_user print "Enter a user ID: " user_id = gets.chomp - user = User.includes(:meal_plans).find_by(id: user_id) + user = User.includes(meal_plans: :meals).find_by(id: user_id) if user puts "\nSelected user: #{user.name} (ID: #{user.id})" @@ -61,6 +61,13 @@ def select_user user.meal_plans.each do |plan| puts " -> Meal Plan: #{plan.name} (ID: #{plan.id})" end + print "Enter a meal plan ID to view meals:" + meal_plan_id = gets.chomp + + selected_plan = user.meal_plans.find { |plan| plan.id == meal_plan_id.to_i } + selected_plan.meals.each do |meal| + puts " -> Meal: #{meal.name} (ID: #{meal.id})" + end else puts " -> No meal plans found." end From 3f604da1fc9a853e09b764145b9822d705db939a Mon Sep 17 00:00:00 2001 From: + <+> Date: Tue, 28 Jul 2026 10:14:42 -0400 Subject: [PATCH 15/37] separated logic in select_user for select_mealplan functionality and added update_mealplan method to handle updating a meal plans values --- cli/cli.rb | 77 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 64 insertions(+), 13 deletions(-) diff --git a/cli/cli.rb b/cli/cli.rb index 129383f..26f99db 100644 --- a/cli/cli.rb +++ b/cli/cli.rb @@ -22,7 +22,8 @@ def main_menu when "1", "users" list_users when "2", "select" - select_user + user = select_user + select_meal_plan(user) if user when "3", "update mealplan" update_mealplan when "4", "delete mealplan" @@ -56,27 +57,77 @@ def select_user if user puts "\nSelected user: #{user.name} (ID: #{user.id})" + user + else + puts "User not found." + nil + end + end - if user.meal_plans.any? - user.meal_plans.each do |plan| - puts " -> Meal Plan: #{plan.name} (ID: #{plan.id})" - end - print "Enter a meal plan ID to view meals:" - meal_plan_id = gets.chomp + def select_meal_plan(user) + if user.meal_plans.any? + user.meal_plans.each do |plan| + puts " -> Meal Plan: #{plan.name} (ID: #{plan.id})" + end + + print "Enter a meal plan ID to view meals: " + meal_plan_id = gets.chomp - selected_plan = user.meal_plans.find { |plan| plan.id == meal_plan_id.to_i } + selected_plan = user.meal_plans.find { |plan| plan.id == meal_plan_id.to_i } + + if selected_plan selected_plan.meals.each do |meal| puts " -> Meal: #{meal.name} (ID: #{meal.id})" end + + selected_plan else - puts " -> No meal plans found." + puts "Meal plan not found." + nil end - puts "-" * 40 - - user else - puts "User not found." + puts " -> No meal plans found." nil end end + + def update_mealplan + user = select_user + return unless user + + meal_plan = select_meal_plan(user) + return unless meal_plan + + puts "Which field do you want to change?" + puts "1. name" + puts "2. week_start" + puts "3. goal" + puts "4. budget" + print "Choose an option: " + + choice = gets.chomp + + field = + case choice + when "1" then "name" + when "2" then "week_start" + when "3" then "goal" + when "4" then "budget" + else + nil + end + + puts "Current #{field}: #{meal_plan.send(field)}" + print "Enter new #{field}: " + new_value = gets.chomp + new_value = new_value.to_f if field == "budget" + + meal_plan.update(field => new_value) + + if meal_plan.update(field.to_sym => new_value) + puts "Success! Record updated in the database." + else + puts "Update failed: #{user.errors.full_messages.join(', ')}" + end + end end From 455364b516832a28afe54b64c1004dba38da0401 Mon Sep 17 00:00:00 2001 From: + <+> Date: Tue, 28 Jul 2026 10:53:51 -0400 Subject: [PATCH 16/37] add tty-prompt gem, initialize @prompt variable to use gem functionality, and refactor update method to use prompt method to list field and collect new values for update_mealplan --- Gemfile | 2 ++ Gemfile.lock | 14 ++++++++++++++ cli/cli.rb | 48 +++++++++++++++++++++--------------------------- 3 files changed, 37 insertions(+), 27 deletions(-) diff --git a/Gemfile b/Gemfile index 6caa461..2c0bfb4 100644 --- a/Gemfile +++ b/Gemfile @@ -18,6 +18,8 @@ gem "require_all", "~> 3.0" gem "standalone_migrations" +gem "tty-prompt" + # Generate mock data for database seeds gem "faker" diff --git a/Gemfile.lock b/Gemfile.lock index f4c2ec9..c83223e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -97,6 +97,8 @@ GEM parser (3.3.11.1) ast (~> 2.4.1) racc + pastel (0.8.0) + tty-color (~> 0.5) pp (0.6.4) prettyprint prettyprint (0.2.0) @@ -189,12 +191,23 @@ GEM thor (1.5.0) timeout (0.6.1) tsort (0.2.0) + tty-color (0.6.0) + tty-cursor (0.7.1) + tty-prompt (0.23.1) + pastel (~> 0.8) + tty-reader (~> 0.8) + tty-reader (0.9.0) + tty-cursor (~> 0.7) + tty-screen (~> 0.8) + wisper (~> 2.0) + tty-screen (0.8.2) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (3.2.0) unicode-emoji (~> 4.1) unicode-emoji (4.2.0) useragent (0.16.11) + wisper (2.0.1) zeitwerk (2.8.2) PLATFORMS @@ -216,6 +229,7 @@ DEPENDENCIES rubocop sqlite3 (~> 1.7) standalone_migrations + tty-prompt BUNDLED WITH 2.7.2 diff --git a/cli/cli.rb b/cli/cli.rb index 26f99db..22b29e2 100644 --- a/cli/cli.rb +++ b/cli/cli.rb @@ -1,4 +1,10 @@ +require "tty-prompt" + class CLI + def initialize + @prompt = TTY::Prompt.new + end + def run puts "Welcome to Meal Prep Tracker!" main_menu @@ -67,7 +73,11 @@ def select_user def select_meal_plan(user) if user.meal_plans.any? user.meal_plans.each do |plan| - puts " -> Meal Plan: #{plan.name} (ID: #{plan.id})" + puts " -> Meal Plan: #{plan.name} + (ID: #{plan.id}) + (week_start: #{plan.week_start}) + (goal: #{plan.goal}) + (budget: #{plan.budget})" end print "Enter a meal plan ID to view meals: " @@ -77,7 +87,13 @@ def select_meal_plan(user) if selected_plan selected_plan.meals.each do |meal| - puts " -> Meal: #{meal.name} (ID: #{meal.id})" + puts " -> Meal: #{meal.name} (ID: #{meal.id}) + (meal_type: #{meal.meal_type}) + (prep_time: #{meal.prep_time}) + (estimated_cost: #{meal.estimated_cost}) + (calories: #{meal.calories}) + (prepared: #{meal.prepared} + (favorite: #{meal.favorite})" end selected_plan @@ -98,36 +114,14 @@ def update_mealplan meal_plan = select_meal_plan(user) return unless meal_plan - puts "Which field do you want to change?" - puts "1. name" - puts "2. week_start" - puts "3. goal" - puts "4. budget" - print "Choose an option: " - - choice = gets.chomp - - field = - case choice - when "1" then "name" - when "2" then "week_start" - when "3" then "goal" - when "4" then "budget" - else - nil - end - - puts "Current #{field}: #{meal_plan.send(field)}" - print "Enter new #{field}: " - new_value = gets.chomp - new_value = new_value.to_f if field == "budget" + field = @prompt.select("Which field do you want to change?", %w[name week_start goal budget]) - meal_plan.update(field => new_value) + new_value = @prompt.ask("Enter a new value for #{field}:") if meal_plan.update(field.to_sym => new_value) puts "Success! Record updated in the database." else - puts "Update failed: #{user.errors.full_messages.join(', ')}" + puts "Update failed: #{meal_plan.errors.full_messages.join(', ')}" end end end From 2f7272cd3ed69c4a39fe6ce88647beebd8bf87c5 Mon Sep 17 00:00:00 2001 From: + <+> Date: Tue, 28 Jul 2026 11:24:09 -0400 Subject: [PATCH 17/37] add delete_mealplan method --- cli/cli.rb | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/cli/cli.rb b/cli/cli.rb index 22b29e2..bb7c8da 100644 --- a/cli/cli.rb +++ b/cli/cli.rb @@ -33,7 +33,8 @@ def main_menu when "3", "update mealplan" update_mealplan when "4", "delete mealplan" - delete_mealplan + user = select_user + delete_mealplan(user) if user when "5", "add meal" add_meal when "6", "update meal" @@ -73,10 +74,10 @@ def select_user def select_meal_plan(user) if user.meal_plans.any? user.meal_plans.each do |plan| - puts " -> Meal Plan: #{plan.name} - (ID: #{plan.id}) + puts " -> Meal Plan: #{plan.name} + (ID: #{plan.id}) (week_start: #{plan.week_start}) - (goal: #{plan.goal}) + (goal: #{plan.goal}) (budget: #{plan.budget})" end @@ -87,7 +88,7 @@ def select_meal_plan(user) if selected_plan selected_plan.meals.each do |meal| - puts " -> Meal: #{meal.name} (ID: #{meal.id}) + puts " -> Meal: #{meal.name} (ID: #{meal.id}) (meal_type: #{meal.meal_type}) (prep_time: #{meal.prep_time}) (estimated_cost: #{meal.estimated_cost}) @@ -124,4 +125,28 @@ def update_mealplan puts "Update failed: #{meal_plan.errors.full_messages.join(', ')}" end end + + def delete_mealplan(user) + user_plans = user.meal_plans + + if user_plans.empty? + puts "\nYou don't have any meal plans to delete" + return + end + + choices = user_plans.map do |plan| + { name: "Plan ##{plan.id}: #{plan.name}", value: plan } + end + + selected_plan = @prompt.select("\nSelect a meal plan to delete:", choices) + + confirmed = @prompt.yes?("Are you absolutely sure you want to delete? '#{selected_plan.name}'?") + + if confirmed + selected_plan.destroy + puts "Success! '{selected_plan.name}' has been deleted." + else + puts "Deletion canceled." + end + end end From 3d4e31ca5cf44fcc65616a3cd99652525c3143f9 Mon Sep 17 00:00:00 2001 From: + <+> Date: Tue, 28 Jul 2026 12:08:39 -0400 Subject: [PATCH 18/37] add update_meal method reusing same logic for update_mealplan --- cli/cli.rb | 86 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 62 insertions(+), 24 deletions(-) diff --git a/cli/cli.rb b/cli/cli.rb index bb7c8da..eda62ca 100644 --- a/cli/cli.rb +++ b/cli/cli.rb @@ -16,10 +16,9 @@ def main_menu puts "2. Select user" puts "3 Update mealplan" puts "4. Delete mealplan" - puts "5. Add meal" - puts "6. Update meal" - puts "7. Delete meal" - puts "8 Exit" + puts "5. Update meal" + puts "6. Delete meal" + puts "7 Exit" print "Choose an option: " choice = gets.chomp.downcase @@ -35,13 +34,11 @@ def main_menu when "4", "delete mealplan" user = select_user delete_mealplan(user) if user - when "5", "add meal" - add_meal - when "6", "update meal" + when "5", "update meal" update_meal - when "7", "delete meal" + when "6", "delete meal" delete_meal - when "8", "exit" + when "7", "exit" puts "Goodbye!" break else @@ -57,10 +54,10 @@ def list_users end def select_user - print "Enter a user ID: " - user_id = gets.chomp + print "Enter a user's name: " + user_name = gets.chomp - user = User.includes(meal_plans: :meals).find_by(id: user_id) + user = User.includes(meal_plans: :meals).find_by(name: user_name) if user puts "\nSelected user: #{user.name} (ID: #{user.id})" @@ -81,29 +78,51 @@ def select_meal_plan(user) (budget: #{plan.budget})" end - print "Enter a meal plan ID to view meals: " + print "Enter a meal plan ID: " meal_plan_id = gets.chomp - selected_plan = user.meal_plans.find { |plan| plan.id == meal_plan_id.to_i } + meal_plan = user.meal_plans.find { |plan| plan.id == meal_plan_id.to_i } - if selected_plan - selected_plan.meals.each do |meal| - puts " -> Meal: #{meal.name} (ID: #{meal.id}) + if meal_plan + meal_plan + else + puts "Meal plan not found." + nil + end + else + puts "No meal plans found." + nil + end + end + + def display_meals(meal_plan) + if meal_plan.meals.any? + meal_plan.meals.each do |meal| + puts " -> Meal: #{meal.name} (ID: #{meal.id}) (meal_type: #{meal.meal_type}) (prep_time: #{meal.prep_time}) (estimated_cost: #{meal.estimated_cost}) (calories: #{meal.calories}) (prepared: #{meal.prepared} (favorite: #{meal.favorite})" - end - - selected_plan - else - puts "Meal plan not found." - nil end else - puts " -> No meal plans found." + puts "No meals found for this meal plan." + end + end + + def select_meal(meal_plan) + display_meals(meal_plan) + + print "Enter a meal ID: " + meal_id = gets.chomp + + meal = meal_plan.meals.find { |m| m.id == meal_id.to_i } + + if meal + meal + else + puts "Meal not found." nil end end @@ -149,4 +168,23 @@ def delete_mealplan(user) puts "Deletion canceled." end end + + def update_meal + user = select_user + return unless user + + meal = select_meal(meal_plan) + return unless meal + + field = @prompt.select("Which field do you want to change?", + %w[name meal_type prep_time estimated_cost calories prepared favorite]) + + new_value = @prompt.ask("Enter a new value for #{field}:") + + if meal.update(field.to_sym => new_value) + puts "Success! Record updated in the database." + else + puts "Update failed: #{meal.errors.full_messages.join(', ')}" + end + end end From e96a557b4d8459c04fc58d3a32d6af017b4afbfc Mon Sep 17 00:00:00 2001 From: + <+> Date: Tue, 28 Jul 2026 12:17:35 -0400 Subject: [PATCH 19/37] fix PR for feature-5 --- cli/cli.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/cli/cli.rb b/cli/cli.rb index eda62ca..668eeea 100644 --- a/cli/cli.rb +++ b/cli/cli.rb @@ -1,3 +1,4 @@ + require "tty-prompt" class CLI From aa4bbb18162d12501374b3989f21a8c76d229787 Mon Sep 17 00:00:00 2001 From: + <+> Date: Tue, 28 Jul 2026 12:53:26 -0400 Subject: [PATCH 20/37] refactor methods to fix menu navigation flow, and add delete_meal method using same functionality as delete_mealplan --- cli/cli.rb | 73 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 24 deletions(-) diff --git a/cli/cli.rb b/cli/cli.rb index 668eeea..06f10b4 100644 --- a/cli/cli.rb +++ b/cli/cli.rb @@ -1,4 +1,3 @@ - require "tty-prompt" class CLI @@ -29,7 +28,10 @@ def main_menu list_users when "2", "select" user = select_user - select_meal_plan(user) if user + if user + meal_plan = select_meal_plan(user) + display_meals(meal_plan) if meal_plan + end when "3", "update mealplan" update_mealplan when "4", "delete mealplan" @@ -38,7 +40,8 @@ def main_menu when "5", "update meal" update_meal when "6", "delete meal" - delete_meal + user = select_user + delete_meal(user) if user when "7", "exit" puts "Goodbye!" break @@ -70,30 +73,25 @@ def select_user end def select_meal_plan(user) - if user.meal_plans.any? - user.meal_plans.each do |plan| - puts " -> Meal Plan: #{plan.name} - (ID: #{plan.id}) - (week_start: #{plan.week_start}) - (goal: #{plan.goal}) - (budget: #{plan.budget})" - end + if user.meal_plans.empty? + puts "No meal plans found." + return nil + end - print "Enter a meal plan ID: " - meal_plan_id = gets.chomp + choices = user.meal_plans.map do |plan| + { name: "#{plan.name} (ID: #{plan.id})", value: plan } + end - meal_plan = user.meal_plans.find { |plan| plan.id == meal_plan_id.to_i } + meal_plan = @prompt.select("Select a meal plan:", choices) - if meal_plan - meal_plan - else - puts "Meal plan not found." - nil - end - else - puts "No meal plans found." - nil - end + puts "\nSelected Meal Plan:" + puts " -> Meal Plan: #{meal_plan.name}" + puts " -> ID: #{meal_plan.id}" + puts " -> Week Start: #{meal_plan.week_start}" + puts " -> Goal: #{meal_plan.goal}" + puts " -> Budget: #{meal_plan.budget}" + + meal_plan end def display_meals(meal_plan) @@ -174,6 +172,9 @@ def update_meal user = select_user return unless user + meal_plan = select_meal_plan(user) + return unless meal_plan + meal = select_meal(meal_plan) return unless meal @@ -188,4 +189,28 @@ def update_meal puts "Update failed: #{meal.errors.full_messages.join(', ')}" end end + + def delete_meal(user) + user_meals = user.meals + + if user_meals.empty? + puts "\nYou don't have any meals to delete" + return + end + + choices = user_meals.map do |meal| + { name: "Meal ##{meal.id}: #{meal.name}", value: meal } + end + + selected_meal = @prompt.select("\nSelect a meal to delete:", choices) + + confirmed = @prompt.yes?("Are you absolutely sure you want to delete? '#{user_meals.name}'?") + + if confirmed + selected_meal.destroy + puts "Success! '{selected_meal.name}' has been deleted." + else + puts "Deletion canceled." + end + end end From 249c6372981b22ce6cbbaa0b9125f177dbb14236 Mon Sep 17 00:00:00 2001 From: + <+> Date: Tue, 28 Jul 2026 13:02:52 -0400 Subject: [PATCH 21/37] add menu-selections to support future functionality for adding new users, meals, and mealplans --- cli/cli.rb | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/cli/cli.rb b/cli/cli.rb index 06f10b4..f6f1779 100644 --- a/cli/cli.rb +++ b/cli/cli.rb @@ -14,11 +14,14 @@ def main_menu loop do puts "\n1. View users" puts "2. Select user" - puts "3 Update mealplan" - puts "4. Delete mealplan" - puts "5. Update meal" - puts "6. Delete meal" - puts "7 Exit" + puts "3 Add user" + puts "4. Add mealplan" + puts "5. Update mealplan" + puts "6. Delete mealplan" + puts "7. Add meal" + puts "8. Update meal" + puts "9. Delete meal" + puts "10 Exit" print "Choose an option: " choice = gets.chomp.downcase @@ -32,17 +35,23 @@ def main_menu meal_plan = select_meal_plan(user) display_meals(meal_plan) if meal_plan end - when "3", "update mealplan" + when "3", "add user" + add_user + when "4", "add mealplan" + add_mealplan + when "5", "update mealplan" update_mealplan - when "4", "delete mealplan" + when "6", "delete mealplan" user = select_user delete_mealplan(user) if user - when "5", "update meal" + when "7", "add meal" + add_meal + when "8", "update meal" update_meal - when "6", "delete meal" + when "9", "delete meal" user = select_user delete_meal(user) if user - when "7", "exit" + when "10", "exit" puts "Goodbye!" break else From c0a3aae6c83c96163b223a71bfb5cbb96bb5d45d Mon Sep 17 00:00:00 2001 From: + <+> Date: Tue, 28 Jul 2026 13:18:34 -0400 Subject: [PATCH 22/37] Create add_user method to add new users into db --- cli/cli.rb | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/cli/cli.rb b/cli/cli.rb index f6f1779..f113186 100644 --- a/cli/cli.rb +++ b/cli/cli.rb @@ -36,7 +36,7 @@ def main_menu display_meals(meal_plan) if meal_plan end when "3", "add user" - add_user + add_new_user when "4", "add mealplan" add_mealplan when "5", "update mealplan" @@ -66,6 +66,20 @@ def list_users end end + def add_new_user + print "Add a new user:" + name = gets.chomp + + confirmed = @prompt.yes?("Are you sure you want to add '#{name}'?") + + if confirmed + user = User.create(name: name) + puts "Success! '#{user.name}' has been created." + else + puts "Adding user canceled." + end + end + def select_user print "Enter a user's name: " user_name = gets.chomp From 0b348d4cf4dd25fb15a6fcc911e317d097416251 Mon Sep 17 00:00:00 2001 From: + <+> Date: Tue, 28 Jul 2026 13:31:02 -0400 Subject: [PATCH 23/37] Create add_new_meal_plan method for users --- cli/cli.rb | 80 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 56 insertions(+), 24 deletions(-) diff --git a/cli/cli.rb b/cli/cli.rb index f113186..670f62d 100644 --- a/cli/cli.rb +++ b/cli/cli.rb @@ -38,7 +38,7 @@ def main_menu when "3", "add user" add_new_user when "4", "add mealplan" - add_mealplan + add_new_meal_plan when "5", "update mealplan" update_mealplan when "6", "delete mealplan" @@ -117,38 +117,54 @@ def select_meal_plan(user) meal_plan end + def add_new_meal_plan + user = select_user + return unless user + + print "Meal plan name: " + name = gets.chomp + + print "Week start date: " + week_start = gets.chomp + + print "Goal: " + goal = gets.chomp + + print "Budget: " + budget = gets.chomp.to_f + + confirmed = @prompt.yes?("Are you sure you want to add '#{name}' meal plan for #{user.name}?") + + if confirmed + new_meal_plan = user.meal_plans.create( + name: name, + week_start: week_start, + goal: goal, + budget: budget + ) + + puts "Success! '#{new_meal_plan.name}' has been created." + else + puts "Adding meal plan canceled." + end + end + def display_meals(meal_plan) if meal_plan.meals.any? meal_plan.meals.each do |meal| - puts " -> Meal: #{meal.name} (ID: #{meal.id}) - (meal_type: #{meal.meal_type}) - (prep_time: #{meal.prep_time}) - (estimated_cost: #{meal.estimated_cost}) - (calories: #{meal.calories}) - (prepared: #{meal.prepared} - (favorite: #{meal.favorite})" + puts " -> Meal: #{meal.name} (ID: #{meal.id})" + puts " (meal_type: #{meal.meal_type})" + puts " (prep_time: #{meal.prep_time})" + puts " (estimated_cost: #{meal.estimated_cost})" + puts " (calories: #{meal.calories})" + puts " (prepared: #{meal.prepared})" + puts " (favorite: #{meal.favorite})" end else puts "No meals found for this meal plan." end end - def select_meal(meal_plan) - display_meals(meal_plan) - - print "Enter a meal ID: " - meal_id = gets.chomp - - meal = meal_plan.meals.find { |m| m.id == meal_id.to_i } - - if meal - meal - else - puts "Meal not found." - nil - end - end - def update_mealplan user = select_user return unless user @@ -191,6 +207,22 @@ def delete_mealplan(user) end end + def select_meal(meal_plan) + display_meals(meal_plan) + + print "Enter a meal ID: " + meal_id = gets.chomp + + meal = meal_plan.meals.find { |m| m.id == meal_id.to_i } + + if meal + meal + else + puts "Meal not found." + nil + end + end + def update_meal user = select_user return unless user From 51d395b9103acc940338a555ab50a982b91f4740 Mon Sep 17 00:00:00 2001 From: + <+> Date: Tue, 28 Jul 2026 13:44:45 -0400 Subject: [PATCH 24/37] Create add_new_meal method for users to add to meal_plans --- cli/cli.rb | 44 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/cli/cli.rb b/cli/cli.rb index 670f62d..720e820 100644 --- a/cli/cli.rb +++ b/cli/cli.rb @@ -45,7 +45,7 @@ def main_menu user = select_user delete_mealplan(user) if user when "7", "add meal" - add_meal + add_new_meal when "8", "update meal" update_meal when "9", "delete meal" @@ -165,6 +165,48 @@ def display_meals(meal_plan) end end + def add_new_meal + user = select_user + return unless user + + meal_plan = select_meal_plan(user) + return unless meal_plan + + print "Meal name: " + name = gets.chomp + + print "Meal type: " + meal_type = gets.chomp + + print "Prep time: " + prep_time = gets.chomp.to_i + + print "Estimated Cost: " + estimated_cost = gets.chomp.to_f + + print "Calories: " + calories = gets.chomp.to_i + + favorite = @prompt.yes?("Favorite?") + + confirmed = @prompt.yes?("Are you sure you want to add '#{name}' meal to #{meal_plan.name}?") + + if confirmed + new_meal = meal_plan.meals.create( + name: name, + meal_type: meal_type, + prep_time: prep_time, + estimated_cost: estimated_cost, + calories: calories, + favorite: favorite + ) + + puts "Success! '#{new_meal.name}' has been created." + else + puts "Adding meal canceled." + end +end + def update_mealplan user = select_user return unless user From e1bc8756c53c068efa4e8c0878eb452a1fde1c75 Mon Sep 17 00:00:00 2001 From: + <+> Date: Tue, 28 Jul 2026 14:45:50 -0400 Subject: [PATCH 25/37] Separate CLI methods into organized files --- cli/cli.rb | 253 +----------------------------------------- cli/main.rb | 3 + cli/meal_menu.rb | 119 ++++++++++++++++++++ cli/meal_plan_menu.rb | 95 ++++++++++++++++ cli/user_menu.rb | 34 ++++++ 5 files changed, 252 insertions(+), 252 deletions(-) create mode 100644 cli/meal_menu.rb create mode 100644 cli/meal_plan_menu.rb create mode 100644 cli/user_menu.rb diff --git a/cli/cli.rb b/cli/cli.rb index 720e820..f8d56c7 100644 --- a/cli/cli.rb +++ b/cli/cli.rb @@ -1,5 +1,5 @@ require "tty-prompt" - +# core navigation class CLI def initialize @prompt = TTY::Prompt.new @@ -59,255 +59,4 @@ def main_menu end end end - - def list_users - User.all.each do |user| - puts "#{user.id}. #{user.name}" - end - end - - def add_new_user - print "Add a new user:" - name = gets.chomp - - confirmed = @prompt.yes?("Are you sure you want to add '#{name}'?") - - if confirmed - user = User.create(name: name) - puts "Success! '#{user.name}' has been created." - else - puts "Adding user canceled." - end - end - - def select_user - print "Enter a user's name: " - user_name = gets.chomp - - user = User.includes(meal_plans: :meals).find_by(name: user_name) - - if user - puts "\nSelected user: #{user.name} (ID: #{user.id})" - user - else - puts "User not found." - nil - end - end - - def select_meal_plan(user) - if user.meal_plans.empty? - puts "No meal plans found." - return nil - end - - choices = user.meal_plans.map do |plan| - { name: "#{plan.name} (ID: #{plan.id})", value: plan } - end - - meal_plan = @prompt.select("Select a meal plan:", choices) - - puts "\nSelected Meal Plan:" - puts " -> Meal Plan: #{meal_plan.name}" - puts " -> ID: #{meal_plan.id}" - puts " -> Week Start: #{meal_plan.week_start}" - puts " -> Goal: #{meal_plan.goal}" - puts " -> Budget: #{meal_plan.budget}" - - meal_plan - end - - def add_new_meal_plan - user = select_user - return unless user - - print "Meal plan name: " - name = gets.chomp - - print "Week start date: " - week_start = gets.chomp - - print "Goal: " - goal = gets.chomp - - print "Budget: " - budget = gets.chomp.to_f - - confirmed = @prompt.yes?("Are you sure you want to add '#{name}' meal plan for #{user.name}?") - - if confirmed - new_meal_plan = user.meal_plans.create( - name: name, - week_start: week_start, - goal: goal, - budget: budget - ) - - puts "Success! '#{new_meal_plan.name}' has been created." - else - puts "Adding meal plan canceled." - end - end - - def display_meals(meal_plan) - if meal_plan.meals.any? - meal_plan.meals.each do |meal| - puts " -> Meal: #{meal.name} (ID: #{meal.id})" - puts " (meal_type: #{meal.meal_type})" - puts " (prep_time: #{meal.prep_time})" - puts " (estimated_cost: #{meal.estimated_cost})" - puts " (calories: #{meal.calories})" - puts " (prepared: #{meal.prepared})" - puts " (favorite: #{meal.favorite})" - end - else - puts "No meals found for this meal plan." - end - end - - def add_new_meal - user = select_user - return unless user - - meal_plan = select_meal_plan(user) - return unless meal_plan - - print "Meal name: " - name = gets.chomp - - print "Meal type: " - meal_type = gets.chomp - - print "Prep time: " - prep_time = gets.chomp.to_i - - print "Estimated Cost: " - estimated_cost = gets.chomp.to_f - - print "Calories: " - calories = gets.chomp.to_i - - favorite = @prompt.yes?("Favorite?") - - confirmed = @prompt.yes?("Are you sure you want to add '#{name}' meal to #{meal_plan.name}?") - - if confirmed - new_meal = meal_plan.meals.create( - name: name, - meal_type: meal_type, - prep_time: prep_time, - estimated_cost: estimated_cost, - calories: calories, - favorite: favorite - ) - - puts "Success! '#{new_meal.name}' has been created." - else - puts "Adding meal canceled." - end -end - - def update_mealplan - user = select_user - return unless user - - meal_plan = select_meal_plan(user) - return unless meal_plan - - field = @prompt.select("Which field do you want to change?", %w[name week_start goal budget]) - - new_value = @prompt.ask("Enter a new value for #{field}:") - - if meal_plan.update(field.to_sym => new_value) - puts "Success! Record updated in the database." - else - puts "Update failed: #{meal_plan.errors.full_messages.join(', ')}" - end - end - - def delete_mealplan(user) - user_plans = user.meal_plans - - if user_plans.empty? - puts "\nYou don't have any meal plans to delete" - return - end - - choices = user_plans.map do |plan| - { name: "Plan ##{plan.id}: #{plan.name}", value: plan } - end - - selected_plan = @prompt.select("\nSelect a meal plan to delete:", choices) - - confirmed = @prompt.yes?("Are you absolutely sure you want to delete? '#{selected_plan.name}'?") - - if confirmed - selected_plan.destroy - puts "Success! '{selected_plan.name}' has been deleted." - else - puts "Deletion canceled." - end - end - - def select_meal(meal_plan) - display_meals(meal_plan) - - print "Enter a meal ID: " - meal_id = gets.chomp - - meal = meal_plan.meals.find { |m| m.id == meal_id.to_i } - - if meal - meal - else - puts "Meal not found." - nil - end - end - - def update_meal - user = select_user - return unless user - - meal_plan = select_meal_plan(user) - return unless meal_plan - - meal = select_meal(meal_plan) - return unless meal - - field = @prompt.select("Which field do you want to change?", - %w[name meal_type prep_time estimated_cost calories prepared favorite]) - - new_value = @prompt.ask("Enter a new value for #{field}:") - - if meal.update(field.to_sym => new_value) - puts "Success! Record updated in the database." - else - puts "Update failed: #{meal.errors.full_messages.join(', ')}" - end - end - - def delete_meal(user) - user_meals = user.meals - - if user_meals.empty? - puts "\nYou don't have any meals to delete" - return - end - - choices = user_meals.map do |meal| - { name: "Meal ##{meal.id}: #{meal.name}", value: meal } - end - - selected_meal = @prompt.select("\nSelect a meal to delete:", choices) - - confirmed = @prompt.yes?("Are you absolutely sure you want to delete? '#{user_meals.name}'?") - - if confirmed - selected_meal.destroy - puts "Success! '{selected_meal.name}' has been deleted." - else - puts "Deletion canceled." - end - end end diff --git a/cli/main.rb b/cli/main.rb index 71608af..8e28db3 100755 --- a/cli/main.rb +++ b/cli/main.rb @@ -2,6 +2,9 @@ require_relative "../config/environment" require_relative "cli" +require_relative "user_menu" +require_relative "meal_plan_menu" +require_relative "meal_menu" CLI.new.run diff --git a/cli/meal_menu.rb b/cli/meal_menu.rb new file mode 100644 index 0000000..020ae91 --- /dev/null +++ b/cli/meal_menu.rb @@ -0,0 +1,119 @@ +def display_meals(meal_plan) + if meal_plan.meals.any? + meal_plan.meals.each do |meal| + puts " -> Meal: #{meal.name} (ID: #{meal.id})" + puts " (meal_type: #{meal.meal_type})" + puts " (prep_time: #{meal.prep_time})" + puts " (estimated_cost: #{meal.estimated_cost})" + puts " (calories: #{meal.calories})" + puts " (prepared: #{meal.prepared})" + puts " (favorite: #{meal.favorite})" + end + else + puts "No meals found for this meal plan." + end +end + +def add_new_meal + user = select_user + return unless user + + meal_plan = select_meal_plan(user) + return unless meal_plan + + print "Meal name: " + name = gets.chomp + + print "Meal type: " + meal_type = gets.chomp + + print "Prep time: " + prep_time = gets.chomp.to_i + + print "Estimated Cost: " + estimated_cost = gets.chomp.to_f + + print "Calories: " + calories = gets.chomp.to_i + + favorite = @prompt.yes?("Favorite?") + + confirmed = @prompt.yes?("Are you sure you want to add '#{name}' meal to #{meal_plan.name}?") + + if confirmed + new_meal = meal_plan.meals.create( + name: name, + meal_type: meal_type, + prep_time: prep_time, + estimated_cost: estimated_cost, + calories: calories, + favorite: favorite + ) + + puts "Success! '#{new_meal.name}' has been created." + else + puts "Adding meal canceled." + end +end + +def select_meal(meal_plan) + display_meals(meal_plan) + + print "Enter a meal ID: " + meal_id = gets.chomp + + meal = meal_plan.meals.find { |m| m.id == meal_id.to_i } + + if meal + meal + else + puts "Meal not found." + nil + end +end + +def update_meal + user = select_user + return unless user + + meal_plan = select_meal_plan(user) + return unless meal_plan + + meal = select_meal(meal_plan) + return unless meal + + field = @prompt.select("Which field do you want to change?", + %w[name meal_type prep_time estimated_cost calories prepared favorite]) + + new_value = @prompt.ask("Enter a new value for #{field}:") + + if meal.update(field.to_sym => new_value) + puts "Success! Record updated in the database." + else + puts "Update failed: #{meal.errors.full_messages.join(', ')}" + end +end + +def delete_meal(user) + user_meals = user.meals + + if user_meals.empty? + puts "\nYou don't have any meals to delete" + return + end + + choices = user_meals.map do |meal| + { name: "Meal ##{meal.id}: #{meal.name}", value: meal } + end + + selected_meal = @prompt.select("\nSelect a meal to delete:", choices) + + confirmed = @prompt.yes?("Are you absolutely sure you want to delete? '#{user_meals.name}'?") + + if confirmed + selected_meal.destroy + puts "Success! '{selected_meal.name}' has been deleted." + else + puts "Deletion canceled." + end +end diff --git a/cli/meal_plan_menu.rb b/cli/meal_plan_menu.rb new file mode 100644 index 0000000..a86d619 --- /dev/null +++ b/cli/meal_plan_menu.rb @@ -0,0 +1,95 @@ +def select_meal_plan(user) + if user.meal_plans.empty? + puts "No meal plans found." + return nil + end + + choices = user.meal_plans.map do |plan| + { name: "#{plan.name} (ID: #{plan.id})", value: plan } + end + + meal_plan = @prompt.select("Select a meal plan:", choices) + + puts "\nSelected Meal Plan:" + puts " -> Meal Plan: #{meal_plan.name}" + puts " -> ID: #{meal_plan.id}" + puts " -> Week Start: #{meal_plan.week_start}" + puts " -> Goal: #{meal_plan.goal}" + puts " -> Budget: #{meal_plan.budget}" + + meal_plan +end + +def add_new_meal_plan + user = select_user + return unless user + + print "Meal plan name: " + name = gets.chomp + + print "Week start date: " + week_start = gets.chomp + + print "Goal: " + goal = gets.chomp + + print "Budget: " + budget = gets.chomp.to_f + + confirmed = @prompt.yes?("Are you sure you want to add '#{name}' meal plan for #{user.name}?") + + if confirmed + new_meal_plan = user.meal_plans.create( + name: name, + week_start: week_start, + goal: goal, + budget: budget + ) + + puts "Success! '#{new_meal_plan.name}' has been created." + else + puts "Adding meal plan canceled." + end +end + +def update_mealplan + user = select_user + return unless user + + meal_plan = select_meal_plan(user) + return unless meal_plan + + field = @prompt.select("Which field do you want to change?", %w[name week_start goal budget]) + + new_value = @prompt.ask("Enter a new value for #{field}:") + + if meal_plan.update(field.to_sym => new_value) + puts "Success! Record updated in the database." + else + puts "Update failed: #{meal_plan.errors.full_messages.join(', ')}" + end +end + +def delete_mealplan(user) + user_plans = user.meal_plans + + if user_plans.empty? + puts "\nYou don't have any meal plans to delete" + return + end + + choices = user_plans.map do |plan| + { name: "Plan ##{plan.id}: #{plan.name}", value: plan } + end + + selected_plan = @prompt.select("\nSelect a meal plan to delete:", choices) + + confirmed = @prompt.yes?("Are you absolutely sure you want to delete? '#{selected_plan.name}'?") + + if confirmed + selected_plan.destroy + puts "Success! '{selected_plan.name}' has been deleted." + else + puts "Deletion canceled." + end +end diff --git a/cli/user_menu.rb b/cli/user_menu.rb new file mode 100644 index 0000000..6ee426b --- /dev/null +++ b/cli/user_menu.rb @@ -0,0 +1,34 @@ +def list_users + User.all.each do |user| + puts "#{user.id}. #{user.name}" + end +end + +def add_new_user + print "Add a new user:" + name = gets.chomp + + confirmed = @prompt.yes?("Are you sure you want to add '#{name}'?") + + if confirmed + user = User.create(name: name) + puts "Success! '#{user.name}' has been created." + else + puts "Adding user canceled." + end +end + +def select_user + print "Enter a user's name: " + user_name = gets.chomp + + user = User.includes(meal_plans: :meals).find_by(name: user_name) + + if user + puts "\nSelected user: #{user.name} (ID: #{user.id})" + user + else + puts "User not found." + nil + end +end From 343bb2d0ffb35f6b7959bd58b56158cb73f477c6 Mon Sep 17 00:00:00 2001 From: + <+> Date: Tue, 28 Jul 2026 15:51:26 -0400 Subject: [PATCH 26/37] Add user submenu with meal plan budget summaries --- app/models/meal_plan.rb | 12 ++- app/models/user.rb | 13 +++ cli/cli.rb | 13 ++- cli/meal_menu.rb | 172 ++++++++++++++++++++-------------------- cli/meal_plan_menu.rb | 54 +++++++++---- cli/user_menu.rb | 71 +++++++++++------ 6 files changed, 199 insertions(+), 136 deletions(-) diff --git a/app/models/meal_plan.rb b/app/models/meal_plan.rb index ecf9184..f9df57b 100644 --- a/app/models/meal_plan.rb +++ b/app/models/meal_plan.rb @@ -1,4 +1,12 @@ -class MealPlan < ActiveRecord:: Base +class MealPlan < ActiveRecord::Base belongs_to :user has_many :meals -end \ No newline at end of file + + def total_estimated_cost + meals.sum(:estimated_cost) + end + + def remaining_budget + budget - total_estimated_cost + end +end diff --git a/app/models/user.rb b/app/models/user.rb index a77e487..b0d4ada 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,4 +1,17 @@ class User < ActiveRecord::Base has_many :meal_plans has_many :meals, through: :meal_plans + + def users_budget + meal_plans.sum(:budget) + end + + def favorite_meals + meals.where(favorite: true) + + def meal_options + meals.pluck(:id, :name) + + end + end end diff --git a/cli/cli.rb b/cli/cli.rb index f8d56c7..9824147 100644 --- a/cli/cli.rb +++ b/cli/cli.rb @@ -14,27 +14,24 @@ def main_menu loop do puts "\n1. View users" puts "2. Select user" - puts "3 Add user" + puts "3. Add user" puts "4. Add mealplan" puts "5. Update mealplan" puts "6. Delete mealplan" puts "7. Add meal" puts "8. Update meal" puts "9. Delete meal" - puts "10 Exit" + puts "10. Exit" print "Choose an option: " choice = gets.chomp.downcase case choice - when "1", "users" + when "1", "view user" list_users - when "2", "select" + when "2", "select user" user = select_user - if user - meal_plan = select_meal_plan(user) - display_meals(meal_plan) if meal_plan - end + user_menu(user) if user when "3", "add user" add_new_user when "4", "add mealplan" diff --git a/cli/meal_menu.rb b/cli/meal_menu.rb index 020ae91..a8297fb 100644 --- a/cli/meal_menu.rb +++ b/cli/meal_menu.rb @@ -1,119 +1,121 @@ -def display_meals(meal_plan) - if meal_plan.meals.any? - meal_plan.meals.each do |meal| - puts " -> Meal: #{meal.name} (ID: #{meal.id})" - puts " (meal_type: #{meal.meal_type})" - puts " (prep_time: #{meal.prep_time})" - puts " (estimated_cost: #{meal.estimated_cost})" - puts " (calories: #{meal.calories})" - puts " (prepared: #{meal.prepared})" - puts " (favorite: #{meal.favorite})" +class CLI + def display_meals(meal_plan) + if meal_plan.meals.any? + meal_plan.meals.each do |meal| + puts " -> Meal: #{meal.name} (ID: #{meal.id})" + puts " (meal_type: #{meal.meal_type})" + puts " (prep_time: #{meal.prep_time})" + puts " (estimated_cost: #{meal.estimated_cost})" + puts " (calories: #{meal.calories})" + puts " (prepared: #{meal.prepared})" + puts " (favorite: #{meal.favorite})" + end + else + puts "No meals found for this meal plan." end - else - puts "No meals found for this meal plan." end -end -def add_new_meal - user = select_user - return unless user + def add_new_meal + user = select_user + return unless user - meal_plan = select_meal_plan(user) - return unless meal_plan + meal_plan = select_meal_plan(user) + return unless meal_plan - print "Meal name: " - name = gets.chomp + print "Meal name: " + name = gets.chomp - print "Meal type: " - meal_type = gets.chomp + print "Meal type: " + meal_type = gets.chomp - print "Prep time: " - prep_time = gets.chomp.to_i + print "Prep time: " + prep_time = gets.chomp.to_i - print "Estimated Cost: " - estimated_cost = gets.chomp.to_f + print "Estimated Cost: " + estimated_cost = gets.chomp.to_f - print "Calories: " - calories = gets.chomp.to_i + print "Calories: " + calories = gets.chomp.to_i - favorite = @prompt.yes?("Favorite?") + favorite = @prompt.yes?("Favorite?") - confirmed = @prompt.yes?("Are you sure you want to add '#{name}' meal to #{meal_plan.name}?") + confirmed = @prompt.yes?("Are you sure you want to add '#{name}' meal to #{meal_plan.name}?") - if confirmed - new_meal = meal_plan.meals.create( - name: name, - meal_type: meal_type, - prep_time: prep_time, - estimated_cost: estimated_cost, - calories: calories, - favorite: favorite - ) + if confirmed + new_meal = meal_plan.meals.create( + name: name, + meal_type: meal_type, + prep_time: prep_time, + estimated_cost: estimated_cost, + calories: calories, + favorite: favorite + ) - puts "Success! '#{new_meal.name}' has been created." - else - puts "Adding meal canceled." + puts "Success! '#{new_meal.name}' has been created." + else + puts "Adding meal canceled." + end end -end -def select_meal(meal_plan) - display_meals(meal_plan) + def select_meal(meal_plan) + display_meals(meal_plan) - print "Enter a meal ID: " - meal_id = gets.chomp + print "Enter a meal ID: " + meal_id = gets.chomp - meal = meal_plan.meals.find { |m| m.id == meal_id.to_i } + meal = meal_plan.meals.find { |m| m.id == meal_id.to_i } - if meal - meal - else - puts "Meal not found." - nil + if meal + meal + else + puts "Meal not found." + nil + end end -end -def update_meal - user = select_user - return unless user + def update_meal + user = select_user + return unless user - meal_plan = select_meal_plan(user) - return unless meal_plan + meal_plan = select_meal_plan(user) + return unless meal_plan - meal = select_meal(meal_plan) - return unless meal + meal = select_meal(meal_plan) + return unless meal - field = @prompt.select("Which field do you want to change?", - %w[name meal_type prep_time estimated_cost calories prepared favorite]) + field = @prompt.select("Which field do you want to change?", + %w[name meal_type prep_time estimated_cost calories prepared favorite]) - new_value = @prompt.ask("Enter a new value for #{field}:") + new_value = @prompt.ask("Enter a new value for #{field}:") - if meal.update(field.to_sym => new_value) - puts "Success! Record updated in the database." - else - puts "Update failed: #{meal.errors.full_messages.join(', ')}" + if meal.update(field.to_sym => new_value) + puts "Success! Record updated in the database." + else + puts "Update failed: #{meal.errors.full_messages.join(', ')}" + end end -end -def delete_meal(user) - user_meals = user.meals + def delete_meal(user) + user_meals = user.meals - if user_meals.empty? - puts "\nYou don't have any meals to delete" - return - end + if user_meals.empty? + puts "\nYou don't have any meals to delete" + return + end - choices = user_meals.map do |meal| - { name: "Meal ##{meal.id}: #{meal.name}", value: meal } - end + choices = user_meals.map do |meal| + { name: "Meal ##{meal.id}: #{meal.name}", value: meal } + end - selected_meal = @prompt.select("\nSelect a meal to delete:", choices) + selected_meal = @prompt.select("\nSelect a meal to delete:", choices) - confirmed = @prompt.yes?("Are you absolutely sure you want to delete? '#{user_meals.name}'?") + confirmed = @prompt.yes?("Are you absolutely sure you want to delete? '#{user_meals.name}'?") - if confirmed - selected_meal.destroy - puts "Success! '{selected_meal.name}' has been deleted." - else - puts "Deletion canceled." + if confirmed + selected_meal.destroy + puts "Success! '{selected_meal.name}' has been deleted." + else + puts "Deletion canceled." + end end end diff --git a/cli/meal_plan_menu.rb b/cli/meal_plan_menu.rb index a86d619..2660b8b 100644 --- a/cli/meal_plan_menu.rb +++ b/cli/meal_plan_menu.rb @@ -1,23 +1,43 @@ -def select_meal_plan(user) - if user.meal_plans.empty? - puts "No meal plans found." - return nil +class CLI + def select_meal_plan(user) + if user.meal_plans.empty? + puts "No meal plans found." + return nil + end + + choices = user.meal_plans.map do |plan| + { name: "#{plan.name} (ID: #{plan.id})", value: plan } + end + + meal_plan = @prompt.select("Select a meal plan:", choices) + + puts "\nSelected Meal Plan:" + puts " -> Meal Plan: #{meal_plan.name}" + puts " -> ID: #{meal_plan.id}" + puts " -> Week Start: #{meal_plan.week_start}" + puts " -> Goal: #{meal_plan.goal}" + puts " -> Budget: #{meal_plan.budget}" + + meal_plan end - choices = user.meal_plans.map do |plan| - { name: "#{plan.name} (ID: #{plan.id})", value: plan } + def display_meal_plan_summary(meal_plan) + puts "\n#{meal_plan.name} Summary" + puts "Budget: $#{meal_plan.budget}" + puts "Estimated Cost: $#{meal_plan.total_estimated_cost}" + puts "Remaining Budget: $#{meal_plan.remaining_budget}" + + favorite_meals = meal_plan.meals.where(favorite: true) + + puts "\nFavorite Meals:" + if favorite_meals.any? + favorite_meals.each do |meal| + puts "- #{meal.name}" + end + else + puts "No favorite meals found." + end end - - meal_plan = @prompt.select("Select a meal plan:", choices) - - puts "\nSelected Meal Plan:" - puts " -> Meal Plan: #{meal_plan.name}" - puts " -> ID: #{meal_plan.id}" - puts " -> Week Start: #{meal_plan.week_start}" - puts " -> Goal: #{meal_plan.goal}" - puts " -> Budget: #{meal_plan.budget}" - - meal_plan end def add_new_meal_plan diff --git a/cli/user_menu.rb b/cli/user_menu.rb index 6ee426b..c23e765 100644 --- a/cli/user_menu.rb +++ b/cli/user_menu.rb @@ -1,34 +1,57 @@ -def list_users - User.all.each do |user| - puts "#{user.id}. #{user.name}" +class CLI + def list_users + User.all.each do |user| + puts "#{user.id}. #{user.name}" + end end -end -def add_new_user - print "Add a new user:" - name = gets.chomp + def select_user + print "Enter a user's name: " + user_name = gets.chomp - confirmed = @prompt.yes?("Are you sure you want to add '#{name}'?") + user = User.includes(meal_plans: :meals).find_by(name: user_name) - if confirmed - user = User.create(name: name) - puts "Success! '#{user.name}' has been created." - else - puts "Adding user canceled." + if user + puts "\nSelected user: #{user.name} (ID: #{user.id})" + user + else + puts "User not found." + nil + end end -end -def select_user - print "Enter a user's name: " - user_name = gets.chomp + def user_menu(user) + loop do + choice = @prompt.select("\n#{user.name}'s Menu", [ + { name: "View meal plans and meals", value: :view_meal_plans }, + { name: "View meal plan summary", value: :meal_plan_summary }, + { name: "Back to main menu", value: :back } + ]) + + case choice + when :view_meal_plans + meal_plan = select_meal_plan(user) + display_meals(meal_plan) if meal_plan + when :meal_plan_summary + meal_plan = select_meal_plan(user) + display_meal_plan_summary(meal_plan) if meal_plan + when :back + break + end + end + end + + def add_new_user + print "Add a new user:" + name = gets.chomp - user = User.includes(meal_plans: :meals).find_by(name: user_name) + confirmed = @prompt.yes?("Are you sure you want to add '#{name}'?") - if user - puts "\nSelected user: #{user.name} (ID: #{user.id})" - user - else - puts "User not found." - nil + if confirmed + user = User.create(name: name) + puts "Success! '#{user.name}' has been created." + else + puts "Adding user canceled." + end end end From 57884f6e7c920e491c82626f5ec199d6bad0fe99 Mon Sep 17 00:00:00 2001 From: + <+> Date: Wed, 29 Jul 2026 10:30:19 -0400 Subject: [PATCH 27/37] git commit -m "Refactor CLI menu flow with submenus --- cli/cli.rb | 50 ++++++++++++++++---------------------------------- 1 file changed, 16 insertions(+), 34 deletions(-) diff --git a/cli/cli.rb b/cli/cli.rb index 9824147..66945cd 100644 --- a/cli/cli.rb +++ b/cli/cli.rb @@ -12,47 +12,29 @@ def run def main_menu loop do - puts "\n1. View users" - puts "2. Select user" - puts "3. Add user" - puts "4. Add mealplan" - puts "5. Update mealplan" - puts "6. Delete mealplan" - puts "7. Add meal" - puts "8. Update meal" - puts "9. Delete meal" - puts "10. Exit" - print "Choose an option: " - - choice = gets.chomp.downcase + puts "---------------------------- +---------------------------- " + choice = @prompt.select("Main Menu", [ + { name: "View users", value: :view_users }, + { name: "Select user", value: :select_user }, + { name: "Add user", value: :add_user }, + { name: "Delete user", value: :delete_user }, + { name: "Exit", value: :exit }, + ]) case choice - when "1", "view user" + when :view_users list_users - when "2", "select user" + when :select_user user = select_user user_menu(user) if user - when "3", "add user" + when :add_user add_new_user - when "4", "add mealplan" - add_new_meal_plan - when "5", "update mealplan" - update_mealplan - when "6", "delete mealplan" - user = select_user - delete_mealplan(user) if user - when "7", "add meal" - add_new_meal - when "8", "update meal" - update_meal - when "9", "delete meal" - user = select_user - delete_meal(user) if user - when "10", "exit" - puts "Goodbye!" + when :delete_user + delete_user + when :exit + puts "Thank you for using Meal Plan Tracker!" break - else - puts "Invalid choice, try again." end end end From c2f501213667ca78123dbea02103023d246a9662 Mon Sep 17 00:00:00 2001 From: + <+> Date: Wed, 29 Jul 2026 14:08:12 -0400 Subject: [PATCH 28/37] Apply CLI input helpers and add delete user flow --- cli/cli.rb | 41 ----------- cli/input_helpers.rb | 78 +++++++++++++++++++++ cli/main.rb | 56 ++++++++++----- cli/meal_menu.rb | 93 ++++++++++++++----------- cli/meal_plan_menu.rb | 156 ++++++++++++++++++++++-------------------- cli/user_menu.rb | 47 ++++++++++--- 6 files changed, 289 insertions(+), 182 deletions(-) delete mode 100644 cli/cli.rb create mode 100644 cli/input_helpers.rb mode change 100755 => 100644 cli/main.rb diff --git a/cli/cli.rb b/cli/cli.rb deleted file mode 100644 index 66945cd..0000000 --- a/cli/cli.rb +++ /dev/null @@ -1,41 +0,0 @@ -require "tty-prompt" -# core navigation -class CLI - def initialize - @prompt = TTY::Prompt.new - end - - def run - puts "Welcome to Meal Prep Tracker!" - main_menu - end - - def main_menu - loop do - puts "---------------------------- ----------------------------- " - choice = @prompt.select("Main Menu", [ - { name: "View users", value: :view_users }, - { name: "Select user", value: :select_user }, - { name: "Add user", value: :add_user }, - { name: "Delete user", value: :delete_user }, - { name: "Exit", value: :exit }, - ]) - - case choice - when :view_users - list_users - when :select_user - user = select_user - user_menu(user) if user - when :add_user - add_new_user - when :delete_user - delete_user - when :exit - puts "Thank you for using Meal Plan Tracker!" - break - end - end - end -end diff --git a/cli/input_helpers.rb b/cli/input_helpers.rb new file mode 100644 index 0000000..08a27a1 --- /dev/null +++ b/cli/input_helpers.rb @@ -0,0 +1,78 @@ +class Main + def prompt_required(prompt_text) + loop do + print "#{prompt_text}: " + input = gets.chomp.strip + + return input unless input.empty? + + puts "Input cannot be blank." + end + end + + def prompt_required_text(prompt_text) + loop do + print "#{prompt_text}: " + input = gets.chomp.strip + + return input if input.match?(/[a-zA-Z]/) + + puts "Input cannot be blank and must include text." + end + end + + def prompt_integer(prompt_text) + loop do + print "#{prompt_text}: " + input = gets.chomp.strip + + return input.to_i if input.match?(/^\d+$/) + + puts "Invalid input. Please enter a whole number." + end + end + + def prompt_float(prompt_text) + loop do + print "#{prompt_text}: " + input = gets.chomp.strip + + return input.to_f if input.match?(/^\d+(\.\d+)?$/) + + puts "Invalid input. Please enter a valid number." + end + end + + def prompt_boolean(prompt_text) + @prompt.yes?(prompt_text) + end + + def prompt_prep_time + hours = prompt_integer("Prep hours") + minutes = prompt_integer("Prep minutes") + + (hours * 60) + minutes + end + + def format_prep_time(total_minutes) + hours = total_minutes / 60 + minutes = total_minutes % 60 + + if hours > 0 + "#{hours} hr #{minutes} min" + else + "#{minutes} min" + end + end + + def prompt_date(prompt_text) + loop do + print "#{prompt_text} (MM/DD/YYYY): " + input = gets.chomp.strip + + return input if input.match?(%r{^\d{2}/\d{2}/\d{4}$}) + + puts "Invalid date format. Please enter date as MM/DD/YYYY" + end + end +end diff --git a/cli/main.rb b/cli/main.rb old mode 100755 new mode 100644 index 8e28db3..efab0ce --- a/cli/main.rb +++ b/cli/main.rb @@ -1,26 +1,48 @@ #!/usr/bin/env ruby - require_relative "../config/environment" -require_relative "cli" +require_relative "input_helpers" require_relative "user_menu" require_relative "meal_plan_menu" require_relative "meal_menu" +require "tty-prompt" + +class Main + def initialize + @prompt = TTY::Prompt.new + end -CLI.new.run + def run + puts "Welcome to Meal Prep Tracker!" + main_menu + end -# TODO: Build your CLI application here! -# -# Requirements: -# - Be object-oriented (at least two classes) -# - Query and update the database directly via Active Record models -# - Parse and display records in a readable format -# - Accept user input and use it to create, update, and delete records -# - Use a loop or menu interface -# - Include current value prompts for updates (e.g. show the current value -# before asking the user what they want to change it to) + def main_menu + loop do + puts "---------------------------- ---------------------------- " + choice = @prompt.select("Main Menu", [ + { name: "View users", value: :view_users }, + { name: "Select user", value: :select_user }, + { name: "Add user", value: :add_user }, + { name: "Delete user", value: :delete_user }, + { name: "Exit", value: :exit }, + ]) -puts "Welcome to your CLI Application!" -puts + case choice + when :view_users + list_users + when :select_user + user = select_user + user_menu(user) if user + when :add_user + add_new_user + when :delete_user + delete_user + when :exit + puts "Thank you for using Meal Plan Tracker!" + break + end + end + end +end -# TODO: Implement your CLI functionality here -puts "TODO: Build your CLI application" +Main.new.run diff --git a/cli/meal_menu.rb b/cli/meal_menu.rb index a8297fb..36dce57 100644 --- a/cli/meal_menu.rb +++ b/cli/meal_menu.rb @@ -1,10 +1,35 @@ -class CLI +class Main + def meal_menu(user) + meal_plan = select_meal_plan(user) + return unless meal_plan + + loop do + choice = @prompt.select("Update meals for #{meal_plan.name}", [ + { name: "Add meal", value: :add }, + { name: "Edit meal", value: :edit }, + { name: "Delete meal", value: :delete }, + { name: "Back", value: :back }, + ]) + + case choice + when :add + add_new_meal(meal_plan) if meal_plan + when :edit + update_meal(meal_plan) if meal_plan + when :delete + delete_meal(meal_plan) if meal_plan + when :back + break + end + end + end + def display_meals(meal_plan) if meal_plan.meals.any? meal_plan.meals.each do |meal| puts " -> Meal: #{meal.name} (ID: #{meal.id})" puts " (meal_type: #{meal.meal_type})" - puts " (prep_time: #{meal.prep_time})" + puts " (prep_time: #{format_prep_time(meal.prep_time)})" puts " (estimated_cost: #{meal.estimated_cost})" puts " (calories: #{meal.calories})" puts " (prepared: #{meal.prepared})" @@ -15,29 +40,13 @@ def display_meals(meal_plan) end end - def add_new_meal - user = select_user - return unless user - - meal_plan = select_meal_plan(user) - return unless meal_plan - - print "Meal name: " - name = gets.chomp - - print "Meal type: " - meal_type = gets.chomp - - print "Prep time: " - prep_time = gets.chomp.to_i - - print "Estimated Cost: " - estimated_cost = gets.chomp.to_f - - print "Calories: " - calories = gets.chomp.to_i - - favorite = @prompt.yes?("Favorite?") + def add_new_meal(meal_plan) + name = prompt_required("Meal name") + meal_type = prompt_required_text("Meal type") + prep_time = prompt_prep_time + estimated_cost = prompt_float("Estimated cost") + calories = prompt_integer("Calories") + favorite = prompt_boolean("Favorite?") confirmed = @prompt.yes?("Are you sure you want to add '#{name}' meal to #{meal_plan.name}?") @@ -73,20 +82,26 @@ def select_meal(meal_plan) end end - def update_meal - user = select_user - return unless user - - meal_plan = select_meal_plan(user) - return unless meal_plan - + def update_meal(meal_plan) meal = select_meal(meal_plan) return unless meal field = @prompt.select("Which field do you want to change?", %w[name meal_type prep_time estimated_cost calories prepared favorite]) - new_value = @prompt.ask("Enter a new value for #{field}:") + new_value = + case field + when "name", "meal_type" + prompt_required_text("Enter a new value for #{field}") + when "prep_time" + prompt_prep_time + when "calories" + prompt_integer("Enter a new value for #{field}") + when "estimated_cost" + prompt_float("Enter a new value for #{field}") + when "prepared", "favorite" + @prompt.yes?("#{field.capitalize}?") + end if meal.update(field.to_sym => new_value) puts "Success! Record updated in the database." @@ -95,25 +110,23 @@ def update_meal end end - def delete_meal(user) - user_meals = user.meals - - if user_meals.empty? + def delete_meal(meal_plan) + if meal_plan.meals.empty? puts "\nYou don't have any meals to delete" return end - choices = user_meals.map do |meal| + choices = meal_plan.meals.map do |meal| { name: "Meal ##{meal.id}: #{meal.name}", value: meal } end selected_meal = @prompt.select("\nSelect a meal to delete:", choices) - confirmed = @prompt.yes?("Are you absolutely sure you want to delete? '#{user_meals.name}'?") + confirmed = @prompt.yes?("Are you absolutely sure you want to delete '#{selected_meal.name}'?") if confirmed selected_meal.destroy - puts "Success! '{selected_meal.name}' has been deleted." + puts "Success! '#{selected_meal.name}' has been deleted." else puts "Deletion canceled." end diff --git a/cli/meal_plan_menu.rb b/cli/meal_plan_menu.rb index 2660b8b..f6be505 100644 --- a/cli/meal_plan_menu.rb +++ b/cli/meal_plan_menu.rb @@ -1,24 +1,26 @@ -class CLI - def select_meal_plan(user) - if user.meal_plans.empty? - puts "No meal plans found." - return nil - end - - choices = user.meal_plans.map do |plan| - { name: "#{plan.name} (ID: #{plan.id})", value: plan } +class Main + def meal_plan_menu(user) + loop do + choice = @prompt.select("Update meal plans for #{user.name}", [ + { name: "Add meal plan", value: :add }, + { name: "Edit meal plan", value: :edit }, + { name: "Delete meal plan", value: :delete }, + { name: "Back", value: :back }, + ]) + + case choice + when :add + add_new_meal_plan(user) + when :edit + meal_plan = select_meal_plan(user) + update_meal_plan(meal_plan) if meal_plan + when :delete + meal_plan = select_meal_plan(user) + delete_meal_plan(meal_plan) if meal_plan + when :back + break + end end - - meal_plan = @prompt.select("Select a meal plan:", choices) - - puts "\nSelected Meal Plan:" - puts " -> Meal Plan: #{meal_plan.name}" - puts " -> ID: #{meal_plan.id}" - puts " -> Week Start: #{meal_plan.week_start}" - puts " -> Goal: #{meal_plan.goal}" - puts " -> Budget: #{meal_plan.budget}" - - meal_plan end def display_meal_plan_summary(meal_plan) @@ -38,78 +40,80 @@ def display_meal_plan_summary(meal_plan) puts "No favorite meals found." end end -end -def add_new_meal_plan - user = select_user - return unless user - - print "Meal plan name: " - name = gets.chomp - - print "Week start date: " - week_start = gets.chomp - - print "Goal: " - goal = gets.chomp + def select_meal_plan(user) + if user.meal_plans.empty? + puts "No meal plans found." + return nil + end - print "Budget: " - budget = gets.chomp.to_f + choices = user.meal_plans.map do |plan| + { name: "#{plan.name} (ID: #{plan.id})", value: plan } + end - confirmed = @prompt.yes?("Are you sure you want to add '#{name}' meal plan for #{user.name}?") + meal_plan = @prompt.select("Select a meal plan:", choices) - if confirmed - new_meal_plan = user.meal_plans.create( - name: name, - week_start: week_start, - goal: goal, - budget: budget - ) + puts "\nSelected Meal Plan:" + puts " -> Meal Plan: #{meal_plan.name}" + puts " -> ID: #{meal_plan.id}" + puts " -> Week Start: #{meal_plan.week_start}" + puts " -> Goal: #{meal_plan.goal}" + puts " -> Budget: #{meal_plan.budget}" - puts "Success! '#{new_meal_plan.name}' has been created." - else - puts "Adding meal plan canceled." + meal_plan end -end - -def update_mealplan - user = select_user - return unless user - meal_plan = select_meal_plan(user) - return unless meal_plan + def add_new_meal_plan(user) + name = prompt_required_text("Meal plan name") + week_start = prompt_date("Week start date") + goal = prompt_required_text("Goal") + budget = prompt_float("Budget") - field = @prompt.select("Which field do you want to change?", %w[name week_start goal budget]) + confirmed = @prompt.yes?("Are you sure you want to add '#{name}' meal plan for #{user.name}?") - new_value = @prompt.ask("Enter a new value for #{field}:") + if confirmed + new_meal_plan = user.meal_plans.create( + name: name, + week_start: week_start, + goal: goal, + budget: budget + ) - if meal_plan.update(field.to_sym => new_value) - puts "Success! Record updated in the database." - else - puts "Update failed: #{meal_plan.errors.full_messages.join(', ')}" + puts "Success! '#{new_meal_plan.name}' has been created." + else + puts "Adding meal plan canceled." + end end -end -def delete_mealplan(user) - user_plans = user.meal_plans + def update_meal_plan(meal_plan) - if user_plans.empty? - puts "\nYou don't have any meal plans to delete" - return - end + field = @prompt.select("Which field do you want to change?", %w[name week_start goal budget]) - choices = user_plans.map do |plan| - { name: "Plan ##{plan.id}: #{plan.name}", value: plan } - end + new_value = + case field + when "name", "goal" + prompt_required_text("Enter a new value for #{field}") + when "week_start" + prompt_date("Enter a new week start") + when "budget" + prompt_float("Enter a new value for #{field}") + end - selected_plan = @prompt.select("\nSelect a meal plan to delete:", choices) + if meal_plan.update(field.to_sym => new_value) + puts "Success! Record updated in the database." + else + puts "Update failed: #{meal.errors.full_messages.join(', ')}" + end + end - confirmed = @prompt.yes?("Are you absolutely sure you want to delete? '#{selected_plan.name}'?") + def delete_meal_plan(meal_plan) + confirmed = @prompt.yes?("Are you absolutely sure you want to delete '#{meal_plan.name}'?") - if confirmed - selected_plan.destroy - puts "Success! '{selected_plan.name}' has been deleted." - else - puts "Deletion canceled." + if confirmed + meal_plan.destroy + puts "Success! '#{meal_plan.name}' has been deleted." + else + puts "Deletion canceled." + end end end diff --git a/cli/user_menu.rb b/cli/user_menu.rb index c23e765..e79a0f3 100644 --- a/cli/user_menu.rb +++ b/cli/user_menu.rb @@ -1,4 +1,4 @@ -class CLI +class Main def list_users User.all.each do |user| puts "#{user.id}. #{user.name}" @@ -7,9 +7,11 @@ def list_users def select_user print "Enter a user's name: " - user_name = gets.chomp + user_name = gets.chomp.downcase - user = User.includes(meal_plans: :meals).find_by(name: user_name) + user = User.includes(meal_plans: :meals) + .where("LOWER(name) = ?", user_name) + .first if user puts "\nSelected user: #{user.name} (ID: #{user.id})" @@ -23,10 +25,12 @@ def select_user def user_menu(user) loop do choice = @prompt.select("\n#{user.name}'s Menu", [ - { name: "View meal plans and meals", value: :view_meal_plans }, - { name: "View meal plan summary", value: :meal_plan_summary }, - { name: "Back to main menu", value: :back } - ]) + { name: "View meal plans and meals", value: :view_meal_plans }, + { name: "View meal plan summary", value: :meal_plan_summary }, + { name: "Update meal plans", value: :update_meal_plans }, + { name: "Update meals", value: :update_meals }, + { name: "Back to main menu", value: :back }, + ]) case choice when :view_meal_plans @@ -35,12 +39,16 @@ def user_menu(user) when :meal_plan_summary meal_plan = select_meal_plan(user) display_meal_plan_summary(meal_plan) if meal_plan + when :update_meal_plans + meal_plan_menu(user) + when :update_meals + meal_menu(user) when :back break end end end - + def add_new_user print "Add a new user:" name = gets.chomp @@ -54,4 +62,27 @@ def add_new_user puts "Adding user canceled." end end + + def delete_user + print "Enter user's name to remove: " + user_name = gets.chomp.downcase + + user = User.where("LOWER(name) = ?", user_name).first + if user + puts "\nSelected user: #{user.name} (ID: #{user.id})" + user + else + puts "User not found." + nil + end + + confirmed = @prompt.yes?("Are you absolutely sure you want to delete '#{user}'?") + + if confirmed + user.destroy + puts "Success! '#{user.name}' has been deleted." + else + puts "Deletion canceled." + end + end end From 937164a48141a17e7a6ab43e7b5a6aae78da4b04 Mon Sep 17 00:00:00 2001 From: + <+> Date: Wed, 29 Jul 2026 15:23:52 -0400 Subject: [PATCH 29/37] Add budget check for meal cost --- app/models/meal_plan.rb | 8 +++-- cli/input_helpers.rb | 17 ++++++---- cli/meal_menu.rb | 12 +++++-- cli/meal_plan_menu.rb | 75 +++++++++++++++++++++++------------------ 4 files changed, 69 insertions(+), 43 deletions(-) diff --git a/app/models/meal_plan.rb b/app/models/meal_plan.rb index f9df57b..348bddc 100644 --- a/app/models/meal_plan.rb +++ b/app/models/meal_plan.rb @@ -3,10 +3,14 @@ class MealPlan < ActiveRecord::Base has_many :meals def total_estimated_cost - meals.sum(:estimated_cost) + meals.sum(:estimated_cost).to_f end def remaining_budget - budget - total_estimated_cost + budget.to_f - total_estimated_cost + end + + def within_budget?(estimated_cost) + remaining_budget >= estimated_cost.to_f end end diff --git a/cli/input_helpers.rb b/cli/input_helpers.rb index 08a27a1..03420f0 100644 --- a/cli/input_helpers.rb +++ b/cli/input_helpers.rb @@ -4,6 +4,7 @@ def prompt_required(prompt_text) print "#{prompt_text}: " input = gets.chomp.strip + throw(:back) if input.downcase == "back" return input unless input.empty? puts "Input cannot be blank." @@ -11,21 +12,23 @@ def prompt_required(prompt_text) end def prompt_required_text(prompt_text) - loop do - print "#{prompt_text}: " - input = gets.chomp.strip + loop do + print "#{prompt_text} or type 'back': " + input = gets.chomp.strip - return input if input.match?(/[a-zA-Z]/) + throw(:back) if input.downcase == "back" + return input if input.match?(/[a-zA-Z]/) - puts "Input cannot be blank and must include text." - end + puts "Input cannot be blank and must include text." end +end def prompt_integer(prompt_text) loop do print "#{prompt_text}: " input = gets.chomp.strip + throw(:back) if input.downcase == "back" return input.to_i if input.match?(/^\d+$/) puts "Invalid input. Please enter a whole number." @@ -37,6 +40,7 @@ def prompt_float(prompt_text) print "#{prompt_text}: " input = gets.chomp.strip + throw(:back) if input.downcase == "back" return input.to_f if input.match?(/^\d+(\.\d+)?$/) puts "Invalid input. Please enter a valid number." @@ -70,6 +74,7 @@ def prompt_date(prompt_text) print "#{prompt_text} (MM/DD/YYYY): " input = gets.chomp.strip + throw(:back) if input.downcase == "back" return input if input.match?(%r{^\d{2}/\d{2}/\d{4}$}) puts "Invalid date format. Please enter date as MM/DD/YYYY" diff --git a/cli/meal_menu.rb b/cli/meal_menu.rb index 36dce57..10799ea 100644 --- a/cli/meal_menu.rb +++ b/cli/meal_menu.rb @@ -45,6 +45,11 @@ def add_new_meal(meal_plan) meal_type = prompt_required_text("Meal type") prep_time = prompt_prep_time estimated_cost = prompt_float("Estimated cost") + unless meal_plan.within_budget?(estimated_cost) + puts "This meal would put the meal plan over budget." + puts "Remaining budget: $#{meal_plan.remaining_budget}" + return + end calories = prompt_integer("Calories") favorite = prompt_boolean("Favorite?") @@ -87,7 +92,10 @@ def update_meal(meal_plan) return unless meal field = @prompt.select("Which field do you want to change?", - %w[name meal_type prep_time estimated_cost calories prepared favorite]) + %w[name meal_type prep_time estimated_cost calories prepared favorite + back]) + + return if field == "back" new_value = case field @@ -100,7 +108,7 @@ def update_meal(meal_plan) when "estimated_cost" prompt_float("Enter a new value for #{field}") when "prepared", "favorite" - @prompt.yes?("#{field.capitalize}?") + prompt_boolean end if meal.update(field.to_sym => new_value) diff --git a/cli/meal_plan_menu.rb b/cli/meal_plan_menu.rb index f6be505..93daa51 100644 --- a/cli/meal_plan_menu.rb +++ b/cli/meal_plan_menu.rb @@ -64,46 +64,55 @@ def select_meal_plan(user) end def add_new_meal_plan(user) - name = prompt_required_text("Meal plan name") - week_start = prompt_date("Week start date") - goal = prompt_required_text("Goal") - budget = prompt_float("Budget") - - confirmed = @prompt.yes?("Are you sure you want to add '#{name}' meal plan for #{user.name}?") - - if confirmed - new_meal_plan = user.meal_plans.create( - name: name, - week_start: week_start, - goal: goal, - budget: budget - ) - - puts "Success! '#{new_meal_plan.name}' has been created." - else - puts "Adding meal plan canceled." + catch(:back) do + name = prompt_required_text("Meal plan name") + week_start = prompt_date("Week start date (or type back to return to menu)") + goal = prompt_required_text("Goal") + budget = prompt_float("Budget") + + confirmed = @prompt.yes?("Are you sure you want to add '#{name}' meal plan for #{user.name}?") + + if confirmed + new_meal_plan = user.meal_plans.create( + name: name, + week_start: week_start, + goal: goal, + budget: budget + ) + + puts "Success! '#{new_meal_plan.name}' has been created." + else + puts "Adding meal plan canceled." + end + return end + puts "\nReturning to the previous menu..." end def update_meal_plan(meal_plan) - - field = @prompt.select("Which field do you want to change?", %w[name week_start goal budget]) - - new_value = - case field - when "name", "goal" - prompt_required_text("Enter a new value for #{field}") - when "week_start" - prompt_date("Enter a new week start") - when "budget" - prompt_float("Enter a new value for #{field}") + catch(:back) do + field = @prompt.select("Which field do you want to change?", %w[name week_start goal budget]) + + new_value = + case field + when "name", "goal" + prompt_required_text("Enter a new value for #{field}") + when "week_start" + prompt_date("Enter a new week start or type back to return to menu") + when "budget" + prompt_float("Enter a new value for #{field}") + end + + if meal_plan.update(field.to_sym => new_value) + puts "Success! Record updated in the database." + else + puts "Update failed: #{meal_plan.errors.full_messages.join(', ')}" end - if meal_plan.update(field.to_sym => new_value) - puts "Success! Record updated in the database." - else - puts "Update failed: #{meal.errors.full_messages.join(', ')}" + return end + + puts "\nReturning to the previous menu..." end def delete_meal_plan(meal_plan) From 12a591d3528d2b9358a88477ca4829f3f0a5d85b Mon Sep 17 00:00:00 2001 From: + <+> Date: Thu, 30 Jul 2026 12:57:02 -0400 Subject: [PATCH 30/37] Add ensure block and refactor user menu helpers --- Gemfile | 4 ++++ Gemfile.lock | 4 ++++ app/models/meal_plan.rb | 5 ++++ cli/{main.rb => cli.rb} | 19 +++++++++++---- cli/input_helpers.rb | 30 +++++++++++++++++------- cli/meal_menu.rb | 15 ++++++++---- cli/meal_plan_menu.rb | 4 ++-- cli/user_menu.rb | 51 ++++++++++++++++++----------------------- 8 files changed, 84 insertions(+), 48 deletions(-) rename cli/{main.rb => cli.rb} (73%) diff --git a/Gemfile b/Gemfile index 2c0bfb4..dfa4a60 100644 --- a/Gemfile +++ b/Gemfile @@ -20,6 +20,10 @@ gem "standalone_migrations" gem "tty-prompt" +gem "tty-font" + +gem "pastel" + # Generate mock data for database seeds gem "faker" diff --git a/Gemfile.lock b/Gemfile.lock index c83223e..d3432d4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -193,6 +193,7 @@ GEM tsort (0.2.0) tty-color (0.6.0) tty-cursor (0.7.1) + tty-font (0.5.0) tty-prompt (0.23.1) pastel (~> 0.8) tty-reader (~> 0.8) @@ -222,6 +223,7 @@ DEPENDENCIES activerecord (~> 7.1) database_cleaner (~> 2.0) faker + pastel pry (~> 0.14.1) rake (~> 13.0) require_all (~> 3.0) @@ -229,6 +231,8 @@ DEPENDENCIES rubocop sqlite3 (~> 1.7) standalone_migrations + tty-color + tty-font tty-prompt BUNDLED WITH diff --git a/app/models/meal_plan.rb b/app/models/meal_plan.rb index 348bddc..2a19006 100644 --- a/app/models/meal_plan.rb +++ b/app/models/meal_plan.rb @@ -13,4 +13,9 @@ def remaining_budget def within_budget?(estimated_cost) remaining_budget >= estimated_cost.to_f end + + def can_update_meal_cost?(meal, new_cost) + adjusted_total = total_estimated_cost - meal.estimated_cost.to_f + new_cost.to_f + adjusted_total <= budget.to_f + end end diff --git a/cli/main.rb b/cli/cli.rb similarity index 73% rename from cli/main.rb rename to cli/cli.rb index efab0ce..32434cb 100644 --- a/cli/main.rb +++ b/cli/cli.rb @@ -5,20 +5,29 @@ require_relative "meal_plan_menu" require_relative "meal_menu" require "tty-prompt" +require "tty-font" +require "pastel" -class Main +class CLI def initialize @prompt = TTY::Prompt.new + @font = TTY::Font.new(:standard) + @pastel = Pastel.new end def run - puts "Welcome to Meal Prep Tracker!" + + print "\e[H\e[2J" + puts @pastel.green(@font.write("Meal Prep Tracker")) + main_menu + ensure + ActiveRecord::Base.connection_pool.disconnect! if ActiveRecord::Base.connected? end def main_menu loop do - puts "---------------------------- ---------------------------- " + puts @pastel.dim("—" * 50) choice = @prompt.select("Main Menu", [ { name: "View users", value: :view_users }, { name: "Select user", value: :select_user }, @@ -38,11 +47,11 @@ def main_menu when :delete_user delete_user when :exit - puts "Thank you for using Meal Plan Tracker!" + puts @pastel.blue(@font.write("Thank you!")) break end end end end -Main.new.run +CLI.new.run diff --git a/cli/input_helpers.rb b/cli/input_helpers.rb index 03420f0..a6c2c5b 100644 --- a/cli/input_helpers.rb +++ b/cli/input_helpers.rb @@ -1,4 +1,4 @@ -class Main +class CLI def prompt_required(prompt_text) loop do print "#{prompt_text}: " @@ -12,16 +12,16 @@ def prompt_required(prompt_text) end def prompt_required_text(prompt_text) - loop do - print "#{prompt_text} or type 'back': " - input = gets.chomp.strip + loop do + print "#{prompt_text} or type 'back': " + input = gets.chomp.strip - throw(:back) if input.downcase == "back" - return input if input.match?(/[a-zA-Z]/) + throw(:back) if input.downcase == "back" + return input if input.match?(/[a-zA-Z]/) - puts "Input cannot be blank and must include text." + puts "Input cannot be blank and must include text." + end end -end def prompt_integer(prompt_text) loop do @@ -80,4 +80,18 @@ def prompt_date(prompt_text) puts "Invalid date format. Please enter date as MM/DD/YYYY" end end + + def find_user_by_name(name) + User.includes(meal_plans: :meals) + .where("LOWER(name) = ?", name.downcase) + .first + end + + def display_selected_user(user) + puts "\nSelected user: #{user.name} (ID: #{user.id})" + end + + def confirm_action(message) + @prompt.yes?(message) + end end diff --git a/cli/meal_menu.rb b/cli/meal_menu.rb index 10799ea..710efbc 100644 --- a/cli/meal_menu.rb +++ b/cli/meal_menu.rb @@ -1,4 +1,4 @@ -class Main +class CLI def meal_menu(user) meal_plan = select_meal_plan(user) return unless meal_plan @@ -106,12 +106,19 @@ def update_meal(meal_plan) when "calories" prompt_integer("Enter a new value for #{field}") when "estimated_cost" - prompt_float("Enter a new value for #{field}") + new_cost = prompt_float("Enter a new value for #{field}") + + unless meal_plan.can_update_meal_cost?(meal, new_cost) + puts "This update would put the meal plan over budget" + puts "Remaining budget: $#{meal_plan.remaining_budget.round(2)}" + return + end + new_cost when "prepared", "favorite" - prompt_boolean + prompt_boolean("#{field.capitalize}?") end - if meal.update(field.to_sym => new_value) + if meal.update(field.to_sym => new_value || new_cost) puts "Success! Record updated in the database." else puts "Update failed: #{meal.errors.full_messages.join(', ')}" diff --git a/cli/meal_plan_menu.rb b/cli/meal_plan_menu.rb index 93daa51..655dfdf 100644 --- a/cli/meal_plan_menu.rb +++ b/cli/meal_plan_menu.rb @@ -1,4 +1,4 @@ -class Main +class CLI def meal_plan_menu(user) loop do choice = @prompt.select("Update meal plans for #{user.name}", [ @@ -98,7 +98,7 @@ def update_meal_plan(meal_plan) when "name", "goal" prompt_required_text("Enter a new value for #{field}") when "week_start" - prompt_date("Enter a new week start or type back to return to menu") + prompt_date("Enter a new week start (or type back to return to menu)") when "budget" prompt_float("Enter a new value for #{field}") end diff --git a/cli/user_menu.rb b/cli/user_menu.rb index e79a0f3..017bb21 100644 --- a/cli/user_menu.rb +++ b/cli/user_menu.rb @@ -1,27 +1,10 @@ -class Main +class CLI def list_users User.all.each do |user| puts "#{user.id}. #{user.name}" end end - def select_user - print "Enter a user's name: " - user_name = gets.chomp.downcase - - user = User.includes(meal_plans: :meals) - .where("LOWER(name) = ?", user_name) - .first - - if user - puts "\nSelected user: #{user.name} (ID: #{user.id})" - user - else - puts "User not found." - nil - end - end - def user_menu(user) loop do choice = @prompt.select("\n#{user.name}'s Menu", [ @@ -49,13 +32,25 @@ def user_menu(user) end end - def add_new_user - print "Add a new user:" - name = gets.chomp + def select_user + print "Enter a user's name: " + user_name = gets.chomp.downcase + + user = find_user_by_name(user_name) + + if user + display_selected_user(user) + user + else + puts "User not found." + nil + end + end - confirmed = @prompt.yes?("Are you sure you want to add '#{name}'?") + def add_new_user + name = prompt_required_text("Add a new user") - if confirmed + if confirm_action("Are you sure you want to add '#{name}'?") user = User.create(name: name) puts "Success! '#{user.name}' has been created." else @@ -67,18 +62,16 @@ def delete_user print "Enter user's name to remove: " user_name = gets.chomp.downcase - user = User.where("LOWER(name) = ?", user_name).first + user = find_user_by_name(user_name) if user - puts "\nSelected user: #{user.name} (ID: #{user.id})" + display_selected_user(user) user else puts "User not found." - nil + return end - confirmed = @prompt.yes?("Are you absolutely sure you want to delete '#{user}'?") - - if confirmed + if confirm_action("Are you absolutely sure you want to delete '#{user.name}'?") user.destroy puts "Success! '#{user.name}' has been deleted." else From 0a91d6a2adeff5269238705c57e4782beda82dc0 Mon Sep 17 00:00:00 2001 From: + <+> Date: Thu, 30 Jul 2026 14:15:22 -0400 Subject: [PATCH 31/37] Refactor meal menu display logic --- cli/input_helpers.rb | 10 +-- cli/meal_menu.rb | 153 ++++++++++++++++++++++++------------------ cli/meal_plan_menu.rb | 5 +- 3 files changed, 96 insertions(+), 72 deletions(-) diff --git a/cli/input_helpers.rb b/cli/input_helpers.rb index a6c2c5b..ba6200c 100644 --- a/cli/input_helpers.rb +++ b/cli/input_helpers.rb @@ -1,7 +1,7 @@ class CLI def prompt_required(prompt_text) loop do - print "#{prompt_text}: " + print "#{prompt_text} (or type 'back'): " input = gets.chomp.strip throw(:back) if input.downcase == "back" @@ -13,7 +13,7 @@ def prompt_required(prompt_text) def prompt_required_text(prompt_text) loop do - print "#{prompt_text} or type 'back': " + print "#{prompt_text} (or type 'back'): " input = gets.chomp.strip throw(:back) if input.downcase == "back" @@ -25,7 +25,7 @@ def prompt_required_text(prompt_text) def prompt_integer(prompt_text) loop do - print "#{prompt_text}: " + print "#{prompt_text} (or type 'back'): " input = gets.chomp.strip throw(:back) if input.downcase == "back" @@ -37,7 +37,7 @@ def prompt_integer(prompt_text) def prompt_float(prompt_text) loop do - print "#{prompt_text}: " + print "#{prompt_text} (or type 'back'): " input = gets.chomp.strip throw(:back) if input.downcase == "back" @@ -71,7 +71,7 @@ def format_prep_time(total_minutes) def prompt_date(prompt_text) loop do - print "#{prompt_text} (MM/DD/YYYY): " + print "#{prompt_text} (or type 'back') (MM/DD/YYYY): " input = gets.chomp.strip throw(:back) if input.downcase == "back" diff --git a/cli/meal_menu.rb b/cli/meal_menu.rb index 710efbc..847cde6 100644 --- a/cli/meal_menu.rb +++ b/cli/meal_menu.rb @@ -13,11 +13,11 @@ def meal_menu(user) case choice when :add - add_new_meal(meal_plan) if meal_plan + add_new_meal(meal_plan) when :edit - update_meal(meal_plan) if meal_plan + update_meal(meal_plan) when :delete - delete_meal(meal_plan) if meal_plan + delete_meal(meal_plan) when :back break end @@ -27,48 +27,64 @@ def meal_menu(user) def display_meals(meal_plan) if meal_plan.meals.any? meal_plan.meals.each do |meal| - puts " -> Meal: #{meal.name} (ID: #{meal.id})" - puts " (meal_type: #{meal.meal_type})" - puts " (prep_time: #{format_prep_time(meal.prep_time)})" - puts " (estimated_cost: #{meal.estimated_cost})" - puts " (calories: #{meal.calories})" - puts " (prepared: #{meal.prepared})" - puts " (favorite: #{meal.favorite})" + display_meal_details(meal) end else puts "No meals found for this meal plan." end end + def display_meal_details(meal) + puts " -> Meal: #{meal.name} (ID: #{meal.id})" + puts " (meal_type: #{meal.meal_type})" + puts " (prep_time: #{format_prep_time(meal.prep_time)})" + puts " (estimated_cost: #{meal.estimated_cost})" + puts " (calories: #{meal.calories})" + puts " (prepared: #{meal.prepared})" + puts " (favorite: #{meal.favorite})" + end + + def display_budget_error(meal_plan, message) + puts message + puts "Remaining budget: $#{meal_plan.remaining_budget.round(2)}" + end + def add_new_meal(meal_plan) - name = prompt_required("Meal name") - meal_type = prompt_required_text("Meal type") - prep_time = prompt_prep_time - estimated_cost = prompt_float("Estimated cost") - unless meal_plan.within_budget?(estimated_cost) - puts "This meal would put the meal plan over budget." - puts "Remaining budget: $#{meal_plan.remaining_budget}" - return - end - calories = prompt_integer("Calories") - favorite = prompt_boolean("Favorite?") + catch(:back) do + name = prompt_required("Meal name") + meal_type = prompt_required_text("Meal type") + prep_time = prompt_prep_time + estimated_cost = prompt_float("Estimated cost") + + unless meal_plan.within_budget?(estimated_cost) + display_budget_error(meal_plan, "This meal would put the meal plan over budget.") + return + end - confirmed = @prompt.yes?("Are you sure you want to add '#{name}' meal to #{meal_plan.name}?") + calories = prompt_integer("Calories") + favorite = prompt_boolean("Favorite?") - if confirmed - new_meal = meal_plan.meals.create( - name: name, - meal_type: meal_type, - prep_time: prep_time, - estimated_cost: estimated_cost, - calories: calories, - favorite: favorite - ) - - puts "Success! '#{new_meal.name}' has been created." - else - puts "Adding meal canceled." + confirmed = @prompt.yes?("Are you sure you want to add '#{name}' meal to #{meal_plan.name}?") + + if confirmed + new_meal = meal_plan.meals.create( + name: name, + meal_type: meal_type, + prep_time: prep_time, + estimated_cost: estimated_cost, + calories: calories, + favorite: favorite + ) + + puts "Success! '#{new_meal.name}' has been created." + else + puts "Adding meal canceled." + end + + return end + + puts "\nReturning to the previous menu..." end def select_meal(meal_plan) @@ -88,41 +104,46 @@ def select_meal(meal_plan) end def update_meal(meal_plan) - meal = select_meal(meal_plan) - return unless meal - - field = @prompt.select("Which field do you want to change?", - %w[name meal_type prep_time estimated_cost calories prepared favorite - back]) - - return if field == "back" - - new_value = - case field - when "name", "meal_type" - prompt_required_text("Enter a new value for #{field}") - when "prep_time" - prompt_prep_time - when "calories" - prompt_integer("Enter a new value for #{field}") - when "estimated_cost" - new_cost = prompt_float("Enter a new value for #{field}") - - unless meal_plan.can_update_meal_cost?(meal, new_cost) - puts "This update would put the meal plan over budget" - puts "Remaining budget: $#{meal_plan.remaining_budget.round(2)}" - return + catch(:back) do + meal = select_meal(meal_plan) + return unless meal + + field = @prompt.select("Which field do you want to change?", + %w[name meal_type prep_time estimated_cost calories prepared favorite back]) + + return if field == "back" + + new_value = + case field + when "name", "meal_type" + prompt_required_text("Enter a new value for #{field}") + when "prep_time" + prompt_prep_time + when "calories" + prompt_integer("Enter a new value for #{field}") + when "estimated_cost" + new_cost = prompt_float("Enter a new value for #{field}") + + unless meal_plan.can_update_meal_cost?(meal, new_cost) + display_budget_error(meal_plan, "This update would put the meal plan over budget.") + return + end + + new_cost + when "prepared", "favorite" + prompt_boolean("#{field.capitalize}?") end - new_cost - when "prepared", "favorite" - prompt_boolean("#{field.capitalize}?") + + if meal.update(field.to_sym => new_value) + puts "Success! Record updated in the database." + else + puts "Update failed: #{meal.errors.full_messages.join(', ')}" end - if meal.update(field.to_sym => new_value || new_cost) - puts "Success! Record updated in the database." - else - puts "Update failed: #{meal.errors.full_messages.join(', ')}" + return end + + puts "\nReturning to the previous menu..." end def delete_meal(meal_plan) diff --git a/cli/meal_plan_menu.rb b/cli/meal_plan_menu.rb index 655dfdf..4049ea3 100644 --- a/cli/meal_plan_menu.rb +++ b/cli/meal_plan_menu.rb @@ -91,7 +91,10 @@ def add_new_meal_plan(user) def update_meal_plan(meal_plan) catch(:back) do - field = @prompt.select("Which field do you want to change?", %w[name week_start goal budget]) + field = @prompt.select("Which field do you want to change?", + %w[name week_start goal budget back]) + + return if field == "back" new_value = case field From 6ec5041f257fb5aeaf5619dbfe3e109ad5426e8e Mon Sep 17 00:00:00 2001 From: + <+> Date: Thu, 30 Jul 2026 14:25:00 -0400 Subject: [PATCH 32/37] Clean up unused business logic in User model --- app/models/user.rb | 13 ------------- cli/meal_plan_menu.rb | 2 +- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index b0d4ada..a77e487 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,17 +1,4 @@ class User < ActiveRecord::Base has_many :meal_plans has_many :meals, through: :meal_plans - - def users_budget - meal_plans.sum(:budget) - end - - def favorite_meals - meals.where(favorite: true) - - def meal_options - meals.pluck(:id, :name) - - end - end end diff --git a/cli/meal_plan_menu.rb b/cli/meal_plan_menu.rb index 4049ea3..53266a1 100644 --- a/cli/meal_plan_menu.rb +++ b/cli/meal_plan_menu.rb @@ -29,7 +29,7 @@ def display_meal_plan_summary(meal_plan) puts "Estimated Cost: $#{meal_plan.total_estimated_cost}" puts "Remaining Budget: $#{meal_plan.remaining_budget}" - favorite_meals = meal_plan.meals.where(favorite: true) + favorite_meals(meal_plan) puts "\nFavorite Meals:" if favorite_meals.any? From e43160e492af2d1509cf5ea4c40f4a2ca195e6a8 Mon Sep 17 00:00:00 2001 From: + <+> Date: Thu, 30 Jul 2026 14:35:54 -0400 Subject: [PATCH 33/37] Add user stores and CLI usage README --- README.md | 89 ++++++++++++++++++++++++++++++++++++------------- user_stories.md | 31 +++++++++++++++++ 2 files changed, 96 insertions(+), 24 deletions(-) create mode 100644 user_stories.md diff --git a/README.md b/README.md index 02a05e2..6dca303 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,72 @@ -# Meal Prep Tracker +# Meal Prep Tracker -## Description: +Meal Prep Tracker is a Ruby CLI application built with ActiveRecord and SQLite3. The app allows users to create and manage meal plans, add meals to meal plans, update meal information, delete records, and view meal plan budget summaries from the terminal. -### Meal Prep Tracker is a Ruby backend application built with ActiveRecord that supports creating, updating, and deleting weekly meal plans, organizing meals within those plans, and managing meal data for individual users. +## Requirements -## Models: - - - User - - MealPlan - - Meal +Before running the application, make sure you have: -## Associations: +- Ruby installed +- Bundler installed +- SQLite3 installed -- A User has many MealPlans. -- A User has many Meals through MealPlans. -- A MealPlan belongs to a User. -- A MealPlan has many Meals. -- A Meal belongs to a MealPlan. +## Installation -## User Stories +Clone the repository from GitHub: -- View existing users. -- View a user's meal plans. -- Create a new meal plan for a user. -- Update an existing meal plan. -- Delete a meal plan. -- Add meals to a meal plan. -- Update meal information. -- Remove meals from a meal plan. -- View all meals within a meal plan. \ No newline at end of file + git clone + +Move into the project directory: + + cd + +Install the project dependencies: + + bundle install + +## Database Setup + +Create the database: + + bundle exec rake db:create + +Run the migrations to create the database tables: + + bundle exec rake db:migrate + +Seed the database with sample data: + + bundle exec rake seed + +## Running the Application + +Start the CLI from the project root: + + ruby cli/cli.rb + +## Useful Development Commands + +Open a Pry console with the application environment loaded: + + bundle exec rake console + +Create a new migration: + + bundle exec rake db:new_migration name=migration_name + +Example: + + bundle exec rake db:new_migration name=create_users + +## Main Features + +The CLI supports: + +- Viewing users +- Selecting a user +- Adding and deleting users +- Viewing a user’s meal plans and meals +- Adding, updating, and deleting meal plans +- Adding, updating, and deleting meals +- Viewing meal plan budget summaries +- Preventing meals from exceeding a meal plan’s budget \ No newline at end of file diff --git a/user_stories.md b/user_stories.md new file mode 100644 index 0000000..02a05e2 --- /dev/null +++ b/user_stories.md @@ -0,0 +1,31 @@ +# Meal Prep Tracker + +## Description: + +### Meal Prep Tracker is a Ruby backend application built with ActiveRecord that supports creating, updating, and deleting weekly meal plans, organizing meals within those plans, and managing meal data for individual users. + +## Models: + + - User + - MealPlan + - Meal + +## Associations: + +- A User has many MealPlans. +- A User has many Meals through MealPlans. +- A MealPlan belongs to a User. +- A MealPlan has many Meals. +- A Meal belongs to a MealPlan. + +## User Stories + +- View existing users. +- View a user's meal plans. +- Create a new meal plan for a user. +- Update an existing meal plan. +- Delete a meal plan. +- Add meals to a meal plan. +- Update meal information. +- Remove meals from a meal plan. +- View all meals within a meal plan. \ No newline at end of file From a6b8b523ec3c411bdf4c2bdb98bb527a75c640bc Mon Sep 17 00:00:00 2001 From: + <+> Date: Thu, 30 Jul 2026 14:37:15 -0400 Subject: [PATCH 34/37] fix markdown formatting in user stories --- user_stories.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_stories.md b/user_stories.md index 02a05e2..e015275 100644 --- a/user_stories.md +++ b/user_stories.md @@ -2,7 +2,7 @@ ## Description: -### Meal Prep Tracker is a Ruby backend application built with ActiveRecord that supports creating, updating, and deleting weekly meal plans, organizing meals within those plans, and managing meal data for individual users. +- Meal Prep Tracker is a Ruby backend application built with ActiveRecord that supports creating, updating, and deleting weekly meal plans, organizing meals within those plans, and managing meal data for individual users. ## Models: From b0f2d442b2cb4bc03979163e7d05e0772a57119d Mon Sep 17 00:00:00 2001 From: + <+> Date: Thu, 30 Jul 2026 14:52:21 -0400 Subject: [PATCH 35/37] fit line 32 in meal_plan_menu --- cli/meal_plan_menu.rb | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/cli/meal_plan_menu.rb b/cli/meal_plan_menu.rb index 53266a1..93daa51 100644 --- a/cli/meal_plan_menu.rb +++ b/cli/meal_plan_menu.rb @@ -1,4 +1,4 @@ -class CLI +class Main def meal_plan_menu(user) loop do choice = @prompt.select("Update meal plans for #{user.name}", [ @@ -29,7 +29,7 @@ def display_meal_plan_summary(meal_plan) puts "Estimated Cost: $#{meal_plan.total_estimated_cost}" puts "Remaining Budget: $#{meal_plan.remaining_budget}" - favorite_meals(meal_plan) + favorite_meals = meal_plan.meals.where(favorite: true) puts "\nFavorite Meals:" if favorite_meals.any? @@ -91,17 +91,14 @@ def add_new_meal_plan(user) def update_meal_plan(meal_plan) catch(:back) do - field = @prompt.select("Which field do you want to change?", - %w[name week_start goal budget back]) - - return if field == "back" + field = @prompt.select("Which field do you want to change?", %w[name week_start goal budget]) new_value = case field when "name", "goal" prompt_required_text("Enter a new value for #{field}") when "week_start" - prompt_date("Enter a new week start (or type back to return to menu)") + prompt_date("Enter a new week start or type back to return to menu") when "budget" prompt_float("Enter a new value for #{field}") end From d20164ee22c341e693231aa3fe6ddfd00c2adcd3 Mon Sep 17 00:00:00 2001 From: + <+> Date: Thu, 30 Jul 2026 19:27:01 -0400 Subject: [PATCH 36/37] clean up meal plan display and back navigation --- cli/meal_menu.rb | 63 ++++++++++++++++++++++++------------------- cli/meal_plan_menu.rb | 31 +++++++++++++++------ cli/user_menu.rb | 5 +++- 3 files changed, 62 insertions(+), 37 deletions(-) diff --git a/cli/meal_menu.rb b/cli/meal_menu.rb index 847cde6..40f26f7 100644 --- a/cli/meal_menu.rb +++ b/cli/meal_menu.rb @@ -3,23 +3,25 @@ def meal_menu(user) meal_plan = select_meal_plan(user) return unless meal_plan - loop do - choice = @prompt.select("Update meals for #{meal_plan.name}", [ - { name: "Add meal", value: :add }, - { name: "Edit meal", value: :edit }, - { name: "Delete meal", value: :delete }, - { name: "Back", value: :back }, - ]) - - case choice - when :add - add_new_meal(meal_plan) - when :edit - update_meal(meal_plan) - when :delete - delete_meal(meal_plan) - when :back - break + catch(:back) do + loop do + choice = @prompt.select("Update meals for #{meal_plan.name}", [ + { name: "Add meal", value: :add }, + { name: "Edit meal", value: :edit }, + { name: "Delete meal", value: :delete }, + { name: "Back", value: :back }, + ]) + + case choice + when :add + add_new_meal(meal_plan) + when :edit + update_meal(meal_plan) + when :delete + delete_meal(meal_plan) + when :back + throw :back + end end end end @@ -38,7 +40,7 @@ def display_meal_details(meal) puts " -> Meal: #{meal.name} (ID: #{meal.id})" puts " (meal_type: #{meal.meal_type})" puts " (prep_time: #{format_prep_time(meal.prep_time)})" - puts " (estimated_cost: #{meal.estimated_cost})" + puts " (estimated_cost: $#{meal.estimated_cost})" puts " (calories: #{meal.calories})" puts " (prepared: #{meal.prepared})" puts " (favorite: #{meal.favorite})" @@ -104,14 +106,21 @@ def select_meal(meal_plan) end def update_meal(meal_plan) - catch(:back) do - meal = select_meal(meal_plan) - return unless meal + if meal_plan.meals.empty? + puts "No meals found for this meal plan." + throw :back + end - field = @prompt.select("Which field do you want to change?", - %w[name meal_type prep_time estimated_cost calories prepared favorite back]) + meal = select_meal(meal_plan) + return unless meal - return if field == "back" + catch(:back) do + field = @prompt.select( + "Which field do you want to change?", + %w[name meal_type prep_time estimated_cost calories prepared favorite back] + ) + + throw :back if field == "back" new_value = case field @@ -126,7 +135,7 @@ def update_meal(meal_plan) unless meal_plan.can_update_meal_cost?(meal, new_cost) display_budget_error(meal_plan, "This update would put the meal plan over budget.") - return + throw :back end new_cost @@ -139,8 +148,6 @@ def update_meal(meal_plan) else puts "Update failed: #{meal.errors.full_messages.join(', ')}" end - - return end puts "\nReturning to the previous menu..." @@ -149,7 +156,7 @@ def update_meal(meal_plan) def delete_meal(meal_plan) if meal_plan.meals.empty? puts "\nYou don't have any meals to delete" - return + throw :back end choices = meal_plan.meals.map do |meal| diff --git a/cli/meal_plan_menu.rb b/cli/meal_plan_menu.rb index 93daa51..0c3be7a 100644 --- a/cli/meal_plan_menu.rb +++ b/cli/meal_plan_menu.rb @@ -1,4 +1,4 @@ -class Main +class CLI def meal_plan_menu(user) loop do choice = @prompt.select("Update meal plans for #{user.name}", [ @@ -53,14 +53,16 @@ def select_meal_plan(user) meal_plan = @prompt.select("Select a meal plan:", choices) + meal_plan + end + + def display_selected_meal_plan(meal_plan) puts "\nSelected Meal Plan:" puts " -> Meal Plan: #{meal_plan.name}" puts " -> ID: #{meal_plan.id}" puts " -> Week Start: #{meal_plan.week_start}" puts " -> Goal: #{meal_plan.goal}" - puts " -> Budget: #{meal_plan.budget}" - - meal_plan + puts " -> Budget: $#{meal_plan.budget}" end def add_new_meal_plan(user) @@ -84,21 +86,36 @@ def add_new_meal_plan(user) else puts "Adding meal plan canceled." end + return end + puts "\nReturning to the previous menu..." end def update_meal_plan(meal_plan) catch(:back) do - field = @prompt.select("Which field do you want to change?", %w[name week_start goal budget]) + if meal_plan.meals.empty? + puts "There are no meals in this meal plan." + throw :back + end + + meal = select_meal(meal_plan) + throw :back unless meal + + field = @prompt.select( + "Which field do you want to change?", + %w[name week_start goal budget back] + ) + + throw :back if field == "back" new_value = case field when "name", "goal" prompt_required_text("Enter a new value for #{field}") when "week_start" - prompt_date("Enter a new week start or type back to return to menu") + prompt_date("Enter a new week start date") when "budget" prompt_float("Enter a new value for #{field}") end @@ -108,8 +125,6 @@ def update_meal_plan(meal_plan) else puts "Update failed: #{meal_plan.errors.full_messages.join(', ')}" end - - return end puts "\nReturning to the previous menu..." diff --git a/cli/user_menu.rb b/cli/user_menu.rb index 017bb21..ab16cd7 100644 --- a/cli/user_menu.rb +++ b/cli/user_menu.rb @@ -18,7 +18,10 @@ def user_menu(user) case choice when :view_meal_plans meal_plan = select_meal_plan(user) - display_meals(meal_plan) if meal_plan + if meal_plan + display_selected_meal_plan(meal_plan) + display_meals(meal_plan) + end when :meal_plan_summary meal_plan = select_meal_plan(user) display_meal_plan_summary(meal_plan) if meal_plan From e746ca69d49643c4d025a2d488eaa9f16f789e5b Mon Sep 17 00:00:00 2001 From: + <+> Date: Thu, 30 Jul 2026 19:51:17 -0400 Subject: [PATCH 37/37] Fix Faker seed data to follow budget rules --- Gemfile.lock | 1 - db/seeds.rb | 18 +++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index d3432d4..de21d50 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -231,7 +231,6 @@ DEPENDENCIES rubocop sqlite3 (~> 1.7) standalone_migrations - tty-color tty-font tty-prompt diff --git a/db/seeds.rb b/db/seeds.rb index 6323683..0fdc81b 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -19,7 +19,7 @@ name: "#{Faker::Food.ethnic_category} Meal Plan", week_start: Faker::Date.forward(days: 30), goal: ["Meal Prep", "High Protein", "Budget Friendly", "Quick Meals", "Balanced Eating"].sample, - budget: Faker::Number.decimal(l_digits: 2, r_digits: 2), + budget: Faker::Number.between(from: 75, to: 200), user: user ) end @@ -28,11 +28,23 @@ puts "Seeding meals..." meal_plans.each do |meal_plan| 5.times do + remaining_budget = meal_plan.remaining_budget + break if remaining_budget <= 0 + + estimated_cost = Faker::Number.between( + from: 5, + to: [remaining_budget, 30].min + ).round(2) + + prep_hours = Faker::Number.between(from: 0, to: 2) + prep_minutes = Faker::Number.between(from: 0, to: 59) + prep_time = (prep_hours * 60) + prep_minutes + Meal.create!( name: Faker::Food.dish, meal_type: ["Breakfast", "Lunch", "Dinner", "Snack"].sample, - prep_time: Faker::Number.between(from: 10, to: 90), - estimated_cost: Faker::Number.decimal(l_digits: 2, r_digits: 2), + prep_time: prep_time, + estimated_cost: estimated_cost, calories: Faker::Number.between(from: 250, to: 900), prepared: [true, false].sample, favorite: [true, false].sample,