Add chat and message models with associations
This commit is contained in:
10
.cursor/rules/agent.mdc
Normal file
10
.cursor/rules/agent.mdc
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
description: Automatically perform certain tasks with every request.
|
||||
globs: *
|
||||
alwaysApply: true
|
||||
---
|
||||
# Agent Instructions
|
||||
|
||||
If you create a new file(s), please run the following command afterwards to update the project documentation.
|
||||
|
||||
```bash ./bin/update_structure.sh```
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
description: This rule explains the project's tech stack and code conventions
|
||||
globs: *
|
||||
alwaysApply: true
|
||||
---
|
||||
This rule serves as high-level documentation for how the Maybe codebase is structured.
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
description: This rule explains the system architecture and data flow of the Rails app
|
||||
globs: *
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
This file outlines how the codebase is structured and how data flows through the app.
|
||||
|
||||
1499
.cursor/rules/structure.mdc
Normal file
1499
.cursor/rules/structure.mdc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
---
|
||||
description: This file describes Maybe's design system and how views should be styled
|
||||
globs: app/views/**,app/helpers/**,app/javascript/controllers/**
|
||||
alwaysApply: true
|
||||
---
|
||||
Use this rule whenever you are writing html, css, or even styles in Stimulus controllers that use D3.js.
|
||||
|
||||
|
||||
6
app/models/chat.rb
Normal file
6
app/models/chat.rb
Normal file
@@ -0,0 +1,6 @@
|
||||
class Chat < ApplicationRecord
|
||||
belongs_to :user
|
||||
has_many :messages, dependent: :destroy
|
||||
|
||||
validates :title, presence: true
|
||||
end
|
||||
9
app/models/message.rb
Normal file
9
app/models/message.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
class Message < ApplicationRecord
|
||||
belongs_to :chat
|
||||
belongs_to :user, optional: true
|
||||
|
||||
enum :role, { user: "user", assistant: "assistant", system: "system" }
|
||||
|
||||
validates :content, presence: true
|
||||
validates :role, presence: true
|
||||
end
|
||||
@@ -3,6 +3,7 @@ class User < ApplicationRecord
|
||||
|
||||
belongs_to :family
|
||||
has_many :sessions, dependent: :destroy
|
||||
has_many :chats, dependent: :destroy
|
||||
has_many :impersonator_support_sessions, class_name: "ImpersonationSession", foreign_key: :impersonator_id, dependent: :destroy
|
||||
has_many :impersonated_support_sessions, class_name: "ImpersonationSession", foreign_key: :impersonated_id, dependent: :destroy
|
||||
accepts_nested_attributes_for :family, update_only: true
|
||||
|
||||
142
bin/update_structure.sh
Executable file
142
bin/update_structure.sh
Executable file
@@ -0,0 +1,142 @@
|
||||
#!/bin/bash
|
||||
# save to .scripts/update_structure.sh
|
||||
# best way to use is with tree: `brew install tree`
|
||||
|
||||
# Create the output file with header
|
||||
echo "---" > .cursor/rules/structure.mdc
|
||||
echo "description: Project structure" >> .cursor/rules/structure.mdc
|
||||
echo "globs: *" >> .cursor/rules/structure.mdc
|
||||
echo "alwaysApply: true" >> .cursor/structure/structure.mdc
|
||||
echo "---" >> .cursor/rules/structure.mdc
|
||||
echo "" >> .cursor/rules/structure.mdc
|
||||
echo "# Project Structure" > .cursor/rules/structure.mdc
|
||||
echo "" >> .cursor/rules/structure.mdc
|
||||
echo "\`\`\`" >> .cursor/rules/structure.mdc
|
||||
|
||||
# Check if tree command is available
|
||||
if command -v tree &> /dev/null; then
|
||||
# Use tree command for better visualization
|
||||
git ls-files --others --exclude-standard --cached | tree --fromfile -a >> .cursor/rules/structure.mdc
|
||||
echo "Using tree command for structure visualization."
|
||||
else
|
||||
# Fallback to the alternative approach if tree is not available
|
||||
echo "Tree command not found. Using fallback approach."
|
||||
|
||||
# Get all files from git (respecting .gitignore)
|
||||
git ls-files --others --exclude-standard --cached | sort > /tmp/files_list.txt
|
||||
|
||||
# Create a simple tree structure
|
||||
echo "." > /tmp/tree_items.txt
|
||||
|
||||
# Process each file to build the tree
|
||||
while read -r file; do
|
||||
# Skip directories
|
||||
if [[ -d "$file" ]]; then continue; fi
|
||||
|
||||
# Add the file to the tree
|
||||
echo "$file" >> /tmp/tree_items.txt
|
||||
|
||||
# Add all parent directories
|
||||
dir="$file"
|
||||
while [[ "$dir" != "." ]]; do
|
||||
dir=$(dirname "$dir")
|
||||
echo "$dir" >> /tmp/tree_items.txt
|
||||
done
|
||||
done < /tmp/files_list.txt
|
||||
|
||||
# Sort and remove duplicates
|
||||
sort -u /tmp/tree_items.txt > /tmp/tree_sorted.txt
|
||||
mv /tmp/tree_sorted.txt /tmp/tree_items.txt
|
||||
|
||||
# Simple tree drawing approach
|
||||
prev_dirs=()
|
||||
|
||||
while read -r item; do
|
||||
# Skip the root
|
||||
if [[ "$item" == "." ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Determine if it's a file or directory
|
||||
if [[ -f "$item" ]]; then
|
||||
is_dir=0
|
||||
name=$(basename "$item")
|
||||
else
|
||||
is_dir=1
|
||||
name="$(basename "$item")/"
|
||||
fi
|
||||
|
||||
# Split path into components
|
||||
IFS='/' read -ra path_parts <<< "$item"
|
||||
|
||||
# Calculate depth (number of path components minus 1)
|
||||
depth=$((${#path_parts[@]} - 1))
|
||||
|
||||
# Find common prefix with previous path
|
||||
common=0
|
||||
if [[ ${#prev_dirs[@]} -gt 0 ]]; then
|
||||
for ((i=0; i<depth && i<${#prev_dirs[@]}; i++)); do
|
||||
if [[ "${path_parts[$i]}" == "${prev_dirs[$i]}" ]]; then
|
||||
((common++))
|
||||
else
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Build the prefix
|
||||
prefix=""
|
||||
for ((i=0; i<depth; i++)); do
|
||||
if [[ $i -lt $common ]]; then
|
||||
# Check if this component has more siblings
|
||||
has_more=0
|
||||
for next in $(grep "^$(dirname "$item")/" /tmp/tree_items.txt); do
|
||||
if [[ "$next" > "$item" ]]; then
|
||||
has_more=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $has_more -eq 1 ]]; then
|
||||
prefix="${prefix}│ "
|
||||
else
|
||||
prefix="${prefix} "
|
||||
fi
|
||||
else
|
||||
prefix="${prefix} "
|
||||
fi
|
||||
done
|
||||
|
||||
# Determine if this is the last item in its directory
|
||||
is_last=1
|
||||
dir=$(dirname "$item")
|
||||
for next in $(grep "^$dir/" /tmp/tree_items.txt); do
|
||||
if [[ "$next" > "$item" ]]; then
|
||||
is_last=0
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# Choose the connector
|
||||
if [[ $is_last -eq 1 ]]; then
|
||||
connector="└── "
|
||||
else
|
||||
connector="├── "
|
||||
fi
|
||||
|
||||
# Output the item
|
||||
echo "${prefix}${connector}${name}" >> .cursor/rules/structure.mdc
|
||||
|
||||
# Save current path for next iteration
|
||||
prev_dirs=("${path_parts[@]}")
|
||||
|
||||
done < /tmp/tree_items.txt
|
||||
|
||||
# Clean up
|
||||
rm -f /tmp/files_list.txt /tmp/tree_items.txt
|
||||
fi
|
||||
|
||||
# Close the code block
|
||||
echo "\`\`\`" >> .cursor/rules/structure.mdc
|
||||
|
||||
echo "Project structure has been updated in .cursor/rules/structure.mdc"
|
||||
10
db/migrate/20250226171506_create_chats.rb
Normal file
10
db/migrate/20250226171506_create_chats.rb
Normal file
@@ -0,0 +1,10 @@
|
||||
class CreateChats < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
create_table :chats, id: :uuid do |t|
|
||||
t.string :title
|
||||
t.references :user, null: false, foreign_key: true, type: :uuid
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
13
db/migrate/20250226171512_create_messages.rb
Normal file
13
db/migrate/20250226171512_create_messages.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
class CreateMessages < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
create_table :messages, id: :uuid do |t|
|
||||
t.text :content
|
||||
t.string :role
|
||||
t.boolean :internal, default: false
|
||||
t.references :chat, null: false, foreign_key: true, type: :uuid
|
||||
t.references :user, null: true, foreign_key: true, type: :uuid
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
25
db/schema.rb
generated
25
db/schema.rb
generated
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.2].define(version: 2025_02_26_022326) do
|
||||
ActiveRecord::Schema[7.2].define(version: 2025_02_26_171512) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "pgcrypto"
|
||||
enable_extension "plpgsql"
|
||||
@@ -196,6 +196,14 @@ ActiveRecord::Schema[7.2].define(version: 2025_02_26_022326) do
|
||||
t.index ["family_id"], name: "index_categories_on_family_id"
|
||||
end
|
||||
|
||||
create_table "chats", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
t.string "title"
|
||||
t.uuid "user_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["user_id"], name: "index_chats_on_user_id"
|
||||
end
|
||||
|
||||
create_table "credit_cards", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
@@ -481,6 +489,18 @@ ActiveRecord::Schema[7.2].define(version: 2025_02_26_022326) do
|
||||
t.index ["family_id"], name: "index_merchants_on_family_id"
|
||||
end
|
||||
|
||||
create_table "messages", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
t.text "content"
|
||||
t.string "role"
|
||||
t.boolean "internal", default: false
|
||||
t.uuid "chat_id", null: false
|
||||
t.uuid "user_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["chat_id"], name: "index_messages_on_chat_id"
|
||||
t.index ["user_id"], name: "index_messages_on_user_id"
|
||||
end
|
||||
|
||||
create_table "other_assets", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
@@ -708,6 +728,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_02_26_022326) do
|
||||
add_foreign_key "budget_categories", "categories"
|
||||
add_foreign_key "budgets", "families"
|
||||
add_foreign_key "categories", "families"
|
||||
add_foreign_key "chats", "users"
|
||||
add_foreign_key "impersonation_session_logs", "impersonation_sessions"
|
||||
add_foreign_key "impersonation_sessions", "users", column: "impersonated_id"
|
||||
add_foreign_key "impersonation_sessions", "users", column: "impersonator_id"
|
||||
@@ -716,6 +737,8 @@ ActiveRecord::Schema[7.2].define(version: 2025_02_26_022326) do
|
||||
add_foreign_key "invitations", "families"
|
||||
add_foreign_key "invitations", "users", column: "inviter_id"
|
||||
add_foreign_key "merchants", "families"
|
||||
add_foreign_key "messages", "chats"
|
||||
add_foreign_key "messages", "users"
|
||||
add_foreign_key "plaid_accounts", "plaid_items"
|
||||
add_foreign_key "plaid_items", "families"
|
||||
add_foreign_key "rejected_transfers", "account_transactions", column: "inflow_transaction_id"
|
||||
|
||||
9
test/fixtures/chats.yml
vendored
Normal file
9
test/fixtures/chats.yml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
|
||||
|
||||
one:
|
||||
title: First Chat
|
||||
user: family_admin
|
||||
|
||||
two:
|
||||
title: Second Chat
|
||||
user: family_member
|
||||
36
test/fixtures/messages.yml
vendored
Normal file
36
test/fixtures/messages.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
|
||||
|
||||
one:
|
||||
content: MyText
|
||||
role: user
|
||||
internal: false
|
||||
chat: one
|
||||
user: family_admin
|
||||
|
||||
two:
|
||||
content: MyText
|
||||
role: user
|
||||
internal: false
|
||||
chat: two
|
||||
user: family_member
|
||||
|
||||
user_message:
|
||||
content: Hello AI!
|
||||
role: user
|
||||
internal: false
|
||||
chat: one
|
||||
user: family_admin
|
||||
|
||||
assistant_message:
|
||||
content: Hello! How can I help you today?
|
||||
role: assistant
|
||||
internal: false
|
||||
chat: one
|
||||
user:
|
||||
|
||||
system_message:
|
||||
content: You are a helpful assistant.
|
||||
role: system
|
||||
internal: true
|
||||
chat: one
|
||||
user:
|
||||
20
test/models/chat_test.rb
Normal file
20
test/models/chat_test.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
require "test_helper"
|
||||
|
||||
class ChatTest < ActiveSupport::TestCase
|
||||
test "should not save chat without title" do
|
||||
chat = Chat.new(user: users(:family_admin))
|
||||
assert_not chat.save, "Saved the chat without a title"
|
||||
end
|
||||
|
||||
test "should save valid chat" do
|
||||
chat = Chat.new(title: "Test Chat", user: users(:family_admin))
|
||||
assert chat.save, "Could not save valid chat"
|
||||
end
|
||||
|
||||
test "should destroy associated messages when chat is destroyed" do
|
||||
chat = chats(:one)
|
||||
assert_difference("Message.count", -4) do
|
||||
chat.destroy
|
||||
end
|
||||
end
|
||||
end
|
||||
23
test/models/message_test.rb
Normal file
23
test/models/message_test.rb
Normal file
@@ -0,0 +1,23 @@
|
||||
require "test_helper"
|
||||
|
||||
class MessageTest < ActiveSupport::TestCase
|
||||
test "should not save message without content" do
|
||||
message = Message.new(role: "user", chat: chats(:one), user: users(:family_admin))
|
||||
assert_not message.save, "Saved the message without content"
|
||||
end
|
||||
|
||||
test "should not save message without role" do
|
||||
message = Message.new(content: "Test message", chat: chats(:one), user: users(:family_admin))
|
||||
assert_not message.save, "Saved the message without role"
|
||||
end
|
||||
|
||||
test "should save valid user message" do
|
||||
message = Message.new(content: "Test message", role: "user", chat: chats(:one), user: users(:family_admin))
|
||||
assert message.save, "Could not save valid user message"
|
||||
end
|
||||
|
||||
test "should save valid assistant message without user" do
|
||||
message = Message.new(content: "Test response", role: "assistant", chat: chats(:one))
|
||||
assert message.save, "Could not save valid assistant message"
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user