Rubyists! In the process of doing an "extract method object" refactoring, I wound up with a service object that calls several other (inner) service objects. A simplified version is below.
Note that (a) I'm using a simple Result object to report success/failure back to the calling code, and (b) I'm passing the same Result instance around to subservices (to keep track of overall process state).
My question is this: in a multistep process, how should I handle failure in an intermediate step?
The options I can think of are:
- As in the example below (
do_phase_3 unless @result.failure?
), explicitly check for failure in a substep, and abort manually. - Raise an exception, which gets caught in the outer (class-level) .perform call, which then returns the result object as usual.
- Use throw/catch to accomplish the same effect as #2, above.
Am I missing other options? Which one would be best?
Following up on a tweet from @JEG2, I did a quick implementation of throw/catch. It does feel better than raising an exception, but doing something like
throw(:instafail)
and catching it in Service.perform doesn't work.I'll admit to a few minutes of head-scratching, but eventually I realized that there were two catch blocks on the stack, and the throw was terminating the child service (Phase[123], above), and the parent service was happily proceeding there.
I can think of a variety of ways to work around that (and @davetron5000 had two more), but they all suffer from excessive cleverness. In combination with the fact that both exceptions and throw/catch are a kind of "action at a distance", I think I have my answer: leave it alone.
Thanks to all (including several people at LivingSocial) for the feedback!