Class: LcpRuby::Metadata::Loader
- Inherits:
-
Object
- Object
- LcpRuby::Metadata::Loader
- Defined in:
- lib/lcp_ruby/metadata/loader.rb
Instance Attribute Summary collapse
-
#abstract_model_names ⇒ Object
readonly
Returns the value of attribute abstract_model_names.
-
#base_path ⇒ Object
readonly
Returns the value of attribute base_path.
-
#job_definitions ⇒ Object
readonly
Returns the value of attribute job_definitions.
-
#menu_definition ⇒ Object
readonly
Returns the value of attribute menu_definition.
-
#model_definitions ⇒ Object
readonly
Returns the value of attribute model_definitions.
-
#page_definitions ⇒ Object
readonly
Returns the value of attribute page_definitions.
-
#permission_definitions ⇒ Object
readonly
Returns the value of attribute permission_definitions.
-
#presenter_definitions ⇒ Object
readonly
Returns the value of attribute presenter_definitions.
-
#view_group_definitions ⇒ Object
readonly
Returns the value of attribute view_group_definitions.
-
#workflow_definitions ⇒ Object
readonly
Returns the value of attribute workflow_definitions.
Instance Method Summary collapse
-
#has_permission_definition?(model_name) ⇒ Boolean
Returns true iff a non-fallback permission definition exists for this model.
-
#initialize(base_path) ⇒ Loader
constructor
A new instance of Loader.
- #load_all ⇒ Object
- #load_models ⇒ Object
- #load_permissions ⇒ Object
- #load_presenters ⇒ Object
- #load_types ⇒ Object
- #load_view_groups ⇒ Object
- #menu_defined? ⇒ Boolean
-
#merge_db_pages! ⇒ Object
Merges DB-stored page definitions into the loaded page definitions.
- #model_definition(name) ⇒ Object
-
#navigable_view_groups ⇒ Object
Returns navigable view groups (those not opted out with navigation: false).
- #page_definition(name) ⇒ Object
-
#permission_definition(model_name) ⇒ Object
Returns the merged permission definition for a model: the per-model definition (resolved through DB→YAML→STI fallback by SourceResolver) coverage-merged with the ‘_default` entry.
- #presenter_definition(name) ⇒ Object
- #presenters_for_model(model_name) ⇒ Object
-
#sti_builder_definition(name) ⇒ Object
Returns a ModelDefinition built from the original (un-merged) child hash, suitable for passing to Builder to avoid double-applying parent’s validations/callbacks/enums (which are already inherited via AR class hierarchy).
- #unreachable_presenter_names ⇒ Object
- #view_group_for_page(page_name) ⇒ Object
- #view_groups_for_model(model_name) ⇒ Object
- #yaml_permission_definition(model_name) ⇒ Object
Constructor Details
#initialize(base_path) ⇒ Loader
Returns a new instance of Loader.
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
# File 'lib/lcp_ruby/metadata/loader.rb', line 12 def initialize(base_path) @base_path = Pathname.new(base_path) @model_definitions = {} @presenter_definitions = {} @permission_definitions = {} @view_group_definitions = {} @page_definitions = {} @workflow_definitions = {} @job_definitions = {} @menu_definition = nil @sti_builder_hashes = {} @abstract_model_names = [] # Memoizes merged permission definitions per model_name. Keyed by # input identity so DB-backed permission_source paths invalidate # naturally: Permissions::Registry.reload! returns a fresh # PermissionDefinition instance after a DB change → object_id # differs → cache miss → recompute. YAML inputs are stable across # the loader's lifetime, so they cache forever (until LcpRuby.reset! # rebuilds the loader). @merged_permission_cache = {} end |
Instance Attribute Details
#abstract_model_names ⇒ Object (readonly)
Returns the value of attribute abstract_model_names.
7 8 9 |
# File 'lib/lcp_ruby/metadata/loader.rb', line 7 def abstract_model_names @abstract_model_names end |
#base_path ⇒ Object (readonly)
Returns the value of attribute base_path.
7 8 9 |
# File 'lib/lcp_ruby/metadata/loader.rb', line 7 def base_path @base_path end |
#job_definitions ⇒ Object (readonly)
Returns the value of attribute job_definitions.
7 8 9 |
# File 'lib/lcp_ruby/metadata/loader.rb', line 7 def job_definitions @job_definitions end |
#menu_definition ⇒ Object (readonly)
Returns the value of attribute menu_definition.
7 8 9 |
# File 'lib/lcp_ruby/metadata/loader.rb', line 7 def @menu_definition end |
#model_definitions ⇒ Object (readonly)
Returns the value of attribute model_definitions.
7 8 9 |
# File 'lib/lcp_ruby/metadata/loader.rb', line 7 def model_definitions @model_definitions end |
#page_definitions ⇒ Object (readonly)
Returns the value of attribute page_definitions.
7 8 9 |
# File 'lib/lcp_ruby/metadata/loader.rb', line 7 def page_definitions @page_definitions end |
#permission_definitions ⇒ Object (readonly)
Returns the value of attribute permission_definitions.
7 8 9 |
# File 'lib/lcp_ruby/metadata/loader.rb', line 7 def @permission_definitions end |
#presenter_definitions ⇒ Object (readonly)
Returns the value of attribute presenter_definitions.
7 8 9 |
# File 'lib/lcp_ruby/metadata/loader.rb', line 7 def presenter_definitions @presenter_definitions end |
#view_group_definitions ⇒ Object (readonly)
Returns the value of attribute view_group_definitions.
7 8 9 |
# File 'lib/lcp_ruby/metadata/loader.rb', line 7 def view_group_definitions @view_group_definitions end |
#workflow_definitions ⇒ Object (readonly)
Returns the value of attribute workflow_definitions.
7 8 9 |
# File 'lib/lcp_ruby/metadata/loader.rb', line 7 def workflow_definitions @workflow_definitions end |
Instance Method Details
#has_permission_definition?(model_name) ⇒ Boolean
Returns true iff a non-fallback permission definition exists for this model. Distinguishes “the configurator wrote a permission file” from “the lookup chain returned _default.”
Used by the inherits_from validator to confirm parent references point to real permission files, not phantom names.
194 195 196 197 198 199 200 201 202 203 |
# File 'lib/lcp_ruby/metadata/loader.rb', line 194 def (model_name) key = model_name.to_s return false if key == "_default" return true if @permission_definitions.key?(key) # STI parent fallback (mirror of yaml_permission_definition's logic) model_def = @model_definitions[key] parent_name = model_def&.sti_parent_name !!(parent_name && @permission_definitions.key?(parent_name)) end |
#load_all ⇒ Object
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/lcp_ruby/metadata/loader.rb', line 34 def load_all load_types load_models load_presenters load_pages auto_create_pages warn_unreachable_presenters warn_depends_on_without_action_cable load_view_groups validate_references auto_create_view_groups load_workflows load_jobs load_theme end |
#load_models ⇒ Object
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/lcp_ruby/metadata/loader.rb', line 71 def load_models # Phase 1: Collect raw hashes from YAML and DSL raw_entries = collect_model_hashes # Phase 2: Resolve inheritance (merges abstract parents, excludes abstract models) @abstract_model_names = raw_entries.select { |_, e| e[:hash]["abstract"] }.keys resolved = ModelInheritanceResolver.resolve(raw_entries) # Phase 3: Create ModelDefinitions from resolved hashes resolved.each do |name, entry| definition = ModelDefinition.from_hash(entry[:hash]) definition.source_path = entry[:source_path] definition.source_type = entry[:source_type] @model_definitions[name] = definition # Store original child hash for STI children (used by Builder to avoid double-applying) @sti_builder_hashes[name] = entry[:sti_builder_hash] if entry[:sti_builder_hash] end end |
#load_permissions ⇒ Object
102 103 104 105 106 107 108 109 |
# File 'lib/lcp_ruby/metadata/loader.rb', line 102 def load_yamls("permissions") do |data, file_path| = data["permissions"] || raise(MetadataError, "Missing 'permissions' key in #{file_path}") definition = PermissionDefinition.from_hash() set_source!(definition, PathUtils.relative_path(file_path), "yaml") @permission_definitions[definition.model] = definition end end |
#load_presenters ⇒ Object
91 92 93 94 95 96 97 98 99 100 |
# File 'lib/lcp_ruby/metadata/loader.rb', line 91 def load_presenters load_yamls("presenters") do |data, file_path| presenter_data = data["presenter"] || raise(MetadataError, "Missing 'presenter' key in #{file_path}") definition = PresenterDefinition.from_hash(presenter_data) set_source!(definition, PathUtils.relative_path(file_path), "yaml") @presenter_definitions[definition.name] = definition end load_dsl_presenters("presenters") end |
#load_types ⇒ Object
61 62 63 64 65 66 67 68 69 |
# File 'lib/lcp_ruby/metadata/loader.rb', line 61 def load_types load_yamls("types") do |data, file_path| type_data = data["type"] || raise(MetadataError, "Missing 'type' key in #{file_path}") type_def = Types::TypeDefinition.from_hash(type_data) Types::TypeRegistry.register(type_def.name, type_def) end load_dsl_types("types") end |
#load_view_groups ⇒ Object
111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/lcp_ruby/metadata/loader.rb', line 111 def load_view_groups @view_group_definitions = {} load_yamls("views") do |data, file_path| vg_data = data["view_group"] || raise(MetadataError, "Missing 'view_group' key in #{file_path}") # Inject file-based name if not present vg_data["name"] ||= File.basename(file_path, ".*") definition = ViewGroupDefinition.from_hash({ "view_group" => vg_data }) set_source!(definition, PathUtils.relative_path(file_path), "yaml") @view_group_definitions[definition.name] = definition end load_dsl_view_groups("views") end |
#menu_defined? ⇒ Boolean
52 53 54 |
# File 'lib/lcp_ruby/metadata/loader.rb', line 52 def !@menu_definition.nil? end |
#merge_db_pages! ⇒ Object
Merges DB-stored page definitions into the loaded page definitions. DB pages override YAML pages with the same name. Auto-pages are recreated after merge to account for newly claimed presenters. Called from engine boot after Pages::Setup marks the registry available.
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 |
# File 'lib/lcp_ruby/metadata/loader.rb', line 228 def merge_db_pages! return unless Pages::Registry.available? db_pages = begin Pages::Registry.all_definitions rescue LcpRuby::Error, ActiveRecord::StatementInvalid => e safe_log_warn("[LcpRuby] Failed to load DB page definitions: #{e.}") raise unless Rails.env.production? if defined?(Rails) return end db_pages.each do |db_page| db_page.instance_variable_set(:@source_type, "database") @page_definitions[db_page.name] = db_page end # Re-create auto-pages since DB pages may claim presenters that were auto-paged @page_definitions.reject! { |_, p| p.auto_generated? } auto_create_pages # Clear resolver cache since pages changed Pages::Resolver.clear! end |
#model_definition(name) ⇒ Object
134 135 136 |
# File 'lib/lcp_ruby/metadata/loader.rb', line 134 def model_definition(name) @model_definitions[name.to_s] || raise(MetadataError, "Model '#{name}' not found") end |
#navigable_view_groups ⇒ Object
Returns navigable view groups (those not opted out with navigation: false)
57 58 59 |
# File 'lib/lcp_ruby/metadata/loader.rb', line 57 def navigable_view_groups @view_group_definitions.values.select(&:navigable?) end |
#page_definition(name) ⇒ Object
138 139 140 |
# File 'lib/lcp_ruby/metadata/loader.rb', line 138 def page_definition(name) @page_definitions[name.to_s] || raise(MetadataError, "Page '#{name}' not found") end |
#permission_definition(model_name) ⇒ Object
Returns the merged permission definition for a model: the per-model definition (resolved through DB→YAML→STI fallback by SourceResolver) coverage-merged with the ‘_default` entry. Authors who want full shadow behavior declare `extends: “none”` on their per-model file.
See docs/design/permissions_default_role_coverage.md.
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
# File 'lib/lcp_ruby/metadata/loader.rb', line 156 def (model_name) key = model_name.to_s per_model = Permissions::SourceResolver.for(key, self) return per_model unless per_model return per_model if per_model.default? return per_model if per_model.extends == "none" default_def = Permissions::SourceResolver.for("_default", self) return per_model unless default_def cached = @merged_permission_cache[key] return cached[2] if cached && cached[0].equal?(per_model) && cached[1].equal?(default_def) merged = Metadata::PermissionMerger.merge(default: default_def, per_model: per_model) @merged_permission_cache[key] = [ per_model, default_def, merged ] merged end |
#presenter_definition(name) ⇒ Object
142 143 144 |
# File 'lib/lcp_ruby/metadata/loader.rb', line 142 def presenter_definition(name) @presenter_definitions[name.to_s] || raise(MetadataError, "Presenter '#{name}' not found") end |
#presenters_for_model(model_name) ⇒ Object
146 147 148 |
# File 'lib/lcp_ruby/metadata/loader.rb', line 146 def presenters_for_model(model_name) @presenter_definitions.values.select { |p| p.model == model_name.to_s } end |
#sti_builder_definition(name) ⇒ Object
Returns a ModelDefinition built from the original (un-merged) child hash, suitable for passing to Builder to avoid double-applying parent’s validations/callbacks/enums (which are already inherited via AR class hierarchy).
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 |
# File 'lib/lcp_ruby/metadata/loader.rb', line 208 def sti_builder_definition(name) builder_hash = @sti_builder_hashes[name.to_s] return nil unless builder_hash full_def = @model_definitions[name.to_s] return nil unless full_def # Build a hash with child-only content but with STI metadata child_hash = builder_hash.dup child_hash["table_name"] = full_def.table_name child_hash[ModelInheritanceResolver::STI_CHILD_KEY] = true child_hash["inherits"] = full_def.inherits ModelDefinition.from_hash(child_hash) end |
#unreachable_presenter_names ⇒ Object
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'lib/lcp_ruby/metadata/loader.rb', line 252 def unreachable_presenter_names used_in_zones = Set.new @page_definitions.each_value do |page| next if page.auto_generated? page.zones.each { |z| used_in_zones << z.presenter if z.presenter_zone? } used_in_zones << page.index_presenter if page.index_presenter end @presenter_definitions.each_value.filter_map do |presenter| auto_page = @page_definitions[presenter.name] next unless auto_page&.auto_generated? next if auto_page.routable? next if used_in_zones.include?(presenter.name) next if auto_page.dialog_only? presenter.name end end |
#view_group_for_page(page_name) ⇒ Object
130 131 132 |
# File 'lib/lcp_ruby/metadata/loader.rb', line 130 def view_group_for_page(page_name) @view_group_definitions.values.find { |vg| vg.page_names.include?(page_name.to_s) } end |
#view_groups_for_model(model_name) ⇒ Object
126 127 128 |
# File 'lib/lcp_ruby/metadata/loader.rb', line 126 def view_groups_for_model(model_name) @view_group_definitions.values.select { |vg| vg.model == model_name.to_s } end |
#yaml_permission_definition(model_name) ⇒ Object
174 175 176 177 178 179 180 181 182 183 184 185 186 |
# File 'lib/lcp_ruby/metadata/loader.rb', line 174 def (model_name) perm = @permission_definitions[model_name.to_s] return perm if perm # STI child fallback: try parent model permissions model_def = @model_definitions[model_name.to_s] if (parent_name = model_def&.sti_parent_name) perm = @permission_definitions[parent_name] return perm if perm end @permission_definitions["_default"] end |