Hello, my name is Jeremy Fairbank, and I am a front-end and back-end web developer.
So, I have wanted to do a development blog for a long time, but I become easily distracted by Reddit, video games, or that random programming question that has been racking my brain for the past few days, and I will not be able to sleep tonight unless I sit down at my computer to solve it… Whew. I admit I like to see instantaneous results from my work (that is probably why I am a programmer), so I simply cannot be bothered with all the minutiae of setting up a blog.
However, I get frustrated when I have a stroke of brilliance, swooping in to save the day with an incredibly elegant solution to a programming problem, and really want to share that with someone. You see, I am the development team where I work, so I do not have the junior developer to teach or the senior developer to probe for feedback and criticism. Half of my solutions could be terrible, unmaintable wrecks just waiting to rear their ugly heads in production months later.
Don’t get me wrong, I still read articles and books and follow many a twitterer, so I’m always learning the thing I did last week was really stupid. OK, maybe not stupid, but definitely not as scaleable or maintainable as it could be. Ultimately, I LOVE to talk about programming, just ask my wife. I really want to be able to share my thoughts and opinions and garner feedback from fellow developers.
Therefore, what better way than to finally start my blog and jump right in with an interesting fix I implemented last week for a feature spec!
I can’t really share the specific domain models used, so I will use some suitable substitutes for the problem.
car/upcharges.js immediately fetches the upcharges as soon as the browser loads the file. In a normal user session, this is completely acceptable because by the time the user can begin to use the application, the upcharges have loaded.
As the user navigates throughout the application, he/she will eventually make decisions that may require the application to add or remove different upcharges to a car. To handle this, the application uses another collection for managing which upcharges have been applied to the car based on a
key field for each upcharge. Canonical upcharges have been seeded into the application with constant
key names. For example, if the user wanted chrome rims, the application would add an upcharge via:
The collection for applied upcharges might look something like this then:
OK, so this is all fine and dandy and it works. Ehh, maybe we should move the logic for adding and removing upcharges to the back-end, but that’s for another time and place to discuss. In fact, I’m more in favor of that idea, but it would defeat the purpose of this blog post.
Now, it’s time to set up a feature spec because we want to make sure that the upcharges display on the order totals page. Given we use RSpec, Capybara, and FactoryGirl, a very simplified version of the feature spec might look something like this:
So, I run the spec and it fails. Okay… Did I not properly set up the order and car? Is there some random attribute that is still
nil on a model that needs to have a value? After strategically (maybe a little randomly) placing
puts’s, I discovered that my
availableUpcharges variable was an empty collection. But, I added the chrome rims upcharge in my
A couple more
console.log’s and it begins to make sense. RSpec and Capybara load my application’s assets before the
background block runs. So, I fetch the available upcharges before any have even been created. This is problematic for a single page application that pulls remote data down once to use it for the remainder of the user’s session. Yes, it’s good to not create additional requests for remote data, but maybe immediately loading the data when the file loads isn’t a good idea either.
What can we do then? A better solution would be to asynchronously load and use the available upcharges the first time they’re needed. We could use a singleton object for managing the available upcharges like so:
Now, we just call
availableUpcharges.ensured first, passing it a callback function. We create one interface for accessing the real available upcharges, whether they’re loaded yet or not. Our singleton object handles loading them the first time. Once they’re loaded, it redefines it’s
ensured method to just simply pass the loaded upcharges to the callback.
I test it out in my application, but then I start to see multiple requests for the available upcharges. That’s the exact opposite of what this is supposed to do. Then, I realize my error. You see, our application automatically checks for multiple upcharges based on measurement constraints. There are multiple configurable measurements that are interrelated. So, if a user’s car had an atypical length-to-width ratio, then the application catches that on the order totals page and applies an appropriate upcharge. That way, if the user left the application and came back later, the application can recheck the car and update the upcharges as needed. Now, imagine there are several more of those measurement checks occurring all at once, meaning the application would call
removeUpchargeByKey several times, calling
_getUpchargeByKeyAndRun several times.
This presents a problem; if the application calls
_getUpchargeByKeyAndRun multiple consecutive times and the available upcharges haven’t loaded yet, then it is going to naturally create multiple requests for them because the
ensured function has not been redefined yet. We need to tweak our singleton object to take this into account:
So, what’s the difference? Well, until the available upcharges are loaded and the
ensured function is redefined, we simply queue up every callback passed in and check to see if a request has already been made. Once the upcharges load, we redefine the function as before. We also clear our queue, calling the new
ensured function definition with each queued callback. I run through the application again, and now there is only one request, and all the upcharge checks work properly. I rerun my spec and get my glorious green dot!
I hope you enjoyed this post and please provide whatever questions or feedback you have!