Module: Collavre::Creative::Permissible

Extended by:
ActiveSupport::Concern
Included in:
Collavre::Creative
Defined in:
app/models/collavre/creative/permissible.rb

Constant Summary collapse

PERMISSION_INVALIDATING_ATTRIBUTES =

Single declarative registry of which persisted-attribute changes invalidate the permission cache, and how each is rebuilt. A single after_commit dispatcher maps the accumulated changes through this map so a new mutation path can never silently skip a required rebuild.

:rebuild       -> rebuild_for_creative (self + descendant subtree)
:rebuild_owner -> update_owner (move the owner cache entry)

origin_id is immutable after create (see Linkable#attr_readonly), so its entry only fires on link creation and is defensive.

{
  "parent_id" => :rebuild,
  "user_id"   => :rebuild_owner,
  "origin_id" => :rebuild
}.freeze

Instance Method Summary collapse

Instance Method Details

#all_shared_users(required_permission = :no_access) ⇒ Object



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'app/models/collavre/creative/permissible.rb', line 60

def all_shared_users(required_permission = :no_access)
  base_creative = effective_origin(Set.new)
  ancestor_ids = [ base_creative.id ] + base_creative.ancestors.pluck(:id)
  required_permission_level = CreativeShare.permissions.fetch(required_permission.to_s)

  shares = CreativeShare.where(creative_id: ancestor_ids).includes(:user)
  shares_for_user_hash = shares.group_by(&:user_id)

  shares_for_user_hash.filter_map do |_user_id, user_shares|
    closest_share = CreativeShare.closest_parent_share(ancestor_ids, user_shares)
    next unless closest_share

    closest_permission_level = CreativeShare.permissions.fetch(closest_share.permission.to_s)
    next if closest_permission_level < required_permission_level

    closest_share
  end
end

#children_with_permission(user = nil, min_permission = :read) ⇒ Object

Returns only children for which the user has at least the given permission



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'app/models/collavre/creative/permissible.rb', line 37

def children_with_permission(user = nil, min_permission = :read)
  user ||= Collavre.current_user
  children_scope = effective_origin(Set.new).children
  children_ids = children_scope.pluck(:id)
  return [] if children_ids.empty?

  # The deny-invariant (owner wins → user entry, incl. a no_access deny,
  # beats public → effective-origin resolution) now lives in exactly one
  # place: PermissionFilter. This site used to re-implement it inline.
  accessible_ids = Collavre::Creatives::PermissionFilter.new(user: user)
    .readable_ids(children_ids, min_permission: min_permission)

  # readable_ids gates a linked shell on its origin being readable AND the
  # viewer being able to see the shell's placement (owns it, or it sits in
  # a subtree shared with the viewer). Listing one's OWN tree keeps the
  # prior policy that a viewer always sees their own children — including a
  # shell they own whose origin is no longer shared with them — so union
  # those owned rows back in. (Owner has admin, so this is rank-independent.)
  accessible_ids |= children_scope.where(user_id: user.id).pluck(:id) if user

  children_scope.where(id: accessible_ids).order(:sequence).to_a
end

#find_ai_agent(required_permission = :write) ⇒ Object



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'app/models/collavre/creative/permissible.rb', line 79

def find_ai_agent(required_permission = :write)
  base_creative = effective_origin(Set.new)
  ancestor_ids = [ base_creative.id ] + base_creative.ancestors.pluck(:id)
  required_permission_level = CreativeShare.permissions.fetch(required_permission.to_s)

  shares = CreativeShare.where(creative_id: ancestor_ids)
                        .joins(:user).merge(User.ai_agents)
                        .includes(:user)
  shares_for_user_hash = shares.group_by(&:user_id)

  shares_for_user_hash.each_value do |user_shares|
    closest_share = CreativeShare.closest_parent_share(ancestor_ids, user_shares)
    next unless closest_share

    closest_permission_level = CreativeShare.permissions.fetch(closest_share.permission.to_s)
    next if closest_permission_level < required_permission_level

    return closest_share.user
  end

  nil
end

#has_permission?(user, required_permission = :read) ⇒ Boolean

Returns:

  • (Boolean)


32
33
34
# File 'app/models/collavre/creative/permissible.rb', line 32

def has_permission?(user, required_permission = :read)
  Collavre::Creatives::PermissionChecker.new(self, user).allowed?(required_permission)
end