JavaScript Object and Array Constructor and Access Speed

I was curious which would be the choice to store a large number of simple data structures in JavaScript. An object is the more convenient since you can name your properties, but I if I wanted to store and access a million sets of X,Y coordinates, would arrays be faster than objects.

I set up four tests using jsperf:

  1. Arrays containing three floats
  2. Arrays containing two floats and one string
  3. Objects with three  float properties
  4. Objects with two float properties and one string

I took some precautions in each test to hopefully level the playing files. For every test, before I create a new object or array, I instantiate three float variables with Math.random() and one string variable with “text”, even though I only use three of them in each case. For objects I used property rather than dictionary access because it’s faster.

You can see the results here: http://jsperf.com/obj-vs-array-constructor-and-access/4

  • Safari 5 and IE9 perform better with objects than arrays regardless of whether mixed values are stored.
  • Firefox 13 performs nearly identical in all four cases.
  • Chrome 19 performs better in both object tests and mixed value arrays.

I’m not sure what conclusions to draw about the underlying reasons for this performance favoring objects in Chrome, Safari, and IE.

If you have any comments, please leave them on the jsperf test page.

[EDIT] I forgot that since everything is an object in JS, arrays are just objects with more functionality, so they are obviously heavier to construct.

Selling Ad Space on CWC

During my research into selling downloadable PDFs to semi-computer-phobic women I picked up the notion that placing ads on my site would be not only sacrilege, but tantamount to throwing in the towel. I put so much time and effort into attracting users to my site, wouldn’t it be a loser move to send them away for mere pennies?

Placing ads on my site is like saying, “Howdy Jane Potential-customer, I see you’ve just come over from Google to find Kustom Kupcake Wrappers. Well, I have those and you could be making them in seconds for as little as $19.95, and even though this is the only site on the internet where you can design them right in your browser, wouldn’t you rather click on this link for industrial boat wrapping film?” Or worse, sometimes Google realizes the page is not geared toward boat dealers and serves up an ad for premium reusable kupcake wrappers or custom lollipops (they seem to be serving up more relavent ads as time goes on).

Are ads strictly a bad idea when you’re selling a product? Burger King doesn’t let McDonalds put ads in their menu. Perhaps, but it’s an experiment. Nearly everyone who comes to my site is looking for free wrappers. In fact, the page with the word “free” in the title is the second most visited one after the home page. There are many, many free kupcake wrapper templates on the internet. Women have fun on my site. The average engagement time is over 3.5 minutes. I’m competing for eyeball time with other sites and winning. So the questions is: how do I monetize those visitors?

I consistently compare my kupcake wrapper creator site to Patrick McKenzie’s Bingo Card Creator site, because his blog inspired me to get started. Now I don’t have the kind of sales volume he has over there. There are several reasons why. First, the market for kupcake wrappers isn’t as big as for learning to read. Second, let’s compare pain points — on one hand you have a teacher, who can go to bed once she comes up with a fun activity for Friday, and on the hand you have a grandma looking for something fun and creative for her granddaughter’s upcoming birthday party. The big difference is that, for the teacher, it’s her job, and for the grandma, it’s just a nice idea. There are other reasons, like teachers being part of a community who share tips and tricks, where the community of grandma cupcake wrapper decorators is, well, disjoint.

Patrick is laser focused on optimizing his funnel. Placing outgoing links on his site would be like periodically deleting random blocks of his source code repository in an effort to complete a project faster.

For the past year, I’ve never questioned the wisdom of an ad free site, but now it’s time to experiment. During the past two weeks, I’ve placed big flashy ads all over the site. In that time, I’ve made four sales. I typically average one sale per week. I’ve made $11 in ad revenue. I’ll let this play out for a while, but so far I’m feeling good.

P.S. Some ads pay nearly $2 for a single click!

Quick Cupcake Wrapper Creator Update

Sales are going better. I’m making about one sale per week, split pretty evenly between the $19.95 and the $29.95 versons. A quick SQL query reveals that total sales are $502.57. I had to issue one refund to a confused customer who somehow accidentally purchased my product. I flubbed up the PayPal refund due to poor record-keeping and gave it to the wrong customer. So then I refunded the right customer, losing two sales and my appetite for the rest of the day.

Daily visits are steadily increasing. I’m averaging right under 100 visitors per day, which is the threshold where, once crossed, I plan to start A/B testing.

There are 440 total users, 32 of which are paying customers.

I’ve spent about $50 of my own and $200 free Google-Bucks on AdWords. I track conversions, have AdWords integrated with Analytics, and to the best of my knowledge, not one sale can be attributed to an ad campaign. AdWords are turned off indefinitely. Lest you think I didn’t take AdWords seriously, let me tell you, I used 16 different ad groups, each with unique keywords, and each with at least two ads. I targeted Search and Content networks for the US, UK and Oz.

The site is on auto-pilot now — no more long evenings implementing new features or enhancing the copy. I do have a short list of improvement to make. They include: Taking pictures of a dozen cupcake wrapper designs, this time with white backgrounds, finishing cupcake toppers, and adding baseball and Christmas designs.

I had hoped to be generating a lot of traffic right now for Halloween Cupcake Wrappers, but the second page of Google just isn’t cutting it. Oh well, there’s always next year :)

 

OSX Text to Speech and FFMPEG

I’m trying to memorize bible verses. So far I’ve just done a little hacking. OSX has a command line program, “say”, that will do just that, say whatever you tell it to. It has several voices — check out the manual.

BibleGateway has an ESV Bible audio API where you can download bible passages. So I threw together a shell script and a Python script to make MP3 files for the passages I want to memorize.

The first step was to make stock audio: The ordinal numbers I’d need (first, second and third) and all the cardinal numbers (1-150), the books of the bible (Genesis - Revelation) and some pauses. The say command recognizes by the string ”[[slnc 300]]” where e.g. 300 is the length of the pause. I put all these string in a text file and ran the following bash script to produce the stock MP3s.

 

Bash script to make MP3s from OSX’s say command

#!/bin/bash
trap "echo signal received; exit" 0 INT HUP QUIT TERM PIPE SEGV

while read line; do
lower=$(echo $line | awk '{print tolower($0)}');
lowertrimmed=$(echo $lower | sed 's/ //g');
say -o $lowertrimmed.m4af $lowertrimmed;
ffmpeg -i $lowertrimmed.m4af -ac 1 -ar 22050 -ab 32k $lowertrimmed.mp3 < /dev/null
rm $lowertrimmed.m4af
done < books.txt

Stock audio complete, the next step was to write a Python script to read in a file with the passages I want to memorize, download them from BibleGateway, and append a header and footer with the passage name. For example, Proverbs 25: 8-10 would sound like this:

Proverbs twenty-five, eight through ten… Do not hastily bring into court for what will you do in the end, when your neighbor puts you to shame? Argue your case with your neighbor himself, and do not reveal another’s secret, lest he who hears you bring shame upon you, and your ill repute have no end… Proverbs twenty-five, eight through ten

 

Python script to make audio bible passages using FFMPEG

#!/usr/bin/env python

import urllib
import sys
import codecs
import re
import getopt
import os.path
import shutil
import os

KEY = 'IP'

OUTDIR = "/Users/joshuapearce/Dropbox/Code/MakeEsvAudio/output"
PASSAGEDIR = "/Users/joshuapearce/Dropbox/Code/MakeEsvAudio/passages"
STOCKDIR = "/Users/joshuapearce/Dropbox/Code/MakeEsvAudio/stock"
TEMPDIR = "/Users/joshuapearce/Dropbox/Code/MakeEsvAudio/temp"

PAUSE100 = STOCKDIR + '/' + 'pause100.mp3'
PAUSE200 = STOCKDIR + '/' + 'pause200.mp3'
PAUSE300 = STOCKDIR + '/' + 'pause300.mp3'
PAUSE400 = STOCKDIR + '/' + 'pause400.mp3'
PAUSE800 = STOCKDIR + '/' + 'pause800.mp3'
FIRST = STOCKDIR + '/' + 'first.mp3'
SECOND = STOCKDIR + '/' + 'second.mp3'
THIRD = STOCKDIR + '/' + 'third.mp3'
THROUGH = STOCKDIR + '/' + 'through.mp3'

class ESVSession:
def __init__(self, key):
textoptions = ['include-short-copyright=0',
'output-format=plain-text',
'include-passage-horizontal-lines=0',
'include-heading-horizontal-lines=0',
'include-passage-references=false',
'include-first-verse-numbers=false',
'include-headings=false',
'include-subheadings=false',
'include-selahs=false',
'include-footnotes=false']

mp3options = ['output-format=mp3']

self.textoptions = '&'.join(textoptions)
self.textBaseUrl = 'http://www.esvapi.org/v2/rest/passageQuery?key=%s' % (key)

self.mp3options = '&'.join(mp3options)
self.mp3BaseUrl = 'http://www.esvapi.org/v2/rest/passageQuery?key=%s' % (key)

def getPassage(self, passage):
passage = passage.split()
passage = '+'.join(passage)
url = self.textBaseUrl + '&passage=%s&%s' % (passage, self.textoptions)
page = urllib.urlopen(url)
return page.read()

def getMp3(self, passage, filename):
passage = passage.split()
passage = '+'.join(passage)
url = self.mp3BaseUrl + '&passage=%s&%s' % (passage, self.mp3options)

urllib.urlretrieve (url, filename)

def usage():
print "USAGE: program.py -f /path/to/file"

class Usage(Exception):
def __init__(self, msg):
self.msg = msg

# This doesn't work, but I'd like it to. I have no idea what this guy's talking about:
# http://www.artima.com/weblogs/viewpost.jsp?thread=4829
class Error(Exception):
def __init__(self, msg):
self.msg = msg

def main(argv=None):
if argv is None:
argv = sys.argv
try:
try:
opts, args = getopt.getopt(argv[1:], "f:h", ["file=", "help"])
except getopt.error, msg:
raise Usage(msg)

file_path = ''

for opt, arg in opts:
if opt in ("-h", "--help"):
usage()
if opt == "-f":
file_path = arg
if (file_path == ''):
usage()
return

f = open(file_path, 'r')
passages = f.read().splitlines()
f.close()

bible = ESVSession(KEY)

line_num = 0
for p in passages:
line_num = line_num + 1
if not p.startswith("#"):
match = re.search("^([0-3]{0,1})([a-zA-Z ]+)([0-9]{1,3}) ?: ?([0-9]{1,2}) ?([\-0-9]{0,3})$", p)
#try:
book_num = match.group(1)
book_num_str = book_num
book = match.group(2).strip()
chapter = match.group(3)
verse_start = match.group(4)
verse_end_str = match.group(5)
verse_end = verse_end_str.replace('-', '')
if book_num != '':
book_num_str = book_num + ' '

passage = '%s%s %s:%s%s' % (book_num_str, book, chapter, verse_start, verse_end_str)
bgw_filename = '%s%s_%s_%s%s.mp3' % (book_num_str.replace(' ', '_'), book.replace(' ', '_'), chapter, verse_start, verse_end_str)
bgw_filepath = PASSAGEDIR + '/' + bgw_filename
cat_filename = bgw_filename.replace('.mp3', '_cat.mp3')
cat_filepath = TEMPDIR + '/' + cat_filename
final_filepath = OUTDIR + '/' + bgw_filename

if not os.path.isfile(bgw_filepath):
bible.getMp3(passage, bgw_filepath)

mp3_files = [PAUSE300]

if book_num == '1':
mp3_files.append(FIRST)
elif book_num == '2':
mp3_files.append(SECOND)
elif book_num == '3':
mp3_files.append(THIRD)

mp3_files.append(STOCKDIR + '/' + book.replace(' ', '').lower() + '.mp3')
mp3_files.append(STOCKDIR + '/' + chapter + '.mp3')
mp3_files.append(STOCKDIR + '/' + verse_start + '.mp3')

if verse_end != '':
mp3_files.append(THROUGH)
mp3_files.append(STOCKDIR + '/' + verse_end + '.mp3')

mp3_files.append(PAUSE200)
mp3_files.append(bgw_filepath)
mp3_files.append(PAUSE200)

mp3_files.extend(mp3_files[:-2])
mp3_files.append(PAUSE800)
cat_file = open(cat_filepath, 'wb')

for mp3_file in mp3_files:
shutil.copyfileobj(open(mp3_file, 'rb'), cat_file)

cat_file.close()

popen_args = ['ffmpeg', '-i', cat_filepath]
popen_args += ['-metadata', 'title="' + passage + '"']
popen_args += ['-metadata', 'author="God"']
popen_args += ['-metadata', 'artist="God"']
popen_args += ['-metadata', 'album="Verses to Memorize"']
popen_args += ['-metadata', 'comment="Created by Josh Pearce, www.beechtreetech.com"']
popen_args += ['-y', final_filepath]
#print " ".join(popen_args)
proc = os.popen(" ".join(popen_args))

#except Exception as err:
#print "trouble reading line: " + str(line_num)
#return 1

except Usage as err:
print >>sys.stderr, err.msg
print >>sys.stderr, "for help use --help"
return 2

if __name__ == "__main__":
sys.exit(main())

 

Resources for say, FFMPEG and the Bible Gateway API

Blog to Podcast

say manual from Apple

Bible Gateway ESV MP3 API

Cupcake Wrapper Creator at Eight Months

Eight months ago we launched a two page website to see if anyone was interested in paying money to design printable cupcake wrappers online. I read somewhere that 700 was the minimum word count for a page to look respectable to Google. So on the landing page we had just over 700 words describing the fun things that Cupcake Wrapper Creator could do and why you should buy it. At the bottom was a “Sign Up” button for the yet non-existent site. Clicking it took you to the “Coming Soon” page which said the service is not ready yet, but if you give us your email address, we’ll be happy to notify you when it is. We wanted to know that enough people were interested before we wasted three months of our lives building a product nobody wants.

Three months later, 36 people had said they would like to be notified when Cupcake Wrapper Creator was ready. While minuscule in absolute terms, they represented 12% of the 291 total visitors who came to the website.  Having over 10% of strangers on the internet giving us their email address for a product that didn’t even exist yet, struck me as a good indication that the design-your-own-cupcake-wrappers-online niche was not being well served. Actually it wasn’t being served at all. We had done the research to know that beforehand. We took the sign-ups as evidence that, if we did build the service, people would pay us for it.

Numbers

On February 27, 2011 Cupcake Wrapper Creator went live. From the next five months we’ve had the following numbers:

  • 26,358 Pageviews
  • 4,837 Unique Visitors
  • 235 Trial Users
  • 20 Paying Customers
  • $283 in Receipts
  • Over 4000 Designs Created
  • Over 7,000 PDFs Downloaded

4.9% of people who visit the website decide to sign up for a trial membership. 8.5% of people who sign up for the trial go on to buy. And overall, 0.41% of visitors decide to purchase. If these figures extrapolate linearly, they mean that if we could increase our traffic from 1,100/month to 5,000/month, then we should get 20 purchases per month. We have two prices: $12.95 and $24.95. Most people choose the less $13 plan, but if we figure the average customer pays $15, then I would be making $300/month. This is actually sort of sad, considering the work we’ve put into the product so far, but we always thought of the cupcake site as a test bed where we would learn how to market and sell digital goods.

Easy Parts

Building the website part was easy since I build enterprise and industrial web applications in my day job. But unlike my day job, I used the latest technologies for an ASP.NET MVC application; Such as the Razor view engine, and two nice open source projects by Troy Goode, the membership starter kit that papers over some glaring holes in the membership capabilities that come out of the box with ASP.NET, and ABsoluteMaybe, an A/B testing plugin.

I just want to take a quick monument to say, I wanted to build this site on Ruby on Rails. I’ve never build a site with RoR, but I’m convinced it’s a more modern framework that ASP.NET MVC and has community contributions for nearly every reusable website feature I could ever need. But I know ASP.NET, and though the server costs are higher, I’m productive in it and the goal of every web business should be to make server costs a rounding error. My time was just too valuable to learn a new framework.

My app is basically a PDF generator. Users customize their cupcake wrappers by choosing colors and fonts and entering text in an HTML form. But, I don’t rely on ASP.NET to process the print queue and generate the PDFs. For that, I’ve tapped into the rich Java ecosystem. At the day job, I’ve wrote a good deal of original functioning code, but for this, I’m just stitching pre-existing APIs together. I highly recommend the Jav-o-sphere for it’s abundance of APIs. Now when the twice weekly Java update pops up, I’m like, “Heck yeah! Update ahoy! ” But, I’m terrible at Java; If I have three or four Google windows open and Eclipse, I might be able to write Hello World swing app. Good thing I haven’t had to write a single line of Java for this application.

I access the rich world of Java by writing JavaScript code and running it in Rhino. Yep, no Scala or Clojure coolness for me. I’ll stick with good old fashion JavaScript in all it’s slow glory on Rhino.

Hard Parts

Making designs for the wrappers is hard. It’s slower than I anticipated and requires much hand editing and tweaking to get them to look just right. All of my templates start out as a Python script with some fun 6th grade geometry. This is a gating task in my process. I’d hoped to hire freelancers to make the templates, but with a budget of $30, that’s not an option.

Harder Parts

Unsurprisingly, marketing the website consumes most of the time I spend on this project. Tasks include: promoting the website on various cupcake forums, writing blog owners, writing copy, hiring freelancers to write articles, flushing money down the AdWords commode, and constant tweaking of the website’s content as my knowledge of how Google sees my site increases. I’ve found it helpful to give my site some basic CMS functionality, so my wife, me, or maybe one day a freelancer can create new content, like the classroom cupcake wrappers page.

Synchronizing Server and Javascript Enumerations

I came up with a quick extension method that you can use to convert a C# enumeration definition to a dictionary. After you get the dictionary, you can then serialize it to JSON, and include it in your JavaScript code using a dynamically generated JS file, or for the hyper organized, incorporate it into your build process so the file is served statically.

Here’s the gist of it:

public enum PetEnum
{
    Dogs = 0;
    Cats = 1;
    Fleas = 2;
}

public static class Dictionary EnumDict(this PetEnum enumConst)
{
    string[] names = Enum.GetNames(enumConst.GetType());
    Array vals = Enum.GetValues(enumConst.GetType());
    
    var result = new Dictionary<string, int>();

    for (int i=0; i<names.Length; i++)
    {
        result.Add(names[i], (int)vals.GetValue(i));
    }

    return result;
}

// using Newtonsoft.Json.Converters or you favorite JSON serialization method
string petEnumDefJson = JsonConvert.SerializeToString(MyEnum.Fleas.EnumDict());

Now you’ve got a string enumDefJson and you need to stick it into your page somehow as a script. You could make it a model property.

<script language="javascript">
var petEnumDefJson = <%=Model.PetEnumFedJson%>;
</script>

Ruby On Rails, Thin and Nginx on Windows

Are you like me? Do you work in an industry or business which requires you to write web applications that runs on Windows? It’s not all that bad, right? C# 3.0 and 4.0 are pretty nice languages, and ASP.NET MVC is light years ahead of reguar ASP.NET. At my day job, I’m replacing traditional desktop applications with web applications. These are highly functional engineering applications, but they will have very few users at any one time, so they don’t need to run on a full blown web server. One big problem I run into is that ASP.NET applications are tied to IIS.

There are hardly any good ways to ship an ASP.NET web application as a stand alone product, which is a shame, because there are some impressive web applications which can run stand-alone, with an embedded web server and there should be more. It’s not like deploying ASP.NET applications is a total nightmare, there is something called “Web Matrix” and the commercial Cassini web server, plus the Windows Platform Installer, and even http.sys. Web Matrix, from Microsoft includes the “IIS Lite” web server, a stand-alone version of IIS, but it’s bundled with Sql Server Express, Visual Studio Express and Expression Studio — hardly a stand alone option for shipping a web application. The commercial Cassini server, might, I suppose, be an option, but I can’t tell, without purchasing it, whether it’s robust and up to date. It’s not sold by Microsoft so it might be out of sync with their latest technologies. The Windows Platform Installer has taken much of the pain out of packaging and deploying Windows web applications (except it doesn’t run aspnet_regiis.exe for some reason), so that a product like Umbraco can be installed with just a few clicks. Finally, you have http.sys, an HTTP server built into modern versins of Windows. It will allow Console or Windows Forms application to host WCF services, but will not, as far as I can tell, host an entire ASP.NET website.

This blog post is aimed at software developers, not really managers, but it may help you if you’re a manager trying to understand what some of your best developers are thinking. What are they thinking, you ask? Well, they really are stoked about ASP.NET MVC, at least they were for a while, until they realized it’s just a cheap knock-off of Ruby On Rails. Yes, it’s an MVC framework, that’s good, and even better, most of the code that MS ships is pretty solid, but as far as features, it’s consistently a year or two behind RoR. Good web developers want to work with the best technology, so being stuck in asenior web developer position writing ASP.NET, drives them mad after a while. They can’t leave and take an entry-level position learning Rails. So what can they do?

Enter RoR on Windows. It’s actually solves two problems:

  1. Your company needs to deploy a web application what is easy to install and run on any desktop (think sales guy on an airplane).
  2. Your good web developers want to work with awesome technologies.

I didn’t invent it, obviously, but I have will show you the steps I used to get it running.

First, a few caveats:

  1. All I’ve done so far is pull up the default rails application page, not run a full application.
  2. I want this installation to be able to sit anywhere in the file system, but right now it needs to stay in the same path you create it at, e.g. C:\StandAlone
  3. I’ll update this post as a progress further, hopefully developing a full, stand-alone RoR web app on Windows, which can be deployed by just copying files.

Procedure to Get RoR, Thin and Nginx Running on Windows

  1. Download the latest RubyInstaller for Windows.
  2. Install to C:\[Stand_Alone_Dir]\Ruby, where [Stand_Alone_Dir] is what ever you want to call it. I’ll call it C:\StandAlone for now on.
  3. During the install, do not add Ruby to the system path of register ruby file types, we want to make sure the web application can be installed by just copying a the StandAlone directory.
  4. After the RubyInstaller is finished, check out the properties of the shortcut in the start menu called, “Command Prompt with Ruby.” Notice that CMD.EXE opens setrbvars.bat with the /K flag which tells the command windows to run the batch file and remain open. setrbvars.bat uses the %~dp0 variable which stores the directory in which the batch file resides; in this case, it’s the Ruby bin. This little bit of DOS magic will be useful for other scripts.
  5. Download the DevKit from RubyInstaller.
  6. Extract it to C:\StandAlone\DevKit.
  7. Open a Ruby command prompt and CD to C:\StandAlone\DevKit. Run ruby dk.rb to generate the config.yaml file.
  8. Run ruby dk.rb install.
  9. gem install thin –platform=ruby
  10. gem install rails –platform=ruby
  11. Download the Sqlite shell (sqlite.exe) plus the Sqlite Dll for Windows and place them both in C:\StandAlone\Ruby\bin.
  12. gem install sqlite (or gem install sqlite3-ruby for for rails2) (notice here, we don’t use –platform=ruby. I don’t know why, but using the –platform=ruby flag here caused a lot of errors. These gem installations seems to be very smart about knowing if you’re running on a Windows platform, but the DekKit documentations is adamant about using the –platform=ruby flag to avoid downloading binaries for other platforms. )
  13. MK DIR C:\StandAlone\www
  14. CD C:\StandAlone\www
  15. rails new mystandalonewebapp.com
  16. Test out with, thin start
  17. Download Nginx binary for Windows and put in C:\StandAlone\nginx
  18. Create two folders: C:\Standalone\nginx\sites_available and C:\StandAlone\nginx\sites_enabled
  19. Create a file, mystandaloneapp.com.txt in sites_enabled.
  20. Copy C:\Standalone\nginx\conf\nginx.conf to nginx.conf.orig.
  21. Edit nginx.conf to look like:
    worker_processes  1;
    error_log  C:/StandAlone/nginx/logs/error.log;
    events {
        worker_connections  1024;
    }
    http {
        include       mime.types;
        default_type  application/octet-stream;
        log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                          '$status $body_bytes_sent "$http_referer" '
                          '"$http_user_agent" "$http_x_forwarded_for"';
    
        sendfile        on;
        #keepalive_timeout  0;
        keepalive_timeout  65;
        #gzip  on;
        include C:/StandAlone/nginx/sites_enabled/*.txt;
    }
    
  22. Create a file, C:\StandAlone\nginx\sites_enabled\mystandaloneapp.com.txt to look like:
    upstream mystandaloneapp {
    	server 127.0.0.1:3000;
    }
    
    server {
    	listen       8080;
    	server_name  localhost;
    	#charset koi8-r;
    
    	access_log C:/StandAlone/www/mystandaloneapp.com/log/access.log;
        error_log  C:/StandAlone/www/mystandaloneapp.com/log/error.log;
        root       C:/StandAlone/www/mystandaloneapp.com;
        index      index.html;
    
    	location / {
            proxy_set_header  X-Real-IP  $remote_addr;
            proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header  Host $http_host;
            proxy_redirect    off;
            try_files C:/StandAlone/www/maintenance.html $uri $uri/index.html $uri.html @ruby;
        }
    
        location @ruby {
            proxy_pass http://mystandaloneapp;
        }
    }
    
  23. That’s it. now you should be able to run, thin start from within the rails app, and then double click nginx.exe from within the nginx directory, and see the default rails website running at http://localhost:8080.

ESV Audio Bible Chapter Lookup

The Audio version of the ESV bible, by Crossway Bibles sounds great, but unfortunately, it’s not broken down by book and chapter. So, I took the opportunity to learn jQuery mobile and Rhino JS, to make a small mobile website which tells you the audio file and bookmark for each book and chapter of the bible.

It’s not perfect, so please email me if you find it’s a chapter or two off, and I’ll make the correction.

ESV Audio Bible Chapter Lookup

My First Micro ISV

It’s taken me years to realize that a tiny, part-time software business has to be based on a simple idea, which can be implemented fast. Beyond simple and fast, it has to be self-contained. All of the other ideas I had before this one required too much customer hand-holding, e.g. services and support for OpenEMR, an open source electronic medical records system. That idea was totally delusional! EMR systems are probably a close second to ERP when it comes to the amount of custom configuration and training which they require.

Inspiration

I wish I could take credit for this new idea, but that goes to my wife. Inspired by Patrick McKenzie of sight-word bingo fame, I knew I wanted to make a simple web-based service which would appeal to women, not your stiletto-wearing city gals, no, more the Midwestern homemaker type of woman. Now, my wife looks great in a pair of high-heels, but she’s still qualified to answer this question. I said: “Baby, what simple software product can I sell to women?” And BAM, she came up with this great idea for printable cupcake wrappers.

The idea is, you go to the website, sign up, (which will cost something like $20-$30 for a year or two of access) and you’re presented with a selection of template designs for printable cupcake wrappers. There will be all kinds of designs for: birthday parties, anniversaries, Halloween, Independence Day and more. So you pick a design, select what colors you want, and then perform further customizations like putting the birthday celebrant’s name on the wrappers, or making a wrapper with each guest’s name on it. Finally, you download a PDF with your wrapper designs, print them on heavy paper, and cut them out with scissors.

Technical Details

I plan on writing more about the technology I use for this project, but for now, here is a laundry list of them:

What I Do

I had a little breakdown after ten minutes trying to fix my wife’s eyeglasses for the second time. I realized, during a postmortem of the breakdown, that what I do, what I’m driven to do, is automate mundane, repetitive tasks.

Two weeks ago, my wife handed me her designer eyeglasses. She was having trouble with a lose screw in the hinge. The repair required holding the glasses in my right hand, a screwdriver in my left, and a second screwdriver in my teeth. After an hour I was finally able to align the screw through the hinge’s holes and thread it. I was satisfied to have solved the problem, but told my wife she needed to tighten the screw every week because it was such a nightmare to get back in.

Yesterday, I came home to see my wife holding her glasses with a yet-again dislodged screw. I looked loathsomely at the object of my affliction, but then lovingly at the object of my affection and decided to give it another shot. After ten minutes with no success, I watched my frustrated hands fight the urge to crush the hipster, lime-colored plastic glasses they held. Breaking away from this astral detachment, I stomped upstairs and grilled my wife, “DID YOU TIGHTEN THESE LIKE I SAID TO?” “Yes” she nervously claimed, “Or no, maybe. I can’t remember” she said. “WELL,” I bellowed, “I CAN’T DO IT! I’M NOT DOING IT! I”M NOT SPENDING ANOTHER HOUR OF MY LIFE ON THIS NOW, AND THEN AGAIN NEXT WEEK!” And that was the epiphany. It wasn’t the past ten minutes spent trying to remember the correct contorted position which bothered me, but knowing it wouldn’t be the last time. No, it would be a futile endless battle in which the glasses would ultimately triumph in stealing hours from my short life.

We have this Veggie-Tales themed nativity scene, plastic vegetables dressed up as Baby Jesus, Mary, Joseph, and the wise men, complete with a singing, flashing, plastic manger. I think Joseph and Mary are squash, Jesus a pear or something, and one of the wise men is a cucumber — the kids love it, but they also love to spread the pieces all over the living room. My wife, and by proxy me, are compelled to reassemble the scene several times a day. This too drives me nuts! It’s not picking up the pieces any single time, but knowing it’s going to keep happening. I will be crawling around on the floor, looking for the immaculate pea or the magi asparagus for twelve minutes a day, twenty days a year, for the next three or four years! 960 minutes of un-automated, repetitive minutes gone!

When writing software, the goal is to take some task which a human once did manually, and to partially or completely automate it. I automate as much of my work-flow as possible. If I could, I would automate myself right out of writing software all together.

I think this attitude explains why I dislike the employee-employer fixed pay relationship so much. Selling your time, day-in, day-out, to write software, is antithetical to the very act of writing software, because, as the developer, you haven’t automated yourself out of any future work. And that is why I’m becoming a Micro Independent Software Vendor , so that I can automate tasks which bring value to customers, and reap the benefits of automation.