Class: Labimotion::KlassShare
- Inherits:
-
ApplicationRecord
- Object
- ApplicationRecord
- Labimotion::KlassShare
- Defined in:
- lib/labimotion/models/klass_share.rb
Overview
One user's right on one template (template-sharing.md §5). The owner row
(permission_level 30) is the transferable right; the klass's created_by column stays
immutable provenance. Deliberately NOT acts_as_paranoid, and the klass models carry no
dependent: towards it: deleting a template must leave its shares alone, so a restored
template comes back owned. Wiping the rows on delete would make restore itself a
privilege escalation — the template would return owner-less and fall open to the legacy
designer-wide gate.
Constant Summary collapse
- LEVELS =
Strictly nested ladder (requested ⊂ viewer ⊂ editor ⊂ owner), which is what makes
>=threshold checks valid. Spaced by ten so a future level is additive; never renumber — the one-owner-per-klass partial index freezespermission_level = 30into the schema.requestedis the odd one: it grants nothing. It is how an access request is stored — a row rather than a message, because the last hop of the request loop lands on the Designer, which polls no message list, so a message nobody sees is no notification at all. As a row it survives acknowledgement, granting and rejecting become ordinary state changes on it, and the unique (klass_type, klass_id, shared_with_id) index dedupes repeat asks for free.0 >= 10is false, so every threshold check refuses it without a special case. { requested: 0, viewer: 10, editor: 20, owner: 30 }.freeze
- SHAREABLE_USER_TYPES =
Which account types may hold a share row.
Adminis in the list because both seeding paths have to reach the same verdict: the seed migration is raw SQL overcreated_byand does write an owner row naming an administrator, while this validation used to refuse the runtime self-grant for the same actor and leave the template owner-less — the worse of the two, since owner-less falls back to the permanent legacy gate where any designer of the family may edit and delete it. Not hypothetical: thebackfill_klass_created_columnsmigration fills a NULLcreated_bywith the first system administrator, so a real population of templates is admin-created.GroupandDeviceDeprecatedstay out: a group is not an individual, and ELN reservesGroupfor device management. %w[Person Admin].freeze
Class Method Summary collapse
- .key_of(klass) ⇒ Object
- .owner_row_for(klass) ⇒ Object
-
.seed_owner!(klass, user_id) ⇒ Object
Writes the owner self-grant a fresh klass gets on every create path — bookkeeping, not enforcement, so it is unconditional but must never break the create it rides on: a host that has not migrated yet simply skips it (log only), and a concurrent duplicate means the owner row already exists, which is the goal state.
Instance Method Summary collapse
-
#level ⇒ Object
The integer, whatever the reader: the AR enum getter answers the label string, a bare test double answers the raw integer.
Class Method Details
.key_of(klass) ⇒ Object
77 78 79 |
# File 'lib/labimotion/models/klass_share.rb', line 77 def self.key_of(klass) "#{klass.class.name.split('::').last}:#{klass.id}" end |
.owner_row_for(klass) ⇒ Object
81 82 83 84 |
# File 'lib/labimotion/models/klass_share.rb', line 81 def self.owner_row_for(klass) for_klass(klass.class.name.split('::').last, klass.id) .find_by(permission_level: LEVELS[:owner]) end |
.seed_owner!(klass, user_id) ⇒ Object
Writes the owner self-grant a fresh klass gets on every create path — bookkeeping, not enforcement, so it is unconditional but must never break the create it rides on: a host that has not migrated yet simply skips it (log only), and a concurrent duplicate means the owner row already exists, which is the goal state.
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/labimotion/models/klass_share.rb', line 90 def self.seed_owner!(klass, user_id) return unless table_exists? return if owner_row_for(klass).present? create!(klass_type: klass.class.name.split('::').last, klass_id: klass.id, shared_with_id: user_id, created_by: user_id, permission_level: LEVELS[:owner]) rescue ActiveRecord::RecordNotUnique nil rescue ActiveRecord::RecordInvalid => e # An Admin creator no longer lands here — SHAREABLE_USER_TYPES admits it, which is the # point of the allowlist. What still can: a `created_by` naming an excluded type or an # account that no longer exists at all. The klass then stays owner-less on the legacy # gate, logged, rather than taking the create down with it. Labimotion.log_exception(e) nil end |
Instance Method Details
#level ⇒ Object
The integer, whatever the reader: the AR enum getter answers the label string, a bare test double answers the raw integer.
68 69 70 71 |
# File 'lib/labimotion/models/klass_share.rb', line 68 def level raw = raw.is_a?(Integer) ? raw : LEVELS.fetch(raw.to_sym) end |