Project

General

Profile

Actions

Patch #44169

open

Refactor context menus controller to namespaced sub-controllers

Added by Marius BĂLTEANU 1 day ago. Updated 1 day ago.

Status:
New
Priority:
Normal
Category:
Code cleanup/refactoring

Description

The current ContextMenusController acts as a monolithic controller, managing context menu logic for multiple disparate objects: issues, time_entries, projects, and users.

Maintaining this kind of controller is difficult from many reasons and the complexity increases with each new object type added; we should refactor this to decouple the logic for each object type.


Files

0001-wip.patch (19 KB) 0001-wip.patch Marius BĂLTEANU, 2026-06-14 00:39
Actions #1

Updated by Marius BĂLTEANU 1 day ago

  • File 0001-wip.patch added

The attached WIP patch demonstrates the approach for refactoring ContextMenusController. The logic is being moved into individual namespaced domain sub-controllers classes, inheriting from a base_controller.

Actions #2

Updated by Marius BĂLTEANU 1 day ago

  • File deleted (0001-wip.patch)
Actions #3

Updated by Marius BĂLTEANU 1 day ago

  • File 0001-wip.patch added
Actions #4

Updated by Marius BĂLTEANU 1 day ago

Actions #5

Updated by Marius BĂLTEANU 1 day ago

  • File deleted (0001-wip.patch)
Actions #6

Updated by Marius BĂLTEANU 1 day ago

Beside moving to dedicated and namespaced sub-controllers, there is still one more issue to resolve, the duplicated code for find_* methods that are shared by multiple controllers. This can be observed in the patch, the method find_time_entries is very similar to TimelogController#find_time_entries.

Options to fix this duplicated code:

1. Each dedicated sub-controller inherits the controller. In our case, ContextMenus::TimeEntriesController inherits TimelogController. The logic from the ContextMenus::BaseController can be moved to a concern.

2. Move the find_* methods that are shared by multiple controllers to ApplicationController, but this bloats this controller which is already quite huge.

3. Move these methods to concerns:

app/controllers/concerns/time_entry_finder.rb:

app/controllers/concerns/time_entry_finder.rb
module TimeEntryFinder
  extend ActiveSupport::Concern
  private

  def find_time_entries
    @time_entries = TimeEntry.where(:id => params[:id] || params[:ids]).
      preload(:project => :time_entry_activities).
      preload(:user).to_a
    raise ActiveRecord::RecordNotFound if @time_entries.empty?
    raise Unauthorized unless @time_entries.all?(&:visible?)

    @projects = @time_entries.filter_map(&:project).uniq
    @project = @projects.first if @projects.size == 1
  end
end

Then, TimelogController and TimeEntriesController include this concern. The downside is that the finder method remains tightly coupled to the controller context and it cannot be easily reused in background jobs or rake tasks.

4. Move the find_* logic to dedicated objects, for example:

app/finders/time_entry_finder.rb

# app/finders/time_entry_finder.rb
class TimeEntryFinder
  class UnauthorizedError < StandardError; end

  def initialize(ids, user = User.current)
    @ids = Array(ids).reject(&:blank?)
    @user = user
  end

  # Executes the lookup with preloading and visibility authorization
  def find_visible!
    entries = TimeEntry.where(:id => @ids).
      preload(:project => :time_entry_activities).
      preload(:user).to_a

    raise ActiveRecord::RecordNotFound if entries.empty?
    raise UnauthorizedError unless entries.all? { |e| e.visible?(@user) }
    entries
  end
end

Any feedback is welcome.

Actions #8

Updated by Marius BĂLTEANU 1 day ago

  • Description updated (diff)
  • Target version set to Candidate for next major release
Actions

Also available in: Atom PDF