Mark Daggett's Blog

Innovator & Bricoleur

Crowd Sourcing Concepts

Below is the results of a brainstorming session with myself and Hege Sæbjørnsen as we thought through the concepts of crowd sourcing. We proposed several questions to one another and then attempted to answer them, or at least frame the question more fully. These notes are pretty raw, and much of the answers are well-documented elsewhere. However, I still thought it was worth sharing.

Crowd Sourcing

The use of a collection of people with expert knowledge / interest around a very specific topic to fill a role that has been typically held by a few highly educated “editors” (wisdom of the crowds).

Crowd sourcing is an active engagement not only passively viewing the content. This is a two way street, you can’t strip mine the crowd, you must engage in a conversation and allow for two way conversation. You must harvest and plant.

Crowd

Collections subgroups or individuals at varying levels of engagement linked around some common attribute(s).

potential subgroups

  • The Unaware Public
  • Students
  • Researcher / Educator
  • Artists / Designers
  • Makers
  • Engineers / Scientists
  • Social Innovators / Entrepreneurs
  • Politicians
  • Professionals
  • Grassroots Organizations
  • Physically located within a specific proximity
  • Contextual community on another platform (Facebook, Twitter, Flickr)
  • Griefers / Trolls (spreaders of misinformation, or just enjoy conflict)

Do we have priority subgroups and who are they, and how do we speak to them?

Once you define the crowd composition then you choose the target audience to source from.

Source

  • Ideas / Opinions / Solutions / Visions in response to our brief
  • Their social graph, and sphere of influence
  • Their money
  • Curation (potentially & to what extent)
  • Vet and or Improve Submissions
  • Volunteers / local ambassadors

What Media Do We Source

Are we limiting people’s inputs when we state “read” stories?

  • Biographies / Essays
  • Comments on sourced material
  • Poems / Quotes etc.
  • Documentary Photography
  • Fine-art Photography
  • Documentary Video
  • Fine-art Video
  • Tools & Tool Making
  • Research
  • Schematics / Blueprints

Goals Crowd Sourcing

  • Engaging a larger distributed audience
  • Collecting and then refining of raw materials
  • Democratic path to engagement / flattened hierarchy / grass-roots
  • composition over aggregation
  • Build a base of future (contextually relevant) engagement with a slice of the crowd

Tasks for the crowd

  • create or upload their content to the site
  • find existing relevant content created by others and share it on the site.
  • make linkages between content through activities like tagging (folksonomies)
  • download readymade content and share it in the local community
  • build a knowledge base around future

Ways To Curate

  • Crowd Voting, likes, retweets, tags, replies etc.
  • Local Expert, who promotes certain content over other
  • Selection Panel / Judging Panel, which offers periodic content review and selection

Expectation of the Crowd

  • Clarity of engagement a specific call to action
  • A way to visualize the crowd’s input
  • A way to visualize the crowd through metrics (e.g. countries, total count, visitors)
  • A way to visualize the crowd’s influence / impact
  • A low barrier of entry
  • A way to find my community

What’s in it for the Crowd member

  • Ready made distribution platform
  • Tools to mine the wisdom of the crowd
  • A way to fund your involvement in the project
  • Recognition and enhancement of your social reputation
  • Be a part of a larger well-respected initiative
  • The potential to participate in person in Rio
  • A tool to find other members you share interests with

Measurement / Quantifying The Crowd

  • How do you measure engagement with the crowd?
  • How do you measure distribution within the crowd?
  • How do you measure the inertia of the crowd?
  • How do you measure changes in the sentiment? (moving from casually aware to active participant)

Crowd Composition

Less like a mob and more like filling the seats in structure or setting we’ve defined. This allows us to initially curate the incoming content, and facilitate our “experts” (problematic?)

Questions

  • How do we handle out of scope submissions?
  • How much is online vs. offline?
  • How is attribution of content handled?
  • Is there some greater tangible reward for contributing content?

Questions About Crowd Curation

  • Are we asking too much of our audience
  • Are we going to allow for total organic organization of the content or will we offer an initial framework.

Spineless 0.2.1 Released

I have just pushed a new release of my JavaScript application framework called Spineless. Spineless is a simple MVC stack without the need of a backbone. https://github.com/heavysixer/spineless

The goal of Spineless is to provide “just enough framework” to succeed. If I have done my job, you should be able to write your first Spineless app in less than 10 minutes.

Spineless is meant to run with virtually no dependencies. In the age of frameworks with massive dependency chains, here is a list of things you DO NOT need to run spineless.

  1. A persistance layer (e.g. database)
  2. A backend server (e.g. node.js)
  3. An internet connection! (srsly)

Spineless has only two dependencies, JQuery and Mustache.js, both which come bundled with the project inside the /lib directory.

Like any good MVC framework Spineless uses the concept of models, controllers and views.

  1. Spineless models are essentially JavaScript objects and completely optional.
  2. Controllers are used to marshall commands from the views to the models where needed.
  3. Views are the visual interface that the user sees.

In addition to the normal MVC stack, Spineless also uses the concept of helpers and templates.

  1. Templates are HTML snippets, which are used by views to get better use of reusable code.
  2. Helpers are functions that modify a template’s variables any way you choose.

Going Spineless in 10 minutes or less

The entire Spineless application resides inside the “.application” div. An application consists of a collection of controllers which in turn contain a collection of views. Consider the following example:

1
2
3
4
5
6
7
<div class="application">
  <div class="controller" data-controller='application'>
    <div class="view" data-action='index'>
      Hello World!
    </div>
  </div>
</div>

In this example you’ll see that we have defined an application with a single controller. The name of the controller is defined by the data-controller attribute. This attribute is required by Spineless to route requests to the proper location. Views are much like controllers, but instead of using the data-controller attribute they use the data-action.

Routing Requests

Routing requests through Spineless is incredibly painless to make any link a spineless request just add the “route” class. For example:

1
<a class="route" href="/application/hello">Hello</a>

When the user clicks on this link they will now be routed to the application controller where the #hello method will be called. If you are not using an element that support the href attribute you can also place your url inside a data-href attribute:

1
<div class="route" data-href="/application/hello">Hello</div>

If you want to manually trigger a route request from within JavaScript you can call the get function:

1
spineless.get('application', 'index');`

Passing local variables to templates

When rendering templates, Spineless substitutes predefined template variables with those you supply using JSON. The JSON can be provided in at least two ways:

  1. By url encoded a json object into the data-locals attribute.
  2. Creating of modifying the JSON object using a helper function.

I will explain the helper function method next, but here is a simple example of what the data-locals method looks like:

1
<div data-locals="{&quot;name&quot;:&quot;Mark&quot;}" data-template='hi-my-name-is'></div>

Helper functions

Helpers are developer-created functions that execute during the rendering of specific templates. Just like in Rails, helpers are available globally across all views. To demonstrate, imagine we have two DIV tags with locals supplied as urlencoded JSON object:

1
2
<div data-locals="{&quot;name&quot;:&quot;Mark&quot;}" data-template='hi-my-name-is'></div>
<div data-locals="{&quot;name&quot;:&quot;Slim Shady&quot;}" data-template='hi-my-name-is'></div>

As you can see these objects have a property called name, each with unique values. These locals are linked to the “hi-my-name-is” template. To create a helper we’ll bind a function to execute whenever the hi-my-name-is template is rendered. Doing this will allows us intercept the template instance’s data-locals object and modify it anyway we choose before passing it along to Mustache to render. Here is the full example of the helper function:

1
2
3
4
5
6
7
8
9
10
var sp = $.spineless({
    helpers: {
        'hi-my-name-is': function(obj) {
            if (obj.name === 'Slim Shady') {
                obj.name = "*wikka wikka* " + obj.name;
            }
            return (obj);
        }
    }
});

PubSub for Spineless events

Spineless now has a very minimal publisher subscriber (PubSub) events framework. The goal of this is to allow other code executing outside of Spineless to receive updates when internal Spineless events execute, without having to know anything about how Spineless is implemented. Here is a trivial example of creating an observer that is triggered every time a view is done rendering.

1
2
3
4
5
6
7
8
$(document).ready(function() {
    var sp = $.spineless();
    sp.subscribe('afterRender',
    function(publisher, app) {
        app.request.view.append("<h1>Yes it has!</h1>")
    })
    sp.get('application', 'index');
});

When the publisher executes a subscriber’s function it passes a reference to itself and the Spineless app instance as arguments. This allows the receiver to manage it’s subscriptions and gives the function access to the the Spineless current request, params hash among other things.

Controller functions

Controller functions are optional code that developers can write to augment the rendering of the view. Controller functions work much like helper functions do, in that they are executed before the view is returned to the screen. Unlike helper functions which are linked to an arbitrary number of templates; controller functions are scoped to just one controller action. Consider this example which executes when someone visits /users/update:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var sp = $.spineless({
    controllers: {
        users: {
            update: function(elements, request) {
                if ($.currentUser.isAdmin()) {
                    this.render(elements);
                } else {
                    alert(Access Denied);
                }
            }
        }
    }
});
sp.get('application', 'index');

I have added examples of all of these new features in the /samples folder of the public Github repo. Please feel free to open bug reports or feature requests, and I will do my best to oblige.

Task Chunking – or Why We Leave Our Cards in the ATM

Have you ever left your bank card inside an ATM machine? You are not alone, I have done it more times than I care to admit, and each time it happens I am left with the pants around your ankles feeling that you get when you realize you are the worlds biggest idiot. Until recently, I didn’t have a way to explain this reoccurring blind spot; but now I do. ATM designers don’t understand the concept of task chunking.

Recently, when rereading through “The Humane Interface” by Jef Raskin I rediscovered his explanation of chunking and gestures in interface design. I won’t touch of gestures, but Raskin defines chunking as: “the combining of separate items of cognition into a single mental unit, a process that allows us to deal with many items as though they are one.”

Humans can only focus on one thing a time, therefore when planning a system for use by humans you should make sure that the system doesn’t require user’s to do more than one thing at a time. This is not to say that systems can’t be a mix of short and longterm goals, games do this all the time. For example, players may have a small task like collecting an amulet, but this is done within the larger scope of completing the level and the even larger arc of beating the game. Task chunking is about short-term cognition, which means when players are focused on getting the amulet they cannot be simultaneously thinking about completing the level.

In the case of using ATMs there is a single short-term task, which is to deposit or withdraw cash. However, banks for the most part get this wrong. They treat the entire time you are standing in front of their machines as a single mental unit. In their mental model the task begins with you inserting your card and ends with you reclaiming it. However, the customer’s mental model is different. They only want to receive or deposit money, and when they do either of these they often consider their task complete.

The problem with using most ATMs is that they put the customer’s most important event in the middle of their process. This would be like placing a quarter of the movie after the end credits. Nobody would see that part of the movie because, while the credits may be the most important to the actor they are the least important to the view. Moreover, moviegoers are trained to head for the exits when they see the credits start rolling.

I would wager that if the ATM never dispensed money, virtually nobody would leave their card in the machine. However, when the machine spits bills out onto the street, customers recognize this as the start of the task the mean to complete. This is the point where the initial task of using the ATM bifurcates into a new task of securing the exposed money. This is also where people like me forget all about their debit card.

Most of the time customers do get their card back, realizing even though they are done with the ATM, the ATM is not done with them. However, if there are other environmental factors at play like poor weather, lack of time, or thuggish people in the bushes customers may forget about the less important task of completing the bank’s arbitrary process for using their ATM.

Over the years, banks have tried various tactics to get their customer’s attention after the cash is dispensed. Sometimes they refresh ATM screen hoping that the interface shift will cause the customer to look back to the monitor. They might also play a reoccurring sound that signals the customer that their attention is needed.

Fundamentally, these attempts are just patches, and should signal to the bank that their process is broken. The real solution is not to bifurcate the original task. This can be accomplished by placing all essential but less important tasks before money is dispensed.

As a designer I try to be conscious of task chunking when planning out my applications. First I enumerate the discrete tasks in my process and map any potential hotspots where bifurcation by the user may occur. Next, I determine if I can wrap this potential offshoot into the master task chunk. If consolidation is not possible I try to move important tasks in my task chunk before this potential offshoot. My goal is to keep the user’s attention focused on a single pathway. Anytime I make them double-back to complete a task for the benefit of my application I know I have done something wrong.

As a footnote I am happy to report that Bank Of America’s new ATMs improve their approach to task chunking. They return your card immediately after you enter your pin. Of course now I have to unlearn years of using the ATMs which has taught me to walk away from the machine once I get my card back!

Reading RFID Cards With Ruby and the Mac

This weekend I fooled around with the Sparkfun “RFID Starter Kit”. I purchased it from my local technology barn for around $50.00. I am teaching myself physical computing, and projects like that can be completed in an afternoon, are a great way to learn “just enough” to keep a junior hardware hacker like myself from getting frustrated.

I was thrilled to find out that getting this board to talk to Ruby took virtually no effort. In fact, it was so simple it almost felt like I was cheating some how. Here are the steps that I followed to get the ID-12 RFID Scanner and Reader talking to Ruby.

  1. Download the most recent drivers from Future Technology Devices International: http://www.ftdichip.com/Drivers/VCP/MacOSX/FTDIUSBSerialDriver_v2_2_14.dmg The package contains two sets of drivers. Make sure that you install the version that is right for your operating system.

  2. Install the serialport gem for Ruby gem install serialport (I used version 1.0.4)

  3. Mount the scanner on top of the reader by lining up the prongs in the appropriate slot. Then plug the reader into the Mini-USB port of the mac.

  4. From the console, locate the virtual com port the drivers created when you plugged in your Mini-USB cable. :~ ls -la /dev

    Scanning the output from this command you should see the device listed similar to this: cu.usbserial-XXXXXX (where X’s represent a driver id). Mine showed up as “cu.usbserial-A900ftPb” but yours may be different.

  5. Use this sample code to print the unique RFID id stored inside the card, when someone swipes the card over the scanner.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# Simple example of reading from serial port to interface with the RFID reader.
require "serialport"
class RfidReader
 attr_accessor :key

 def initialize(port)
   port_str = port
   baud_rate = 9600
   data_bits = 8
   stop_bits = 1
   parity = SerialPort::NONE
   @sp = SerialPort.new(port_str, baud_rate, data_bits, stop_bits, parity)
   @key_parts = []
   @key_limit = 16 # number of slots in the RFID card.
   while true do
     main
   end
   @sp.close
 end

 def key_detected?
   @key_parts << @sp.getc
   if @key_parts.size >= @key_limit
     self.key = @key_parts.join()
     @key_parts = []
     true
   else
     false
   end
 end

 def main
   if key_detected?
     puts self.key
   end
 end
end

RfidReader.new("/dev/cu.usbserial-A900ftPb")  #may be different for you

Once you have the unique ID the possibilities become nearly limitless, you can use the card to update twitter, or turn on your lights, turn on the coffee pot, fire the nerf guns, release the hounds etc. Have fun, I know I will!

9 Secrets to Running a Successful Crowdfunding Campaign.

As many of your know, I am one of the co-founders of Pledgie. Occasionally, I get asked for advice on how to make an effective campaign on Pledgie and so I began to seriously research this topic about three months ago. Using research, expert advice and by analyzing the thousands of existing Pledgie campaigns I attempted to distill the qualities of a successful campaign into a series of tips (actually nine of them). Whether you are an individual or an organization using Pledgie to raise money, these points may help you to craft a winning message.

1. Empathy Over Sympathy

The job of your campaign message is to focus on the empathetic link between your cause and your donors. Instead of trying to elicit feelings of guilt or pity, focus on positive connections your donors share with your campaign.

Humans, like many other animals, feel empathy and sympathy for one another. Both are incredibly powerful emotions but when it comes to triggering a donor to make a commitment empathy rules the day, and here’s why. Empathy is both the ability to logically understand the experience of others, and simultaneously share a visceral emotional link. Sympathy, however is our ability to feel sorry for someone else’s misfortune, but at a more abstract emotional level. For example, a parent might feel empathy looking at a sick child, because they can imagine their own child in a similar state. In contrast someone without kids might just feel sympathy for child without feeling the greater shared circumstance.

2. Tell A Story

When crafting your campaign, tell the story of your cause. People are more likely to connect with a narrative than with facts and figures. When crafting your campaign story, ask yourself if it answers these questions: Who are you? Why is this campaign important to you? What problem does this campaign seek to solve? Why is this campaign important to the donor?

3. Be Yourself

Donors do not contribute to an idea; they contribute to a person. When describing yourself or your organization, use simple and direct language that gives a brief and complete picture of who you are, and why your campaign is a passion worth funding. The goal is to get potential donors to start seeing you as a real person and not just words and images on a webpage.

4. Don’t Forget to Ask

This seems obvious, but you’d be surprised how many campaigns never ask for funds! People often think that the goal of a Pledgie campaign is to describe the need. While that is an essential component of a good campaign, the actual goal of a Pledgie campaign is to get others to do something about your need. When crafting your campaign message you should concentrate on adding language that: Encourages potential donors to act immediately. Gives them explicit actions they can take to help your campaign.

5. Get Your Friends & Supporters Involved

Your friends and supporters are your base, they provide a stable foundation to build your outreach upon. When spreading your message through social networks like Twitter or Facebook, it is essential that your friends help make a personal appeal. As a message spreads across your social graph it can suffer an entropy in trust. This may happen, because as your message spreads the recipients are less likely to know you personally. Asking for personal appeals from others help, because people are more likely to give if someone they know is personally invested.

6. Look for collaborators not benefactors

Donors who are funding a cause want to feel like they are part of a solution, and not just performing an act of charity. Craft your message in a way that demonstrates to the donor that they are investing in the outcome, and that you will keep them up-to-date as you progress towards your goal.

7. Photo Finish

We cannot stress enough how important having a video or photo is when crafting your campaign. Even if the photo is just a portrait of yourself, simply having a photo or video associated with your campaign description can make a huge difference in perceived credibility.

8. Follow Up With Progress Reports

Donors who have donated once are likely to donate again if you ask them. Sometimes the easiest way to ask is to share your progress towards your goals. Giving periodic updates to your donor base shows them that they made a wise investment in you, and that you can be trusted with additional funds.

9. Be Grateful

When someone makes an effort to support your cause, be sure to say thank you. It gives them a sense of connection and fulfillment that can lead to future support. It is also the right thing to do!

On Jumping

Jump and the net will appear.

Seven years ago there was much handwringing around leaving my job to start my own consultancy. That is until my friend told me this quote from John Burroughs. It is a compelling illustration about faith in yourself and that the world values you. It often resurfaces for me when I am mulling over a risky proposition. The best thing about this quote is when properly applied, if the net doesn’t appear… well it won’t hurt long!

Thoughts on Requirement Gathering

Requirement documents should be a recipe not a shopping list.

This metaphor is my attempt to encapsulate what a good requirements document is. Both a shopping list and a recipe are essentially lists of ingredients in various quantities. However, the recipe also includes a desired outcome and precise descriptions of how the ingredients are meant to be used.

When evaluating a requirement document ask yourself if it gives you a clear understanding of what it is you are being asked to build, and how the independent parts work together. If it doesn’t do that then, my friend, you are reading a shopping list.

Client-side Request Caching With JavaScript

Recently I was writing an enterprise data visualization application that made heavy user of interactive charts and graphs. Like most best-of-breed data-viz apps this one supported very robust filters for slicing and dicing through the dataset. Each time the user adjusted one of these filters the application made new AJAX request and idled until the results were returned.

Technically, this approach worked fine, but because the data-segmentation occurred on the server the charts felt sluggish because they were always polling or data. Additionally, the user quite frequently toggled between only a couple filters to compare the results. What should have been an experience of rapidly flipping between two views on the data was actually a belabored rendering experience. As the developer this was frustrating because they were asking for and receiving the same data over and over again.

To solve this problem, I built a very simple mechanism that affords just enough caching to persist these payload objects only while the user is viewing the page. In this way the user would be guaranteed to get a fresh copy from the server on each page load.

Essentially, I hooked my caching routine around the function that made the AJAX request for new chart data. Using this approach an AJAX request only occurred once, and all future requests pulled from the cache.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Called when someone adjusts a filter
function updateChart(url, chart, key) {

    // Builds the request params needed to correctly query the server. 
    var opts = requestParamsFor(chart, key);

    // Generate a cache key based on this object
    var cacheKey = $.cache.getKey(opts);

    if ($.hh.cache.exists(cacheKey)) {

        // If the key exists then the request has happened in the past
        // use the cached result to refresh the chart.
        var result = $.cache.get(cacheKey);
        onSuccess(kind, opts, chart, code, result);
    } else {
        $.ajax({
            url: url,
            type: 'POST',
            data: opts,
            success: function(result) {

                // Since this was a new request store the results in the cache 
                // at the location specified by the cache key.
                $.cache.add(cacheKey, result);
                onSuccess(kind, opts, chart, code, result);
            }
        });
    }
}

Here is the local cache class in all it’s detail:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
$.cache = (function() {
    var _cache = {};
    var _keys = [];
    var _indexOf = function(arr, obj) {
        var len = arr.length;
        for (var i = 0; i < len; i++) {
            if (arr[i] == obj) {
                return i;
            }
        }
        return - 1;
    };
    var _serialize = function(opts) {
        if ((opts).toString() === "[object Object]") {
            return $.param(opts);
        } else {
            return (opts).toString();
        }
    };
    var _remove = function(key) {
        var t;
        if ((t = _indexOf(_keys, key)) > -1) {
            _keys.splice(t, 1);
            delete _cache[key];
        }
    };
    var _removeAll = function() {
        _cache = {};
        _keys = [];
    };
    var add = function(key, obj) {
        if (_keys.indexOf(key) === -1) {
            _keys.push(key);
        }
        _cache[key] = obj;
        return $.hh.cache.get(key);
    };
    var exists = function(key) {
        return _cache.hasOwnProperty(key);
    };
    var purge = function() {
        if (arguments.length > 0) {
            _remove(arguments[0]);
        } else {
            _removeAll();
        }
        return $.extend(true, {},
        _cache);
    };
    var searchKeys = function(str) {
        var keys = [];
        var rStr;
        rStr = new RegExp('\\b' + str + '\\b', 'i');
        $.each(_keys,
        function(i, e) {
            if (e.match(rStr)) {
                keys.push(e);
            }
        });
        return keys;
    };
    var get = function(key) {
        var val;
        if (_cache[key] !== undefined) {
            if ((_cache[key]).toString() === "[object Object]") {
                val = $.extend(true, {},
                _cache[key]);
            } else {
                val = _cache[key];
            }
        }
        return val;
    };
    var getKey = function(opts) {
        return _serialize(opts);
    };
    var getKeys = function() {
        return _keys;
    };
    return {
        add: add,
        exists: exists,
        purge: purge,
        searchKeys: searchKeys,
        get: get,
        getKey: getKey,
        getKeys: getKeys
    };
}).call(this);

Here are some jasmine tests which explain more features of the cache not covered in this post, and prove that it works!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
it("should allow you to build a cache using keys",
function() {
    var obj = {
        'foo': 'bar'
    };
    expect($.cache.exists("foo=bar")).toEqual(false);
    expect($.cache.getKey(obj)).toEqual('foo=bar');
    expect($.cache.getKey('foo')).toEqual('foo');
    expect($.cache.add("foo=bar", obj)).toEqual(obj);
    expect($.cache.exists("foo=bar")).toEqual(true);
    expect($.cache.get("foo=bar")).toEqual(obj);
    expect($.cache.get("bar")).toEqual(undefined);
});

it("should allow you to empty the cache completely",
function() {
    $.cache.purge();
    expect($.cache.add("baz", 'baz')).toEqual('baz');
    expect($.cache.getKeys().length).toEqual(1);
    expect($.cache.purge()).toEqual({});
});

it("should allow you to empty the cache of just a specific record",
function() {
    $.cache.purge();
    expect($.cache.add("baz", 'baz')).toEqual('baz');
    expect($.cache.add("boff", 'ball')).toEqual('ball');
    expect($.cache.getKeys()).toEqual(['baz', 'boff']);
    expect($.cache.purge('boff')).toEqual({
        'baz': 'baz'
    });
    expect($.cache.getKeys()).toEqual(['baz']);
    expect($.cache.purge('bozz')).toEqual({
        'baz': 'baz'
    });
    expect($.cache.getKeys()).toEqual(['baz']);
});

it("should allow you to search for keys in the cache",
function() {
    $.cache.purge();
    var obj = {
        'bar': 'baz'
    };
    $.cache.add('bar=baz', obj);
    expect($.cache.getKeys().length).toEqual(1);
    expect($.cache.getKeys()).toEqual(["bar=baz"]);
    expect($.cache.searchKeys("bar")).toEqual(["bar=baz"]);
    expect($.cache.searchKeys("bar=")).toEqual(["bar=baz"]);
    expect($.cache.searchKeys("bat")).toEqual([]);
});

Rails Protip: hash.slice

Rails has hidden gems just waiting to be discovered. I will demonstrate the use of Hash.slice, which is one of the core extensions of ActiveSupport.

Here is an example of how Hash.slice can clean up a controller, take this existing code for example:

1
2
3
def index
  @users = User.paginate({ :page => params[:page].present? ? params[:page].to_i : 1, :per_page => params[:per_page].present? ? params[:per_page].to_i : 12 })
end

With Hash.slice we can shorten it to:

1
2
3
def index
  @users = User.paginate({ :page => 1, :per_page => 12 }.merge(params.slice(:page, :per_page)))
end

Hash.slice is that it is very forgiving. The method only returns the attributes if they exist. In our example we are assured all conditions will be met because the default values will only be overwritten if Hash.slice returns them.