DSL — это специализированный язык для конкретной предметной области. Ruby с его гибким синтаксисом идеально подходит для создания внутренних 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"
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
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)
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
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
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
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 должен быть выразительным, ограниченным предметной областью и хорошо документированным. Начинайте с простых конструкций, постепенно добавляя сложность по мере необходимости.