Test Coverage Matters

Posted over 6 years back at Alloy Code - Home

100 Percent Test Coverage

Inspired by City Cliq’s RSpec screenshot, we wanted to brag on our 100% RCov coverage as well. We’ve been using Test::Unit as opposed to RSpec, along with a healthy dose of Mocha to mock out external services.

For example, our application provides a convenient, tiny url to access user content, which is generated as part of a create action, generated with the ShortURL gem. In our functional test, we mock out the “shorten” method of ShortURL, and have it feed us back consistent data:

def test_create
  WWW::ShortURL.expects(:shorten).returns("http://tinyurl.com/fake")
  assert_difference "Document.count", 1 do
    post :create, :document => @@document_default_values
    assert_equal "http://tinyurl.com/fake", assigns(:document).shorturl
  end
end

Since the generated URL could potentially be different each time the test is run, I’ve removed the dependency on an external library and service to gain repeatability in my tests. I’ll take for granted that the ShortURL gem works and has it’s own tests; there’s no reason for me to duplicate their efforts. The above test simply assumes the ShortURL library is performing as expected, and allowing me to test my own code atomically.

Test Coverage Matters

Posted over 6 years back at Alloy Code - Home

100 Percent Test Coverage

Inspired by City Cliq’s RSpec screenshot, we wanted to brag on our 100% RCov coverage as well. We’ve been using Test::Unit as opposed to RSpec, along with a healthy dose of Mocha to mock out external services.

For example, our application provides a convenient, tiny url to access user content, which is generated as part of a create action, generated with the ShortURL gem. In our functional test, we mock out the “shorten” method of ShortURL, and have it feed us back consistent data:

def test_create
  WWW::ShortURL.expects(:shorten).returns("http://tinyurl.com/fake")
  assert_difference "Document.count", 1 do
    post :create, :document => @@document_default_values
    assert_equal "http://tinyurl.com/fake", assigns(:document).shorturl
  end
end

Since the generated URL could potentially be different each time the test is run, I’ve removed the dependency on an external library and service to gain repeatability in my tests. I’ll take for granted that the ShortURL gem works and has it’s own tests; there’s no reason for me to duplicate their efforts. The above test simply assumes the ShortURL library is performing as expected, and allowing me to test my own code atomically.

Can You Feel The Rumble?

Posted over 6 years back at zerosum dirt(nap) - Home

So the Rails Rumble is on! We’ve worked pretty hard to hook this up and it’s great to see it alive and breathing, and #railsrumble buzzing with activity. There were some capacity issues at first (ouch!) but things seem to be going pretty smoothly now.

Greets to Carter Parks, Rodrigo Kochenburger, and Erin & Tommy Shine, who all poured a lot of precious blood into this beastie. And our friends Chris Aker and Tom Asaro at Linode. And our other sponsors too, of course. Oh, and best of luck to all the Rumblers. Launch some cool-ass web properties and win some swag, guys! Hope you’ve got plenty of Red Bull to keep you caffeinated.

Me? I’m going to bed. See you tomorrow in IRC.

RubyConf 2007

Posted over 6 years back at Alloy Code - Home

The RubyConf preliminary agenda has been posted, and it looks like a really outstanding lineup!

Even better (for me, at least), is the fact that I’ve already been privileged enough to see some of these talks at the Ruby Hoedown, so I don’t feel quite as bad about having to pick and choose which afternoon sessions I’d like to attend. For those who weren’t lucky enough to make it to the Hoedown, I’d especially recommend Jay Phillips’ talk on Adhearsion. Not only were we all on the edge of our collective seat listening to him, he spent the remainder of the Hoedown with a cloud of people constantly hovering around him, peppering him with questions and was gracious enough to make sure he answered everything we could throw at him.

I’d say the feature I’m most excited about, though, is “Room 3.” I think that offering a more freeform ‘workshop’ environment is a great idea, and I’m going to have to watch my time carefully, because I can definitely see myself accidentally missing sessions I’d like to attend due to spending too long in Room 3.

Online Registration is now open, $250 per person (since the site wasn’t exactly clear on the price unless you click around a bit).

RubyConf 2007

Posted over 6 years back at Alloy Code - Home

The RubyConf preliminary agenda has been posted, and it looks like a really outstanding lineup!

Even better (for me, at least), is the fact that I’ve already been privileged enough to see some of these talks at the Ruby Hoedown, so I don’t feel quite as bad about having to pick and choose which afternoon sessions I’d like to attend. For those who weren’t lucky enough to make it to the Hoedown, I’d especially recommend Jay Phillips’ talk on Adhearsion. Not only were we all on the edge of our collective seat listening to him, he spent the remainder of the Hoedown with a cloud of people constantly hovering around him, peppering him with questions and was gracious enough to make sure he answered everything we could throw at him.

I’d say the feature I’m most excited about, though, is “Room 3.” I think that offering a more freeform ‘workshop’ environment is a great idea, and I’m going to have to watch my time carefully, because I can definitely see myself accidentally missing sessions I’d like to attend due to spending too long in Room 3.

Online Registration is now open, $250 per person (since the site wasn’t exactly clear on the price unless you click around a bit).

Introducing Addressable

Posted over 6 years back at Sporkmonger

So, after 3 months, I’m back from my excursion to Africa. Don’t worry, I’m not coming back empty-handed.

I thought my fellow rubyists might benefit from the URI implementation I’ve been using in various projects, so I’ve extracted it out into its own library. In the process, I improved a few things here and there, added support for URI Templates, and fleshed out the specifications a bit more.

I introduce to you the Addressable library.

It has about a 2:1 spec to code ratio, 100.0% code coverage, and it has gone through plenty of code heckling. It ought to be pretty reliable.

Hope you like it. Let me know how it works.

Update:

I’ve added URI Template variable extraction and I updated the documentation. The extract_mapping method should be useful for template-based routing systems for frameworks.

ActiveRecord - all vs all relationships

Posted over 6 years back at Saaien Tist

Modeling genetics or genomics data presents its own challenges. One of the issues is that the actual definition of things change over time. A database system can only be based on the scientific knowledge at the time of conception. The prime example of course is the definition of a gene over the years. Before 1997, it was believed that the vast majority of these encoded proteins. As a result, 'genes' tables in databases typically had columns to store information on the start and stop codon. However, it became clear that many genes actually do not encode proteins, forcing the remodeling of biological databases. But that's not the topic of this post.

What is the topic here, is how relationships can be stored in a database. Suppose I want to store mapping data: markers mapped to linkage groups, clones mapped to physical maps, ... Markers are stored in a markers table, clones are stored in a clones table, linkage groups in a linkage_groups table; you get the point.

The database that I'm working with at the moment (and only have read-access to), stores the mappings in a mappings table which includes the following columns:

  • map_type
  • map_name
  • mapped_object_type
  • mapped_object_name
So records could look like:
 map_type       map_id  map_name      mapped_object_type  mapped_object_id  mapped_object_name
--------------+-------+-------------+-------------------+-----------------+------------------
chromosome 1 chromosome_1 marker 1 marker_A
chromosome 1 chromosome_1 marker 2 marker_B
physical_map 2 ctg1 clone 1 clone_A
physical_map 3 ctg2 clone 2 clone_B


To make things worse, markers can also be mapped to clones. This means that any clone can act as a marker, but also as a map at the same time.
 map_type       map_id  map_name      mapped_object_type  mapped_object_id  mapped_object_name
--------------+-------+-------------+-------------------+-----------------+------------------
clone 1 clone_A marker 1 marker_A


How can I model this in ActiveRecord? There's the concept of polymorphisms in ruby, which could solve this relationship nightmare if there would be only one thing in the mappings table that's polymorphic. But as it happens, there's two... Evan Weaver wrote this rails plugin has_many_polymorphs, which should do the trick (see here for a tutorial and background if it's unclear what I'm talking about). Unfortunately, as it is focussed on rails and not on ActiveRecord in general, it doesn't handle namespaces.

So here's what I've come up with:

module MyNameSpace
class Mapping < ActiveRecord::Base
# Relationships to feature-like things
belongs_to :marker, :foreign_key => 'mapped_object_id', :conditions => ["mapped_object_type = 'marker'"]
belongs_to :clone, :foreign_key => 'mapped_object_id', :conditions => ["mapped_object_type = 'clone'"]

# Relationships to map-like things
belongs_to :chromosome, :foreign_key => 'map_id', :conditions => ["map_type = 'chromosome'"]
belongs_to :physical_map, :foreign_key => 'map_id', :conditions => ["map_type = 'physical_map'"]
belongs_to :clone, :foreign_key => 'map_id', :conditions => ["map_type = 'clone'"]
end

class Marker < ActiveRecord::Base
has_many :mappings_as_feature, :class_name => 'Mapping', :foreign_key => 'mapped_object_id', :conditions => "mapped_object_type = 'marker'"
has_many :chromosomes, :through => :mappings_as_feature
has_many :clones, :through => :mappings_as_feature
end

class Chromosome < ActiveRecord::Base
has_many :mappings_as_map, :class_name => 'Mapping', :foreign_key => 'map_id', :conditions => "map_type = 'chromosome'"
has_many :markers, :through => :mappings_as_map
end

class PhysicalMap < ActiveRecord::Base
has_many :mappings_as_map, :class_name => 'Mapping', :foreign_key => 'map_id', :conditions => "map_type = 'physical_map'"
has_many :clones, :through => :mappings_as_map
end

class Clone < ActiveRecord::Base
# Relationships where the clone is the feature
has_many :mappings_as_feature, :class_name => 'Mapping', :foreign_key => 'mapped_object_id', :conditions => "mapped_object_type = 'clone'"
has_many :physical_maps, :through => :mappings_as_feature

# Relationships where the clone is the map
has_many :mappings_as_map, :class_name => 'Mapping', :foreign_key => 'map_id', :conditions => "map_type = 'clone'"
has_many :markers, :through => :mappings_as_map
end
end


The key here is to make the distinguish between mappings_as_feature and mappings_as_map. A marker object can only have mappings where it acts as a feature, while a clone can both have mappings where it acts as a feature and where it acts as a map.

Using this code, it's now possible to do:

clone = Clone.find_by_name('clone_A')
puts clone.mappings_as_map.to_yaml
puts clone.mappings_as_feature.to_yaml
puts clone.markers.to_yaml
puts clone.physical_maps.to_yaml


Voila (until further notice...).

UPDATE: Pratik blogged about has_many_polymorphs and lists the generated associations here.

JungleDisk - online backup done right?

Posted over 6 years back at work.rowanhick.com

Following some links last week I ended up on jungledisk.com. All of 30 secs later I had the client downloaded and started backing up my hard drive. What is it ? It's a collection of clients for the 3 majors (Windows, OS X, Linux) that essentially mount up an S3 storage bucket as a separate volume. You purchase the jungledisk client for $20 after a trial period, then get an S3 account with Amazon.com and settle your bill with them, based on exactly how much transfer and disk space you use. Set the whole lot up, and bingo you have an online volume, that (one would hope) is near infallible. Over the past week I've had a mac connected up, and an xp box, to the same S3 account. I'm in the process of slowly backing up all of my very precious photos etc. You do need a fat pipe to go and store all this stuff online but that comes with the territory. The beauty of this system is it appears just as another volume, and with cross platform clients, it means you can have your data storage off in some other place, without worrying about it. The cost.. well, hardly anything to get worked up around. So far I've spent a princely sum this month of $0.16USD with Amazon for ~1gb transfer, and 300mb storage. I'm stoked. No iffy external hard drives, or optical media to get damaged. Of anyone I expect Amazon to be the most reliable service, much more than anyone could acheive with a SOHO RAID storage device, for a fraction of the cost. Very tidy solution, and well worth a look at.

The Talking Rails Application

Posted over 6 years back at Spejman On Rails

After a long time without publishing anything, I have finished an example of what you can do with festivaltts for Ruby: A Ruby on Rails application that talks!

You can test it at: http://thetalkingrailsapp.sergioespeja.com/

I hope it gives you ideas for your RoR applications!

Episode 69: Markaby in Helper

Posted over 6 years back at Railscasts

Do you ever need to generate HTML code in a helper method? Placing it directly in Ruby strings is not very pretty. Learn a great way to generate HTML through Markaby in this episode.

Using ActiveResource to consume web-services

Posted over 6 years back at The Rails Way - all

Today I’m reviewing Joe Van Dyk’s monkeycharger application, which is a web-service for storing and charging credit cards. I loved looking at this app, because its only interface is a RESTful web service: there is no HTML involved. (If you’ve never written an app that only exposes a web-service UI, you ought to. It’s a blast.)

In general, Joe has done a fantastic job with keeping the controllers slim and moving logic to models. The only significant gripe I had with the application is that it is not ActiveResource compatible.

For those of you that are late to the party, ActiveResource is the newest addition to the Rails family. It lets you declare and consume web-services using an ActiveRecord-like interface…BUT. It is opinionated software, just like the rest of Rails, and makes certain assumptions about the web-services being consumed.

  1. The service must understand Rails-style REST URLs. (e.g. “POST /credit_cards.xml” to create a credit card, etc.)
  2. The service must respond with a single XML-serialized object (Rails-style).
  3. The service must make appropriate use of HTTP status codes (404 if the requested record cannot be found, 422 if any validations fail, etc.).

It’s really not much to ask, and working with ActiveResource (or “ares” as we affectively call it) is a real joy.

However, monkeycharger tends to do things like the following:

1
2
3
4
5
6
7
8
9
10
class AuthorizationsController < ApplicationController
  def create
    @credit_card   = Authorizer.prepare_credit_card_for_authorization(params)
    transaction_id = Authorizer::authorize!(:amount => params[:amount], :credit_card => @credit_card)
    response.headers['X-AuthorizationSuccess'] = true
    render :text => transaction_id
  rescue AuthorizationError => e
    render :text => e.message
  end
end

Three things: the request is not representing an “authorization” object, the response is not XML, and errors are not employing HTTP status codes to indicate failure.

Fortunately, this is all really, really easy to fix. First, you need (for this specific example) an Authorization model (to encapsulate both the the XML serialization and the actual authorization).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Authorization
  attr_reader :attributes

  def initialize(attributes)
    @attributes = attributes
  end

  def credit_card
    @credit_card ||= Authorizer.prepare_credit_card_for_authorization(attributes)
  end

  def authorize!
    @transaction_id = Authorizer.authorize!(:amount => attributes[:amount],
      :credit_card => credit_card)
  end

  def to_xml
    { :transaction_id => @transaction_id }.to_xml(:root => "authorization")
  end
end

Then, we rework the AuthorizationsController to use the model:

1
2
3
4
5
6
7
8
class AuthorizationsController < ApplicationController
  def create
    authorization = Authorization.new(params[:authorization])
    authorization.authorize!
    render :xml => authorization.to_xml, :status => :created
  rescue AuthorizationError => e
    render :xml => "<errors><error>#{e.message}</error></errors>", :status => :unprocessable_entity
  end

(Note the use of the “created” status, which is HTTP status code 201. Other verbs just use “ok”, status code 200, to indicate success. Also, with an error, we return an “unprocessable_entity” status, which is HTTP status code 422. ActiveResource will treat that as a failed validation.)

With that change, you could now use ActiveResource to authorize a credit card transaction:

1
2
3
4
5
6
7
8
9
10
11
12
class Authorization < ActiveResource::Base
  self.site = "http://my.monkeycharger.site"
end

auth = Authorization.new(:amount => 15, :credit_card_id => 1234,
  :remote_key => remote_key_for_card)

if auth.save
  puts "success: #{auth.transaction_id}"
else
  puts "error: #{auth.errors.full_messages.to_sentence}"
end

It should be mentioned, too, that making an app ActiveResource-compatible does nothing to harm compatibility with non-ActiveResource clients. Everything is XML, both ways, with HTTP status codes being used to report whether a request succeeded or not. Win-win!

Obviously, real, working code trumps theoretical whiteboard sketches every time, and Joe is to be congratulated on what’s done. Even though ActiveResource-compatibility can buy you a lot, you should always evaluate whether you really need it and implement accordingly.

Rimuhosting...singing their praises

Posted over 6 years back at Luke Redpath - Home

I don’t usually post this kind of thing on my blog but when somebody experiences a level of customer service that seems to be so lacking these days, one feels the need to shout about it.

I’ve been hosting this blog on RimuHosting for about a year now – a basic 160MB VPS setup running nginx and one mongrel to power this blog and lighty on my second IP for PHP apps (like Mint Stats and RoundCube Webmail. The VPS also acts as a home to my mail and Subversion repositories.

In all the time I’ve been with RimuHosting I’ve experienced good uptime with problems being sorted out quickly and efficiently. I’ve not had much need to trouble their support team but having just purchased a shiny copy of Warehouse I needed some more RAM to handle a second mongrel instance. I submitted a support ticket asking for a quote, indicating that if it should cost less than an extra $10 a month that they should proceed.

In the time it took me to purchase and download a copy of Warehouse, scp it to my server and untar the thing, my VPS was restarting with its extra memory. All in all, about 5 minutes from the time I submitted my ticket. On a holiday weekend. On a Sunday. That is true customer service.

So, if you are in the market for a personal geek-friendly VPS at a decent price with great support then consider this an obligatory plug for RimuHosting. Tell ’em Luke sent ya!

Update: Warehouse is now up and running.

MyFitBuddy.com Launched!

Posted over 6 years back at Shane's Brain Extension

MyFitBuddy.com logo

I’m pleased to announce the launch of MyFitBuddy.com, a workout tracking tool with social networking features. It allows you to log your workouts at the gym, sports training, your run in the park, or any other type of physical activity, and see the improvement over time. You can add your workout buddies as friends, and keep in touch with their workouts as well.

I mainly built this site for myself, as I wanted a super simple way to keep a record of my workouts and see graphs of my improvement. I thought it would be a great idea for a social site, as many studies have shown that people exercise more when they have a workout buddy or do it as part of a group. In MyFitBuddy.com, you will be motivated to exercise by seeing the activity of your friends and others on the site.

Some of the other interesting features on the site include user generated exercise information from Wikipedia, and videos of exercise form from YouTube. Soon I’ll be adding weight and calorie tracking, as well as the ability to SMS text in your workouts from your mobile phone. The site is currently completely free to use.

Give it a spin and help me knock off some bugs. I’d love to hear feedback and any suggestions.

Update: KillerStarups, a sort of digg for startups, is the first to review MyFitBuddy.com. If you are in a generous mood, please vote for it.

MyFitBuddy.com Launched!

Posted over 6 years back at Shane's Brain Extension

MyFitBuddy.com logo

I’m pleased to announce the launch of MyFitBuddy.com, a workout tracking tool with social networking features. It allows you to log your workouts at the gym, sports training, your run in the park, or any other type of physical activity, and see the improvement over time. You can add your workout buddies as friends, and keep in touch with their workouts as well.

I mainly built this site for myself, as I wanted a super simple way to keep a record of my workouts and see graphs of my improvement. I thought it would be a great idea for a social site, as many studies have shown that people exercise more when they have a workout buddy or do it as part of a group. In MyFitBuddy.com, you will be motivated to exercise by seeing the activity of your friends and others on the site.

Some of the other interesting features on the site include user generated exercise information from Wikipedia, and videos of exercise form from YouTube. Soon I’ll be adding weight and calorie tracking, as well as the ability to SMS text in your workouts from your mobile phone. The site is currently completely free to use.

Give it a spin and help me knock off some bugs. I’d love to hear feedback and any suggestions.

Update: KillerStarups, a sort of digg for startups, is the first to review MyFitBuddy.com. If you are in a generous mood, please vote for it.

Outsmarted by Edge Rails

Posted over 6 years back at Alloy Code - Home

One of the cool things about starting new applications based on Edge Rails (as opposed to a stable gem version) is that I get access to all the awesome new features right away. Sure, there are a few panic moments when I get hit with unexpected bugs, and I don’t use SVN externals to link to the Rails trunk any more [1], but every now and then, I’m pleasantly surprised by something that I hadn’t previously read about.

I was in the process of adding counter_cache support to an application which had some very deep parent-child relationships, and I needed to generate a migration to add the _count column.

script/generate migration add_widgets_count_to_product

Imagine my surprise when I opened up the newly-created migration file in Textmate, and saw the following:

 class AddWidgetsCountToProduct < ActiveRecord::Migration
    def self.up
      add_column :products, :widgets_count, :type, :null => :no?, :default => :maybe?
    end

    def self.down
      remove_column :products, :widgets_count
    end
  end

After a little digging, I found this in the Rails Trac: Automatically generate add/remove column commands in specially named migrations. It would seem that Ryan Davis has put together some REGEX magic along with some meta-programming to help out people like me who give their migrations very literal names about what the migration will do.

This is a great little timesaver, but now it has me wondering what other neat features have slipped without my noticing them. It looks like it may be time for me to add the Trac timeline RSS feed to my reader, so I can keep a sharper eye out for these sorts of treats.

[1]: These days, when I start a new project, I freeze a version of edge Rails into the application, that way, I get most of the cutting edge features, and a much lower chance of introducing unexpected trouble every time I type ‘svn up.’ Periodically, I review what’s changed in trunk since I started, and if there’s some gotta-have-it feature, I’ll re-freeze.