Metaprogramming in Ruby is a powerful feature that allows you to write code that writes code. This can make your programs more flexible and reduce redundancy. In this section, we will explore the key concepts of metaprogramming, provide practical examples, and offer exercises to help you master this advanced topic.
Key Concepts
- Dynamic Methods: Creating methods at runtime.
- Method Missing: Handling calls to undefined methods.
- Class Macros: Using methods to define other methods.
- Eval: Executing Ruby code stored in strings.
- Open Classes: Modifying existing classes.
Dynamic Methods
Dynamic methods allow you to define methods at runtime. This can be useful for creating methods based on user input or other runtime data.
Example
class DynamicMethodExample
def self.create_method(name)
define_method(name) do
puts "Method #{name} called"
end
end
end
DynamicMethodExample.create_method(:hello)
example = DynamicMethodExample.new
example.hello # Output: Method hello calledExplanation
define_methodis used to create a method with the given name.- The method is defined within the class
DynamicMethodExample. - When
example.hellois called, it outputs "Method hello called".
Method Missing
method_missing is a powerful way to handle calls to methods that do not exist.
Example
class MethodMissingExample
def method_missing(name, *args)
puts "You tried to call #{name} with arguments #{args.join(', ')}"
end
end
example = MethodMissingExample.new
example.some_method(1, 2, 3) # Output: You tried to call some_method with arguments 1, 2, 3Explanation
method_missingis overridden to handle calls to undefined methods.- It takes the method name and any arguments passed to it.
- In this example, it simply prints out the method name and arguments.
Class Macros
Class macros are methods that define other methods. They are often used in libraries to provide a DSL (Domain-Specific Language).
Example
class MacroExample
def self.attr_accessor_with_history(attr_name)
attr_name = attr_name.to_s
attr_reader attr_name
attr_reader "#{attr_name}_history"
define_method("#{attr_name}=") do |value|
instance_variable_set("@#{attr_name}", value)
@history ||= []
@history << value
instance_variable_set("@#{attr_name}_history", @history)
end
end
end
class Person < MacroExample
attr_accessor_with_history :name
end
person = Person.new
person.name = "Alice"
person.name = "Bob"
puts person.name_history.inspect # Output: ["Alice", "Bob"]Explanation
attr_accessor_with_historyis a class macro that creates an accessor with history tracking.- It defines a getter for the attribute and its history.
- It also defines a setter that updates the history each time the attribute is set.
Eval
eval allows you to execute Ruby code stored in strings. Use it with caution as it can introduce security risks.
Example
Explanation
evaltakes a string containing Ruby code and executes it.- In this example, it prints "Hello from eval".
Open Classes
Ruby allows you to reopen and modify existing classes. This can be useful for adding methods to built-in classes.
Example
Explanation
- The
Stringclass is reopened, and a new methodshoutis added. - This method converts the string to uppercase and adds exclamation marks.
Practical Exercises
Exercise 1: Dynamic Methods
Create a class DynamicGreeter that dynamically defines a method greet_<name> for each name in an array.
class DynamicGreeter
def self.create_greet_methods(names)
names.each do |name|
define_method("greet_#{name}") do
puts "Hello, #{name}!"
end
end
end
end
DynamicGreeter.create_greet_methods(["Alice", "Bob", "Charlie"])
greeter = DynamicGreeter.new
greeter.greet_Alice # Output: Hello, Alice!
greeter.greet_Bob # Output: Hello, Bob!
greeter.greet_Charlie # Output: Hello, Charlie!Solution
- Use
define_methodwithin a loop to create methods for each name. - Call the dynamically created methods to verify they work.
Exercise 2: Method Missing
Create a class FlexibleCalculator that can handle basic arithmetic operations (add, subtract, multiply, divide) using method_missing.
class FlexibleCalculator
def method_missing(name, *args)
if name.to_s =~ /^(add|subtract|multiply|divide)_(\d+)_and_(\d+)$/
operation, num1, num2 = $1, $2.to_i, $3.to_i
case operation
when "add"
num1 + num2
when "subtract"
num1 - num2
when "multiply"
num1 * num2
when "divide"
num1 / num2
else
super
end
else
super
end
end
end
calculator = FlexibleCalculator.new
puts calculator.add_2_and_3 # Output: 5
puts calculator.subtract_5_and_2 # Output: 3
puts calculator.multiply_3_and_4 # Output: 12
puts calculator.divide_10_and_2 # Output: 5Solution
- Use a regular expression to match method names and extract numbers.
- Perform the corresponding arithmetic operation based on the method name.
Conclusion
Metaprogramming in Ruby allows you to write more flexible and dynamic code. By understanding and utilizing dynamic methods, method_missing, class macros, eval, and open classes, you can create powerful and concise programs. Practice these concepts with the provided exercises to reinforce your understanding and prepare for more advanced Ruby programming.
Ruby Programming Course
Module 1: Introduction to Ruby
Module 2: Basic Ruby Concepts
Module 3: Working with Collections
Module 4: Object-Oriented Programming in Ruby
- Classes and Objects
- Instance Variables and Methods
- Class Variables and Methods
- Inheritance
- Modules and Mixins
Module 5: Advanced Ruby Concepts
Module 6: Ruby on Rails Introduction
- What is Ruby on Rails?
- Setting Up Rails Environment
- Creating a Simple Rails Application
- MVC Architecture
- Routing
Module 7: Testing in Ruby
- Introduction to Testing
- Unit Testing with Minitest
- Behavior-Driven Development with RSpec
- Mocking and Stubbing
