Опишите, как реализовать DSL (предметно-ориентированный язык).ruby-61

DSL — это специализированный язык для конкретной предметной области. Ruby с его гибким синтаксисом идеально подходит для создания внутренних DSL. Рассмотрим ключевые техники.

1. Базовые строительные блоки DSL

Методы-команды

class RecipeDSL
  def ingredient(name, amount)
    @ingredients ||= []
    @ingredients << {name: name, amount: amount}
  end

  def method_missing(name, *args)
    ingredient(name, args.first)
  end
end

Использование:

recipe = RecipeDSL.new
recipe.sugar "200g"
recipe.flour "300g"

2. Паттерн "Контекст выполнения"

module ConfigurationDSL
  def configure(&block)
    @config = OpenStruct.new
    @config.instance_eval(&block)
  end

  def setting(name, value)
    @config.send("#{name}=", value)
  end
end

class App
  extend ConfigurationDSL

  configure do
    setting :timeout, 30
    setting :environment, :production
  end
end

3. Цепочки методов

class QueryBuilder
  def initialize
    @conditions = []
  end

  def where(condition)
    @conditions << condition
    self
  end

  def limit(num)
    @limit = num
    self
  end

  def to_sql
    # Генерация SQL на основе условий
  end
end

query = QueryBuilder.new
          .where("age > 18")
          .where("status = 'active'")
          .limit(10)

4. Блоки и instance_eval

class MigrationDSL
  def initialize(&block)
    @actions = []
    instance_eval(&block)
  end

  def create_table(name, &block)
    @actions << {type: :create, name: name, block: block}
  end

  def add_column(table, name, type)
    @actions << {type: :add_column, table: table, name: name, column_type: type}
  end
end

migration = MigrationDSL.new do
  create_table :users do
    add_column :name, :string
    add_column :age, :integer
  end
end

5. Динамическое определение методов

class ValidatorDSL
  def self.rule_for(attribute, &validation)
    define_method("validate_#{attribute}") do
      instance_exec(&validation)
    end
  end
end

class UserValidator < ValidatorDSL
  rule_for :email do
    # Логика валидации email
  end

  rule_for :password do
    # Логика валидации пароля
  end
end

6. Сочетание с метапрограммированием

module RoutingDSL
  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def route(http_method, path, &handler)
      define_method("handle_#{http_method}_#{path.gsub('/', '_')}", &handler)
    end
  end
end

class Application
  include RoutingDSL

  route :get, '/users' do
    # Обработчик GET /users
  end
end

7. Лучшие практики создания DSL

  1. Ясность важнее краткости: DSL должен быть понятным
  2. Ограничьте область применения: Узкоспециализированный DSL эффективнее
  3. Предусмотрите обработку ошибок: Ясные сообщения об ошибках
  4. Документируйте: Хороший DSL требует хорошей документации
  5. Тестируйте как язык: Напишите тесты для всех возможных конструкций

8. Пример комплексного DSL

class TestFramework
  def self.describe(description, &block)
    test_suite = TestSuite.new(description)
    test_suite.instance_eval(&block)
    test_suite.run
  end
end

class TestSuite
  def initialize(description)
    @description = description
    @tests = []
  end

  def it(description, &test)
    @tests << {description: description, test: test}
  end

  def run
    puts "Running suite: #{@description}"
    @tests.each do |test|
      puts "Test: #{test[:description]}"
      test[:test].call
    end
  end
end

TestFramework.describe "Calculator" do
  it "adds numbers" do
    # Тестовая логика
  end
end

Резюмируем: Ruby предоставляет мощные инструменты для создания DSL через метапрограммирование, блоки, method_missing и динамическое определение методов. Хороший DSL должен быть выразительным, ограниченным предметной областью и хорошо документированным. Начинайте с простых конструкций, постепенно добавляя сложность по мере необходимости.