Skip to content

Lesson 4 — Wiring current_user into the app

current_user in components

With the Authentication concern in ApplicationController, current_user is available in controllers and ERB views via helper_method. In Phlex components it needs to be explicitly made available.

The correct Phlex v2 approach is to include it as a helper:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# app/components/base.rb
class Components::Base < Phlex::HTML
  include Phlex::Rails::Helpers::Routes
  include Phlex::Rails::Helpers::FormWith
  include Phlex::Rails::Helpers::DOMID
  include Phlex::Rails::Helpers::TurboStreamFrom
  extend Literal::Properties

  # Make current_user available in all components
  def current_user
    Current.session&.user
  end


  # ...
end

helpers is available in any Phlex component rendered within a Rails request context — it gives access to all helper_method declarations including current_user.

Updating AppLayout nav

Add sign out and user context to the nav:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# app/views/layouts/app_layout.rb
def render_nav
  nav(class: "bg-surface border-b border-border px-4 py-3") do
    div(class: "mx-auto max-w-7xl flex items-center justify-between") do
      a(href: root_path,
        class: "font-bold text-lg text-text") { "KanbanFlow" }

      div(class: "flex items-center gap-3") do
        render_theme_controls
        render_user_menu
      end
    end
  end
end

def render_theme_controls
  div(class: "flex items-center", data: { controller: "theme-toggle" }) do
    ThemeSelector()
    ThemeToggle()
  end
end

def render_user_menu
  if current_user
    Dropdown(label: current_user.name, align: :right) do |d|
      d.item "Your boards", url: boards_path
      d.item "Sign out",    url: session_path, method: :delete
    end
  else
    a(href: new_session_path,
      class: "text-sm text-primary hover:text-primary-hover") do
      plain "Sign in"
    end
  end
end

Scoping boards to current_user

Update BoardsController to use the real current_user and scope boards to what the user owns:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# app/controllers/boards_controller.rb
class BoardsController < ApplicationController
  def index
    render Views::Boards::Index.new(boards: current_user.owned_boards)
  end

  def show
    render Views::Boards::Show.new(board: board)
  end

  def new
    render Views::Boards::New.new(board: Board.new)
  end

  def create
    @board = current_user.owned_boards.build(board_params)
    if @board.save
      redirect_to board_path(@board), status: :see_other
    else
      render Views::Boards::New.new(board: @board),
             status: :unprocessable_entity
    end
  end

  def edit
    render Views::Boards::Edit.new(board: board)
  end

  def update
    if board.update(board_params)
      redirect_to board_path(board), status: :see_other
    else
      render Views::Boards::Edit.new(board: board),
             status: :unprocessable_entity
    end
  end

  def destroy
    board.destroy
    redirect_to boards_path, status: :see_other
  end

  private

  def board
    @board ||= current_user.owned_boards.find(params[:id])
  end

  def board_params
    params.expect(board: Views::Boards::Form.permitted)
  end
end

current_user.owned_boards.find scopes the lookup to the current user’s boards — a user can’t access another user’s board by guessing the id. A non-existent or unauthorised board raises ActiveRecord::RecordNotFound which Rails handles as a 404.

Membership on board creation

When a user creates a board via owned_boards, a Membership record should also be created so the owner appears in the members list and current_user.boards (the through association) includes the new board.

Add a callback to Board:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# app/models/board.rb
class Board < ApplicationRecord
  belongs_to :user
  has_many :memberships, dependent: :destroy
  has_many :members, through: :memberships, source: :user
  has_many :columns, -> { order(:position) }, dependent: :destroy
  broadcasts_refreshes

  after_create :add_owner_as_member

  validates :name, presence: true, length: { maximum: 100 }

  private

  def add_owner_as_member
    memberships.find_or_create_by!(user: user, role: :admin)
  end
end

This ensures every board has its creator as an admin member from the start. The role: :admin assumes a role column on Membership — if your membership model doesn’t have roles yet, use find_or_create_by!(user: user) and add roles in the companion auth tutorial.

Securing card and column controllers

Cards and columns should only be accessible to the board owner for now. Add a before_action to verify ownership:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# app/controllers/cards_controller.rb
class CardsController < ApplicationController

  def create
    @column = column
    @card   = @column.cards.build(card_params)
    @card.save
    redirect_to board_path(@column.board), status: :see_other
  end

  def update
    card.update(card_params)
    redirect_to board_path(card.column.board), status: :see_other
  end

  def destroy
    card.destroy
    redirect_to board_path(card.column.board), status: :see_other
  end

  private

  def card
    @card ||= Card.find(params[:id])
  end

  def column
    @column ||= Column.find(params[:column_id])
  end

  def card_params
    params.expect(card: Views::Cards::CardForm.permitted)
  end
end
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# app/controllers/columns_controller.rb
class ColumnsController < ApplicationController
  before_action :verify_board_access

  def create
    @board  = Board.find(params[:board_id])
    @column = @board.columns.build(column_params)
    @column.save
    redirect_to board_path(@board), status: :see_other
  end

  def update
    column.update(column_params)
    redirect_to board_path(column.board), status: :see_other
  end

  def destroy
    column.destroy
    redirect_to board_path(column.board), status: :see_other
  end

  private

  def verify_board_access
    board = params[:board_id] ? Board.find(params[:board_id]) : column.board
    unless board.user == current_user
      redirect_to boards_path, alert: "Access denied."
    end
  end

  def column
    @column ||= Column.find(params[:id])
  end

  def column_params
    params.expect(column: Views::Columns::ColumnForm.permitted)
  end
end