# TurboVault - Development Guide ## Quick Start ```bash # Start PostgreSQL task docker:up # Setup database (first time only) task db:setup # Start Rails server task server # Or use bin/dev for Tailwind watch mode bin/dev ``` Visit http://localhost:3000 and create an account! ## Development Workflow ### Daily Development ```bash # Start all services task docker:up # PostgreSQL bin/dev # Rails server + Tailwind watcher ``` ### Database Operations ```bash # Create a new migration rails generate migration AddFieldToModel field:type # Run migrations task db:migrate # Rollback last migration task db:rollback # Reset database (drops, creates, migrates, seeds) task db:reset # Open Rails console task console # Check pending migrations rails db:migrate:status ``` ### Generating Code ```bash # Generate a model rails generate model ModelName field:type # Generate a controller rails generate controller ControllerName action1 action2 # Generate a scaffold (model + controller + views) rails generate scaffold ModelName field:type # Destroy generated code rails destroy model ModelName ``` ### Running Tests ```bash # Run all tests task test # Run specific test file rails test test/models/game_test.rb # Run specific test rails test test/models/game_test.rb:10 # Run system tests task test:system ``` ### Code Quality ```bash # Run RuboCop linter task lint # Auto-fix RuboCop issues task lint:fix # Security checks task security ``` ### Type Checking with Sorbet TurboVault uses Sorbet for gradual static type checking. ```bash # First time setup (after bundle install) task tapioca:init task tapioca:all # Run type checker task typecheck # Watch mode (re-checks on file changes) task typecheck:watch # Update type definitions after gem/model changes task tapioca:gems # After bundle install task tapioca:dsl # After model changes ``` **See [SORBET.md](SORBET.md) for complete type checking guide.** ## Project Structure ``` app/ ├── controllers/ │ ├── concerns/ # Shared controller modules │ ├── api/v1/ # API controllers │ └── *.rb # Web controllers ├── models/ │ ├── concerns/ # Shared model modules │ └── *.rb # ActiveRecord models ├── views/ │ ├── layouts/ # Application layouts │ └── */ # Controller-specific views ├── helpers/ # View helpers ├── mailers/ # Email mailers └── jobs/ # Background jobs config/ ├── routes.rb # URL routing ├── database.yml # Database configuration └── environments/ # Environment-specific config db/ ├── migrate/ # Database migrations ├── seeds.rb # Seed data └── schema.rb # Current database schema test/ ├── models/ # Model tests ├── controllers/ # Controller tests ├── system/ # End-to-end tests └── fixtures/ # Test data ``` ## Common Tasks ### Adding a New Model 1. Generate the model: ```bash rails generate model Post user:references title:string body:text published:boolean ``` 2. Edit the migration (add indexes, constraints, RLS if user-scoped): ```ruby class CreatePosts < ActiveRecord::Migration[8.1] def change create_table :posts do |t| t.references :user, null: false, foreign_key: true, index: true t.string :title, null: false t.text :body t.boolean :published, default: false, null: false t.timestamps end add_index :posts, :title # Enable RLS if user-scoped execute <<-SQL ALTER TABLE posts ENABLE ROW LEVEL SECURITY; CREATE POLICY posts_isolation_policy ON posts USING (user_id = current_setting('app.current_user_id', true)::bigint); SQL end end ``` 3. Run the migration: ```bash rails db:migrate ``` 4. Add associations and validations to the model: ```ruby class Post < ApplicationRecord belongs_to :user validates :title, presence: true validates :body, presence: true scope :published, -> { where(published: true) } end ``` 5. Add association to User model: ```ruby class User < ApplicationRecord has_many :posts, dependent: :destroy end ``` ### Adding a New Controller 1. Generate the controller: ```bash rails generate controller Posts index show new create edit update destroy ``` 2. Implement controller actions: ```ruby class PostsController < ApplicationController before_action :require_authentication before_action :set_post, only: [:show, :edit, :update, :destroy] def index @posts = current_user.posts.order(created_at: :desc) end def show end def new @post = current_user.posts.build end def create @post = current_user.posts.build(post_params) if @post.save redirect_to @post, notice: "Post created successfully." else render :new, status: :unprocessable_entity end end def edit end def update if @post.update(post_params) redirect_to @post, notice: "Post updated successfully." else render :edit, status: :unprocessable_entity end end def destroy @post.destroy redirect_to posts_path, notice: "Post deleted successfully." end private def set_post @post = current_user.posts.find(params[:id]) end def post_params params.require(:post).permit(:title, :body, :published) end end ``` 3. Add routes: ```ruby resources :posts ``` 4. Create views in `app/views/posts/` ### Adding an API Endpoint 1. Create API controller: ```bash mkdir -p app/controllers/api/v1 ``` 2. Create controller file: ```ruby # app/controllers/api/v1/posts_controller.rb module Api module V1 class PostsController < BaseController def index @posts = current_user.posts.order(created_at: :desc) render json: @posts end def show @post = current_user.posts.find(params[:id]) render json: @post end def create @post = current_user.posts.build(post_params) if @post.save render json: @post, status: :created else render json: { errors: @post.errors.full_messages }, status: :unprocessable_entity end end private def post_params params.require(:post).permit(:title, :body, :published) end end end end ``` 3. Add API routes: ```ruby namespace :api do namespace :v1 do resources :posts, only: [:index, :show, :create] end end ``` ### Adding a Background Job 1. Generate the job: ```bash rails generate job ProcessData ``` 2. Implement the job: ```ruby class ProcessDataJob < ApplicationJob queue_as :default def perform(user_id) user = User.find(user_id) # Do some processing end end ``` 3. Enqueue the job: ```ruby ProcessDataJob.perform_later(user.id) ``` ### Adding Email Functionality 1. Generate a mailer: ```bash rails generate mailer UserMailer welcome ``` 2. Implement the mailer: ```ruby class UserMailer < ApplicationMailer def welcome(user) @user = user mail(to: @user.email, subject: "Welcome to TurboVault!") end end ``` 3. Create email templates in `app/views/user_mailer/` 4. Configure SMTP in `config/environments/`: ```ruby config.action_mailer.delivery_method = :smtp config.action_mailer.smtp_settings = { address: ENV['SMTP_ADDRESS'], port: ENV['SMTP_PORT'], user_name: ENV['SMTP_USERNAME'], password: ENV['SMTP_PASSWORD'], authentication: 'plain', enable_starttls_auto: true } ``` 5. Send the email: ```ruby UserMailer.welcome(user).deliver_later ``` ## Testing Guide ### Writing Model Tests ```ruby # test/models/game_test.rb require "test_helper" class GameTest < ActiveSupport::TestCase test "should not save game without title" do game = Game.new assert_not game.save, "Saved game without title" end test "should save valid game" do game = games(:one) # Uses fixture assert game.save, "Failed to save valid game" end test "should belong to user" do game = games(:one) assert_respond_to game, :user end end ``` ### Writing Controller Tests ```ruby # test/controllers/games_controller_test.rb require "test_helper" class GamesControllerTest < ActionDispatch::IntegrationTest setup do @user = users(:one) sign_in_as @user # Helper method to sign in @game = games(:one) end test "should get index" do get games_url assert_response :success end test "should create game" do assert_difference('Game.count') do post games_url, params: { game: { title: "New Game", platform_id: platforms(:one).id, format: "physical" } } end assert_redirected_to game_path(Game.last) end end ``` ### Writing System Tests ```ruby # test/system/games_test.rb require "application_system_test_case" class GamesTest < ApplicationSystemTestCase setup do @user = users(:one) sign_in_as @user end test "visiting the index" do visit games_url assert_selector "h1", text: "My Games" end test "creating a game" do visit games_url click_on "Add Game" fill_in "Title", with: "Test Game" select "Nintendo 64", from: "Platform" select "Physical", from: "Format" click_on "Create Game" assert_text "Game was successfully created" end end ``` ## Debugging ### Rails Console ```bash rails console # Test queries User.first Game.where(format: :physical).count current_user.games.includes(:platform) # Test RLS ActiveRecord::Base.connection.execute( "SET LOCAL app.current_user_id = 1" ) ``` ### Logs ```bash # Tail development log tail -f log/development.log # View specific log cat log/test.log ``` ### Debug with Byebug Add to your code: ```ruby require 'debug' debugger # Execution will pause here ``` Then interact in the terminal: ``` n # Next line c # Continue p var # Print variable exit # Exit debugger ``` ## Performance Tips ### Avoid N+1 Queries Bad: ```ruby @games = current_user.games # In view: @games.each { |g| g.platform.name } # N+1! ``` Good: ```ruby @games = current_user.games.includes(:platform) ``` ### Use Database Indexes ```ruby add_index :games, :title add_index :games, [:user_id, :platform_id] ``` ### Use Counter Caches ```ruby class Collection < ApplicationRecord has_many :games, counter_cache: true end ``` ### Pagination ```ruby @games = current_user.games.page(params[:page]).per(25) ``` ## Environment Variables Create `.env` for development (never commit!): ``` DATABASE_HOST=localhost DATABASE_USERNAME=postgres DATABASE_PASSWORD=postgres SMTP_ADDRESS=smtp.example.com SMTP_USERNAME=user@example.com SMTP_PASSWORD=secret ``` Load with: ```ruby # config/application.rb config.before_configuration do env_file = Rails.root.join('.env') if File.exist?(env_file) File.readlines(env_file).each do |line| key, value = line.split('=', 2) ENV[key.strip] = value.strip if key && value end end end ``` ## Deployment ### Kamal (Recommended) Already configured! Just: ```bash # First time setup kamal setup # Deploy kamal deploy # Check status kamal app exec --interactive --reuse "bin/rails console" ``` ### Railway/Render 1. Push to Git 2. Connect repository 3. Set environment variables 4. Add build command: `bundle install && rails db:migrate` 5. Add start command: `rails server -b 0.0.0.0` ## Troubleshooting ### Database Connection Errors ```bash # Check if PostgreSQL is running docker compose ps # Start PostgreSQL task docker:up # Check database configuration cat config/database.yml ``` ### Asset Issues ```bash # Rebuild assets rails assets:precompile # Rebuild Tailwind rails tailwindcss:build ``` ### Migration Issues ```bash # Check migration status rails db:migrate:status # Rollback and retry rails db:rollback rails db:migrate ``` ## Resources - [Rails Guides](https://guides.rubyonrails.org/) - [Rails API Documentation](https://api.rubyonrails.org/) - [Tailwind CSS Docs](https://tailwindcss.com/docs) - [Hotwire Documentation](https://hotwired.dev/) - [PostgreSQL Documentation](https://www.postgresql.org/docs/) ## Getting Help 1. Check the logs: `tail -f log/development.log` 2. Use Rails console: `rails console` 3. Check database: `rails dbconsole` 4. Read the error message carefully 5. Search Stack Overflow 6. Check Rails Guides ## Best Practices 1. **Always filter by current_user** in controllers 2. **Use strong parameters** for mass assignment 3. **Add validations** to models 4. **Write tests** for new features 5. **Keep controllers thin** - move logic to models 6. **Use concerns** for shared code 7. **Keep views simple** - use helpers for complex logic 8. **Add indexes** for frequently queried columns 9. **Use scopes** for common queries 10. **Document your API** endpoints Happy coding! 🚀