4. Capistrano
namespace :deploy do
task :start, :roles => :app, :except => { :no_release => true } do
run "sudo monit start miapp_unicorn"
run "sudo monit -g resque start all"
end
end
Saturday, February 9, 13
5. RSpec
describe Math do
describe "#pow" do
it "computes the n-th-power of the receiver" do
3.pow(3).should == 27
end
end
end
Saturday, February 9, 13
15. # patient_dsl.rb
class PatientDSL
attr_reader :patient
def initialize(patient)
@patient = patient
end
delegate :age, :allergies, :gender, :labs, :medications,
:prescriptions, :providers, :visits, :survey, :to => :patient
def drug_cat(name)
(1..6).to_a.reverse.detect do |level|
medication = medications.detect do |m|
m.send("lvl#{level}conceptname") == name
end and break medication
end
end
def dosage(name)
drug(name).try(:dosage) || 0
end
# Many, many more methods
end
Saturday, February 9, 13
17. Recuerden, las reglas las ingresas usuarios, en una gran
caja de texto en la interfaz web del sistema
¿Que problemas pueden ocurrir con la solución hasta
ahora?
Saturday, February 9, 13
18. age
>=
18
and
patient.destroy!
Saturday, February 9, 13
25. # pharmmd_dsl.treetop (cont.)
rule boolean_operator
">=" /
"<=" /
">" /
"<" /
"==" /
"!="
end
rule function_call
function_name:([a-zA-Z_] [a-zA-Z_0-9]*) arguments:("(" argument
("," spaces? argument)* ")")? <FunctionNode>
end
rule argument
string /
date /
numeric_value
end
Saturday, February 9, 13
26. # pharmmd_dsl.treetop (cont.)
rule numeric_value
function_call /
number /
"(" spaces? numeric_value spaces? ")"
end
rule number
float /
integer
end
rule integer
"-"? digits
end
rule float
"-"? (digits)? "." digits
end
rule digits
[0-9]+
end
Saturday, February 9, 13
27. # pharmmd_dsl.treetop (cont.)
rule spaces
[sn]+
end
rule string
['"] [^'"]* ['"]
end
rule date
[0-9]+ "." time_unit "s"? ".ago"
end
rule time_unit
"day" /
"month" /
"year"
end
end
Saturday, February 9, 13
28. Por cierto, el lenguaje de gramáticas de
treetop es un DSL “externo”
Saturday, February 9, 13
29. Treetop hace el parsing extremadamente natural.
¡Sigamos el proceso a mano!
(si es que tenemos pizarra a mano)
Saturday, February 9, 13
30. age
>=
18
and
(drug_cat(“XYZ”)
or
drug_cat(“ABC”))
and
dosage(“PARACETAMOL”)
>
1000
Saturday, February 9, 13
34. ¡Rara vez la validación sintáctica es suficiente!
“Saltarina casa llovió perros perrunos”
(¡Español sintácticamente válido!)
Saturday, February 9, 13
35. rule function_call
function_name:([a-zA-Z_] [a-zA-Z_0-9]*)
arguments:("(" argument ("," spaces? argument)* ")")?
<FunctionNode>
end
Saturday, February 9, 13
36. require 'treetop'; require 'pharmmd_dsl'
class FunctionNode < Treetop::Runtime::SyntaxNode; end
class PharmmdDslValidator
attr_accessor :dsl, :errors
def initialize(dsl)
@dsl = dsl; @errors = []
end
def valid_dsl?
parser = PharmmdDslParser.new
parse_tree = parser.parse(@dsl)
if parse_tree.nil?
errors << "You have a syntax error: #{parser.failure_reason}"
else
validate_functions(parse_tree)
end
errors.empty?
end
def valid_functions
@valid_functions ||=
(PatientDSL.instance_methods - Object.instance_methods)
end
Saturday, February 9, 13
37. # (cont.)
def validate_functions(parse_tree)
element = parse_tree
if element.is_a? FunctionNode
name = element.function_name.text_value
unless valid_functions.include? name
errors << ("Function name #{element.text_value} is not a
valid function call")
end
end
if element.elements
parse_tree.elements.each do |element|
validate_functions(element)
end
end
end
end
Saturday, February 9, 13
38. age
>=
18
and
patient.destroy
#
invalido
Patient.destroy_all
#
invalido
system(“rm
-‐rf
/”)
#
invalido!
Saturday, February 9, 13
39. Errores amigables:
“Se esperaba ‘(‘ en linea X, columna Y”
“La función ‘system’ no es válida”
Saturday, February 9, 13
40. Y mucho más: ¡Solucion v3!
(Sólo una mirada rápida, que se nos acaba el tiempo)
Saturday, February 9, 13
42. quantity("lipitor") > 10 or drug("vicodin")
treetop, parse trees
(PharmdDSLValidator)
External
DSL
quantity("lipitor") > 10 or drug("vicodin")
(PharmdDSLPreProcessor)
(quantity("lipitor") > 10).or(drug("vicodin"))
ruby objs/metodos
(PharmdDSL)
Internal
DSL
(NumericExpr(20, ["Lipitor 50mg"]) > 10).or(
BooleanExpr(true, ["Vicodin 20mg"]))
Saturday, February 9, 13
43. quantity("lipitor") > 10 or drug("vicodin")
treetop, parse trees
(PharmdDSLValidator)
External
DSL
quantity("lipitor") > 10 or drug("vicodin")
(PharmdDSLPreProcessor)
(quantity("lipitor") > 10).or(drug("vicodin"))
ruby objs/metodos
(DenominatorQuery)
Internal
DSL
(OrCondition(
Condition('lipitor', {'$gt' => '10'}),
Condition('vicodin', {'$exists' => true}))
Saturday, February 9, 13
44. ...y mas
MongoDB no tenía OR en esa época,
por lo que optimizabamos el árbol de expresiones para
dejar los ORs lo mas “arriba” posible.
Ejemplo:
((X
or
Y)
and
(Z)
(Condition#optimize)
((X
and
Z)
or
(Y
and
Z))
Saturday, February 9, 13
45. Conclusión
• Parsing, árboles de expresiones,
compiladores, etc no fue tiempo perdido en
la U :)
• Pero siempre hay tradeoffs
• Probar un DSL interno primero. La solución
más simple que podría funcionar
• Luego un DSL externo, treetop lo hace fácil
• Finalmente un híbrido, si no queda otra
Saturday, February 9, 13