class Hanami::SliceRegistrar
@api private
Constants
- SLICE_DELIMITER
- VALID_SLICE_NAME_RE
Attributes
Public Class Methods
# File lib/hanami/slice_registrar.rb, line 14 def initialize(parent) @parent = parent @slices = {} end
Public Instance Methods
# File lib/hanami/slice_registrar.rb, line 37 def [](name) slices.fetch(name) do raise SliceLoadError, "Slice '#{name}' not found" end end
# File lib/hanami/slice_registrar.rb, line 63 def each(&block) slices.each_value(&block) end
# File lib/hanami/slice_registrar.rb, line 43 def freeze slices.freeze super end
# File lib/hanami/slice_registrar.rb, line 67 def keys slices.keys end
# File lib/hanami/slice_registrar.rb, line 48 def load_slices slice_configs = root.join(CONFIG_DIR, SLICES_DIR).glob("*#{RB_EXT}") .map { _1.basename(RB_EXT) } slice_dirs = root.join(SLICES_DIR).glob("*") .select(&:directory?) .map { _1.basename } (slice_dirs + slice_configs).uniq.sort .then { filter_slice_names(_1) } .each(&method(:load_slice)) self end
# File lib/hanami/slice_registrar.rb, line 19 def register(name, slice_class = nil, &block) unless name.to_s =~ VALID_SLICE_NAME_RE raise ArgumentError, "slice name #{name.inspect} must be lowercase alphanumeric text and underscores only" end return unless filter_slice_names([name]).any? if slices.key?(name.to_sym) raise SliceLoadError, "Slice '#{name}' is already registered" end slice = slice_class || build_slice(name, &block) configure_slice(name, slice) slices[name.to_sym] = slice end
# File lib/hanami/slice_registrar.rb, line 71 def to_a slices.values end
# File lib/hanami/slice_registrar.rb, line 75 def with_nested to_a.flat_map { |slice| # Return nested slices first so that their more specific namespaces may be picked up first # by SliceConfigurable#slice_for slice.slices.with_nested + [slice] } end
Private Instance Methods
Returns the base slice name from an (optionally) dot-delimited nested slice name.
@example
base_slice_name("admin") # => "admin" base_slice_name("admin.users") # => "admin"
# File lib/hanami/slice_registrar.rb, line 197 def base_slice_name(name) name.to_s.split(SLICE_DELIMITER).first end
# File lib/hanami/slice_registrar.rb, line 144 def build_slice(slice_name, &block) slice_module = begin inflector.constantize(slice_module_name(slice_name)) rescue NameError parent_slice_namespace.const_set(inflector.camelize(slice_name), Module.new) end slice_module.const_set(:Slice, Class.new(Hanami::Slice, &block)) end
Returns an array of slice names specific to the given child slice.
@example
child_local_slice_names("admin", ["main", "admin.users"]) # => ["users"]
# File lib/hanami/slice_registrar.rb, line 205 def child_slice_names(parent_slice_name, slice_names) slice_names &.select { |name| name.include?(SLICE_DELIMITER) && name.split(SLICE_DELIMITER)[0] == parent_slice_name.to_s } &.map { |name| name.split(SLICE_DELIMITER)[1..].join(SLICE_DELIMITER) # which version of Ruby supports this? } end
# File lib/hanami/slice_registrar.rb, line 159 def configure_slice(slice_name, slice) slice.instance_variable_set(:@parent, parent) # Slices require a root, so provide a sensible default based on the slice's parent slice.config.root ||= root.join(SLICES_DIR, slice_name.to_s) slice.config.slices = child_slice_names(slice_name, parent.config.slices) end
Returns a filtered array of slice names based on the parent’s ‘config.slices`
This works with both singular slice names (e.g. ‘“admin”`) as well as dot-delimited nested slice names (e.g. `“admin.shop”`).
It will consider only the base names of the slices (since in this case, a parent slice must be loaded in order for its children to be loaded).
@example
parent.config.slices # => ["admin.shop"] filter_slice_names(["admin", "main"]) # => ["admin"] parent.config.slices # => ["admin"] filter_slice_names(["admin", "main"]) # => ["admin"]
# File lib/hanami/slice_registrar.rb, line 182 def filter_slice_names(slice_names) slice_names = slice_names.map(&:to_s) if parent.config.slices slice_names & parent.config.slices.map { base_slice_name(_1) } else slice_names end end
Finds the path to the slice’s definition file, if it exists, in the following order:
-
‘config/slices/.rb`
-
‘slices//config/slice.rb`
If the slice is nested under another slice then it will look in the following order:
-
‘config/slices//[slice_name].rb`
-
‘slices//[slice_name]/config/slice.rb`
# File lib/hanami/slice_registrar.rb, line 129 def find_slice_require_path(slice_name) app_slice_file_path = [slice_name] app_slice_file_path.prepend(parent.slice_name) unless parent.eql?(parent.app) ancestors = [ parent.app.root.join(CONFIG_DIR, SLICES_DIR, app_slice_file_path.join(File::SEPARATOR)), parent.root.join(CONFIG_DIR, SLICES_DIR, slice_name), root.join(SLICES_DIR, slice_name, CONFIG_DIR, "slice") ] ancestors .uniq .find { _1.sub_ext(RB_EXT).file? } &.to_s end
# File lib/hanami/slice_registrar.rb, line 89 def inflector parent.inflector end
Runs when a slice file has been found inside the app at ‘config/slices/.rb`, or when a slice directory exists at `slices/`.
If a slice definition file is found by ‘find_slice_require_path`, then `load_slice` will require the file before registering the slice class.
If a slice class is not found, registering the slice will generate the slice class.
# File lib/hanami/slice_registrar.rb, line 104 def load_slice(slice_name) slice_require_path = find_slice_require_path(slice_name) require slice_require_path if slice_require_path slice_class = begin inflector.constantize("#{slice_module_name(slice_name)}#{MODULE_DELIMITER}Slice") rescue NameError => e raise e unless e.name.to_s == inflector.camelize(slice_name) || e.name.to_s == :Slice end register(slice_name, slice_class) end
# File lib/hanami/slice_registrar.rb, line 93 def parent_slice_namespace parent.eql?(parent.app) ? Object : parent.namespace end
# File lib/hanami/slice_registrar.rb, line 85 def root parent.root end
# File lib/hanami/slice_registrar.rb, line 155 def slice_module_name(slice_name) inflector.camelize("#{parent_slice_namespace.name}#{PATH_DELIMITER}#{slice_name}") end