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.
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):
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:
1234567891011121314151617181920212223242526
{"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
As well as uninstalling it – scripts/uninstall.js:
123456789101112
#!/usr/bin/env node//After uninstall script to remove the uglify.js script from the users hooks/after_prepare directoryvarfs=require('fs')varpath=require('path')varcwd=process.cwd()varuglifyJsPath=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.
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!
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.
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:
12
//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:
12
//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:
123456789101112131415161718192021222324252627
//NOTE: Using Moment.js, as well as having channelWatchList being populatedvarchannelWatchList=['Lobby','RedisChat'];functionremoveKeys(){console.log('We are removing old messages');for(varchannelIndexinchannelWatchList){varchannel=channelWatchList[channelIndex];varmessageChannel='messages:'+channel;console.log('message channel',messageChannel)vartimeToRemove=moment().subtract('m',1).unix();//Remove messages before min agoredisClient.zrangebyscore(messageChannel,0,timeToRemove,function(err,result){if(result&&result.length>0){for(varresultIndexinresult){varmessage=JSON.parse(result[resultIndex]);//NOTE: Using socket.ioio.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:
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',functionchannels(channels){console.log('channels',channels);console.log(channels);$scope.channels=channels;});socket.on('message:received',functionmessageReceived(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=functionlistenChannel(channel){socket.on('messages:channel:'+channel,functionmessages(messages){console.log('got messages: ',messages);console.log(messages.length);for(vari=0,j=messages.length;i<j;i++){varmessage=messages[i];console.log('message');console.log(message);console.log('apply with function');$scope.messages.push(message);}});socket.on('message:channel:'+channel,functionmessage(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);varexpires=removalInfo.message.expires;varexpireMessageIndex=$filter('messageByExpires')($scope.messages,expires);if(expireMessageIndex){$scope.messages.splice(expireMessageIndex,1);}});$scope.listeningChannels.push(channel);}//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// Controller methods//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////$scope.joinChannel=functionjoinChannel(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=functionsendMessage(draft){if(!draft.message||draft.message==null||typeofdraft=='undefined'||draft.length==0){return;}socket.emit('message:send',{message:draft.message,name:Auth.currentUser().name,channel:$scope.activeChannel});$scope.draft.message='';};$scope.logout=functionlogout(){Auth.logout();$state.go('login');}//Auto join the lobby$scope.joinChannel('Lobby');})
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 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
// 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-adapterframeworks:['jasmine'],// list of files / patterns to load in the browserfiles:['./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 excludeexclude:[],// preprocess matching files before serving them to the browser// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessorpreprocessors:{},// test results reporter to use// possible values: 'dots', 'progress'// available reporters: https://npmjs.org/browse/keyword/karma-reporterreporters:['spec'],// web server portport: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_DEBUGlogLevel:config.LOG_INFO,// enable / disable watching file and executing tests whenever any file changesautoWatch:true,// start these browsers// available browser launchers: https://npmjs.org/browse/keyword/karma-launcherbrowsers:['PhantomJS'// , 'Chrome'// , 'Firefox'// , 'Safari'],// Continuous Integration mode// if true, Karma captures browsers, runs the tests and exitssingleRun: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:
12345678910111213141516171819202122
varkarma=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 !returngulp.src('./foobar').pipe(karma({configFile:'karma.conf.js',action:'run'})).on('error',function(err){// Make sure failed tests cause gulp to exit non-zeroconsole.log(err);this.emit('end');//instead of erroring the stream, end it});});gulp.task('autotest',function(){returngulp.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.
angular.module('services').factory('Auth',functionAuth($http,$q){varuser=null;varreadStoredUser=functionreadStoredUser(){//Try to read in from localStorage if one existsvarstoredUser=window.localStorage.getItem('user');try{if(storedUser){// Note: Using a simple user model hereuser=newUser(JSON.parse(storedUser));}}catch(ex){/* Silently fail..*/}}readStoredUser();varcurrentUser=functioncurrentUser(){if(!user){readStoredUser();}returnuser;}varsaveUser=functionsaveUser(userToSave){window.localStorage.setItem('user',JSON.stringify(userToSave));user=userToSave;}varloginWithEmail=functionloginWithEmail(name,email){vardeferred=$q.defer();varpostPath='http://someurl.dev/api/v1/login';varpostData={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);});returndeferred.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.
//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 availablemodule('starter.services');module('ngCordova.plugins.facebookConnect');});// instantiate servicevarapiResponse={name:'Josh Bavari',email:'jbavari@gmail.com',id:'4409480064'};varAuth;varFB={init:function(){},login:function(){},api:function(url,params,callback){returncallback(apiResponse);}};varFacebookConnect={login:FB.login};varhttpBackend=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(){varuser={name:'Josh Bavari',id:1};Auth.saveUser(user);varcurrUser=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(){varuser={name:'Josh Bavari',id:1};Auth.saveUser(user);varlocalUser=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(){varuser={name:'Josh Bavari',id:1};Auth.saveUser(user);varlocalUser=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;varname='Josh Bavari';varemail='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(){varresult=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.
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:
<divclass="container"ng-controller="SettingsCtrl"><divclass="row"><divclass="col-md-12"><divclass="header-copy">Account Settings</div><divclass="section-title"></div></div></div><divclass="row"><divclass="col-md-6"><divclass="header-copy">Facebook Settings</div><divclass="divider"></div><divng-show="facebookId"> Currently ({{ facebookName}}) <spanclass="btn dash-subs"ng-click="logout('facebook')">Logout</span></div><divng-hide="facebookId"><spanclass="btn dash-subs login-btn"ng-click="authNetwork('facebook')">Login With Facebook</span></div></div><divclass="col-md-6"><divclass="header-copy">Twitter Settings</div><divclass="divider"></div><divng-show="twitterName"> Currently (@{{twitterName}}) <spanclass="btn dash-subs"ng-click="logout('twitter')">Logout</span></div><divng-hide="twitterName"><spanclass="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.
123456789101112131415161718192021
angular.module('myApp',['ui.bootstrap']);functionSettingsCtrl($scope,$http){//..snip!..$scope.handlePopupAuthentication=functionhandlePopupAuthentication(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=functionauthNetwork(network){varopenUrl='/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)
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.
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:
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 functionsr=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.eachdo|user|beginresult=r.table('Users').insert(user).run(@rdb_connection)# Grab user id from result to use later for assigning the programuser_id=result['generated_keys'][0]rescuep'Error: '+result.to_sendend@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.eachdo|exercise|beginresult=r.table('Exercises').insert(exercise).run(@rdb_connection)exercise_list.pushresult['generated_keys'][0]rescuep'Error: '+result.to_sendend@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}beginresult=r.table('Programs').insert(@joshs_program).run(@rdb_connection)rescuep'Error: '+result.to_send
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:
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:
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.beforedoheaders'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)rescueException=>errlogger.error"Cannot connect to RethinkDB database #{RDB_CONFIG[:host]}:#{RDB_CONFIG[:port]} (#{err.message})"halt501,'This page could look nicer, unfortunately the error is the same: database not available.'endend# After each request we [close the database connection](http://www.rethinkdb.com/api/ruby/close/).afterdobegin@rdb_connection.closeif@rdb_connectionrescuelogger.warn"Couldn't close connection"endendget'/'do@snippet={}erb:newendpost'/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)ifresult['inserted']==1redirect"/#{result['generated_keys'][0]}"elselogger.errorresultredirect'/'endendget'/programs/:userId'docontent_type:json@userId=params[:userId].downcasemax_results=params[:limit]||10results=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_jsonendget'/exercises/:programId'docontent_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_jsonendget'/getuser/:patientId'docontent_type:jsonuser=r.table('Users').filter({'patientId'=>params[:patientId]}).run(@rdb_connection)user.first.to_jsonend
That’s all folks! Hope this helps some in understanding how to do foreign key references in RethinkDB!
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.
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.
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:
12345
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:
12
require ::File.expand_path('../config/environment', __FILE__)
run Rails.application