GitHub Post-Receive Hook for Coop
At Business Logic we use Harvest to track our time. Harvest offers an integrated campfire-style chat room called Coop. The cool feature of Coop is it shows the time entries of members in a group, interspersed with the messages in the group. While it’s sometimes useful to see what others are doing as they go, our main use is to look at what your pair was doing 3 days ago when you forgot to do your time. This gives a pretty good idea of what’s going on, but when there isn’t an entry for that day, and you’re trying to do your time, I usually go back and look at our commit history on github.
The trick is to integrate those together, and for that, enter the coop api, github post-receive hooks and sinatra.
Implementation notes
Because the coop api is sort of Restful, but not totally, it took a bit of finagling to get it all together, but it did and is now on github as coop_github_hook. I personally haven’t ever used ActiveResource or sinatra for anything but toying around, so it was nice to be able to use both.
I’m now a big sinatra fan since it makes short-and-simple web services incredibly simple. Here’s the sinatra portion of this hook:
post '/hooks/coop' do
push = JSON.parse(params[:payload])
push['commits'].each do |commit|
Coop::Status.from_github_commit_info(group_id, commit).save
end
"Thanks for playing"
end
The other interesting thing was dealing with the “sort of Restful” Coop status api (chat entries in the group). “Sort of Restful” really means “sort of useable by ActiveResource.” The issue arose in the way coop expects you to send the content for a post. If you have something called a Status which represents a text message, ActiveResource assumes, and Rails by default provides, data that looks like:
<status>
<message>Good morning</message>
</status>
Coop’s api however wants that data to be sent as:
<status>Good morning</status>
To get the data into that format all you have to do is override ActiveResource::Base#encode in your ActiveResource class:
module Coop
class Status < ActiveResource::Base
self.site = "http://coopapp.com/groups/:group_id"
# Co-op takes a non-ActiveResource friendly format for their status, so manually write that format
def encode(options={})
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<status>#{attributes['text']}</status>\n"
end
end
end
I’m not sure if this is something people normally do, but it is a pretty handy way to switch formats.
For usage info hook feel free to checkout the github repo and it’s associated Readme. If you have any problems feel free to shoot me a message over github or email.
Gmail POP & Outlook Tip
Gmail doesn’t pull read messages down when accessing a POP account. Therefore, if you’re using gmail and outlook on the same email account, you have to make sure to mark the messages you read in outlook as unread when you’re done looking at them. Otherwise they won’t make it to gmail.
To simplify your life, a better solution is to just tell Outlook not to automatically mark messages as read. Then messages will make their way to gmail unless you specifically mark it as read.
Stealing directions from a Faq:
Q: How can I avoid the message be marked as read?
A: To avoid the message to be marked as read, you can do the followings:
- In Outlook » Tool menu » Options
- Click on Other tab » Preview Pane » click on the Preview Pane button
- Uncheck the Mark Messages as Read in Preview Pane section.
- If you use the Preview Pane to read messages, use the space bar to mark the message as read. Otherwise, opening the message will mark the message as read as well. This way, you won’t miss some important messages be marked as read by accident.
Inserting a rake dependency first
Usually, if you’re trying to extend a rake task by adding new dependencies to it you just redefine the task with the new dependency.
namespace :tickle do task :cow => 'relax' end
So if originally tickling the cow would first milk it, typing rake tickle:cow will now:
- Milk the cow
- Relax the cow
- Tickle the cow
If you instead want the relaxing to happen first, you have to go straight at the definition of the task and alter its prerequisites. These prerequisites are just an array of task names, so to make your new task happen first, just insert your task at the beginning.
# fetch the task definition task = Rake::Task['tickle:cow'] # insert our new prerequisite at the beginning task.prerequisites.insert(0, 'relax')
A real-life example of this is if you want to pull some data down before resetting your database with a rake db:migrate:reset. If you define a task like rake data:retrieve and don’t want to remember to do rake data:retrieve db:migrate:reset you can insert data:retrieve as an early prerequisite for db:migrate:reset.
Rake::Task['db:migrate:reset'].prerequisites.insert(0, 'data:retrieve')
And if you have to do this a lot, this may be handy
module Rake
module OrderedPrerequisites
def require_first(*tasks)
prerequisites.insert(0, *tasks)
end
end
class Task
include OrderedPrerequisites
end
end
And now you can do
Rake::Task['db:migrate:reset'].require_first('data:retrieve')
TypeError: Error #1115: ClassName.as$123::Cabbage is not a constructor.
Had this happen today, I believe the cause for this message is the specified class’s definition has not been loaded.
Example where this can happen is you have code like:
package good {
public class Muppet {
private static var ball:Ball = new Ball();
}
}
class Ball {}
At the time of the running of the ball = new Ball(); code, the class definition of Ball has not yet been evaluated.
Solution is to move Ball into its own file.
workaround actionscript's sucky map function
Instead of writing
function(element:Object, i:uint, a:Array):Object{ … }
and then always ignoring everything but element, just make your function var-arg and ignore the var-arg:
function(element:Object, …a):Object {..}
still ugly, but less characters and noise.
respond_to? in actionscript
The ruby code
object.respond_to?(‘methodName’)
in actionscript is
‘methodName’ in object