Objective C-like Null Object Pattern in Ruby

At Custora we allow all the numbers on the screen to be lazy-loaded. We precache all of the main page views, but there are too many possible ways that a user can slice the data to pre-compute all of the values.

Writing code to deal with lazy loading can be a bit of a challenge. Basically, it means that whenever we get a number from our internal statistics api, we have to accept the possibility that it is not rendered correctly. And we use the statistic term loosely, it can be a float, an array, or some other ruby class. Anything that is computed from the raw transaction data goes through this API.

So to render the pages we make two passes. First to run through and figure out what statistics need to be calculated. We then send these all to delayed job, display a pending page that perodically polls to see if the jobs are done, and when the jobs are completed we display the rendered page.

We have an interesting technical challenge, how do we handle these uncomputed statistics. When we first started we ran into all kinds of errors. We would have an expression like:

number_with_precision(client.new_customer_value)

When client.new_customer_value was hit, the job would be enqueued, but then the page would fail to load.

To solve this we tried checking if a statistic was nil, but this made convoluted code

So to solve this we made a do nothing class with the goal to fail silently as much as possible. So we have a class that is designed to fail gracefully no matter what you do with it. It should fail silently if you call

stat * 100

or

stat[1]

or

1 * stat

or even

stat[1][0].first.to_s

We could have done something like:

class NilClass
def method_missing(*)
return nil
end
end

But this would have changed how nil behaves all over our application. Instead we decided to make a new class for unevaluated model statistics.

Another solution would have been to use the .try method on all statistics. But this is cumbersome, and doesn't work when the unevaluated statistic is passed to another function.

So we came up with the unevaluated model statistic class.

Code:

class UnevaluatedModelStatistic
def ==(_other)
false
end

def method_missing(_method,*_args) self end

def to_f 0.0 end

def to_str "" end

#for cohorts statistics def number_of_display_columns(_arg) 1 end

def *(_arg) 0 end

def +(_arg) 0 end

def -(_arg) 0 end

def coerce(_other) [self,_other] end

def /(_arg) 1 end end

On the second pass we end up with the completely rendered page.

This is the way we are attacking the problem. How would you approach it?