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.
If you hit one of these pages, you see a loading bar that looks like this:
Writing code to deal with all the 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:
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
(client.new_customer_value ? number_with_precision(client.new_customer_value) : nil)
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
1 * stat
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.
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?