Ruby
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Methods and Blocks
-
Section 4: Object-Oriented Ruby
-
Section 5: Metaprogramming
-
Section 6: Working with Data and Files
-
Section 7: Ruby Frameworks Overview
-
Section 8: Ecosystem and Testing
-
Section 9: Practical Projects
-
Section 10: Interview Practice
-
Section 11: Data Structures and Algorithms in Ruby
-
Section 12: More Practice Exercises
-
Section 13: Enumerable and Functional Style
-
Section 14: More OOP Practice
-
Section 15: More Testing
-
Section 16: Enumerable and Comparable Modules In Depth
-
Section 17: Ruby Standard Library: Core Utilities
-
Section 18: Ruby Standard Library: Data and Security
-
Section 19: Ruby Standard Library: CLI and Text
-
Section 20: Ruby Networking
-
Section 21: Ruby on Rails Deep Dive
-
Section 22: Ruby Metaprogramming Deep Dive
-
Section 23: Ruby Design Patterns
-
Section 24: Ruby Concurrency
-
Section 25: Ruby Testing Deep Dive
-
Section 26: Ruby Gems and Packaging
-
Section 27: Ruby Performance
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Rails API Development
-
Section 31: Rails Authentication and Authorization
-
Section 32: Rails Testing Deep Dive
-
Section 33: Rails Performance
-
Section 34: Rails Deployment
-
Section 35: Sinatra and Lightweight Ruby Web Apps
-
Section 36: More Ruby Language Deep Dive
-
Section 37: Ruby 3.x Modern Features
-
Section 38: More Data Structures in Ruby
-
Section 39: More Practical Projects
-
Section 40: Ruby Ecosystem Tools
-
Section 41: More Practice and Review
-
Section 42: Final Practice and Mastery
-
Section 43: Ruby Interview Deep Dive
-
Section 44: Ruby Background Processing Deep Dive
-
Section 45: Ruby GraphQL
-
Section 46: Ruby Object Model Deep Dive
-
Section 47: Ruby Hanami Framework Overview
-
Section 48: Ruby gRPC and Protocol Buffers
-
Section 49: Ruby Data Processing
-
Section 50: Ruby Search Integration
-
Section 51: Ruby File Upload and Media
-
Section 52: Ruby Email and Notifications
-
Section 53: Ruby Admin Panels
-
Section 54: Ruby Feature Flags and Experimentation
-
Section 55: Ruby Monitoring and Observability
-
Section 56: Ruby Docker and Deployment Deep Dive
-
Section 57: Ruby Security Deep Dive
-
Section 58: More Advanced Metaprogramming
-
Section 59: More Final Projects
128: Pundit for Authorization Policies
Authorization is one of those things that seems trivial when you're starting a project. You've got a user, you've got a resource, and you just need to make sure the user is allowed to touch that resource. In the beginning, the most intuitive place to put that logic is right in the controller action. It feels direct, and for a tiny app, it works.
The sprawl of conditional checks
Imagine we're building a project management tool where users can edit tasks. If you're taking the naive approach, your TasksController probably looks something like this:
def edit
@task = Task.find(params[:id])
if current_user.admin? || @task.project.owner == current_user
render :edit
else
redirect_to root_path, alert: "You aren't allowed to do that."
end
end
def update
@task = Task.find(params[:id])
if current_user.admin? || @task.project.owner == current_user
if @task.update(task_params)
redirect_to @task
else
render :edit
end
else
redirect_to root_path, alert: "You aren't allowed to do that."
end
end
At first glance, this is clear. But look at the repetition. I've written the same if statement twice. Now, imagine this app grows. You add a destroy action, a change_status action, and maybe a separate ProjectController that needs similar checks. Suddenly, your authorization logic is scattered across a dozen files. If the business requirement changes—say, we now allow "Project Managers" to edit tasks too—you have to hunt down every single one of those if blocks and update them. It's a recipe for a security hole because you'll inevitably miss one.
Moving the logic into Policy objects
This is where Pundit comes in. Instead of asking the controller to figure out if a user can do something, Pundit suggests we move that question into a plain old Ruby object (PORO) called a Policy. The idea is simple: for every model you have, you create a corresponding policy class.
Here is how we'd rewrite that task logic. We create a TaskPolicy that takes a user and the record they are trying to manipulate:
class TaskPolicy < ApplicationPolicy
def update?
user.admin? || record.project.owner == user
end
def edit?
update?
end
def destroy?
user.admin?
end
end
Now, back in the controller, we don't write conditionals. We just call authorize. Pundit looks at the class of the object you passed in (a Task), finds the TaskPolicy, and calls the method that matches the current controller action (update?).
def update
@task = Task.find(params[:id])
authorize @task
if @task.update(task_params)
redirect_to @task
else
render :edit
end
end
I love this approach because the controller is now only responsible for the flow of the request, not the rules of the business. If I need to change who can edit a task, I go to one file—the policy—and I change it once. The controller doesn't even need to know the rules changed; it just knows it asked Pundit and Pundit said "yes" or "no".
The trade-off: Boilerplate vs. Maintainability
To be fair, Pundit adds a bit of overhead. You're creating more files. You're adding a dependency to your Gemfile. If you're building a prototype that will only ever have two users, this is probably overkill. You're adding a layer of indirection that a junior dev might find confusing for the first ten minutes.
But the moment your app has more than three roles or more than five models, that "boilerplate" becomes your safety net. More importantly, these policy objects are incredibly easy to test. Since they're just Ruby classes, you don't have to spin up a full integration test or mock a session to check if an admin can delete a task; you just instantiate the policy with a mock user and a mock task and assert that update? returns true. That kind of test speed is what keeps a project maintainable over years, not weeks.
📋 Practical Task
Implementing a Document Access Policy
You are working on a document sharing app. Currently, the DocumentsController has authorization logic leaked into the show and destroy actions. Your goal is to move this logic into a Pundit policy to clean up the controller.
The Requirements:
- Any user can
showa document if it is marked aspublic: true. - A user can
showa document if they are theauthorof that document. - Only the
authorof the document or a user with theadminrole candestroya document.
Your Task:
- Create a
DocumentPolicyclass. - Implement the
show?method based on the public status and authorship rules. - Implement the
destroy?method based on the authorship and admin rules. - Update the
DocumentsController#showandDocumentsController#destroyactions to use theauthorize @documentmethod, removing the existingif/elseauthorization blocks.
There are no comments for now.