As I was looking to implement the new Spaces feature (see forem/forem#16842), I began looking at how to adjust the admin menu. I found the Menu and AdminMenu. I wanted to add the "spaces" item under the `:content_manager` scope. Looking at the implementation, I saw I would need some additional branching logic. To get an understanding of the implementation, I chose factor towards classes. This refactor does a few things: 1) Clarifies a method name (e.g. "children?" becomes "has_multiple_children?") 2) Adds some tests of the menu implementation. 3) Removes deeply nested hashs in favor of first class objects. 4) Removes a complex method for determining menu visibility, instead favoring a method that either takes a boolean OR a lambda. There are further refactors to consider, especially in regards to the helper methods and some of the coercion of strings into different formats. But this refactor gets me the part I most am interested in: Making it easier to specify a menu item as visible or not.
97 lines
2.5 KiB
Ruby
97 lines
2.5 KiB
Ruby
# This class is responsible for helping to build a menu.
|
|
#
|
|
# @see {AdminMenu}
|
|
class Menu
|
|
# @return [Hash<Symbol, Menu::Scope>]
|
|
def self.define(&block)
|
|
builder = new
|
|
builder.instance_exec(&block)
|
|
builder.items
|
|
end
|
|
|
|
attr_reader :items
|
|
|
|
def initialize
|
|
@items = {}
|
|
end
|
|
|
|
# @param name [String]
|
|
# @param svg [String]
|
|
# @param children [Array<Hash>]
|
|
def scope(name, svg, children)
|
|
@items[name] = Scope.new(name: name, svg: "#{svg}.svg", children: children)
|
|
end
|
|
|
|
# @see {Menu::Item#initialize} for details on the parameters.
|
|
# @return [Menu::Item]
|
|
def item(...)
|
|
Item.new(...)
|
|
end
|
|
|
|
class Scope
|
|
attr_reader :name, :svg, :children
|
|
|
|
def initialize(name:, svg:, children:)
|
|
@name = name
|
|
@svg = svg
|
|
@children = children
|
|
end
|
|
|
|
# Does this Scope have multiple children? From a presentation stand point
|
|
# we render menu items with multiple children a bit differently than those
|
|
# with only one child.
|
|
#
|
|
# @return [TrueClass] when this item has more than 1 child.
|
|
# @return [FalseClass] when this item has one or less children.
|
|
def has_multiple_children?
|
|
@children.length > 1
|
|
end
|
|
|
|
# @return [TrueClass] when this item has at least one child.
|
|
# @return [FalseClass] when this item has no children.
|
|
def has_children?
|
|
!@children.empty?
|
|
end
|
|
end
|
|
|
|
class Item
|
|
attr_reader :name, :controller, :children, :parent
|
|
|
|
# @param name [String]
|
|
# @param controller [String]
|
|
# @param children [Array<Hash>]
|
|
# @param visible [Boolean, #call]
|
|
# @param parent [NilClass, String]
|
|
def initialize(name:, controller: name, children: [], visible: true, parent: nil)
|
|
@name = name
|
|
@controller = controller.tr(" ", "_")
|
|
@children = children
|
|
@visible = visible
|
|
@parent = parent
|
|
end
|
|
|
|
# @return [TrueClass] if this menu item should be visible
|
|
# @return [FalseClass] if this menu item should not be visible
|
|
def visible?
|
|
if @visible.respond_to?(:call)
|
|
@visible.call
|
|
else
|
|
@visible
|
|
end
|
|
end
|
|
|
|
alias visible visible?
|
|
|
|
# @return [TrueClass] when this item has more than 1 child.
|
|
# @return [FalseClass] when this item has one or less children.
|
|
def has_multiple_children?
|
|
@children.length > 1
|
|
end
|
|
|
|
# @return [TrueClass] when this item has at least one child.
|
|
# @return [FalseClass] when this item has no children.
|
|
def has_children?
|
|
!@children.empty?
|
|
end
|
|
end
|
|
end
|