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
89: instance_eval and class_eval
I was working on a project recently where I wanted to create a configuration DSL—you know, one of those clean blocks of code where you just list settings without having to repeat the object name over and over. I started with a simple class, but I quickly hit a wall regarding how Ruby handles scope.
The "Self" Problem
Let's look at what I first tried. I wanted a ProjectConfig class that could take a block of settings. It felt intuitive: I'd pass a block, and the class would run it.
class ProjectConfig
attr_accessor :name, :version
def initialize(&block)
block.call if block
end
end
config = ProjectConfig.new do
self.name = "MyAwesomeApp"
self.version = "1.0.0"
end
Now, this actually works, but it's clunky. I still have to use self.name because inside that block, self is actually the main object (the top-level scope), not the ProjectConfig instance. If I tried to just call name = "MyAwesomeApp", Ruby would think I'm trying to create a local variable called name, and my object would stay empty. That's not the "clean" feel I'm after.
Shifting Context with instance_eval
I wondered if there was a way to actually *move* the execution of that block inside the object. That's where I stumbled back into instance_eval. Let's try swapping block.call for instance_eval and see what happens to the scope.
class ProjectConfig
attr_accessor :name, :version
def initialize(&block)
instance_eval(&block) if block
end
end
config = ProjectConfig.new do
# No 'self' needed anymore!
@name = "MyAwesomeApp"
@version = "1.0.0"
end
puts config.name # => MyAwesomeApp
Wait, look at what happened. By using instance_eval, I've effectively told Ruby: "Take this block and execute it as if it were written right inside the instance of this class." Suddenly, self inside the block is the config object. I can access instance variables directly or call private methods that would normally be off-limits from the outside.
It's powerful, but a bit dangerous. If I have a local variable in my main script with the same name as a method in my class, instance_eval shifts the priority. It's essentially a "context switch."
Modifying the Blueprint with class_eval
While I was playing with this, I realized I wanted to do something different. I didn't want to change a specific *instance*; I wanted to add a new method to the ProjectConfig class itself, but I wanted to do it from outside the original class definition—maybe based on some plugin or external configuration.
I tried instance_eval on the class itself, like this:
ProjectConfig.instance_eval do
def summary
"#{name} v#{version}"
end
end
When I ran this, I got a NoMethodError when calling config.summary. Why? Because ProjectConfig is an instance of the class Class. Using instance_eval on it added a class method, not an instance method. I could call ProjectConfig.summary, but not config.summary.
To fix this, I need class_eval. This tells Ruby: "Treat this block as if it were written inside the class ProjectConfig ... end block."
ProjectConfig.class_eval do
def summary
"#{name} v#{version}"
end
end
puts config.summary # => MyAwesomeApp v1.0.0
Now the method is available to every single instance of ProjectConfig. The distinction is subtle but critical: instance_eval changes the context to a specific object (which could be a class object, resulting in a class method), while class_eval specifically targets the class's body to define instance methods.
📋 Practical Task
Building a Dynamic API Permission Guard
You are building a security module where permissions are defined dynamically. Your goal is to create a PermissionGuard class that uses instance_eval to allow a "clean" DSL for defining which roles have access to which actions.
Requirements:
- Create a
PermissionGuardclass with aninitializemethod that accepts a block and usesinstance_evalto execute it. - Inside the class, create a method called
allow(role, action)that stores these permissions in a hash (e.g.,@permissions = { "admin" => ["delete", "edit"] }). - Implement a method
permitted?(role, action)that returnstrueorfalse. - Instantiate the guard using a block like this:
guard = PermissionGuard.new do allow "admin", "delete" allow "editor", "edit" end - Verify that
guard.permitted?("admin", "delete")returnstrueandguard.permitted?("editor", "delete")returnsfalse.
There are no comments for now.