A plain Ruby policy object (no Pundit dependency) driven by the event_amigo_connectors join table, with a fallback so an event's creator is never locked out.
app/policies/event_policy.rb
Ruby
full file, 48 lines
# app/policies/event_policy.rb
class EventPolicy
attr_reader :user, :record
alias_method :amigo, :user
def initialize(user, record)
@user = user
@record = record
end
# --- top-level permissions (public) ---
def create? = user.present?
def show? = true
def update? = admin? || lead? || assistant?
def destroy? = admin? || lead?
# Connectors may be managed by admin, lead, or assistant
def manage_connectors? = admin? || lead? || assistant?
def manage_locations? = manage_connectors?
# Roles may be changed only by admin or lead (NO assistant)
def manage_roles? = admin? || lead?
private
def admin?
amigo&.admin? || false
end
def connector
return nil unless amigo && record
@connector ||= record.event_amigo_connectors.find_by(amigo_id: amigo.id)
end
def lead?
# Primary: check the connector join table (the authoritative source).
# Fallback: compare directly against events.lead_coordinator_id so the
# event creator is never locked out if their connector row is missing
# (e.g. events created before the connector backfill).
!!connector&.lead_coordinator? || record&.lead_coordinator_id == amigo&.id
end
def assistant?
!!connector&.assistant_coordinator?
end
end
One-liner endless-method definitions (Ruby 3) keep the permission matrix scannable at a glance.
# app/controllers/api/v1/events_controller.rb
create / update / destroy + authorize_event!
# POST /api/v1/events
def create
policy = EventPolicy.new(current_amigo, nil)
return render(json: { error: "Unauthorized" }, status: :unauthorized) unless policy.create?
event = Events::CreateEvent.new.call(creator: current_amigo, attrs: event_params)
render json: event, serializer: EventSerializer,
include: [:primary_event_location], status: :created
end
# PATCH/PUT /api/v1/events/:id
def update
return unless authorize_event!(@event, :update?)
Event.transaction do
if params[:new_lead_coordinator_id].present?
roles_policy = EventPolicy.new(current_amigo, @event)
unless roles_policy.manage_roles?
render json: { error: "Unauthorized" }, status: :unauthorized
raise ActiveRecord::Rollback
end
# ...reassign lead_coordinator via the connector table
end
@event.update!(event_params)
end
end
# DELETE /api/v1/events/:id
def destroy
return unless authorize_event!(@event, :destroy?)
@event.destroy!
render json: { message: "Event successfully deleted." }, status: :ok
end
private
def authorize_event!(record, action)
policy = EventPolicy.new(current_amigo, record)
return true if policy.public_send(action)
render json: { error: "Unauthorized" }, status: :unauthorized
false
end
A single authorize_event! helper checks the right predicate per action; reassigning the lead coordinator gets its own stricter check inline.