Tuesday, July 14, 2009

Why I Love Using User Stories

A few days ago, someone on the business side of our project created a task in the system with a single sentence:

Add Flash to front page.

This requirement is, in many ways, the exact opposite of useful. Here are some concerns:

- What is the business value?
- What is the audience?
- What will it do? There is no predefined "done."
- How big is this?

This got me thinking about the way I generally write requirements -- user stories. A user story is a single sentence that describes how a user should be able to use the system. I have been using user stories for almost two years now. I can honestly say that they have changed the way I write software, and for the better; but I hadn't really taken the time to put my finger on why this way is better.

One thing that is important to me as a developer is to be creating software that users will love to use. Obviously, a technically-cool table structure doesn't make for happy users. Software can not be considered a success from the end-user perspective if it doesn't solve their problems. That's where user stories come in to the picture -- their goal is to describe how a user will be working with the system and how it will solve their problems.

User stories are great because they keep the user, their concerns, motivations and experiences, in the foreground.
  • Keep each requirement discreet and small-ish.
  • If we can't justify the business value of a requirement, we shouldn't do it.
  • If we can't create acceptance criteria, then we don't really understand what we want done. This is a bad time to start doing it.

Wednesday, July 1, 2009

Polymorphic Associations in Rails using xid ( ids unique across the system )

On a recent project, the tech-savvy client told me we would be using xids. These are different from regular IDs in that they are a unique identifier across the domain. So, if you create a user with the ID 1000, then you create a ticket, it would have the id 1001, and then when you created some other object, it would have the id 1002.

His logic was that it would allow you to go to one central place to find any resource, if you only had the ID. Although we haven't found a use for that on the public surface of the site, it has been very useful from the polymorphic association side.

Here's what the Rails documentation shows for a polymorphic association:

class Asset <>
belongs_to :attachable, :polymorphic => true
end

class Post <>
has_many :assets, :as => :attachable # The :as option specifies the polymorphic interface to use.
end

@asset.attachable = @post


Say you have a Bus, and it can either be owned by a coop, an individual, or a company.

class Bus <>
include link_to_map

belongs_to_mapped :owner
...
end

The busses table has an owner_xid field. Since the xid field is unique, there is no need to store the type of owner. This makes me happy because it seems to extend Ruby's loose typing to the data level. You can stuff any kind of object there without extra work.

Here's how we're doing it...

belongs_to_mapped (see lib/mappable_associations.rb, below) dynamically defines the getters and setters. In this case, owner, owner=, owner_xid, owner_xid=.

Bus#owner would look something like

def owner
@owner ||= Map.lookup( owner_xid )
end

- @owner keeps the object cached so you don't have to load it from the database every time you access it.
- Map is the class that hands out the XIDs. It keeps a record of the klass associated with each XID.
- Map.lookup takes any xid and returns the record it represents, raising an exception if it isn't found.

All the code is below.

As I said before, I like the idea of belongs_to_mapped. With that said, it is the only benefit we're getting from the XID system, which was implemented to solve a problem we weren't having. While it isn't high-maintenance, it's more than no-maintenance. So, unless you already have such a system in place, this probably wouldn't be a good enough reason to implement one.

------------------------------------------------

the Map table:
- xid
- item_type

class Map <>
set_primary_key "xid"

def klass
item_type.classify.constantize
end

def entry
klass.find xid
end
class <<>
def lookup(xid)
map = find xid # raises if not found
map.entry
end

end
end

lib/mappable_associations.rb defines belongs_to_mapped()

module MappableAssociations

def belongs_to_mapped(name)
cache = "@#{name}"
foreign_key = "#{name}_xid"
define_method name do
instance_variable_get(cache) ||
(
key = read_attribute(foreign_key)
model = Map.lookup( key ) if key
instance_variable_set(cache, model)
)
end

define_method("#{name}=") do |value|
instance_variable_set(cache, value)
new_xid = value ? value.xid : nil
write_attribute(foreign_key, new_xid)
end

define_method("#{foreign_key}=") do |value|
id = value.blank? ? nil : value.to_i
instance_variable_set(cache, Map.lookup(id)) if id
write_attribute(foreign_key, id)
end

define_method(foreign_key) do
read_attribute(foreign_key)
end
define_method("save_#{name}") do
instance = instance_variable_get(cache)
instance.save if instance
end

before_save "save_#{name}"

end

finally, here's lib/link_to_map.rb

module LinkToMap

def self.included(base)
base.set_primary_key "xid"
base.has_one :map, :dependent => :destroy, :foreign_key => 'xid'
base.before_create :link_to_map
base.extend MappableAssociations
define_link_to_map
end
def self.define_link_to_map
class_eval(<<-EOS, __FILE__, __LINE__)
EOS
end
end