29.03.2012
Mit throw und catch den Code aufräumen
Angenommen, wir möchten eine einfache Controller-Action, die prüft, ob ein bestimmter Code gültig ist. Sie soll true oder false zurückgeben und, falls der Code ungültig ist, den Grund angeben (ob er unbekannt ist oder ob er bereits verwendet wurde). Die Action könnte so aussehen:
def check
code = Code.find_by_value(params[:code])
if code
if code.used?
respond_with do |format|
format.json { render json: {valid: false, reason: "used"} }
end
else
respond_with do |format|
format.json { render json: {valid: true} }
end
end
else
respond_with do |format|
format.json { render json: {valid: false, reason: "unknown"} }
end
end
end
Durch die tiefe Verschachtelung wird der eigentliche Algorithmus – in diesem Fall ein einfacher – effektiv versteckt.
Drückt man das Ganze mit throw und catch aus, wird der Code etwas geradliniger:
def check
result = catch(:result) do
code = Code.find_by_value(params[:code])
throw :result, {valid: false, reason: "unknown"} unless code
throw :result, {valid: false, reason: "used"} if code.used?
{valid: true}
end
respond_with do |format|
format.json { render json: result }
end
end
Wir sind bei einer einzigen respond_with-Zeile angekommen, aber das Hash-Merging und die throws am Zeilenanfang sind nicht besonders hübsch.
Führen wir ein kleines Ruby-Mixin für die Hash-Klasse ein, das ihr eine compact-Methode spendiert, die ihr Pendant Array schon immer hatte:
class Hash
def compact
delete_if { |k, v| v.nil? }
end
end
Wenn wir damit den Response-Hash aufräumen und die throw-Anweisungen ans Ende verschieben, damit die einzelnen Schritte des Algorithmus sichtbar werden, erhalten wir die finale Version der Action:
def check
result = catch(:result) do
code = Code.find_by_value(params[:code])
throw :result, reason: "unknown" unless code
throw :result, reason: "used" if code.used?
{valid: true}
end
respond_with do |format|
format.json { render json: {valid: result[:reason].nil?, reason: result[:reason]}.compact }
end
end