JavaScript, CoffeeScript, and Closures

I’ve really liked CoffeeScript because it mimics behaviors and syntax I’m used to in Ruby; thus the speed of development for me is skyrocketing as the context switch between Ruby & JS has been minimized. But when I first started mucking around with it a few weeks ago I hit some oddities that I wanted to share with other developers since I’ve seen the question asked a few times.

CoffeeScript can, if this feature is enabled, wrap your .coffee code inside of an anonymous function that acts as a closure. Rails 3.1 enables this by default. I am also using jQuery, so the $ -> is merely the wrapper that provides the jQuery namespace context.

test.coffee

 
$ ->
  show_dialog = (title,msg) ->
    dialog = $('#dialog')
    dialog.find('h2').text(title)
    dialog.find('p').text(msg)
    dialog.modal('show')
 
  $('#about_us').click ->
    show_dialog('About Us', 'Pinning is fun!')

Becomes

test.compiled.js

(function() {
 
  $(function() {
    show_dialog = function(title, msg) {
      dialog = $('#general_modal');
      dialog.find('h3').text(title);
      dialog.find('p').text(msg);
      return dialog.modal('show');
    };
    return $('#about_d').click(function() {
      return show_dialog('About Us', 'Pinning is fun!');
    });
  });
 
}).call(this);

Uh oh. This creates a problem: if you want your view to call one of the functions you have defined in a CoffeeScript file you’re SOL unless you make those functions globally available (I don’t encourage cluttering the global namespace). Causing further headache, when you realize your functions are not being called and try to debug things you find yourself looking at reference/undefined method errors. Solution: throw your functions (JS or CS) in a namespace and have globally accessibly, albeit scoped appropriately, JavaScript. And, for added benefit, have a cleaner code base :)

(Displaying actual code to give you more context of how I use this solution)

welcome.js.coffee

 
window.Pinned ||= {}
 
$ ->
  Pinned.handle_invite_request = (json) ->
    console.log(json)
    console.log(json.status)
    if json.status == 'success'
      $('#invite_success').html("We'll e-mail <b>"+json.email+"</b> when we're ready!").fadeIn('fast')
      $('#invite_request').slideUp('fast')
    else
      $('#invite_error').html(json.message).show()
 
  Pinned.request_invite = () ->
    $.get("/welcome/request_invite",{'email':$('#invite_email').val()}, Pinned.handle_invite_request, "json")
 
  genModal = $('#general_modal')
 
  Pinned.show_dialog = (title,msg) ->
    genModal.find('h3').text(title)
    genModal.find('p').text(msg)
    genModal.modal('show')
 
  $('#about_d').click -> 
    Pinned.show_dialog('GetPinned',"We believe there is a better way to encourage sharing, collaboration, and love for all things artsy and digital. Our passion is to help you share your passion.")

Becomes

welcome.js

(function() {
 
  window.Pinned || (window.Pinned = {});
 
  $(function() {
    var genModal;
    Pinned.handle_invite_request = function(json) {
      console.log(json);
      console.log(json.status);
      if (json.status === 'success') {
        $('#invite_success').html("We'll e-mail <b>" + json.email + "</b> when we're ready!").fadeIn('fast');
        return $('#invite_request').slideUp('fast');
      } else {
        return $('#invite_error').html(json.message).show();
      }
    };
    Pinned.request_invite = function() {
      return $.get("/welcome/request_invite", {
        'email': $('#invite_email').val()
      }, Pinned.handle_invite_request, "json");
    };
    genModal = $('#general_modal');
    Pinned.show_dialog = function(title, msg) {
      genModal.find('h3').text(title);
      genModal.find('p').text(msg);
      return genModal.modal('show');
    };
    return $('#about_d').click(function() {
      return Pinned.show_dialog('GetPinned', "We believe there is a better way to encourage sharing, collaboration, and love for all things artsy and digital. Our passion is to help you share your passion.");
    });
  });
 
}).call(this);

You can now reference your functions that have been defined anywhere in your code!

Two ways to Saving E-mail Attachments (AppleScript & Ruby+EWS)

I had this fun project of storing & processing some financial analysis reports. The goal was to receive periodic (bi-weekly) reports that were sent by an automated reporting software. The catch was that these reports were only deliverable via e-mail and the e-mail address they were being delivered to was a Microsoft Exchange account with no POP/SMTP access. Since I had recently been mucking with my Mac Mail.app rules I figured I’d write a little AppleScipt… Check it out:

(My dev machines already talk to each other over authorized SSH keys so this became a snap)

on perform_mail_action(ruleData)
	tell application "Mail"
		set theMessages to |SelectedMessages| of ruleData
		repeat with themessage in theMessages
			set theAttachments to mail attachments of themessage
 
			try
				repeat with anAttachment in theAttachments
					repeat 1 times
						if name of anAttachment does not end with ".htm" then exit repeat
						set {seconds:s} to (current date)
 
						set thePath to "/tmp/reports/" & s & (random number from 10000 to 900000) & name of anAttachment
						save anAttachment in thePath
						set cmd to "scp " & thePath & " ov2:/opt/svn/optisol/src/ruby/transit/reports"
						do shell script cmd
					end repeat
				end repeat
			end try
		end repeat
	end tell
end perform_mail_action

So my fun with AppleScript was short-lived. To have a more reliable solution I switched over to the the pretty decent SOAP API for Microsoft Exchange called Microsoft Exchange Web Services Managed API (or just EWS). And luckily a neat Ruby Gem also exists called Viewpoint that makes life a breeze. In just a few lines of code, here is my script that pulls down a search for the last 7 days of e-mails that have attachments and are sent to a special mailing address:

require 'viewpoint'
require 'kconv' # If on Ruby 1.9
 
Viewpoint::EWS::EWS.endpoint = EWS_URL #... 'https://yahoo.com/EWS/Exchange.asmx'
Viewpoint::EWS::EWS.set_auth EWS_USERNAME,EWS_PASSWORD
 
inbox = Viewpoint::EWS::Folder.get_folder_by_name('inbox')
 
restrict = { 
  :restriction => { 
    :and => [
      :is_greater_than => [ {:field_uRI => {:field_uRI=>'item:DateTimeSent'}}, {:field_uRI_or_constant => {:constant => {:value=>DateTime.now-7}}} ],
      :is_equal_to => [ {:field_uRI => {:field_uRI=>'item:HasAttachments'}}, {:field_uRI_or_constant => {:constant => {:value=>true}}} ],
      :contains => [ {:field_uRI => {:field_uRI=>'message:ToRecipients'}}, {:constant => {:value=>'your@email.com'}} ]
    ]
  } 
}
 
search_results = inbox.find_items(restrict)
puts "#{search_results.size} items to inspect/process"
 
search_results.each do |email|  
  # Attachments are Base64 encoded -- let's unpack and grab the result
  attachment_content = email.attachments.first.unpack('m').first
 
  puts attachment_content
 
  # Viewpoint also gives you a handle save method
  # email.attachments.first.save_file
end

Here are some helpful pages worth referencing