Книга: Metaprogramming Ruby 2
Назад: Callable Objects
Дальше: Quiz: A Better DSL

Writing a Domain-Specific Language

Where you and Bill, at long last, write some code.

“Enough talking about blocks,” Bill says. “It’s time to focus on today’s job. Let’s call it the RedFlag project.”

RedFlag is a monitor utility for the people in the sales department. It should send the sales folks a message when an order is late, when total sales are too low…basically, whenever one of many different things happens. Sales wants to monitor dozens of different events, and the list is bound to change every week or so.

Luckily for you and Bill, sales has full-time programmers, so you don’t have to write the events yourselves. You can just write a simple Domain-Specific Language. (You can read about DSLs in Appendix 2, .) The sales guys can then use this DSL to define events, like this:

 
event ​"we're earning wads of money"​ ​do
 
recent_orders = ... ​# (read from database)
 
recent_orders > 1000
 
end

To define an event, you give it a description and a block of code. If the block returns true, then you get an alert via mail. If it returns false, then nothing happens. The system should check all the events every few minutes.

It’s time to write RedFlag 0.1.

Your First DSL

You and Bill put together a working RedFlag DSL in no time:

 
def​ event(description)
 
puts ​"ALERT: ​#{description}​"​ ​if​ ​yield
 
end
 
load ​'events.rb'

The entire DSL is just one method and a line that executes a file named events.rb. The code in events.rb is supposed to call back into RedFlag’s event method. To test the DSL, you create a quick events file:

 
event ​"an event that always happens"​ ​do
 
true
 
end
 
event ​"an event that never happens"​ ​do
 
false
 
end

You save both redflag.rb and events.rb in the same folder and run redflag.rb:

<= 
ALERT: an event that always happens

“Success!” Bill exclaims. “If we schedule this program to run every few minutes, we have a functional first version of RedFlag. Let’s show it to the boss.”

Sharing Among Events

Your boss is amused by the simplicity of the RedFlag DSL, but she’s not completely convinced. “The people who write the events will want to share data among events,” she observes. “Can I do this with your DSL? For example, can two separate events access the same variable?” she asks the two of you.

“Of course they can,” Bill replies. “We have a Flat Scope ().” To prove that, he whips up a new events file:

 
def​ monthly_sales
 
110 ​# TODO: read the real number from the database
 
end
 
 
target_sales = 100
 
 
event ​"monthly sales are suspiciously high"​ ​do
 
monthly_sales > target_sales
 
end
 
 
event ​"monthly sales are abysmally low"​ ​do
 
monthly_sales < target_sales
 
end

The two events in this file share a method and a local variable. You run redflag.rb, and it prints what you expected:

<= 
ALERT: monthly sales are suspiciously high

“Okay, this works,” the boss concedes. “But I don’t like the idea of variables and methods like monthly_sales and target_sales cluttering the top-level scope. Let me show you what I’d like the DSL to look like instead,” she says. Without further ado, the boss grabs the keyboard and starts churning out code like nobody’s business.

Назад: Callable Objects
Дальше: Quiz: A Better DSL