diff --git a/app/controllers/portfolios_controller.rb b/app/controllers/portfolios_controller.rb
new file mode 100644
index 00000000..dcbf0643
--- /dev/null
+++ b/app/controllers/portfolios_controller.rb
@@ -0,0 +1,63 @@
+class PortfoliosController < ApplicationController
+ before_action :authenticate_user!
+ before_action :set_portfolio, only: %i[ show edit update destroy ]
+
+ def index
+ @portfolios = current_user.portfolios
+ end
+
+ def show
+ end
+
+ def new
+ @portfolio = Portfolio.new
+ end
+
+ def edit
+ end
+
+ def create
+ @portfolio = current_user.portfolios.new(portfolio_params)
+
+ respond_to do |format|
+ if @portfolio.save
+ format.html { redirect_to portfolio_url(@portfolio), notice: "Portfolio was successfully created." }
+ format.json { render :show, status: :created, location: @portfolio }
+ else
+ format.html { render :new, status: :unprocessable_entity }
+ format.json { render json: @portfolio.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ def update
+ respond_to do |format|
+ if @portfolio.update(portfolio_params)
+ format.html { redirect_to portfolio_url(@portfolio), notice: "Portfolio was successfully updated." }
+ format.json { render :show, status: :ok, location: @portfolio }
+ else
+ format.html { render :edit, status: :unprocessable_entity }
+ format.json { render json: @portfolio.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ def destroy
+ @portfolio.destroy!
+
+ respond_to do |format|
+ format.html { redirect_to portfolios_url, notice: "Portfolio was successfully destroyed." }
+ format.json { head :no_content }
+ end
+ end
+
+ private
+ def set_portfolio
+ @portfolio = current_user.portfolios.find(params[:id])
+ end
+
+ # Only allow a list of trusted parameters through.
+ def portfolio_params
+ params.require(:portfolio).permit(:name)
+ end
+end
diff --git a/app/helpers/portfolios_helper.rb b/app/helpers/portfolios_helper.rb
new file mode 100644
index 00000000..04f40c72
--- /dev/null
+++ b/app/helpers/portfolios_helper.rb
@@ -0,0 +1,2 @@
+module PortfoliosHelper
+end
diff --git a/app/models/portfolio.rb b/app/models/portfolio.rb
new file mode 100644
index 00000000..1abf1f0f
--- /dev/null
+++ b/app/models/portfolio.rb
@@ -0,0 +1,5 @@
+class Portfolio < ApplicationRecord
+ belongs_to :user
+
+ validates :name, presence: true
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index 25925b9b..51120e7f 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -2,4 +2,6 @@ class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable,
:confirmable, :lockable, :timeoutable, :trackable
+
+ has_many :portfolios, dependent: :destroy
end
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index 08b66181..cbaefcee 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -33,12 +33,12 @@
<% end %>
- <%= link_to root_path, class: "flex p-2 text-sm font-semibold leading-6 text-gray-600 rounded-md group gap-x-3" do %>
+ <%= link_to portfolios_path, class: "flex p-2 text-sm font-semibold leading-6 text-gray-600 rounded-md group gap-x-3" do %>
- Portfolio
+ Portfolios
<% end %>
diff --git a/app/views/portfolios/_form.html.erb b/app/views/portfolios/_form.html.erb
new file mode 100644
index 00000000..23ebcd4b
--- /dev/null
+++ b/app/views/portfolios/_form.html.erb
@@ -0,0 +1,22 @@
+<%= form_with(model: portfolio, class: "contents") do |form| %>
+ <% if portfolio.errors.any? %>
+
+
<%= pluralize(portfolio.errors.count, "error") %> prohibited this portfolio from being saved:
+
+
+ <% portfolio.errors.each do |error| %>
+ - <%= error.full_message %>
+ <% end %>
+
+
+ <% end %>
+
+
+ <%= form.label :name %>
+ <%= form.text_field :name, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %>
+
+
+
+ <%= form.submit class: "rounded-lg py-3 px-5 bg-blue-600 text-white inline-block font-medium cursor-pointer" %>
+
+<% end %>
diff --git a/app/views/portfolios/_portfolio.html.erb b/app/views/portfolios/_portfolio.html.erb
new file mode 100644
index 00000000..fe6c7d72
--- /dev/null
+++ b/app/views/portfolios/_portfolio.html.erb
@@ -0,0 +1,11 @@
+
+
+ <%= portfolio.name %>
+
+
+ <% if action_name != "show" %>
+ <%= link_to "Show this portfolio", portfolio, class: "rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
+ <%= link_to "Edit this portfolio", edit_portfolio_path(portfolio), class: "rounded-lg py-3 ml-2 px-5 bg-gray-100 inline-block font-medium" %>
+
+ <% end %>
+
diff --git a/app/views/portfolios/edit.html.erb b/app/views/portfolios/edit.html.erb
new file mode 100644
index 00000000..22477163
--- /dev/null
+++ b/app/views/portfolios/edit.html.erb
@@ -0,0 +1,8 @@
+
+
Editing portfolio
+
+ <%= render "form", portfolio: @portfolio %>
+
+ <%= link_to "Show this portfolio", @portfolio, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
+ <%= link_to "Back to portfolios", portfolios_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
+
diff --git a/app/views/portfolios/index.html.erb b/app/views/portfolios/index.html.erb
new file mode 100644
index 00000000..77d90eba
--- /dev/null
+++ b/app/views/portfolios/index.html.erb
@@ -0,0 +1,18 @@
+
+ <% if notice.present? %>
+
<%= notice %>
+ <% end %>
+
+
+
Portfolios
+ <%= link_to "New portfolio", new_portfolio_path, class: "rounded-lg py-3 px-5 bg-blue-600 text-white block font-medium" %>
+
+
+
+ <% @portfolios.each do |portfolio| %>
+ -
+ <%= link_to portfolio.name, portfolio %>
+
+ <% end %>
+
+
diff --git a/app/views/portfolios/new.html.erb b/app/views/portfolios/new.html.erb
new file mode 100644
index 00000000..027c7c34
--- /dev/null
+++ b/app/views/portfolios/new.html.erb
@@ -0,0 +1,7 @@
+
+
New portfolio
+
+ <%= render "form", portfolio: @portfolio %>
+
+ <%= link_to "Back to portfolios", portfolios_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
+
diff --git a/app/views/portfolios/show.html.erb b/app/views/portfolios/show.html.erb
new file mode 100644
index 00000000..5a4d5db9
--- /dev/null
+++ b/app/views/portfolios/show.html.erb
@@ -0,0 +1,17 @@
+
+ <% if notice.present? %>
+
<%= notice %>
+ <% end %>
+
+
+
<%= @portfolio.name %>
+
+
+ <%= link_to "Edit this portfolio", edit_portfolio_path(@portfolio), class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
+
+ <%= button_to "Destroy this portfolio", portfolio_path(@portfolio), method: :delete, class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 font-medium" %>
+
+ <%= link_to "Back to portfolios", portfolios_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
+
+
+
diff --git a/config/routes.rb b/config/routes.rb
index 3639306b..1370a095 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,4 +1,6 @@
Rails.application.routes.draw do
+ resources :portfolios
+
devise_for :users
# Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
diff --git a/db/migrate/20240102020519_create_portfolios.rb b/db/migrate/20240102020519_create_portfolios.rb
new file mode 100644
index 00000000..0e5d50b1
--- /dev/null
+++ b/db/migrate/20240102020519_create_portfolios.rb
@@ -0,0 +1,10 @@
+class CreatePortfolios < ActiveRecord::Migration[7.2]
+ def change
+ create_table :portfolios, id: :uuid do |t|
+ t.references :user, null: false, foreign_key: true, type: :uuid
+ t.string :name
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 2cdd8b18..09d9bef0 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,11 +10,19 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.2].define(version: 2024_01_01_232802) do
+ActiveRecord::Schema[7.2].define(version: 2024_01_02_020519) do
# These are extensions that must be enabled in order to support this database
enable_extension "pgcrypto"
enable_extension "plpgsql"
+ create_table "portfolios", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+ t.uuid "user_id", null: false
+ t.string "name"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["user_id"], name: "index_portfolios_on_user_id"
+ end
+
create_table "users", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
@@ -41,4 +49,5 @@ ActiveRecord::Schema[7.2].define(version: 2024_01_01_232802) do
t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true
end
+ add_foreign_key "portfolios", "users"
end
diff --git a/test/controllers/portfolios_controller_test.rb b/test/controllers/portfolios_controller_test.rb
new file mode 100644
index 00000000..9771a689
--- /dev/null
+++ b/test/controllers/portfolios_controller_test.rb
@@ -0,0 +1,48 @@
+require "test_helper"
+
+class PortfoliosControllerTest < ActionDispatch::IntegrationTest
+ setup do
+ @portfolio = portfolios(:one)
+ end
+
+ test "should get index" do
+ get portfolios_url
+ assert_response :success
+ end
+
+ test "should get new" do
+ get new_portfolio_url
+ assert_response :success
+ end
+
+ test "should create portfolio" do
+ assert_difference("Portfolio.count") do
+ post portfolios_url, params: { portfolio: { name: @portfolio.name, user_id: @portfolio.user_id } }
+ end
+
+ assert_redirected_to portfolio_url(Portfolio.last)
+ end
+
+ test "should show portfolio" do
+ get portfolio_url(@portfolio)
+ assert_response :success
+ end
+
+ test "should get edit" do
+ get edit_portfolio_url(@portfolio)
+ assert_response :success
+ end
+
+ test "should update portfolio" do
+ patch portfolio_url(@portfolio), params: { portfolio: { name: @portfolio.name, user_id: @portfolio.user_id } }
+ assert_redirected_to portfolio_url(@portfolio)
+ end
+
+ test "should destroy portfolio" do
+ assert_difference("Portfolio.count", -1) do
+ delete portfolio_url(@portfolio)
+ end
+
+ assert_redirected_to portfolios_url
+ end
+end
diff --git a/test/fixtures/portfolios.yml b/test/fixtures/portfolios.yml
new file mode 100644
index 00000000..56ad3115
--- /dev/null
+++ b/test/fixtures/portfolios.yml
@@ -0,0 +1,9 @@
+# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
+
+one:
+ user: one
+ name: MyString
+
+two:
+ user: two
+ name: MyString
diff --git a/test/models/portfolio_test.rb b/test/models/portfolio_test.rb
new file mode 100644
index 00000000..2f0dddc7
--- /dev/null
+++ b/test/models/portfolio_test.rb
@@ -0,0 +1,7 @@
+require "test_helper"
+
+class PortfolioTest < ActiveSupport::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+end
diff --git a/test/system/portfolios_test.rb b/test/system/portfolios_test.rb
new file mode 100644
index 00000000..2e4b37c2
--- /dev/null
+++ b/test/system/portfolios_test.rb
@@ -0,0 +1,43 @@
+require "application_system_test_case"
+
+class PortfoliosTest < ApplicationSystemTestCase
+ setup do
+ @portfolio = portfolios(:one)
+ end
+
+ test "visiting the index" do
+ visit portfolios_url
+ assert_selector "h1", text: "Portfolios"
+ end
+
+ test "should create portfolio" do
+ visit portfolios_url
+ click_on "New portfolio"
+
+ fill_in "Name", with: @portfolio.name
+ fill_in "User", with: @portfolio.user_id
+ click_on "Create Portfolio"
+
+ assert_text "Portfolio was successfully created"
+ click_on "Back"
+ end
+
+ test "should update Portfolio" do
+ visit portfolio_url(@portfolio)
+ click_on "Edit this portfolio", match: :first
+
+ fill_in "Name", with: @portfolio.name
+ fill_in "User", with: @portfolio.user_id
+ click_on "Update Portfolio"
+
+ assert_text "Portfolio was successfully updated"
+ click_on "Back"
+ end
+
+ test "should destroy Portfolio" do
+ visit portfolio_url(@portfolio)
+ click_on "Destroy this portfolio", match: :first
+
+ assert_text "Portfolio was successfully destroyed"
+ end
+end