class DocumentMembership < ApplicationRecord belongs_to :document belongs_to :user belongs_to :share_link, optional: true enum :role, { viewer: 0, editor: 1 }, validate: true enum :source, { invite: 0, link: 1 }, validate: true # Grant access. An invitation is explicit: it sets exactly the invited role # and survives revoking any link. A link never weakens existing access and # never turns an invitation back into link-based access. def self.grant!(document:, user:, role:, source:, share_link: nil) return if document.owner_id == user.id m = find_or_initialize_by(document:, user:) if source.to_s == "invite" m.assign_attributes(role:, source: :invite, share_link: nil) elsif m.new_record? m.assign_attributes(role:, source: :link, share_link:) elsif m.link? && roles[role.to_s] > roles[m.role] m.assign_attributes(role:, share_link:) end m.save! m end end