Information Technology Dark Side

Struggles of a Self-Taught Coder

Information Technology Dark Side header image 1

Interns at TroopTrack

July 7th, 2014 · No Comments

Hiring IFL (Intern for Live)
Until recently, we really struggled to provide consistent customer service. Sometimes we were awesome – customers would put in a ticket on a weekend in the wee hours and would get a response right away. Other times… not so great. It was hard to provide consistent customers service when it was just Shannon and I and TroopTrack was growing so rapidly that it was clear we couldn’t do it ourselves. Things got worse when Shannon had to take a leave of absence for a family crisis. We soon found ourselves staring at more than a thousand open help desk tickets. It had gone from kind of bad to horrible.

During this time we experimented with hiring free-lancers to help with customer service. These were typically people with experience in scouting who thought they had extra time. We would train them on TroopTrack basics, then expect them to answer questions. This sometimes worked okay, but never great. The biggest problem was consistency. No one had a set schedule, so we never knew when someone would work. People always seemed to be busier than they thought.

We were feeling pretty desperate, and then one day I thought of my nephew Spencer who was due to return home from a two-year sabbatical in Brazil and would have five months at home before he went back to school. He is smart, technical, and needed a job to pay for his return to school. By smart, I mean super smart – he has a full-tuition academic scholarship to an excellent university.

I hired Spencer for $10/hour, forty hours a week and put him to work on the help desk. I gave him a Macbook Air, set up a local environment so he could run TT locally, pointed him at the queue of questions in the help desk and said “Figure it out”.

And he did. He occasionally asked us questions, but for the most part he used his brains and curiosity to become an expert user in a very short time. Every day, the first thing he does is answer every new help desk ticket. As a result, our customers get a response within a business day. This appears to have affected our conversion rate from trial customers to paying customers in a good way:

TroopTrack_Help_Desk

Two months of data is hardly conclusive, but the early indications are good that we are achieving a more consistent conversion rate than we have had in the past.

Our second intern, Coleman, starts tomorrow. Spencer is going to provide most of his training.

We call Spencer IFL, short for Intern for Life, and pronounced ‘Eifel’.

Here are some details about our internship program that I think may be helpful to other bootstrappers who are considering using interns.

Payment
Our interns all start at $10/hour. The most they can earn as an intern is $15/hour. I give them a $1 raise ever 4 – 8 weeks if they perform well until they hit the max. Once they hit the max, they are stuck there until they become an Apprentice.

An apprentice can earn up to $25/hour. To become an apprentice, an intern completes a special project that demonstrates an aptitude and passion for programming as well as a basic understanding of important programmer topics like SCM, TDD, etc. Once they complete their project they present it to the three C’s (CEO, CTO, and COO) and we decide collectively to promote them to apprentice. We don’t have any apprentices yet, but Spencer is working diligently on his project. I mention this to illustrate two things 1) it’s theory and 2) we have a plan.

An apprentice can eventually become a programmer. To become a programmer the apprentice has to achieve competency in our technology stack and be able to work relatively independently to solve problems and create features. A programmer can earn up to $50/hour.

A TroopTrack employee can progress through all of these stages as a part-time employee, which means that our interns can continue to work for TT after the summer is over and they start back in school.

The gist of all this is that an internship at TroopTrack is a good opportunity. It is totally possible that a sophomore or junior in college could start as an intern at TroopTrack and be a programmer by the time they graduate, finding themselves in a position where their earning potential is considerably higher than their peers.

Schedule
Interns are young. Spencer is very smart and mature for his age, but he’s still a kid. It’s important to establish a set schedule and expect interns to keep it. They need to understand that consistency and reliability are important and that their continued earning growth is dependent on demonstrating those qualities. I have been very frank about this with IFL and will have this very conversation with IFL #2 tomorrow.

Assigning Tasks
Spencer’s primary responsibility is the help desk. But he also answers the phone, changes the trash, vacuums the office, and hauls stuff from my truck up the stairs to our office when I need him to. We also expect him to advance his programming knowledge, fix bugs, and add new features to TroopTrack that are within his wheelhouse. We give him pretty wide control over what he works on in the same way that my parents did: “You can play with your friends when your chores are done.”

This means simply that he needs to get our helpdesk back down to inbox 0 every morning before he does anything else. After that, he can learn ruby, fix bugs, or work on his apprenticeship project.

Establishing these priorities has been important – they mirror the priorities of our company and they create a healthy incentive to do what matters most NOW before moving on to activities with a more speculative value.

Hiring
There are two types of luck:
1) Sheer luck, like winning the lottery or dying in a plane crash.
2) Exploiting a chance opportunity for which you have prepared.

Spencer is awesome, and I don’t deny that he is a lucky hire for us. But it is luck #2, not luck #1. Our experiments with freelancers, our discussions about career progression, and our attempts to manage the helpdesk ourselves put us in a position to fully take advantage of the fact that a very smart kid with nothing to do was within arms reach.

There was still some luck involved and I shouldn’t pretend that I know a lot about hiring interns. There are a few things I think are important.

1) You need to present a learning opportunity. Interns are students – their career is learning. If they aren’t interested in a learning opportunity you have dodged a bullet by not hiring them. If you don’t have a learning opportunity to offer you won’t get the best candidates.
2) Hire someone who wants to learn to program. This is not the same thing as hiring a CS major. People choose majors for a rainbow of reasons – don’t assume that someone who is studying CS wants to learn to program or that someone who is study Ancient Roman Literature doesn’t.
3) Hire someone you know if you can. I have known Spencer his entire life. I know his pros and his cons. I knew before I hired him that he would need phone coaching. I also knew that he would soak up programming concepts like a sponge.

Learning
We hired Spencer and handed him two books and a bunch of web sites. The books were Learn to Program and Agile Web Development with Rails 4. I told him to work through the books every day AFTER he finished the helpdesk. Within a week I could tell that Spencer had an aptitude for programming. The web sites we handed him were about git and github.

→ No CommentsTags: Uncategorized

The Bootstrapping Thing I Suck Most At

June 13th, 2014 · No Comments

Benjamin Franklin said

So convenient a thing it is to be a reasonable creature, since it enables one to find or make a
reason for anything one has a mind to do

I’m not being a reasonable creature, and it’s darned inconvenient

Last night I stayed up playing Civ 5 until 2:00 am. I didn’t enjoy it as much as I should have because I felt guilty that I wasn’t working on my bootstrapped business.

Playing is healthy and we need to do it. I shouldn’t feel guilty about it. I know this is true in a academic sort of way, but I can’t seem to integrate that into my nature.

This is in spite of tremendous evidence in my business that doesn’t even include the experiences of other bootstrappers who have successfully built product businesses without killing themselves. And yet, when I “only” work 50 hours in a week I feel guilty. This has got to change.

The Case Against My Work Guilt
Let’s recap the evidence proving that
1) I don’t need to work 80+ hours a week anymore (if I ever did)
2) That I don’t need to feel guilty about goofing around

  1. In 2009 the partnership that started TroopTrack fell apart and I spent several months dealing with the conflict – first I bought out a partner, then I dissolved and re-formed the company, then I spent a month wallowing in depression and emotionally regrouping, all the while ignoring the day-to-day operations of the business. Our subscriber base didn’t really grow, but hey, we’re still here.
  2. In late 2010 I hurt my back very badly. I struggled to walk for a while and spent six+ months walking with a cane. I made almost no changes to TroopTrack during this time and ignored all but the most critical customer requests. Several customers called me to ask if I was okay. I think they worried that I had died. Here’s the rub: Our Subscriber base continued to grow
  3. In late 2013 my sister-in-law moved in with us. She had terminal cancer and Shannon became her full-time caregiver. Shannon had previously been operating the help desk and had been rocking it. With her gone and TroopTrack growing at an unprecedented rate, we fell far behind and didn’t catch up for more than six months. Guess what? Our subscriber base continued to grow

The Verdict: Guilty of Stupid Guilt
Great. Now I have to feel guilty about feeling guilty when it happens.

Just kidding. Hopefully putting this down in the open like this will make me feel better about it.

The worst part about this guilt is that it’s totally unreasonable. There is no need for me to work that hard anymore. I have Ryan, Spencer, and Shannon backing me up. We are paying the bills and growing like a California wildfire. It’s stupid to feel guilty about not doing something I don’t need to do.

Disclaimer
The fact that TroopTrack’s customer service/support has been on life support a couple of times without negatively impacting the long-term viability of the business should not be construed as a belief on my part that I can just ignore the business and it will be fine. That’s stupid. The point here is that there is absolutely no reason for me to think that I have to work 24/7 just to keep the business growing. There is way too much evidence for that belief to persist any longer in my consciousness. It’s time for it to go.

Be free Dave.

→ No CommentsTags: Uncategorized

Bootstrapping sucks. Everyone should do it.

May 9th, 2014 · No Comments

Bootstrapping Sucks
Winston Churchill famously said the following after losing power following World War 2:

Democracy is the worst form of government, except for all those other forms that have been tried from time to time.

After six long years of bootstrapping, I feel much the same way. Bootstrapping sucks. It’s freaking hard, at least the way I did it, and it’s full of low-points that require you to learn important lessons the hard way.

The hardest thing I’ve had to learn is how to be the CEO. Six years ago I didn’t know Jack about running a business. In 2008 I literally believed that if I built a web-app for managing Boy Scout Troops I would sell 10,000 subscriptions immediately and be rich overnight.

Pfft.

It took four years to get 100 subscriptions. Four long years of working a real job with real demands, then working another 20-40 hours on nights and weekends. For FOUR YEARS. Do the math on the return on that investment real quick. Actually, don’t. It’s too depressing. 100 subscriptions is only about $9000 in revenue. There’s no way that’s a good investment.

That’s the first thing that sucks about bootstrapping:

Reality sucks.

Every bootstrapper has a moment where they realize that this is going to be hard, and lots and lots of bootstrappers don’t make it past that moment.

I remember that moment explicitly. I was sitting in a Qdoba in Indiana in August 2009 talking with some guys about TroopTrack and I suddenly realized the truth. This was an endurance race, and it was a race no one has to finish and no one ever wins. You only lose. All you have to do is stop running and you lose. But there’s no finish line. IT NEVER FREAKING ENDS.

It’s easy to see how that could be a depressing moment. I almost threw in the towel. My product was crap. My business wasn’t growing. My partners were hostile and wanting out. My day job was pushing me hard. My wife was sick and I had gained a crap-ton of weight.

Shannon wouldn’t let me quit, and she helped me see a simple truth:

All we have to do is never quit

That subtle viewpoint shift was important. Instead of seeing bootstrapping as an endless race that I could never “win”, I learned a simple truth: I didn’t need to win. All I had to do was never quit.

Here’s the next thing that sucks about bootstrapping a web-based business:

The Toobs NEVER Sleep
People use TroopTrack at all hours of the day, 7 days a week, 365 days a year. And they have problems. And questions. And feature requests. They expect me to help them, to answer their questions, to fix bugs, and to add the features they ask for. I have customers who, if I don’t respond to a help desk ticket within an hour on a Sunday afternoon, will post their question to my Facebook page, send me an email, and call my cell phone.

Okay, I’m exaggerating a little, but it sometimes feels that way.

TroopTrack has been growing 250% per year since 2009. That’s not a big deal when you start with 2, at least for the first four years. For a long time I was able to keep up with everything and excellent customer service became the cornerstone of our reputation.

Then I broke my body. Well, at least that’s how it felt. I slipped a disk. It was horrible. I could barely walk, even with a cane. I couldn’t even sit up to work, and laying on my back to code was only marginally workable because of the leg spasms that attacked me every few minutes. I was barely able to keep my day-job commitments, much less my TroopTrack commitments. It was months before I was healthy enough to do my day job well and to work on TroopTrack.

In the meantime, TroopTrack hadn’t stopped growing and the requests for help kept coming in. I started to get emails asking if I was still in business. Long time customers called me to see if I was still alive. I had no choice but to tell them the truth and beg for patience. It was humiliating and, sadly, not the last time it would happen.

It happened again last year, when my sister-in-law and her family moved into our home for the final stages of her battle with cancer.

During that time TroopTrack was growing more than ever, and the help desk tickets weren’t slowing down. We went from handling it to completely failing at customer support overnight. By the time the family crisis was over we had 1700 open help desk tickets.

Here’s the third thing that really sucks about bootstrapping, and the last one I’m gonna mention today:

Your family will hate it
Bootstrapping a business is like having a mistress, but without the extramarital sex or intrigue. Or, at least I think it is. I’m not that kind of guy. At any rate, it certainly comes with the guilt and jealousy that I imagine cheaters feel.

Shannon used to describe TroopTrack as the other woman. She was committed to it, she believed in it, and when I wanted to give up she was the rock that kept me going, but she also resented it, and so did my kids. Frankly, so did I. I didn’t want to work all the time. I managed to survive that way for the first five years, but by 2013 I was burned out. I wanted to play. I wanted to go to the movies without worrying about the ****ed distribution list server falling over. That thing goes down and I get irate people within an hour. I hate email. I wanted to go on vacation and not think about TroopTrack for two weeks a year.

All of this was completely impossible, and the guilt was horrible.

Dad, you never play with me anymore.
Jake, age 5

Boom. Cold knife in the heart.

All My Problems Were Solvable
I already talked about solving the first problem (Reality sucks), but the other two problems had one solution: Hire Shannon.

She was looking for a way to earn some money on the side and was talking about getting a part-time job and one day the obvious hit me hard enough for me to recognize it. So I hired Shannon to be the COO. I paid her crap (and frankly still do!) and asked her to get the help desk under control. She was happy to get the money and to have the freedom of a job she can do from home on her own schedule (the toobs never sleep!).

The best part of all is that TroopTrack stopped being the other woman. SHARING MY TROOPTRACK TROUBLES WITH MY SPOUSE WAS THE BEST BUSINESS DECISION I EVER MADE. Almost overnight this:

The whole family is sitting around watching a movie and you have to be on your laptop the whole time!?!?
Shannon, Pre-COO

became this:

You’ve seen this movie before and there are five help desk tickets I need your help with, hint hint.
Shannon, TroopTrack COO

I can’t tell you just how awesome and gratifying it is to have Shannon engaged in our business. Talking about TroopTrack used to be boring to her. She would listen, but not really engage, and the subject would get changed at the first opportunity. Not anymore. Now she schedules business lunches with me. She is in the details of the business. She’s excited about where it’s going, and she’s making it better.

This is a big freaking deal Mr. Bootstrapper.

Everything Else Sucks More
Sure, the last six years have been hard. But look at this:

TroopTrack_Help_Desk

You’ll have to click it if you want to be able to read it.

That’s pretty awesome, IMO. Sure, it took six years to do it, but I have enough subscribers now that I could live completely off TroopTrack. And the growth curve is promising – if we don’t stop running and keep solving problems as we encounter them, we will someday have 10,000 subscriptions. And hey, that’s a crap-ton of money.

But what does this have to do with everything else sucking?

It’s simple. I want you to understand what I currently have because I bootstrapped. That chart is MINE (and Shannon’s). We own TroopTrack outright. Even if TroopTrack leveled off today and stayed level, we would still have the financial equivalent of a winning lottery ticket that pays out $100k every single year.

At this very moment we are a financially independent family. We can’t be fired, laid off, downsized, whatever. We control our own destiny.

So let’s talk about what the other options would have given us and whether or not I would have avoided the three things that suck most about bootstrapping.

VC Backing
First of all, VCs expect you to work like a bootstrapper. So, right off the bat, I would still have the problems associated with working all the time, but with a nasty new twist: if I became temporarily incapacitated because of health or crisis they would leverage that to take more of the company away from me.

There are all sorts of unhealthy dynamics associated with venture capitalism that are well documented elsewhere, so I’ll skip those. Worst of all, I wouldn’t be able to chart my own course. I’d have to deal with VCs and their opinions. No thanks.

Secondly, having VC money doesn’t make the transition from fantasy to reality any easier. In fact, the presence of other people’s money can postpone that transition, all the way up to the day you shut down. When you still have a pile of cash you don’t have to face the facts.

Finally, the toobs still never sleep. If I had piles of money I could have just hired people to worry about the customers. That detachment early in a product company from the customer is not good. That’s how you learn – talking with customers is a critical part of my learning experience. Sure, there are ways you can structure support so that you aren’t totally detached from it, but doing so would have brought other troubles and problems to solve that I didnt’ have to go through as a bootstrapper.

So what if I had taken VC money to start TroopTrack, where would I be?

Out of business. I guarantee it. There have been numerous attempts to do this and every single one ended the same way. Dead. And I think I understand why.

It’s because this market is a slow mover. It doesn’t matter how awesome your product is – it takes years to get this market to change. The purchase cycle of scouting software is at least a month, and the effort for a scouting unit to switch from one package to another is huge. Even if the technology makes switching easy, the social costs of switching are super high. First you have to convince the committee. Then you have to win over the parents. It’s hard. People don’t want to go through it unless they have to, and they don’t want to mess around with software that might not be around tomorrow. They take the process very seriously – we frequently get emails from customers that read like a million dollar RFP. For a $99 product.

There’s no VC in the world that has the patience for that. Even with a million dollars it would have taken six years to get 1000 subscriptions.

The Other Option
The other option I want to discuss is doing nothing. Sure, I would have had an easier six years, probably. I might still be at Liberty Mutual, slogging away at putting a percentage of my blood sweat and tears into a 401k that might someday be able to pay out $100k a year when I am old.

Meh.

The One Thing I would Change
This year we made a big change in the way we bootstrap. I quit my job and started consulting. I landed enough contract work to also hire Ryan Bell. We structured our contracts so that Ryan only has to consult for 30 hours each week and he has time to work on TroopTrack. I still work a lot, but not as much as in the past.

I wish I’d done this sooner. I don’t think I could have made it work prior to 2011 or so because I still had a crap-ton of things to learn about Ruby on Rails development (I was a project manager who hadn’t programmed in years when I started TroopTrack). It’s really hard to hold down a real programming job and launch a product company on the side.

So, if you can, start a consulting company along with your bootstrapped product company. Then structure your contracts so you can live off of 20 – 30 hours per week of consulting. Working as hard as I have done sucks big time and I don’t recommend it.

Everyone Should Bootstrap
I’m so glad I built TroopTrack this way. It feels great to still have complete control of the product when it finally starts to get traction. It’s been super hard and I have had to think long and hard about the answer to THE question:

If you knew in 2008 what you know now, would you still launch TroopTrack as a bootstrapped company?
Everyone I Know

Heck yeah I would. And you should too.

If you want to start a business, bootstrap it. If you want to start a business that can’t be bootstrapped, START A DIFFERENT BUSINESS. It took me four years of evaluating different business ideas before I settled on mine. If you can’t bootstrap your idea, toss it until you find one you can. There are tons of market gaps out there that can be invaded by small businesses. If you look long enough you will find one that is right for you.

Do it.

Gratuitous Acknowledgements
PS. This may sound gratuitous, but I don’t care. TroopTrack was inspired by the success of 37Signals and is an outright attempt to emulate and imitate them. Even though I’ve never met a single one of them, I owe them my thanks. So here it is: Thanks a Crap Ton. I love you guys.

PPS. Also, thanks to you Shannon and my kids. I love you more than TroopTrack. :)

PPPS. I’d be screwed without @kofno. Thanks man.

Other Bootstrapping Posts

Here are a few of the hard lessons I’ve learned over the years:

→ No CommentsTags: Uncategorized

Finally! An excuse to override method missing!

December 2nd, 2013 · 1 Comment

I load a lot of data into TroopTrack from CSV files by mapping fields in the CSV file to active record. Most of the time I am importing data from my competitors using CSV files they provide (and control the format of). In the past I have tended to write pretty sucky code for doing this where I build a hash of values that I then use to update active record, like this:

CSV.parse(@file_to_import, { :col_sep => "t" }) do |row|
  household_hash_1 = {
          :name => row[25],
          :line1 => row[27],
          :line2 => row[28],
          :city => row[29],
          :state => row[30],
          :zip_code => row[31]
        }
end

This code seemed okay when I wrote it, but then I learned that at least one of my competitors would add a new field to the CSV file every couple of months, which meant I had to redo the mapping of the attributes. PFFT.

Last week I started a project loading data into ActiveRecord from Microsoft SQL Server. There are about a hundred different tables that I need to transform and load into TroopTrack. Needless to say I wasn’t feeling very thrilled about using the approach mentioned above. The mere thought of THOUSANDS of lines of code mapping a hash to a CSV file makes me want to vomit.

Method Missing to the Rescue
If you don’t know about method missing you’re probably not reading my blog, so I’m not going to explain. If you wandered here somehow with no ruby or rails experience and still care, just google “override ruby method missing”.

All the code below does is let me treat a row from a CSV file more like an active record entry, so that I can write mapping code that is more readable and avoid thousands of lines of that crap above.

In other words, given a row from a csv file with a header that includes “last_name”, I can write code like this:

row.get_last_name

That’s pretty handy.

class DataGenius
  def initialize(filename)
    @data_items = []
    rows = CSV.read([Rails.root, 'db', 'data', 'csv', filename].join('/'))
    rows.each_with_index do |row, i|
      if i == 1
        @header = Hash[*row.each_with_index.map{|val, idx| [val, idx]}.flatten]
      elsif i > 1
        @data_items << DataItem.new(row, self)
      end
    end
  end

  def header 
    @header
  end

  def data_items
    @data_items
  end

  class DataItem
    def initialize(row, data_genius)
      @row = row
      @data_genius = data_genius
    end

    def row
      @row
    end

    def method_missing(meth, *args, &block)
      if meth.to_s =~ /^get_(.+)$/
        p $1
        @row[@data_genius.header[$1]]
      else
        super # You *must* call super if you don't handle the
              # method, otherwise you'll mess up Ruby's method
              # lookup.
      end
    end
  end
end

→ 1 CommentTags: Uncategorized

Conventional Rails

June 27th, 2013 · No Comments

Otherwise known as…
This post could also be called “Stuff I’ve figured out about Rails but didn’t want to blog about because I suspect everyone else already knows it and will figure out that I’m a moron”.

A code picture is worth a thousand words
Here’s some code. I’ve got some routes in there to set the stage, followed by pairs of roughly equivalent code demonstrating some conventions I’ve learned about and have grown to appreciate. I’ve not seen these conventions documented anywhere, but that doesn’t mean they aren’t. It just means I learned them the hard way.

# Routes
resources :users do
  resources :addresses do
    resources :phone_numbers #NEVER
  end
end

# users
# users/6
# users/6/edit
# users/new
# users/6/addresses
# users/6/addresses/5/edit
# users/6/addresses/5
# users/6/addresses/5/phone_numbers/11
# etc...

resource :tricks
namespace :magic do
  resources :tricks do
    get :secret, on: :member
  end
end

# magic/tricks
# magic/tricks/new
# magic/tricks/6
# magic/tricks/6/edit
# etc...

######### Example 1 - in a view #########
= render 'users/user', user: @user
= render partial: 'users/user', locals: {user: @user}
# Partial in ./_user.html.haml

# is roughly equivalent to...
= render @user
# Partial in users/_user.html.haml

######### Example 2 #########
= render partial 'address', collection: @user.addresses, as: address
# ./_address.html.haml

= render @user.addresses
# addresses/_address.html.haml

######### Example 3 - controller redirects #########
redirect_to show_user_path(@user)
# OR how about this?
redirect_to @user

redirect_to users_path
# OR how about this?
redirect_to :users

######### Example 4 - It works in links too #########
# In a view
link_to :users

# Some equivalents...
link_to edit_user_path(@user)
# Is the same as
link_to [:edit, @user]

link_to edit_user_address_path(@user, @address)
# Is the same as
link_to [:edit, @user, @address]

# Namespaced index view
link_to magic_tricks_path
# Is the same as
link_to [:magic, :tricks]

# Regular edit action in a namespace 
link_to edit_magic_trick_path(@trick)
# Is the same as
link_to [:edit, :magic, @trick]

# Custom action in a namespace
link_to secret_magic_trick_path(@trick)
# Is the same as
link_to [:secret, :magic, @trick]

######### Example 5 - rendering partials with a namespace #########
# In a view
= render [:magic, @tricks]
# Partial in views/magic/tricks/_trick.html.haml

= render @tricks
# Partial in views/tricks/_trick.html.haml

= render [:user, current_user.tricks]
# Partial in views/user/tricks/_trick.html.haml

######### Example 6 - the challenge #########
# What does this redirect to?
class AddressesController < ApplicationController
  def destroy
    @user = User.find(params[:user_id])
    @address = @user.find(params[:id])
    @address.destroy
    redirect_to user_path(@user)
  end
end

# Whatever you said is probably wrong. It actually redirects to a the user destroy action. WAT!?! Or, at least I think that's what I observed... 

# I THINK THIS IS A RAILS BUG!!!!

# Which brings up a point... how do you create a path to delete stuff using this convention?
link_to [:magic, @trick], method: :delete, confirm: "Are you sure?"

README
These conventions are all about two things for me: readability and the principle of least surprise. I don’t think the readability comment needs to be explained, but maybe the principle of least surprise does. When you render partials in this way, there is no question about where the partial lives in the file structure, even when you are rendering an object in a view outside of its normal view folder.

I also like the way this forces you to think about all those partials you create as you build an app. Every time you find yourself in a situation where you need to render an object and you can’t just do ‘render @thingy’ in your view, you should take a minute and think. Are you really doing the right thing? Do you really need to render a thing six different ways? This isn’t just about clean, dry code – it’s also about good UI design. The choice to create a new partial should be a deliberate one.

But, hey, you can have your cake and eat it too. Sometimes. For example, on TroopTrack I have 5 major functional areas: plan, manage, communicate, achieve, and share. Three of these functional areas have different renderings of users, but I can still follow this convention using namespaces, like this:

# In a view

= render [:manage, @user]
# partial: app/views/manage/users/_user.html.haml

= render [:achieve, @user]
# partial: app/views/achieve/users/_user.html.haml

= render [:communicate, @user]
# partial: app/views/communicate/users/_user.html.haml

Happy, happy, happy
I really lourve this convention for rendering. It changes my approach to the user interface in ways that encourages goodness and drives consistency, but that’s not the only reason. When the time comes to start worrying about performance, it makes russian doll cacheing a lot easier.

Ugh. I can’t seem to wrap this post up, so I’m just gonna stop typing.

→ No CommentsTags: Uncategorized

Adding Events to Various Calendars (Google, Live, Yahoo)

March 6th, 2013 · 1 Comment

Everything’s a nail
In the past I’ve used a jquery plugin for generating links to add events to a calendar, but it recently broke when I upgraded jquery on TroopTrack. So I started banging around trying to get it to work for about an hour before I realized I was being stupid. The jquery library was 1) building a drop down menu and 2) generating links.

You don’t need to use jquery to do that. It’s pretty easy to make drop down menus (twitter bootstrap) and generate links (helper). In fact, since my event details were all being stored in active record, using javascript made it harder than it needs to be.

Anyway, in the event you ever find yourself in my shoes, here’s what I did:

Build the urls

  def google_calendar_url(event)
    # https://www.google.com/calendar/render?action=TEMPLATE&text=Joe's+40th+Birthday&details=Joe+turns+40+just+this+once&dates=20111212T190000/20111212T200000&location=Gillette+Stadium&sf=true&output=xml
    "http://www.google.com/calendar/event?action=TEMPLATE&trp=false" +
    "&text=" + event.title + 
    "&dates=" + event.activity_at.to_s(:ics) + 
    "/" + event.end_at.to_s(:ics) +
    "&location=" + event.location +
    "&details=" + event.description 
  end

  def live_calendar_url(event)
    "http://calendar.live.com/calendar/calendar.aspx?rru=addevent" +
    "&dtstart=" + event.activity_at.to_s(:ics) +
    "&dtend=" + event.end_at.to_s(:ics) +
    "&summary=" + event.title + 
    "&location=" + event.location
  end

  def yahoo_calendar_url(event)
    "http://calendar.yahoo.com/?v=60" + 
    "&TITLE=" + event.title + 
    "&ST=" + event.activity_at.to_s(:ics) +  
    "&in_loc=" + event.location +
    "&DESC=" + event.description +
    "&URL=" + plan_event_url(event) + 
    "&DUR=" + event.duration 
  end

With a little help from some friends
The time formats matter, so I created a time formatter in an intializer:

[Time].map do |klass|
  klass::DATE_FORMATS[:ics] = lambda { |date| date.strftime("%Y%m%dT%H%M%S")}
end

and Yahoo expects a duration in the HHMM format, which I did by adding a method on my event class:

def duration
  "%02d" % ((end_at - activity_at)/3600.floor) + "%02d" % (((end_at - activity_at)/60).modulo(60))
end

Boom shaka laka
At this point adding an event to a calendar is as easy as pie. Here’s my Twitter Bootstrap worthy HAML

%ul.nav.pull-left
  %li.dropdown
    = link_to icon('icon-calendar'), '#', {'data-toggle' => "dropdown", :class => 'dropdown-toggle'}
    %ul.dropdown-menu{'role' => "menu"}
      %li= link_to 'Google', google_calendar_url(@event), :target => '_blank'
      %li= link_to 'Live', live_calendar_url(@event), :target => '_blank'
      %li= link_to 'Yahoo', yahoo_calendar_url(@event), :target => '_blank'
      %li= link_to 'iCal', plan_event_path(@event, :format => :ics)

The last link is to a downloadable .ics file, which is a topic for another day.

→ 1 CommentTags: Uncategorized

Useful Way to Automagically Add Controller/Action Selectors to an App

February 4th, 2013 · No Comments

Not Invented Here
I swiped this idea from the work Craig Ulliott did on bodyshopbids.com. Thanks Craig!

Don’t hard-code selectors for action and controller specific CSS and Javascript
Sometimes you need a bit of CSS or javascript that only applies to a specific action, or maybe to every action on a controller. So, you go in and add a class or an id to every view that controller supports.

Then you sigh.

You can do something like this instead:

  %body{:id => body_id, :class => body_class}

where body_id and body_class are helper methods. For TroopTrack, these methods look like this:

def body_id
    body_id = controller.class.name.sub(/Controller$/, '')
      .sub(/::/, '').underscore+'_page'
    body_id.sub!(/^devise_/, "devise_#{controller.scope_name}_") 
     if body_id.match(/^devise_/)
    
    return body_id
  end

  def body_class
    if @body_class.nil?
      @body_class = ""
    end
    
    @body_class << " #{controller.action_name} "
    
    @body_class << " #{controller.class.name.split("::").first.downcase}" 
      unless controller.class.name.index("::").nil?

    @body_class << " unit_type_#{@troop.unit_type_id}" if @troop

    return @body_class
  end

I think TroopTrack’s helpers are a bit overly complicated, but that’s because many of them exist in a namespace and I want a class for the namespace as well as the controller. I also need to adjust my CSS based on the type of unit they are, so that’s in there too. Here’s an example body tag from TroopTrack:

<body class=' index  share unit_type_3' id='share_share_page'>

Index is the action. Share is the name space. Unit_type_3 means it’s a cub scout pack. The id, share_share_page, means it’s on the share controller in the share namespace.

Sometimes when I write a blog post and share my code I see opportunities to dry things up or refactor them. This is one of those moments.

A more reasonable place to start would be something like this:

  def body_id
    controller.class.name.sub(/Controller$/, '').sub(/::/, '').underscore
  end

  def body_class
    controller.action_name
  end

As usual, I’m suspicious that there is a ruby or rails idiom out there that can make that code better. I’m all ears.

With this in place, it’s very easy to override CSS or write javascript that is specific to a given page. If you are following the Rails 3 convention for assets, you could just add this scope to the beginning of every controller-specific CSS and javascript file so that you are forcing the behaviors in those files to only apply to the controllers they relate to. I’m not a big fan of this per se, but I don’t condemn it as a practice either.

Don’t Overdo It!
It’s easy to let this get out of control and start putting every new bit of CSS you write inside a selector so that you don’t accidentally mess up something else. Please don’t fall into this trap. It only makes your CSS worse and will eventually lead to inconsistency in your site styling. Instead, if you can, take the time to make sure you are building a clean and consistent set of styles for your site and use the selectors only when you truly need to override something.

→ No CommentsTags: Uncategorized

Getting Started with the AWS Elastic Transcoder API in Rails

January 31st, 2013 · 5 Comments

Elastic What?
Elastic Transcoder is a brand new product from Amazon that lets you transcode videos into your favorite formats. It comes with lots of awesome preset formats, so you can use it through the AWS console to transcode a hi-def video to a web-friendly or iPhone-friendly resolution. You can even use it to create thumbnails and rotate the video. I’m going to show you how I use it to transcode a video from its URL in a Ruby on Rails app.

Elastic Transcoder only works on AWS S3, so you have to get the video into S3 before you can transcode it.

The Set Up
I have a video at a location on the web. In this case, it’s in Dropbox and I have a signed URL for it, but it really could be anywhere. I’ve stored the URL on a model and set up a resque worker to save the video to S3 and then kick off the transcoder. I am using CarrierWave for uploading files.

Code
You will need to add aws-sdk 1.8.1.1 to your Gemfile first. Prior versions don’t include the transcoder.

require 'open-uri'

class VideoProcessorWorker
  WEB_MP4_PRESET_ID = '1351620000000-100070'
  @queue = :eo_video_queue
  def self.perform(lecture_segment_id)
    lecture_segment = LectureSegment.find(
      lecture_segment_id)
    lecture_segment.remote_video_url = 
      lecture_segment.video_dropbox_path
    lecture_segment.save!

    lecture_segment.update_column 'web_video', 
      File.basename(lecture_segment.video.path)

    transcoder = AWS::ElasticTranscoder::Client.new
    transcoder.create_job(
      pipeline_id: APP_CONFIG[Rails.env][:pipeline_id],
      input: {
        key: lecture_segment.video.path,
        frame_rate: 'auto',
        resolution: 'auto',
        aspect_ratio: 'auto',
        interlaced: 'auto',
        container: 'auto'
      },
      output: {
        key: lecture_segment.web_video.path,
        preset_id: WEB_MP4_PRESET_ID,
        thumbnail_pattern: "",
        rotate: '0'
      }
    )
    
  end
end

Enough code, let’s talk
CarrierWave makes this problem a lot easier because it lets you save files from the interwebs to S3. All you have to do is put the url in the remote_whatever_url field and save it. Lourve the carrierwave!

You might be wondering why I do this:

lecture_segment.update_column 'web_video', 
  File.basename(lecture_segment.video.path)

CarrierWave won’t let you set the file name directly, and “web_video” represents the file that AWS Elastic Transcoder is going to create for me, so it’s not there yet, but AWS still wants to know it’s key. So, I use the update_columm method to update it in the DB without any callbacks, etc to trick CarrierWave into thinking the file exists. Once that is done, I can get its key (S3 talk for path) when I define the output portion of my transcoding job.

There may be better ways to do this, I’m all ears.

Incidentally, the preset id is from Amazon. They have a lot of awesome presets and you can create your own. The one I’m using is for web videos, which is really kind of funny cuz it means…

Boom. Web video from an URL for a web video.

→ 5 CommentsTags: Uncategorized

A Convention for Displaying Privileged Links

January 28th, 2013 · 4 Comments

I really hate view code like this:

- if can_manage_users
  = link_to 'Add a user', new_users_path

I want to get rid of it for a number of reasons, but the biggest reason is for cacheing. It really complicates my cacheing strategy because then I have to make keys that take into account the logged-in user or the privileges they possess, both of which will reduce the benefit of cacheing because I need a different cache for every user or privilege combo. Pfft.

Here’s what I’m replacing it with:

.privileged.can_manage_users = link_to 'Add a user', 
  new_users_path

Or maybe this…

= link_to 'Add a user', new_users_path, 
  'data-privileged' => 'can_manage_users'

To make it all work, I’ve also added the privileges to the body tag using a helper method. So, the body tag for a given user might look like this:

<body class="can_manage_privileges can_manage_users 
  can_manage_leadership can_manage_events">

Then, all I need is a little CSS to make the right links show up, like so:

.privileged {
  display: none;
}
.privileged.can_manage_users, 
.privileged.can_manage_leadership, 
.privileged.can_manage_events {
  display: block;
}

Obviously, you could do the same thing with javascript. You could argue that it makes more sense to do it with javascript because it’s a bit weird to use CSS like this and my response is… Shrug. You might be right. I’m just figuring stuff out here and am open to that approach to.

One more caveat. Obviously, this doesn’t secure your controller actions, so it’s not a replacement for authentication and privilege checking in your controllers. Sheesh, I can’t believe I felt the need to write that.

Please note this is only a half-baked idea, so feel free to suggest better ways but try not to judge me if you think this is stupid.

→ 4 CommentsTags: Uncategorized

Uploading Huge Files with Rails

January 27th, 2013 · 7 Comments

I’m working on an application that involves a lot of video content – great big files of 1 GB or more – and we found that uploading those huge files through a multipart form sucked. Duh. I started looking around at various solutions, such as chunked uploaders, and then a thought occurred to me: uploading huge files through the browser SUCKS. It locks up my browser, and to a certain degree, my laptop for the entire upload (if I start an upload, close my laptop and go somewhere, and then reopen it, the upload ain’t gonna work, so I’m stuck carrying my laptop around the house with the screen up like some crazed internet cats junky).

But you know what rocks for moving huge files around? Dropbox! They’ve already figured all this crap out – what to do if the computer gets turned off mid upload, loses it’s network, etc. And they have a super intuitive user interface – drag the file and wait for it to sync.

Also, when you’re uploading 1.5 GB of video, time isn’t exactly your main concern. You’re totally cool with dragging the file into the dropbox and going to sleep. If it shows up where you want it the next day, Yay!

So, here’s what I ended up building. I might redesign the workflow a bit once a real user sees it.

1) The user puts the huge file in a special dropbox folder that my app has access to
2) When they are ready to “upload” the huge file, they go to my app, pick the file, pick the model they want it to be associated with (in this case, it’s a LectureSegment), and save.
3) Resque kicks off a worker that copies the file to S3 using Carrierwave.

The code for copying the file is pretty simple, once you get the Dropbox session stuff figured out, which is weirdly convoluted IMO.

require 'dropbox_sdk'
class VideoProcessorWorker
  @queue = :eo_video_queue
  def self.perform(lecture_segment_id)
    log = Logger.new 'log/resque.log'
    @lecture_segment = LectureSegment.find(lecture_segment_id)
    dropbox_session = DropboxSession.deserialize(Organization.first.dropbox_session)
    client = DropboxClient.new(dropbox_session, :app_folder)
    log.debug("Got the client for #{@lecture_segment.video_dropbox_path}")
    tmp_file_name = "#{Rails.root}/tmp#{@lecture_segment.video_dropbox_path}"
    tmp_file = File.open(tmp_file_name, 'wb')
    tmp_file.write(client.get_file(@lecture_segment.video_dropbox_path))
    @lecture_segment.video = tmp_file
    @lecture_segment.save!
    log.debug("File saved to: #{@lecture_segment.video.url}")
    File.delete(tmp_file_name)
    
    @lecture_segment.update_attribute :video_dropbox_path, nil
    @lecture_segment.update_attribute :upload_status, "Upload Complete"

    log.debug("Clean up complete")
  end
end

I’m not sure that’s the best way to move the file to S3, but it’s easy to just let CarrierWave worry about it. It’d be cool if I didn’t have to create a local copy of the file. I’d be very interested in opinions on how to make this better. Also, I only just finished early testing of it – I haven’t busted it out on the 1.5 GB file, so it may still not even work!

UPDATE
I switched to the Dropbox Chooser, as suggested, and the code for the worker gets event simpler:

require 'open-uri'
class VideoProcessorWorker
  @queue = :eo_video_queue
  def self.perform(lecture_segment_id)
    lecture_segment = LectureSegment.find(lecture_segment_id)
    lecture_segment.remote_video_url = lecture_segment.video_dropbox_path
    lecture_segment.save!
    
    lecture_segment.update_attribute :video_dropbox_path, nil
    lecture_segment.update_attribute :upload_status, "Upload Complete"

    lecture_segment.update_column 'web_video', File.basename(lecture_segment.video.path)
  end
end

→ 7 CommentsTags: Uncategorized