We're always trying to improve how we develop our product and we recently found a technique that helps us reduce boilerplate when reusing code that we'll share today: using Cocktail to create mixins for Backbone models that respect inheritance.
Sharing implementation via inheritance
In our app we have dozens of Backbone models and collections and hundreds of views, many of which share the same requirements and feature sets. We use inheritance heavily in many places to share common code, but sometimes inheritance isn't the right model when what we simply want to do is inject some logic into a model, collection, or view.
For instance, take the example of a collection that is a nested resource--its url would look something like this:
/api/products/34/digital_assets. This returns the set of digital assets for a product--it's really a collection whose parent is the product with id
Here's what such a model would look like:
This is fine and succinct, but products at Salsify have several nested resources with a collection (or model for single-assign) backing each in the client. We could implement this using inheritance:
But this doesn't work if we also have nested models — we'd have to copy the implementation of initialize so we could call the correct super constructor.
Additionally, what if we needed other functionality for our collections? What if some of our collections supported paging, and others didn't? We could add that to the inheritance chain as well:
But this begins to get unruly, especially if we need our collections to be filterable as well. Do we add that to the inheritance hierarchy as well? Do we implement a does-everything collection that has "switches" to enable and disable features? Neither seems entirely appropriate.
Enter mixins. A mixin is collection of functionality that can be added to a class via any other method than inheritance. Most languages support only single inheritance which makes mixing functionality from multiple parent classes via inheritance impossible. A mixin with backbone can be as simple as:
This works great if all you're doing is adding functionality via adding new methods. But what if you need to bind some events in initialize? This method doesn't work—you almost certainly need to call the super's initialize as well and you can't do that without really knowing your inheritance chain.
Enter Cocktail. Cocktail provides a method that mixes functionality into a class that preserves calling the target class's functionality for methods that conflict between the mixin and the target. Here's an example:
Cocktail will ensure that both initialize methods are called when a DigitalAssetsCollection is instantiated. Cocktail will merge hashes as well — if you mixin a view that defines an event listener the event listener will be bound:
One more thing to make Cocktail even more succinct: patch backbone's extend with:
Cocktail.patch(Backbone); Now you can define your mixins inline in your class definition:
Super simple and succinct. More info about how Cocktail works is available on its Github page.