Patch #44169
openRefactor context menus controller to namespaced sub-controllers
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
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.
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.
Updated by Marius BĂLTEANU 1 day ago
- Description updated (diff)
- Target version set to Candidate for next major release