Friday, February 13, 2009

a little git

In a previous post I mentioned that I had switched my SCM solution on the startups I'm working on to "git" away from "svn". Up until now, though, I hadn't really been making the most use of the new powers git gave me. My workflow had been very similar to the way I used to do svn:

checkout the repo (git checkout)
make changes
run pull in any changes that have been sent to repo during my work time (git pull)
commit my changes to the repository (git push)
repeat


Recently I had a need to do more, though. There's one section of one of web applications I'm developing that needs a serious redesign (I mean several days worth of work). So I'm working through it, but then I have a new "emergency" that comes in that requires I deploy some code NOW. Usually, I could solve this problem by making the change in my working directory, and then just selectively adding and commiting that change. However, some of the work I needed to do was in one of the files I had already edited, so there was no way without losing all my work for the day that I could swing getting this fix out (short of checking out from head to a different folder, changing, pushing, and then pulling in to my modded directory again).

Fortunately, git provides a better way:

$> git stash save

I've just saved all my local modifications to the "stash", which you can think of as a temporary shelf you can set stuff on while you're doing branching stuff.

$> git checkout -b working_branch

Now I've created a new branch named "working_branch" on my machine from this repository.

$> git stash apply

My changes that I saved to the stash have now been resurrected and applied to the new branch that I'm working in. I can now make changes and commit to the "working_branch" branch as often as I need.

$> git add .
$> git commit -m "commiting to branch"

Now I need to go back to my master branch to make the emergency change:

$> git checkout master

OK, make my emergency fix, then commit:

$> git add .
$> git commit -m "commiting to master"
$> git push origin master


Now maybe I want to merge these emergency changes into my current working branch so that I can keep the two branches from diverging to far:

$> git checkout working_branch
$> git merge master


Resolve any conflicts, and commit. Done!

There you have it, simple branching in git for great gains in organization and productivity (IMHO).

Monday, February 9, 2009

PDF Attachments with attachment_fu

The great thing about frameworks like Rails (in my opinion) is that the large number of people doing many of the same tasks results in some really great reusable pieces of code that make common complex features almost trivial to implement. This was brought right to the forefront of my attention again today when I started working on a new feature for our medicaid billing system.


The story is this: We have a physician who writes prescriptions for students who need them. It's better if we don't have to mail him all the documentation for every student he's writing a script for. The best conceived solution was to have school districts upload their docs to our website (associated with a student), and let the physician download them on his side.

Now, surely I'm not the first person to ever want to have uploaded files associated with an entity (that's basically facebook's whole business model, after all).

Sure enough,
attachment_fu
to the rescue!

This was dead simple. After installing the plugin (ruby script/plugin install git://github.com/technoweenie/attachment_fu.git), I dug into the docs and started coding.

First I created a new model to house the necessary file for the student (it's called an "IEP", for Individual Education Plan, so don't get thrown by the acronym):


class Iep < ActiveRecord::Base
belongs_to :student

has_attachment :content_type => "application/pdf",
:storage => :file_system,
:path_prefix => "public/iep",
:max_size => 1000.kilobytes

validates_as_attachment

end


Small and simple, right? Here's what it means:
the "belongs_to :student" is the standard ActiveRecord association, indicating that a student entity is the parent of this object (and also implying that the "IEP" object has a foreign key called student_id).

"has_attachment" is the method that configures the file you're going to upload. :content_type, obviously, is the type of file you expect. In my case (and yours if google brought you to this entry) that type is "application/pdf", and if anything else comes in it won't be accepted.

":storage" is how you tell attachment_fu where you want to store these files. Other options besides the filesystem are the database and amazon_s3, but those are both beyond the scope of this article. I just wanted to get these items onto the disk, and then back off on another computer, and the filesystem was the way to go that required the least configuration.

":path_prefix" is the file path you want prepended to the storage location for all files stored by attachment_fu. In my case, "public/iep" is just a directory on my local filesystem, but in production it's a symlink to a location outside of the application folder so that the uploaded files will persist across new deployments (handy strategy for anyone who needs to upload files, thanks to EngineYard for handling that for me).

":max_size" should be self-explanatory. If it isn't, you may be in the wrong job.

Next I set up a couple controller actions to handle the actual uploading of the file:


def upload_iep
@student = Student.find(params[:id])
@iep = Iep.new
end

def exec_upload_iep
@student = Student.find(params[:id])
if @student.iep.nil?
@iep = Iep.new(params[:iep])
@iep.student_id = @student.id
else
@iep = @student.iep
@iep.update_attributes(params[:iep])
end

if @iep.save
flash[:notice] = 'IEP Uploaded!'
redirect_to :action => :all_students
else
redirect_to :action => :upload_iep,
:id => @iep.student_id
end
end


It ain't restful, but it works. One action to load the form, the other to accept the actual upload. Nothing in there should look different than dealing with regular rails forms. Next we have the view:


<% form_for(:iep, :url => {:action=>:exec_upload_iep},
:html => { :multipart => true }) do |f| -%>
<p>
<label for="iep">Upload An IEP:</label>
<%= f.file_field :uploaded_data %>
<%= hidden_field_tag :id,@student.id%>
</p>
<p><%= submit_tag 'Upload' %></p>
<% end -%>


The only important bits to remember here are the ":multipart => true" which makes sure that the form can accept file uploads, and the ":uploaded_data" attribute which is what attachment_fu searches for when manipulating attached files. Now it's easy to upload an IEP attached with a student anytime. And the best part is, retrieval is a snap:


<td>
<a href='#{student.iep.public_filename}'>Download IEP</a>
</td>


that "public_filename" method returns the necessary path to retrieve the file, making it easy for the physician to download the IEP on his computer. Done and done. Thanks to technoweenie for writing and maintaining this plugin for the community!

Wednesday, February 4, 2009

Yahoo killed my buzz

So I was having a really good week until today. Billing is coming along nicely for our medicaid project, and the other startup I'm working on will be demoing at the Lake on saturday (I'll post a link on this blog after that date, so that I can get feedback from all of you readers as well).

Unfortunately, today my experience with Yahoo really destroyed that "high-on-life" feeling I'd been having. You see, we had this idea for handling incoming email with the application. I wanted to have users be able to send email to the app that would cause things to happen. So I went to our friendly and efficient hosting provider, EngineYard, and asked them to setup a mailbox for us to accept incoming emails. Like usual, engineyard's customer support got done what we needed and did it with a smile. They then told me through email that in order to start receiving email there I would need to add an MX record to my DNS listing that would make "msg.domain.com" point to their mail server. Basically, I just wanted that subdomain to be redirected, while leaving all regular mail sent to "domain.com" to continue as normal.

Figuring that this would not be an issue, I asked our business expert to take care of it. As the title of this post probably indicates to you, it WAS AN ISSUE. This poor woman spent the first half-hour of this project playing with Yahoo's web interface for their small business registrar system. No dice. She called them and spent half an hour waiting, and then longer actually on the phone with a customer service representative who didn't understand what we were asking for. I got on the next call with her and together we talked with a Yahoo support guy named "Phillip" who told us in an apathetic voice that what we were wanting to do simply wasn't possible.

Bullshit! I'm willing to accept something along the lines of "I don't know how to do that", because there are plenty of things that I don't know how to do. But you're going to say to my face that something is downright impossible you'd better be sure that I'm not going to be able to embarrass you with something as simple as a google search: I have Many Online Resources that say this is a perfectly reasonable request that is implemented in DNS records all over the world all the time. Knowing that I have to demo by Saturday, I'm probably just going to have to work around this by directing all email traffic to the engineyard server and forwarding the regular stuff back to our personal email addresses, so I can get past this, but what a freaking drag!

Long story short, don't be suckered in by Yahoo's low prices for domain names. They will screw you if they get the chance. As soon as this demo is over, we're switching. UPDATE: I just checked, and GoDaddy supports this kind of MX record, so that's who we'll be switching to. I recommend you all do the same.