Module: LcpRuby::Metadata::PermissionMerger

Defined in:
lib/lcp_ruby/metadata/permission_merger.rb

Overview

Coverage-merges a per-model PermissionDefinition with the ‘_default` entry. Whole-entry replacement at the keyed level — see docs/design/permissions_default_role_coverage.md.

Class Method Summary collapse

Class Method Details

.merge(default:, per_model:) ⇒ Object



7
8
9
10
11
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/permission_merger.rb', line 7

def self.merge(default:, per_model:)
  roles,     role_sources     = merge_keyed(default.roles,           per_model.roles)
  overrides, override_sources = merge_keyed(default.field_overrides, per_model.field_overrides)
  rules,     rule_sources     = merge_record_rules(default, per_model)

  default_role, default_role_source = merge_default_role(default, per_model)
  inherits_from = per_model.inherits_from

  PermissionDefinition.new(
    model: per_model.model,
    roles: roles,
    default_role: default_role,
    field_overrides: overrides,
    record_rules: rules,
    inherits_from: per_model.raw_hash&.[]("inherits_from"),
    source_path: per_model.source_path,
    source_type: per_model.source_type,
    source_map: {
      roles: role_sources,
      field_overrides: override_sources,
      record_rules: rule_sources,
      default_role: default_role_source,
      inherits_from: inherits_from ? :per_model : nil
    }
  )
end

.merge_default_role(default, per_model) ⇒ Object

default_role sentinel: PermissionDefinition#initialize defaults to “viewer”, so the constructed attribute can’t distinguish “author wrote nothing” from “author wrote viewer”. raw_hash key presence disambiguates.



73
74
75
76
77
78
# File 'lib/lcp_ruby/metadata/permission_merger.rb', line 73

def self.merge_default_role(default, per_model)
  return [ per_model.default_role, :per_model ] if per_model.raw_hash&.key?("default_role")
  return [ default.default_role,   :default ]   if default.raw_hash&.key?("default_role")

  [ per_model.default_role, :default ]
end

.merge_keyed(default_block, per_model_block) ⇒ Object

Whole-entry replacement keyed by name. Per-model wins on collision; entries only in default are copied. Used for both ‘roles` and `field_overrides` — same shape, only the dictionary differs.



37
38
39
40
41
42
43
# File 'lib/lcp_ruby/metadata/permission_merger.rb', line 37

def self.merge_keyed(default_block, per_model_block)
  merged = {}
  sources = {}
  default_block.each   { |k, v| merged[k] = v; sources[k] = :default }
  per_model_block.each { |k, v| merged[k] = v; sources[k] = :per_model }
  [ merged, sources ]
end

.merge_record_rules(default, per_model) ⇒ Object

record_rules: per-model rules first (preserves authoring order; per- model rules thus evaluate first). Then append default rules whose ‘name:` is not already used by a per-model rule, in default’s original order.



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/lcp_ruby/metadata/permission_merger.rb', line 49

def self.merge_record_rules(default, per_model)
  merged = []
  sources = {}

  per_model.record_rules.each do |rule|
    merged << rule
    name = PermissionDefinition.rule_name(rule)
    sources[name] = :per_model if name
  end
  default.record_rules.each do |rule|
    name = PermissionDefinition.rule_name(rule)
    next if name && sources.key?(name)

    merged << rule
    sources[name] = :default if name
  end

  [ merged, sources ]
end