fast npm install check to ensure it is up to date

Ensures that everyone has npm up to date without running “npm install”
Ideally this should be wrapped as “npm check” command, but we use a ruby/rake based workflow anyway.

  desc 'make sure npm is installed'
  task :ensure_npm do
    expected = JSON.parse(File.read('package-lock.json')).fetch('dependencies')
    satisfied = expected.all? do |name, data|
      expected_version = data.fetch('version')
      pack = "node_modules/#{name}/package.json"
      next unless File.exist?(pack)
      resolved = JSON.parse(File.read(pack))
      resolved.fetch('version') == expected_version || # regular
        resolved.fetch('_resolved') == expected_version # git
    end
    sh "npm install" unless satisfied
  end

Sending configuration into a AWS Lambda created via Cloudformation

Lambdas can only have static code (see code upload via cloudformation), so passing in DynamoDB table names/SNS topic ARNs etc is not possible. But there is a neat workaround:

Make the lambda read the stacks output.

# my-stack.json
"Outputs": {
  "World": {
    "Value": {
      "Ref": "MySnsTopic"
    }
  },
  ....
}

var AWS = require('aws-sdk');
var stack = context.invokedFunctionArn.match(/:function:(.*)-.*-.*/)[1];

exports.handler = function(event, context) {
  var cf = new AWS.CloudFormation();
  cf.describeStacks({"StackName": stack}, function(err, data){
    if (err) context.done(err, 'Error!');
    else {
      var config = {}
      data.Stacks[0].Outputs.map(function(out){ config[out.OutputKey] = out.OutputValue });
      context.succeed('hello ' + config.World)
    }
  })
};

# output
"hello arn:aws:sns:ap-northeast-1:8132302344234:my-stack-MySnSTopic-211Z1K3GAGK9"

Transparant redirect for jquery ajax requests in rails with status code 278

There is no perfect backwards compatible solution out there afaik, but this is getting me pretty close to where I want it to be.

  • instead of redirecting set a empty 278 response (made up status code) for xhr requests
  • tell jquery to redirect when receiving a 278 response
  • the success handlers are still executed / there is no way to stop them, but they can either just insert the blank reply or do a != '' check
// application.js
// when we get a fake redirect response, go there
// this will still execute success handlers, but hopefully the fail or are not ugly ...
$.ajaxSetup({
  statusCode: {
    278: function(_,_,response){
      // using turbolinks to not lose back functionality
      Turbolinks.visit(response.getResponseHeader('X-278-redirect'));
    }
  }
})
# some controller
redirect_to_with_xhr signup_path

# application_controller.rb
# ajax requests follow all redirects, so we have to improvise with a
# special code and header to not get placeholders replaced with full pages.
def redirect_to_with_xhr(path)
  if request.xhr?
    response.headers["X-278-redirect"] = path
    head 278
  else
    redirect_to path
  end
end

Running karma js with rails asset pipeline / sprockets

# test/karma.conf.js
...
    basePath: '<%= Bundler.root %>',
...
    files: [
      '<%= resolve_asset('vis.js') %>',
      'app/assets/javascripts/app.js',
      'test/**/*_spec.js'
    ],

# Rakefile
namespace :test do
  task js: :environment do
    with_tmp_karma_config do |config|
      sh "./node_modules/karma/bin/karma start #{config} --single-run"
    end
  end

  private

  def with_tmp_karma_config
    Tempfile.open('karma.js') do |f|
      f.write ERB.new(File.read('test/karma.conf.js')).result(binding)
      f.flush
      yield f.path
    end
  end

  def resolve_asset(file)
    Rails.application.assets.find_asset(file).to_a.first.pathname.to_s
  end
end

Refile upload with jquery-ui and progress

Ran into a few gotschas while implementing this, so I wanted to share 🙂

– buttons need to be disabled via the button method
– e.originalEvent.detail holds progress information


$(document).on("upload:start", "form", function(e) {
  $(e.target).prev().prev('img').hide(); // hide old image we are replacing
  $(this).find("input[type=submit]").
    button({disabled: true}). // do not let user press submit until image is uploaded
    after('<img src="/images/loading.gif" />') // indicate we are waiting
});

$(document).on("upload:progress", "form", function(e) {
  // http://stackoverflow.com/questions/10420352/converting-file-size-in-bytes-to-human-readable
  function humanFileSize(bytes) {
    var thresh = 1024;
    if(bytes < thresh) return bytes + ' B';
    var units = ['kB','MB','GB','TB','PB','EB','ZB','YB'];
    var u = -1;
    do {
      bytes /= thresh;
      ++u;
    } while(bytes >= thresh);
    return bytes.toFixed(1)+' '+units[u];
  };

  var detail = e.originalEvent.detail;
  var percentage = Math.round((detail.loaded / detail.total) * 100);
  $(e.target).next().text(percentage + "% of " + humanFileSize(detail.total))
});

$(document).on("upload:complete", "form", function(e) {
  if(!$(this).find("input.uploading").length) {
    $(this).find("input[type=submit]").
      button({disabled: false}). // all images uploaded, user can submit the form
      next().remove(); // remove loading
  }
});