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
20: define_method in Practice
Imagine you're running a craft shop and you need to create 50 different stamps for 50 different employees' names. You could spend all afternoon carving each single stamp by hand, which is tedious and prone to error. Or, you could buy one "adjustable" stamp kit. With the kit, you have a single frame, and you just slide in a small plate with the specific name you want. The mechanism of stamping is the same every time; only the label changes.
In Ruby, define_method is that adjustable stamp kit. Normally, when you use def, you're carving a stamp by hand. You're telling Ruby exactly what the method is called and exactly what it does at the moment you write the code. But define_method lets you create methods programmatically while the program is actually running. You define the "mechanism" once, and then you can spin up as many methods as you need based on a list of names.
Stopping the Boilerplate Bleed
I've seen a lot of junior devs write code that looks like this when they're handling user roles:
def admin?
role == 'admin'
end
def editor?
role == 'editor'
end
def viewer?
role == 'viewer'
end
It's not "wrong," but it's boring. And the moment your boss tells you that you now need super_admin, billing_manager, and support_agent roles, you're stuck writing more of the same. It's a maintenance nightmare. Instead, we can use define_method to handle this dynamically.
class User
attr_accessor :role
# We define the list of roles we care about here
ROLES = [:admin, :editor, :viewer, :moderator]
ROLES.each do |role_name|
# This is where the magic happens
define_method("#{role_name}?") do
self.role == role_name.to_s
end
end
def initialize(role)
@role = role
end
end
user = User.new('admin')
puts user.admin? # => true
puts user.editor? # => false
The Mechanics of the Block
Here is the crucial part you need to understand: define_method takes a symbol (or a string) as the name of the method and a block of code as the body. Because it's a block, it captures the surrounding scope. In the example above, the role_name variable from the each loop is "remembered" inside the method body.
You might be wondering: "Why can't I just use def inside the loop?" Try it. You'll realize that def expects a hard-coded method name. You can't do def "#{role_name}?" because the Ruby parser doesn't work that way. define_method is the only way to turn a variable into a method name.
Don't Overdo the Magic
I'll give you a piece of advice I wish someone gave me early on: just because you can metaprogram everything doesn't mean you should. When you use define_method, the methods don't exist in your source code. If a new developer joins your team and searches the codebase for def admin?, they won't find it. They'll be staring at the screen wondering where the hell that method is coming from.
Use this when you have a clear, repetitive pattern based on a data set (like roles, API endpoints, or database columns). If the logic for each method is wildly different, just stick to regular def. Clarity always beats cleverness in a production environment.
📋 Practical Task
Building a Dynamic Attribute Validator
You are building a Profile class. Instead of writing individual validation methods for every field (like username_present?, email_present?, etc.), you need to implement a system using define_method.
Requirements:
- Create a
Profileclass with anattr_accessor :details(which will be a Hash). - Create a constant array called
REQUIRED_FIELDScontaining:username,:email, and:bio. - Use
define_methodto dynamically create a method for each field inREQUIRED_FIELDS. - Each generated method should follow the naming convention
field_present?(e.g.,username_present?). - The method should return
trueif the key exists in thedetailshash and the value is not nil or empty; otherwise, returnfalse.
Test your code with:
profile = Profile.new
profile.details = { username: "ruby_pro", email: "" }
puts profile.username_present? # Should be true
puts profile.email_present? # Should be false
puts profile.bio_present? # Should be falseThere are no comments for now.