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 called
Explanation
define_method
is used to create a method with the given name.- The method is defined within the class
DynamicMethodExample
. - When
example.hello
is 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, 3
Explanation
method_missing
is 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_history
is 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
eval
takes 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
String
class is reopened, and a new methodshout
is 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_method
within 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: 5
Solution
- 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