Metaprogramming in Ruby allows developers to write programs that can modify themselves at runtime. This powerful feature can be leveraged to create expressive and flexible domain-specific languages (DSLs).
Defining the Structure
First, let's define the core class of our DSL, TravelSearch
, along with auxiliary classes Air
, Hotel
, and Train
. These classes will encapsulate the details of each travel component.
class TravelSearch
attr_accessor :air, :hotel, :train
def initialize(&block)
instance_eval(&block)
end
def air(&block)
@air = Air.new(&block)
end
def hotel(&block)
@hotel = Hotel.new(&block)
end
def train(&block)
@train = Train.new(&block)
end
def to_s
"Air: #{@air}\nHotel: #{@hotel}\nTrain: #{@train}"
end
end
class Air
attr_accessor :from, :to, :date
def initialize(&block)
instance_eval(&block)
end
def from(from)
@from = from
end
def to(to)
@to = to
end
def date(date)
@date = date
end
def to_s
"from #{@from} to #{@to} on #{@date}"
end
end
class Hotel
attr_accessor :city, :date, :nights
def initialize(&block)
instance_eval(&block)
end
def city(city)
@city = city
end
def date(date)
@date = date
end
def nights(nights)
@nights = nights
end
def to_s
"in #{@city} on #{@date} for #{@nights} nights"
end
end
class Train
attr_accessor :from, :to, :via, :date, :with_seat_reservation
def initialize(&block)
instance_eval(&block)
end
def from(from)
@from = from
end
def to(to)
@to = to
end
def via(via)
@via = via
end
def date(date)
@date = date
end
def with_seat_reservation(with_seat_reservation)
@with_seat_reservation = with_seat_reservation
end
def to_s
"from #{@from} to #{@to} via #{@via} on #{@date} with seat reservation: #{@with_seat_reservation}"
end
end
Utilizing instance_eval
for DSL Construction
The instance_eval
method is a key component in our DSL. It allows us to evaluate the given block within the context of the current object, effectively changing the self
to the object the method is called on. This is crucial for making the DSL syntax intuitive and clean.
Creating the DSL Entry Point
We'll define a method search_travel
as the entry point for our DSL. This method initializes a TravelSearch
object and evaluates the block within its context.
def search_travel(&block)
travel_search = TravelSearch.new(&block)
puts travel_search
end
Example Usage
Here's an example of how our DSL can be used to define a travel search:
search_travel do
air do
from "SFO"
to "JFK"
date "2011-12-25"
end
hotel do
city "New York"
date "2011-12-25"
nights 3
end
train do
from "Milan"
to "Rome"
via "Florence"
date "2011-12-25"
with_seat_reservation true
end
end
Output
Running the above code will produce the following output:
Air: from SFO to JFK on 2011-12-25
Hotel: in New York on 2011-12-25 for 3 nights
Train: from Milan to Rome via Florence on 2011-12-25 with seat reservation: true
Using instance_eval
and metaprogramming, we created a flexible and readable DSL for travel searches in Ruby. This approach allows users to define complex data structures with minimal syntax, making the code more expressive and easier to understand.
Top comments (0)