Josh Bavari's Thoughts

Thoughts on technology and philosophy

Using Npm Scripts for Cordova

less than a 1 minute read

For those of you that don’t know, Cordova has hooks that can run on each of the specific build tasks that Cordova goes through. For example the task that happens after all the platform specific code is set up, the after_prepare hook is fired.

Tonight I had the pleasure of collaborating with my friend Ross Martin over a project he put together. The project is called cordova-uglify and it focuses on uglifying/minifying JavaScript before building your Cordova app. See his comment in response to Dan Moore’s Accessing more build information from your Cordova CLI hooks blog for more information on why.

The project was having an after_prepare hook in Cordova to uglify the application’s JavaScript once the code is put in place for iOS/Android.

This project Ross put together was interesting. There have been some blogs on using hooks in Cordova as well as three hooks every cordova / phonegap project needs. Moving forward, it’d be nice to make some of these hooks and share them out much like we share packages on npm.

The only problem with using them as packages, is we need to place code somewhere outside of the node_modules folder (where the package will be installed from npm install).

This is what we’d get if we just used npm install cordova-uglify (notice uglify.js is only in node_modules directory):

1
2
3
4
5
6
7
8
9
10
// ./CordovaProjectDirectory
//        /hooks
//        /node_modules
//            /cordova-uglify
//                /after_prepare
//                    /uglify.js
//                /scripts
//                    install.js
//                    uninstall.js
//        /www

What we actually want has our uglify.js in our hooks/after_prepare directory:

1
2
3
4
5
6
7
8
// ./CordovaProjectDirectory
//        /hooks
//            /after_prepare
//                uglify.js
//        /node_modules
//            /cordova-uglify
//                /after_prepare
//                    uglify.js

Then it hit me, we can use npm scripts!

The idea

Let’s package up Cordova tools, publish on npm, and then use npm scripts to install/uninstall them as necessary.

npm gives its package owners the ability to run scripts on various events in the npm life cycle. The interesting ones we care about are those being postinstall and postuninstall.

The idea is this:

  • You run npm install cordova-uglify
  • After installing, npm runs the postinstall script to copy files into proper location
  • Profit $$$

Ross put me up to the challenge, so I took it up. Here’s what I put our package.json to be:

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
{
  "name": "cordova-uglify",
  "version": "0.0.5",
  "description": "Cordova hook that allows you to uglify or minify your apps JavaScript and CSS.",
  "homepage": "https://github.com/rossmartin/cordova-uglify",
  "keywords": [
    "cordova",
    "uglify",
    "minify",
    "hook",
    "hooks"
  ],
  "peerDependencies" : {
    "yuicompressor" : "2.4.8"
  },
  "author": "Ross Martin",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/rossmartin/cordova-uglify/issues"
  },
  "readmeFilename": "README.md",
  "scripts": {
    "postinstall": "node scripts/install.js",
    "postuninstall": "node scripts/uninstall.js"
  }
}

To which I then created a quick script to do the file copying – scripts/install.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/env node

var fs = require('fs')
var path = require('path')
var cwd = process.cwd() //proj directory
var scriptPath = __dirname //node_modules/cordova-uglify/scripts

var paths = [ path.join(cwd, '../../hooks'), path.join(cwd, '../../hooks/after_prepare') ];

for(var pathIndex in paths) {
  if(!fs.existsSync(paths[pathIndex])) {
      console.log('Creating directory: ', paths[pathIndex])
      fs.mkdirSync(paths[pathIndex]);
  }    
}

var uglifyScriptPath = path.join(cwd, 'after_prepare', 'uglify.js');

var uglifyFile = fs.readFileSync(uglifyScriptPath);
console.log('uglifyFile: ', uglifyFile)
var uglifyAfterPreparePath = path.join(paths[1], 'uglify.js')

console.log('Creating uglify hook: ', uglifyAfterPreparePath)
fs.writeFileSync(uglifyAfterPreparePath, uglifyFile);

As well as uninstalling it – scripts/uninstall.js:

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/env node

//After uninstall script to remove the uglify.js script from the users hooks/after_prepare directory

var fs = require('fs')
var path = require('path')
var cwd = process.cwd()

var uglifyJsPath = path.join(cwd, '../../', 'hooks', 'after_prepare', 'uglify.js')

fs.unlink(uglifyJsPath)
console.log('Removed: ', uglifyJsPath)

Simple and sweet.

Now, is it an anti-pattern? I’m not sure.

Does it make it easier for other developers to get started and using it? Yes.

That’s exactly what I was going for.

Thanks Ross for planting the idea in my head, and more importantly, for the challenge to learn.

Cordova and the Safari Web Inspector

less than a 1 minute read

If you’ve ever been doing some iOS development in Cordova, you may have used the Safari Web Inspector (SWI) once or twice. You’ve also noticed that SWI closes when the app is put in the background or you switch apps.

I found a sweet hack that allows you to keep your safari web inspector running regardless of whether or not the app is in the foreground or not. I’d like to outline that in this post.

I’m not sure how I stumbled across this sweet hack, but I have to write it down before I forget how it’s done. My system specs at the time of writing this is Mac OSX 10.8.5 (Mountain Lion).

Whats involved

  • Using a simulator or connected device
  • Having Safari web inspector (SWI) running

What normally happens is, when the app closes, SWI will also close. When you relaunch the app, you will need to put Safari in the foreground, click Develop on the menu bar, then hover iPhone Simulator or iPhone, then click the index.html (or your page) to connect & launch SWI.

This is very frustrating because on the app relaunching you may have a callback happening or other code you want to debug/inspect/log on app start up. This hack lets you keep SWI and running gracefully.

Bare with me now, we’re going to get funky.

Setting up a global hotkey

The first thing to do is to set a global hotkey – steps:

  • Open System Preferences / Keyboard
  • On the right panel, select Application Shortcuts
  • Add a hot key – use whatever keys you want (I used CMD + ALT + I)
  • Have the matching key be index.html or whatever your cordova’s main html file is

Steps to keep open SWI

  • Launch Cordova App
  • Open SWI
  • Close SWI
  • Launch SWI with quick key as set up in global hotkey Application Shortcuts
  • Close App
  • Reopen app – notice SWI is still running and continues to give us logging/debugging!

Enjoy, friends!

Building a Chat Client With Ionic / Socket.io / Redis / Node.js

less than a 1 minute read

I wanted a fun challenge to push myself and cross a few things off my ever so growing I want to play with this type of lists. I love learning, and there are so many awesome tools / utilities / libraries out there to evaluate its hard to justify incorporating them into every project at work without having some knowledge of the tools.

DISCLAIMER: I may use some tools incorrectly, but the main purpose of this fun little project was to learn and have fun.

The list was this:

The Idea

I wanted to build a chat client that would have messages that disappear after a certain time, much like SnapChat. The idea also included the ability to create channels that also disappear after a certain time like messages.

In future versions, I’d love to include location to join channels that are near you.

Users can join existing channels, or create their own. All users can see channels, and join any.

Tech details – using Redis / Node.js

At first, I wanted to create messages some how and have them each have expire times. After failing miserably, I got the amazing chance to pair up with Michael Gorsuch to give me some alternative ideas. (Shameless plug – if you need to do some server monitoring, check out his project Canary.io, it’s AWESOME).

The concept is – instead of using separate keys with ezxpire times – use Redis’ sorted sets with scores of the times in UNIX format and the member being a JSON encoded string. I had my channels keys in the format of messages:ChannelName.

Something like:

1
2
//ZADD key score member [score member ...]
zadd messages:RedisChat 10581098019 '{"name": "Josh", "id": "5"}'

Now, when we want to get all messages for a channel, its simply:

1
2
//ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
zrangebyscore messages:RedisChat 0 10924019840

Since I was using Node.js – I simply used setInterval to have a function be run that removes all old posts named removeKeys, and looked as such:

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
//NOTE: Using Moment.js, as well as having channelWatchList being populated
var channelWatchList = ['Lobby', 'RedisChat'];

function removeKeys() {
  console.log('We are removing old messages');

  for(var channelIndex in channelWatchList) {
    var channel = channelWatchList[channelIndex];
    var messageChannel = 'messages:' + channel;
    console.log('message channel', messageChannel)
    var timeToRemove = moment().subtract('m', 1).unix(); //Remove messages before min ago

    redisClient.zrangebyscore(messageChannel, 0, timeToRemove, function(err, result) {
      if(result && result.length > 0) {
        for (var resultIndex in result) {
          var message = JSON.parse(result[resultIndex]);
          //NOTE: Using socket.io
          io.emit('message:remove:channel:' + channel, { message: message, channel: channel });
        }
      }
    });

    redisClient.zremrangebyscore(messageChannel, 0, timeToRemove, function(err, result) {
      console.log('Removed ', result, ' messages');
    });
  }
}

The client – Ionic

This was by far the easy part. First I just used the Ionic CLI to create a basic app.

I started by modifying the index.html file to include Socket.io. Nothing too fancy: <script src="js/socket.io.js"></script>.

Next, I used some AngularJS services for socket.io:

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
angular.module('services', [])

.factory('socket', function socket($rootScope) {
  var socket = io.connect(baseUrl);
  return {
    on: function (eventName, callback) {
      socket.on(eventName, function () {
        var args = arguments;
        $rootScope.$apply(function () {
          callback.apply(socket, args);
        });
      });
    },
    emit: function (eventName, data, callback) {
      socket.emit(eventName, data, function () {
        var args = arguments;
        $rootScope.$apply(function () {
          if (callback) {
            callback.apply(socket, args);
          }
        });
      })
    }
  };
})

Then, I constructed my AppCtrl to handle my controllers interaction with Socket.io:

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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
angular.module('starter.controllers', ['services'])

.controller('AppCtrl', function($scope, $state, $filter, socket, Auth) {
  //Ensure they are authed first.
  if(Auth.currentUser() == null) {
      $state.go('login');
      return;
  }

  //input models
  $scope.draft = { message: '' };
  $scope.channel = { name: '' };

  //App info
  $scope.channels = [];
  $scope.listeningChannels = [];
  $scope.activeChannel = null;
  $scope.userName = Auth.currentUser().name;
  $scope.messages = [];

///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
//Socket.io listeners
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////

  socket.on('channels', function channels(channels){
      console.log('channels', channels);

      console.log(channels);
      $scope.channels = channels;
  });

  socket.on('message:received', function messageReceived(message) {
      $scope.messages.push(message);
  });

  socket.emit('user:joined', {name: Auth.currentUser().name});

  socket.on('user:joined', function(user) {
      console.log('user:joined');
      $scope.messages.push(user);
  });

  $scope.listenChannel = function listenChannel (channel) {
      socket.on('messages:channel:' + channel, function messages(messages) {
          console.log('got messages: ', messages);
          console.log(messages.length);
          for(var i = 0, j = messages.length; i < j; i++) {
              var message = messages[i];
              console.log('message');
              console.log(message);
                  console.log('apply with function');
              $scope.messages.push(message);
          }
      });

      socket.on('message:channel:' + channel, function message(message) {
          console.log('got message: ' + message);
          if(channel != $scope.activeChannel) {
              return;
          }
          $scope.messages.push(message);
      });

      socket.on('message:remove:channel:' + channel, function(removalInfo) {
          console.log('removalInfo to remove: ', removalInfo);
          var expires = removalInfo.message.expires;
          var expireMessageIndex = $filter('messageByExpires')($scope.messages, expires);
          if(expireMessageIndex) {
              $scope.messages.splice(expireMessageIndex, 1);
          }

      });

      $scope.listeningChannels.push(channel);

  }

///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
// Controller methods
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////

  $scope.joinChannel = function joinChannel(channel) {
      $scope.activeChannel = channel;
      $scope.messages = [];

      $scope.channel.name = '';

      //Listen to channel if we dont have it already.
      if($scope.listeningChannels.indexOf(channel) == -1) {
          $scope.listenChannel(channel);     
      }

      socket.emit('channel:join', { channel: channel, name: Auth.currentUser().name });
  }

  $scope.sendMessage = function sendMessage(draft) {
      if(!draft.message || draft.message == null || typeof draft == 'undefined' || draft.length == 0) {
          return;
      }
      socket.emit('message:send', { message: draft.message, name: Auth.currentUser().name, channel: $scope.activeChannel });
      $scope.draft.message = '';
  };

  $scope.logout = function logout() {
      Auth.logout();
      $state.go('login');
  }

  //Auto join the lobby
  $scope.joinChannel('Lobby');
})

All of the code can be found on github here.

Things to improve

  • Testing – for sure. I definitely failed in getting tests first
  • Removing the inline functions from Socket.io callbacks – not sure I like how I handled that to be honest
  • Improve the UI
  • Actually make the channels expire over time – and alert the user
  • Have some kind of location tracking to pull local channels near you

Enjoy! Hope this helps any others learn some tips for developing in any of these technologies used!

Unit Testing AngularJS Services

less than a 1 minute read

I’ve been using AngularJS a lot lately. Since I do a lot of Javascript, that means I’m prone to make a lot of runtime script errors.

You know those silly javascript errors – like ReferenceError and ParseError? Those can always be avoided by just writing some simple unit tests with Jasmine. I’d like to cover just how I do that.

(NOTE – I am forever learning, not teaching or saying THIS is the way it MUST be done)

I read through Andy Shora’s great blog post about Unit Testing Best Practices for AngularJS, but I wanted to record my actual steps so I can reference this again and capture my knowledge.

Tools for Javascript Testing AngularJS Services

There’s a few things going on here. First we need something to set up our tests and set expectations – thats Jasmine. Then we need something to run the tests in browsers (or PhantomJS) – thats Karma. We then need a task runner to go and do these tests for us in some build process, thats Grunt/Gulp. Each tool has a file that will tell it how to run.

Jasmine takes test spec files, Karma takes a config to tell it where to find the test spec files and actual code files, and grunt or gulp will help us run karma. Lets look at how those config files look.

Setting up Karma / Jasmine / Gulp configs

I use gulp these days, that requires me to use the gulp CLI as well as the gulp-jasmine plugin. You can use Grunt as well, just exchange gulp for grunt.

I did the following in my command shell:

1
2
3
4
5
6
npm install -g gulp
npm install -g karma
npm install gulp-jasmine --save-dev
npm install karma-jasmine --save-dev
npm install karma-phantomjs-launcher --save-dev
npm install karma-spec-reporter --save-dev

Setting up Karma config

I simply ran this in my command shell for a nice simple walk through: karma init. It asks a few questions about what browsers to use, to keep running, and what files to use. Pretty basic stuff.

Interesting tidbits:

  • files – simply and array of files and glob’s
  • frameworks – specify here which you want to use
  • reporters – customize your test output
  • browsers – list which you’d want to actually test in
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
// Karma configuration
// Generated on Wed Jun 11 2014 09:51:52 GMT-0500 (CDT)

module.exports = function(config) {
  config.set({

    // base path that will be used to resolve all patterns (eg. files, exclude)
    basePath: '',


    // frameworks to use
    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
    frameworks: ['jasmine'],


    // list of files / patterns to load in the browser
    files: [
        './www/js/moment.min.js'
        , './www/js/controllers/*.js'
        , './www/js/models/*.js'
        , './www/js/services.js'
        , './www/lib/ionic/js/angular/angular.js'
        , './plugins/org.apache.cordova.FacebookConnect/www/angular/facebookConnect.js'
        , './test/lib/angular-mocks.js'
        , './test/spec/**/*.js'
    ],

    // list of files to exclude
    exclude: [

    ],


    // preprocess matching files before serving them to the browser
    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
    preprocessors: {

    },


    // test results reporter to use
    // possible values: 'dots', 'progress'
    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
    reporters: ['spec'],


    // web server port
    port: 9876,


    // enable / disable colors in the output (reporters and logs)
    colors: true,


    // level of logging
    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
    logLevel: config.LOG_INFO,


    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: true,


    // start these browsers
    // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
    browsers: [
        'PhantomJS'
        // , 'Chrome'
        // , 'Firefox'
        // , 'Safari'
    ],


    // Continuous Integration mode
    // if true, Karma captures browsers, runs the tests and exits
    singleRun: false
  });
};

Gulpfile for running tests

Next I had to get a little gulpfile together to run my tests. Right away, I found a quick little SNAFU with the way the gulp task runs the source files VS how I specified them in my Karma config file. A few interesting points here:

  • Dont actually pass in files to gulp.src – instead use a dummy. You specify the files in your karma config file.
  • If you intend on running a gulp.watch task to autorun, dont error out your karma stream! Use this.emit('end') in your error handler

The code itself:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var karma = require('gulp-karma');

gulp.task('test', function() {
  // Be sure to return the stream
  // NOTE: Using the fake './foobar' so as to run the files
  // listed in karma.conf.js INSTEAD of what was passed to
  // gulp.src !
  return gulp.src('./foobar')
    .pipe(karma({
      configFile: 'karma.conf.js',
      action: 'run'
    }))
    .on('error', function(err) {
      // Make sure failed tests cause gulp to exit non-zero
      console.log(err);
      this.emit('end'); //instead of erroring the stream, end it
    });
});

gulp.task('autotest', function() {
  return gulp.watch(['www/js/**/*.js', 'test/spec/*.js'], ['test']);
});

Awesome, not much left to do as far as setting up our test environment, lets get some code to test!

Setting Up AngularJS Service

Its a pretty basic setup – an Auth service with a few methods to get a user and call a back end service to retrieve a user.

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
angular.module('services')
  .factory('Auth', function Auth($http, $q) {
      var user = null;

      var readStoredUser = function readStoredUser() {
          //Try to read in from localStorage if one exists
          var storedUser = window.localStorage.getItem('user');
          try {
              if(storedUser) {
                  // Note: Using a simple user model here
                  user = new User(JSON.parse(storedUser));
              }
          } catch (ex) { /* Silently fail..*/ }
      }

      readStoredUser();

      var currentUser = function currentUser() {
          if(!user) {
              readStoredUser();
          }
          return user;
      }

      var saveUser = function saveUser(userToSave) {
          window.localStorage.setItem('user', JSON.stringify(userToSave));
          user = userToSave;
      }

      var loginWithEmail = function loginWithEmail(name, email) {
          var deferred = $q.defer();

          var postPath = 'http://someurl.dev/api/v1/login';
          var postData = { name: name, email: email };

          $http.post(postPath, postData).success(function(data) {
              if(data.success) {
                  deferred.resolve(data);
              } else {
                  deferred.reject(data);
              }
          }).error(function(error) {
              deferred.reject(error);
          });

          return deferred.promise;
      }

      return {
          currentUser: currentUser,
          loginWithEmail: loginWithEmail,
          saveUser: saveUser
      };
  })

Thats a simple bare bones Auth service above. We have a few interesting parts to test:

  • readStoredUser
  • currentUser
  • loginWithEmail

The first is somewhat hard because it is private to the Auth service. How do we test that? I guess the option is to make it public via a return in the service?

Test Specs

There was a few interesting things going on my spec – first I have a beforeEach that sets up some modules I need to use. Otherwise you’ll get some fun / weird couldnt bind errors.

The second was – In my HTTP tests, I mock out the httpBackend (as provided by angular-mocks) to give me a fake version of my actual HTTP call. This way, I know for sure I’m testing my code, not the outside world.

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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
//Interesting things to test the Auth service for

// Logging in with Facebook
// Handling callback to server for checkuser
// Saving user to localstorage after login
// Logging out (removing user object as well as localstorage)

describe("Auth Service Unit Tests", function() {

  beforeEach(function() {
      //Ensure angular modules available
    module('starter.services');
    module('ngCordova.plugins.facebookConnect');
  });

   // instantiate service
  var apiResponse = {
    name: 'Josh Bavari',
    email: 'jbavari@gmail.com',
    id: '4409480064'
  };
  var Auth;
  var FB = {
    init: function() {

    },
    login: function() {

    },
    api: function(url, params, callback ) {
      return callback(apiResponse);
    }
  };
  var FacebookConnect = {
    login: FB.login
  };
  var httpBackend = null;

  beforeEach(inject(function (_Auth_) {
    Auth = _Auth_;
  }));

  it('should have Auth service be defined', function () {
    expect(Auth).toBeDefined();
  });

  it('should not have a user existing upon starting up', function() {
    expect(Auth.currentUser()).toBe(null);
  });

  it('should save a user', function() {
    var user = { name: 'Josh Bavari', id: 1 };

    Auth.saveUser(user);
    var currUser = Auth.currentUser();
    expect(currUser.name).toBe(user.name);
    expect(currUser.id).toBe(user.id);
  });

  it('should have a user in local storage after calling saveUser', function() {
    var user = { name: 'Josh Bavari', id: 1 };

    Auth.saveUser(user);

    var localUser = JSON.parse(window.localStorage.getItem('user'));

    expect(localUser.name).toBe(user.name);
    expect(localUser.id).toBe(user.id);
  });

  it('should remove the user from local storage after logging out', function() {
    var user = { name: 'Josh Bavari', id: 1 };

    Auth.saveUser(user);

    var localUser = JSON.parse(window.localStorage.getItem('user'));

    expect(localUser.name).toBe(user.name);
    expect(localUser.id).toBe(user.id);

    Auth.logout();

    expect(Auth.currentUser()).toBe(null);
  });

  describe('Mocked HTTP Requests', function() {

    var $httpBackend;
    var name = 'Josh Bavari';
    var email = 'jbavari@gmail.com';

    beforeEach(inject(function($injector) {
      // Set up the mock http service responses
      $httpBackend = $injector.get('$httpBackend');
      $httpBackend.when('POST', 'http://raisemore.dev/api/v1/user/checkuser')
        .respond(200, {name: name, email: email, success: true});
     }));

    afterEach(function() {
     $httpBackend.verifyNoOutstandingExpectation();
     $httpBackend.verifyNoOutstandingRequest();
    });


    it('should have sent a POST request to the checkuser API', function() {
        var result = Auth.checkUser(name, email, 1, '4408064001', null);
        $httpBackend.expectPOST('http://raisemore.dev/api/v1/user/checkuser');
        $httpBackend.flush();
    });

  });

});

Theres a few key points to look at in the Jasmine tests:

  • beforeEach(inject(function (_Auth_) {}) sets our local Auth variable
  • using inject($injector) to get us our mocked out $httpBackend to fake our HTTP requests.

That just about covers it. In recap:

  • Set up the testing framework Karma
  • Got the test runners for Gulp
  • Set up some tests with Jasmine
  • Mocked out $http requests to return us some fake data
  • Ensured our services called the http requests correctly
  • Avoided any future errors from testing – as well as avoiding Parse/Reference errors along the way

AngularJS does a lot of the heavy lifting for you. However, it still gives you just enough rope to hang yourself with.

With just some simple tests you can also avoid any silly run time errors you may encounter.

Hope this gives ideas on how to openly test your services as well as models.

References

Handling AngularJS Popups for OAuth on Rails

less than a 1 minute read

I’ve been using AngularJS a lot lately in some of my projects at work. It’s been a great tool to use to help me solve challenging problems the nicest and cleanest way possible.

I ran into needing some users to log into a variety of different social platforms. Since I was using Rails, I chose to use omniauth for facebook and twitter. It became even more challenging because they needed to login to these platforms with THEIR social application ID’s, not ours.

The Problem

  • Need to have admin window where user clicks login button for facebook or twitter and logs in with their Facebook application (think Coke, Pepsi, etc)
  • User then sees pop up window where OAuth login process happens
  • After OAuth login complete, pop up window goes away and they resume their actions

The solution

Solving dynamic twitter/facebook log ins for Social Platforms

I started by having this config in Rails for my omniauth initializer:

1
2
3
4
5
6
7
8
9
10
11
12
SETUP_FACEBOOK = lambda do |env|
    AccountAuth.setup_facebook_keys(env)
end

SETUP_TWITTER = lambda do |env|
    AccountAuth.setup_twitter_keys(env)
end

Rails.application.config.middleware.use OmniAuth::Builder do
    provider :twitter, :setup => SETUP_TWITTER
    provider :facebook, :setup => SETUP_FACEBOOK
end

Simple and clean. In those AccountAuth methods, I take the env variable (essential the request) and pick off my variables there from an OAuth URL (http://my.dashboard.dev/auth/facebook?appid=123456789).

Solving the User pop up

I had a dashboard with a ton of user actions, as well as two well placed social log in buttons. View template like so:

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
<div class="container" ng-controller="SettingsCtrl">
        <div class="row">
            <div class="col-md-12">
                <div class="header-copy">Account Settings</div>
                <div class="section-title"></div>
            </div>
        </div>

        <div class="row">
            <div class="col-md-6">
                <div class="header-copy">Facebook Settings</div>
                <div class="divider"></div>

                <div ng-show="facebookId">
                    Currently ({{ facebookName}}) &nbsp;
                    <span class="btn dash-subs" ng-click="logout('facebook')">Logout</span>
                </div>
                <div ng-hide="facebookId">
                    <span class="btn dash-subs login-btn" ng-click="authNetwork('facebook')">Login With Facebook</span>
                </div>
            </div>
            <div class="col-md-6">
                <div class="header-copy">Twitter Settings</div>
                <div class="divider"></div>
                <div ng-show="twitterName">
                    Currently (@{{twitterName}}) &nbsp;
                    <span class="btn dash-subs" ng-click="logout('twitter')">Logout</span>
                </div>
                <div ng-hide="twitterName">
                    <span class="btn dash-subs login-btn" ng-click="authNetwork('twitter')">Login With Twitter</span>
                </div>
            </div>
        </div>
    </div>

Now on my SettingsCtrl, I had to respond to the authNetwork clicks in the template above to show my pop up window for the network specified, handle its settings, then update this controller. We get that link by setting a global variable on the window that opened by doing window.$windowScope = $scope.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
angular.module('myApp', ['ui.bootstrap']);

function SettingsCtrl($scope, $http) {
  //..snip!..
   $scope.handlePopupAuthentication = function handlePopupAuthentication(network, account) {

      //Note: using $scope.$apply wrapping
      //the window popup will call this 
      //and is unwatched func 
      //so we need to wrap
      $scope.$apply(function(){
         $scope.applyNetwork(network, account);
      });
   }

   $scope.authNetwork = function authNetwork(network) {
      var openUrl = '/auth/' + network + '?account_id=' + $scope.accountTokens['id'] + "&eid=" + eventId;
      window.$windowScope = $scope;
      window.open(openUrl, "Authenticate Account", "width=500, height=500");
   };
}

Solving popup talking to AngularJS controller

Once the OAuth pop up that is being opened via window.open is completed, it will come back to our server (http://my.dashboard.dev/session/create) in which I will render a view through Rails that will display a simple ‘this window is closing’ message. It will also pass in some information from the Rails controller and pass back its completed information back to our calling AngularJS controller. (Thats a lot of controllers, folks)

1
2
3
4
5
6
7
<p>This view will now self-destruct</p>
<script>
   try {
      window.opener.$windowScope.handlePopupAuthentication('<%= @provider %>', <%= @account.to_json.html_safe %>);
   } catch(err) {}
   window.close();
</script>

Conclusion

That’s pretty much it. That is how I handled my popups reporting back to its calling AngularJS controller through OAuth on Rails. Hope this helps others out there trying to solve problems like these.

Handling Relationships in RethinkDB

less than a 1 minute read

Lately I’ve been playing a lot with RethinkDB and I’m in love with it. Such a sweet document database, amazingly beautiful web interface, and easy to use API’s in three different languages. I started up a side project that involves some relational data, and ran into a few bumps along the road.

I’m writing this post to share some of the knowledge I’ve acquired along the way, and hopefully some will find it helpful.

The problem

I’m using a doc db because I’m still not sure of my database schema, and since its mostly a prototype, I need something flexible. The project is for physical therapy patients involving rehabilitation programs. Each program is comprised of several exercises, and a program is assigned to one user. A user can have multiple programs.

Eat your ER heart out:

Technologies Used

I wanted to keep it light, so I chose using Sinatra for my API and Ionic Framework for my mobile application. BTW – when it comes to choosing a framework for Cordova, I suggest trying Ionic. They are crushing it.

Setting up the tables

First I made a dataload.rb file, which would be run on the init of my server which would set up my database, set up the tables, and dump some initial data in the tables. It looked something like this:

InitialData.rb
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
require 'rethinkdb'

# We will use these settings later in the code to connect 
# to the RethinkDB server.
RDB_CONFIG = {
  :host => ENV['RDB_HOST'] || 'localhost',
  :port => ENV['RDB_PORT'] || 28015,
  :db   => ENV['RDB_DB']   || 'PtMotions'
}

# A friendlly shortcut for accessing ReQL functions
r = RethinkDB::RQL.new

@rdb_connection = r.connect(:host => RDB_CONFIG[:host], :port => RDB_CONFIG[:port], :db => RDB_CONFIG[:db])

@users = [
  {
      :clinicId => 'At Home PT',
      :patientId => 'jbavari'
  }
]

user_id = nil

@users.each do |user|
  begin
      result = r.table('Users').insert(user).run(@rdb_connection)
      # Grab user id from result to use later for assigning the program
      user_id = result['generated_keys'][0]
  rescue
      p 'Error: ' + result.to_s
  end
end

@exercises = [{
          :name => 'Resisted Right Shoulder Internal/External Rotation',
          :startingPosition => 'Lying on your back with your legs bent with your right hand holding a kettle bell.',
          :description => 'Lift the kettle bell straight up in the air and hold. Pull your shoulder into the ground and away from your ear. Slowly rotate your arm all the way in then all the way out without letting your arm sway',
          :whatYouFeel => 'Strengthing in your right shoulder',
          :videoUrl => 'http://ptmotions.com/ptm_mp4_768_432/s11t02_063.mp4'
        },
        {
          :name => 'Side Resisted Right Shoulder Internal/External Rotation',
          :startingPosition => 'Lying on your left side with your right hand holding a kettle bell',
          :description => 'Lift the kettle bell straight up in the air and hold. Pull your shoulder down away from your ear. Slowly rotate your arm all the way in, then all the way out without letting your arm sway',
          :whatYouFeel => 'Strengthing in your right shoulder',
          :videoUrl => 'http://ptmotions.com/ptm_mp4_768_432/s11t02_065.mp4'
        }
]

exercise_list = Array.new

@exercises.each do |exercise|
  begin
      result = r.table('Exercises').insert(exercise).run(@rdb_connection)
      exercise_list.push result['generated_keys'][0]
  rescue
      p 'Error: ' + result.to_s
  end
end

@joshs_program = {
  :name => 'Joshs Shoulder Rehab',
  :notes => 'Focus on keeping core tight',
  :instructions => {
      :howOften => '3 sets per day',
      :howMany => '15 per side'
  },
  :exercises => exercise_list,
  :userId => user_id
}

begin
  result = r.table('Programs').insert(@joshs_program).run(@rdb_connection)
rescue
  p 'Error: ' + result.to_s
end

Above you’ll see I have a list of exercises, as they are inserted I add their ID’s to an array. I then take that array and use that to store in @joshs_program so that I can set up a relationship with exercises.

Retrieving data

Now that I have programs with an array of exercises, I need to get all the exercises by the program. First – I need a query that will get me all of my exercises by program ID – so thats similar to a type of inner join, or a SQL equivalent of SELECT IN. Luckily, RethinkDB has awesome documentation about SQL-to-RQL and data modeling.

From the documentation, they recommend doing the following:

1
2
3
4
r.table("users").filter(lambda doc:
    r.expr(["Peter", "John"])
        .contains(doc["name"])
)

However, the example is in Python, so you’ll need to do a little more work to get it in Ruby.

This led me to take a different path in RQL. I found out how to do a SELECT IN type query, and in Ruby it looks like this with inner_join:

1
2
3
4
5
6
7
8
9
10
@programId = params[:programId] || '37feebf9-54ce-45f5-ba76-d13fe634b035'
exercises = r.table("Programs")
      .filter({'id' => @programId})
      .inner_join( r.table("Exercises")) { |p, e|
          p['exercises'].contains( e['id'] )
      }
      .zip()
      .without('exercises', 'userId')
      .order_by(r.desc('created_at'))
      .run(@rdb_connection)

You’ll see I’m using the RQL inner_join, and as part of my lamba I use the table attribute p['exercises'] which contains my array of exercise ID’s, then using the contains method on my exercise table e['id']. It works wonderfully. I’m not sure if it is the best way to handle this, and I’m still a RethinkDB newbie so this was a good workout for me.

The API code

The rest of my API code relied heavily on the RethinkDB sample app – Pastie. The really interesting joins are found around line 77.

I’m including my own version here to help give some ideas how I’m setting up my API:

server.rb
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
90
91
92
93
94
95
96
97
98
99
require 'sinatra'
require 'rethinkdb'
require 'json'

RDB_CONFIG = {
  :host => ENV['RDB_HOST'] || 'localhost',
  :port => ENV['RDB_PORT'] || 28015,
  :db   => ENV['RDB_DB']   || 'PtMotions'
}

r = RethinkDB::RQL.new

# The pattern we're using for managing database connections is to have **a connection per request**. 
# We're using Sinatra's `before` and `after` for 
# [opening a database connection](http://www.rethinkdb.com/api/ruby/connect/) and 
# [closing it](http://www.rethinkdb.com/api/ruby/close/) respectively.
before do
  headers 'Access-Control-Allow-Origin' => '*',
            'Access-Control-Allow-Methods' => ['OPTIONS', 'GET', 'POST']
  begin
    # When openning a connection we can also specify the database:
    @rdb_connection = r.connect(:host => RDB_CONFIG[:host], :port => RDB_CONFIG[:port], :db => settings.db)
  rescue Exception => err
    logger.error "Cannot connect to RethinkDB database #{RDB_CONFIG[:host]}:#{RDB_CONFIG[:port]} (#{err.message})"
    halt 501, 'This page could look nicer, unfortunately the error is the same: database not available.'
  end
end

# After each request we [close the database connection](http://www.rethinkdb.com/api/ruby/close/).
after do
  begin
    @rdb_connection.close if @rdb_connection
  rescue
    logger.warn "Couldn't close connection"
  end
end

get '/' do
  @snippet = {}
  erb :new
end

post '/add' do
  @user = { :clinicId => params[:clinicId], :patientId => params[:patientId] }
  # result = r.table('Users').insert(@user).run(@rdb_connnection)
  result = r.table('Users').insert(@user).run(@rdb_connection)


  if result['inserted'] == 1
      redirect "/#{result['generated_keys'][0]}"
  else
      logger.error result
      redirect '/'
  end
end

get '/programs/:userId' do
  content_type :json
  @userId = params[:userId].downcase
  max_results = params[:limit] || 10
  results = r.table('Programs').
      filter('userId' => @userId).
      # pluck('id', 'name', 'created_at').
      without('userId').
      order_by(r.desc('created_at')).
      limit(max_results).
      run(@rdb_connection)

  results.to_json
end

get '/exercises/:programId' do
  content_type :json

  @programId = params[:programId] || '37feebf9-54ce-45f5-ba76-d13fe634b035'

  exercises = r.table("Programs")
      .filter({'id' => @programId})
      .inner_join( r.table("Exercises")) { |p, e|
              p['exercises'].contains( e['id'] )
          }
          .zip()
          .without('exercises', 'userId')
          .order_by(r.desc('created_at'))
          .run(@rdb_connection)

  exercises.to_json
  # exercise_ids.to_json

end

get '/getuser/:patientId' do
  content_type :json
  user = r.table('Users')
      .filter({'patientId' => params[:patientId]})
      .run(@rdb_connection)

  user.first.to_json
end

That’s all folks! Hope this helps some in understanding how to do foreign key references in RethinkDB!

Moving Forward With Phonegap / Cordova Plugins

less than a 1 minute read

I spoke about how plugins work and how to create plugins in Cordova at KCDC. I wanted to make sure my slides are available to any who wanted to view them.

The slides are located here.

I hope this is helpful for some out there, Enjoy!

KCDC - Javascript Build System Showdown

less than a 1 minute read

I spoke about the different Javascript Build systems at KCDC. I wanted to make sure my slides are available to any who wanted to view them.

A few things about the slides:

There is a Gruntfile.js that contains tasks that I talked about in the presentation. There is also a gulpfile.js that does the same tasks as the Gruntfile. It will show you the differences between the two systems.

The slides are found here, and the repo for the files here.

I hope this is helpful for some out there, Enjoy!

Pow and Weird Starting Issues

less than a 1 minute read

Lately I’ve been getting this weird error from Pow in Rails 4:

Bundler::GemNotFound: Could not find rake-10.3.1 in any of the sources

Read more below to see what I did.

In boot.rb:

1
2
3
4
5
require 'rubygems'
# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)

require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])

In config.ru:

1
2
require ::File.expand_path('../config/environment',  __FILE__)
run Rails.application

Hope this helps.