Authorization
🚧 Authorization
🏷️ The Simplest Approach: A Role Column
enum stores role as a plain integer in the database but gives you named, readable methods (admin?, member?) instead of comparing raw numbers everywhere — a small taste of Rails' broader convention-over-configuration habit applied to a single column. This is enough for the simplest cases:
The moment authorization rules get more nuanced than "admin or not" — "the post's own author OR an admin can edit it," "a moderator can hide comments but not delete posts" — hand-written unless checks scattered across every controller action become genuinely hard to keep consistent, and easy to forget in a new action entirely. This is the exact same "duplicated across every entry point" problem Chapter 1 Course 1's validations chapter solved by centralizing rules on the model. Authorization needs its own centralizing pattern too.
📜 Pundit — Policy Objects
Pundit's convention: one policy class per model (PostPolicy for Post, matching the same naming pattern controllers and models already follow), with a method per action ending in ? — the same predicate-method naming convention from both Ruby courses. Every rule for a given model lives in exactly one file.
authorize @post automatically looks up PostPolicy (from the object's class name) and calls the method matching the current action name — update calls update?. rescue_from is Rails' framework-wide exception handler declaration, catching the raised error anywhere in the app and redirecting gracefully instead of showing a raw error page.
🔍 Checking Permissions in Views
policy(@post).update? reuses the exact same PostPolicy logic to conditionally show the edit link — no separate, potentially-inconsistent permission check duplicated in the view. Contrast this with Express, where a role check to conditionally render a button in an EJS template is typically a separate, hand-written if statement with no guaranteed relationship to whatever check the corresponding route handler actually performs.
🆚 Pundit vs CanCanCan
Pundit — one policy per model
Rules for Post live in PostPolicy, rules for Comment in CommentPolicy. Fits naturally alongside Rails' existing one-class-per-concern convention (one controller per resource, one model per table).
CanCanCan — one Ability class, everything
A single Ability class defines every permission for every model in one place, using can :update, Post, user_id: user.id-style declarations. Convenient for seeing the whole permission system at a glance; can become one large, sprawling file as an app grows.
Both are well-established, widely-used gems — this course continues with Pundit specifically because its file-per-model split matches the organizational convention already established for controllers, models, and (in the next chapter) test files.
📜 Authorization, Side by Side
Express (manual) vs Rails (Pundit)
| Concern | Express (typical) | Rails + Pundit |
|---|---|---|
| Where rules live | Scattered if-checks per route, or ad hoc middleware | One policy class per model |
| Controller check | Hand-written conditional per route | authorize @record — one line, consistent everywhere |
| View-level check | Separate, potentially inconsistent logic | policy(@record).action? — same source of truth |
| Unauthorized handling | Manual per route | rescue_from, handled once, app-wide |
💻 Coding Challenges
Challenge 1: A Role Column
Write the migration and model changes to add a role enum (member/admin) to User. Then write a destroy action on a CommentsController that only allows admins to delete a comment, redirecting with an alert otherwise.
Goal: Build the simplest possible role-based check before reaching for a gem.
Challenge 2: A Pundit Policy
Write a CommentPolicy where update? allows the comment's own author or an admin, and destroy? allows the comment's author, the post's author, or an admin (assume record.post.user gives you the post's author). Then write the authorize @comment call in the controller's update action.
Goal: Practice writing policy logic more nuanced than a single role check.
Challenge 3: Conditional View Links
Using the CommentPolicy from Challenge 2, write the ERB for a comment partial that only shows "Edit" and "Delete" links when policy(comment) permits each action respectively.
Goal: Reuse policy logic in a view instead of duplicating the permission check.
This chapter is really Course 1's validations chapter, replayed for a different concern: rules written once, in one predictable location, apply consistently everywhere they're needed — the controller, the view, anywhere else the app might eventually check permissions. The specific mechanism differs (validates on a model vs a separate policy class), but the underlying principle — one source of truth beats duplicated logic — is the same idea this course keeps returning to.
🎯 What's Next
Next chapter: Testing Rails Apps — RSpec request and model specs, and FactoryBot, building directly on Ruby Course 2's RSpec chapter to test everything covered so far, including this chapter's policies.