DSL in Ruby with Metaprogramming

RMAG news

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.

Please follow and like us:
Pin Share