DEV Community

francesco agati
francesco agati

Posted on

DSL in Ruby with Metaprogramming

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)