Read about it in this tgethr blog post.
- Caveat Experior: Pivot Mike found a bug in Webrat in Selenium mode when using #click_link. He filed a ticket at Lighthouse.
- Caveat Coracinatus: Attention to those “Riding the Toad” (I didn’t make this up – it’s on Hoptoad’s homepage): the Hoptoad Notifier gem that was released on Jan 20, v2.1.1, is missing a file. Make sure to update to the latest version, v2.1.2 in order for this gem to work. You should be on the latest gem anyway because there’s a deprecation in the session code that will stop working in February. If you have a site that’s not actively being developed you will need to update the gem & redeploy your app in order to continue to receive exception notifications.
If you notice that your classes have more than one responsibility, you can easily split them up into multiple, more cohesive classes using Ruby’s DelegateClass.
Let’s say that you have a Person class, and that people in your system can sell things and/or publish articles. You can’t use subclasses, because a person can be an author and a seller at the same time. At first you might start with something like this:
class Person < ActiveRecord::Base has_many :articles has_many :comments, :through => :articles has_many :items has_many :transactions def is_seller? items.present? end def amount_owed # => some fancy math end def is_author? articles.present? end def can_post_article_to_homepage? # => some fancy permissions end end
This might seem OK at first. You might say “Well, it’s the responsibility of Person to know about both the items they’ve sold, as well as the articles they’ve published.” I say that’s hogwash.
Imagine a new requirement: People can be buyers as well as sellers / authors
The way this is setup, you’d have to re-open the person class and add things like:
class Person < ActiveRecord::Base # ... has_many :purchased_items has_many :purchased_transactions def is_buyer? purchased_items.present? end # ... end
The first thing to notice is that this violates the open / closed principle (open for extension but closed to modification) because you’ve modified the class. Next, you’ll notice that naming can get very confusing in places where you’ve got a person who is on both sides of a transaction. Finally, this code has poor separation of concerns.
Imagine another new requirement: The Person class is now driven by an xml web service, or a non-ActiveRecord class
Now that you can’t use ActiveRecord and your has_many code doesn’t work, you have to rewrite all kinds of code, and feature development grinds to a halt.
Let’s say instead of modifying Person, you extended Person by creating delegate classes, like so:
class Person < ActiveRecord::Base end class Seller < DelegateClass(Person) delegate :id, :to => :__getobj__ def items Item.for_seller_id(id) end def transactions Transaction.for_seller_id(id) end def is_seller? items.present? end def amount_owed # => some fancy math end end class Author < DelegateClass(Person) delegate :id, :to => :__getobj__ def articles Article.for_author_id(id) end def comments Comment.for_author_id(id) end def is_author? articles.present? end def can_post_article_to_homepage? # => some fancy permissions end end
The calls to this involve one extra step, so instead of:
person = Person.find(1) person.items
person = Person.find(1) seller = Seller.new(person) seller.items seller.first_name # => calls person.first_name
Now that this is in place, adding a Buyer is as simple as creating a Buyer delegate class like so:
class Buyer < DelegateClass(Person) delegate :id, :to => :__getobj__ def items Item.for_buyer_id(id) end def is_buyer? purchased_items.present? end end
Now when you need to make Person driven by something other than ActiveRecord::Base, your delegate classes don’t change at all.
Delegate classes aren’t the solution to every problem, and certain behavior, such as #reload can be very confusing at first:
person = Person.find(1) seller = Seller.new(person) seller.class # => Seller seller.reload.class # => Person
Another gotcha is that id doesn’t delegate by default, so you have to add the following line to make sure you get the ActiveRecord id:
delegate :id, :to => :__getobj__
However, delegate classes can go a long way to making your code more supple.
Pivotal NYC was lucky enough to have Ben Stein in the
office to give a beta presentation entitled “Beyond the Hype: What it
Really Takes to Build a Technology Business on the Cloud”. Ben has
built his own successful startup, Mobile Commons,
using some of the principles he discussed.
Boiled down to its essence, Ben’s theme was (with apologies to Chris
Carter): Trust No One.
More specifically, Ben talked about ways to build your business and
technology to better handle the inevitable failures of your vendors,
partners and infrastructure providers.
Technical highlights from Ben’s talk included a recipe for
constructing a vendor-agnostic library (heretofore known as the “Stein
Stack”), and actionable rules-of-thumb to make API consumers and
producers more robust and debuggable.
If you’re interested in attending a future Pivotal NYC Tech Talk,
please subscribe to the mailing list.
We were bitten by a Rails 2.3 bug related to ActionMailer today. It took us a good part of the day to hunt down due to the fact that it only happened in production and even then only occasionally.
Basically ActionMailer occasionally sends your multipart emails as text/plain with html content.
We’re planning a Pivotal Tracker upgrade on Jan 23. As part of this release, we will be introducing a new API version (V3), which will make it easier to follow project activity, allow you to add file attachments, move (re-prioritize) stories, associate source commits with stories, and more.
The current API version (V2) will not change, but V1 will no longer work. If you’re still using V1, you will need to change your client code to use V2 or V3.
To find out what’s changing in V3, continue reading.
How to tell what version of the API you’re using?
The API version identifier is part of the request URLs. For example, this is a V2 request:
What’s New or Changed in V3
The response for the activity queries will change significantly. It will include a version # (to allow you to keep track of unique events and their order), event type, when the activity occurred (with time zone), and a nested element with all story attributes that changed as part of the activity. Example:
<activities type="array"> <activity> <id type="integer">1031</id> <version type="integer">175</version> <event_type>story_update</event_type> <occurred_at type="datetime">2009/12/14 14:12:09 PST</occurred_at> <author>James Kirk</author> <project_id type="integer">26</project_id> <description>James Kirk accepted "More power to shields"</description> <stories> <story> <id type="integer">109</id> <url>https://www.pivotaltracker.com/services/v3/projects/26/stories/109</url> <accepted_at type="datetime">2009/12/14 22:12:09 UTC</accepted_at> <current_state>accepted</current_state> </story> </stories> </activity> </activities>
You’ll also be able to query for all activity since a particular date or version #, and you limit how many entries to return (up to 100):
curl -H "X-TrackerToken: TOKEN" -X GET http://www.pivotaltracker.com/services/v3/activities?newer_than_version=13 curl -H "X-TrackerToken: TOKEN" -X GET http://www.pivotaltracker.com/services/v3/activities?occurred_since_date=2009/12/14&limit=50
Activity Web Hook
This will allow you to specify a URL per project (in project settings), which Tracker will post story activity to, in the same XML format as above. You’ll be able to “pull” story activity out of Tracker via normal API GET requests, or have it POSTed to your client as it occurs via the activity web hook.
The project XML response will include the current and initial velocity, last activity date, and a list of all labels in the project.
<project> <id>1</id> <name>Sample Project</name> <iteration_length type="integer">2</iteration_length> <week_start_day>Monday</week_start_day> <point_scale>0,1,2,3</point_scale> <account>James Kirks Account</account> <velocity_scheme>Average of 4 iterations</velocity_scheme> <initial_velocity>10</initial_velocity> <last_activity_at type="datetime">2010/01/16 17:39:10 CST</last_activity_at> <number_of_done_iterations_to_show>12</number_of_done_iterations_to_show> <labels>shields,transporter</labels> <allow_attachments>true</allow_attachments> <public>false</public> <use_https>true</use_https> <bugs_and_chores_are_estimatable>false</bugs_and_chores_are_estimatable> <commit_mode>false</commit_mode> <memberships> <membership> <id>1006</id> <person> <email>firstname.lastname@example.org</email> <name>James T. Kirk</name> <initials>JTK</initials> </person> <role>Owner</role> </membership> </memberships> </project>
When creating a project via the API, the user represented by the API token will be made an owner of that project by default. To leave the new project without an owner (because your client is acting on behalf of a different user, for example), you’ll need to include
<no_owner type="boolean">true</no_owner> in the post data.
You’ll be able to move (re-prioritize) stories via the API. To move a story to after another story:
curl -H "X-TrackerToken: TOKEN" -X POST http://www.pivotaltracker.com/services/v3/projects/PROJECT_ID/stories/STORY_ID/moves?move[move]=after&move[target]=TARGET_STORY_ID"
Or, move it before a story:
curl -H "X-TrackerToken: TOKEN" -X POST http://www.pivotaltracker.com/services/v3/projects/PROJECT_ID/stories/STORY_ID/moves?move[move]=before&move[target]=TARGET_STORY_ID
As part of the new integrations feature (watch for more on that later), you’ll be able to associate a story with a ticket or issue in an external system, such as Lighthouse or JIRA. You’ll need to specify a ticket/issue ID, and optionally which specific integration to use (a project may be set up with multiple):
curl -H "X-TrackerToken: TOKEN" -H "Content-type: application/xml" -d "<story><lighthouse_id>54</lighthouse_id></story>" -X PUT http://www.pivotaltracker.com/services/v3/projects/PROJECT_ID/stories/STORY_ID"
Stories that are linked to a ticket or issue in an external system (for example JIRA or Lighthouse) will include the external ID as an attribute, as well as the URL to the linked ticket/issue:
<story> <id type="integer">STORY_ID</id> <story_type>feature</story_type> <url>http://www.pivotaltracker.com/story/show/STORY_ID</url> <estimate type="integer">1</estimate> <current_state>unstarted</current_state> <lighthouse_id>43</lighthouse_id> <lighthouse_url>http://mylighthouseapp.com/projects/100/tickets/43</lighthouse_url> <name>More power to shields</name> <requested_by>James Kirk</requested_by> <created_at type="datetime">2008/12/10 00:00:00 UTC</created_at> </story>
Here’s how you’ll be able to upload a file attachment to a story:
curl -H "X-TrackerToken: TOKEN" -X POST -F Filedata=@/path/to/file http://www.pivotaltracker.com/services/v3/projects/PROJECT_ID/stories/STORY_ID/attachments
The story response will include information about file attachments in this nested XML element:
<attachments type="array"> <attachment> <id type="integer">17</id> <filename>Picture_36.png</filename> <description></description> <uploaded_by>Rob</uploaded_by> <uploaded_at type="datetime">2010/01/17 14:57:57 CST</uploaded_at> </attachment> </attachments>
Note: Attachments in the story response XML will most likely not include a URL to the actual AWS S3 file, since these URLs are only valid temporarily. You’ll need to make a separate API call (details TBD) to get the S3 URL for a given story file attachment.
Source Control Post Commit Hooks
This will allow you to set up post-commit hooks in git, github, subversion, etc., to link commits to stories (and optionally mark them as finished) based on this message syntax:
“Torpedoes now sufficiently powered [fixes #123456]“.
Curl example, of what you might do in a custom post-commit hook script for subversion or git:
curl -H "X-TrackerToken: TOKEN" -H "Content-type: application/xml" -d "<source_commit><message>$MESSAGE</message><author>$AUTHOR</author><commit_id>$REV</commit_id><url>http://trac.yourcompany.com/browser/?rev=$REV</url></source_commit>" -X POST http://www.pivotaltracker.com/services/v3/source_commits
Stories will show associated source commits as comments, with a link to the commit if you include a URL in the post body:
The V3 version of the API will also support native Github post-commit hooks, allowing you to configure your Github repo to send commit information directly to Tracker, along with the
[fixes #12345] message syntax.
The format of the Github post-commit request will be:
The RightScale SQS gem returned an exception from SQS multiple times, including retries. Not an unusual event. This could have been caused by the SQS service being unavailable. However, the team noticed that despite the failure the message was actually successfully added to the queue and processed as normal.
ActiveSupport logger appears to open the default ruby logger and remove everything except the basic log message passed through. This is done for all subsequent uses of the logger. Perhaps this is done so that the log message could be passed to a syslog service which will add timestamps.
Whilst trying to parse differently formatted date strings from rss feeds a pivot found that date.now is overridden by DateJS to return a new date.
There was a suggestion, that later proved useful, to use google’s rss reader to first clean up the different rss feeds to ensure that they all can be parsed in much the same way.
Points of Interest
class Thing def foo def bar(args) # some code end # some code that calls bar() end end
The structure of this code suggests that
baris scoped only within
the context of
foo. But alas, that is not the case. Ruby simply
Thing#barthe first time
foois called, analogous to
define_method :bar. Misleading syntax, for sure.
Ryan Davis‘s Flay is awesome. If you’re not
familiar with it, Flay parses your Ruby and compares subtrees with
each other to find where code has been duplicated (or nearly so).
Run on a codebase of over 20,000 lines of Ruby, Flay was able to
quickly indicate places where we had duplicate code lying around, as
well as many likely targets for refactoring work. We’ve found it to
be helpful in keeping code DRY.
In Snow Leopard, mapping CapsLock => Ctrl when two keyboards are
plugged in is problematic. You can do it by plugging in one keyboard
at a time and mapping each one individually. If anyone knows the
story behind why this is, or how to deal more easily, please comment!
Another lesson learned the hard way: starting up a bunch of leopard
machines at the same time wreaks havoc on the network for a few
minutes. Packet storm, dropped packets, etc. Is Bonjour to blame?
Inquiring minds want to know.
If you’re trying to stub a subclass of
Double Ruby (also known as RR), and you’re having issues, try
stubbing the method on
ActionMailer::Basedirectly. There’s some
weirdness there with
method_missingin Rails 2.3.
Does anyone know how to make
tabs inside of
Term.app? By default, these keys are bound to
switching windows, and we’d love to be able to do this on tabs
select_tag(..., :multiple => true)option doesn’t properly
selectedon the generated options. This appears to be a boog,
and anyone who’s interested in helping a Pivot write a patch, please
I recently returned from a trip to Egypt with Pivotal’s own Lara Owen. Like all good tourists, we went and saw the Pyramids of Giza. Like far fewer tourists, we also went to Saqqara to see the Step Pyramid and then on to Dashur, home of the Bent Pyramid and the Red Pyramid.
In the 3rd Dynasty, the Step Pyramid is constructed under the rule of Djoser. The pyramid has entered the market. By the 4th Dynasty, under the rule of Snofru, six steps are no longer enough, users want seven steps and smooth sides.
In an attempt to meet these new market demands, the Bent Pyramid construction begins. Half way through, they realize they made a mistake, and adapt their plan accordingly by changing the angle down from 54° to 45°. The next attempt, still within Snofru’s lifetime, becomes the gold standard of pyramids and is known as the Red Pyramid. With it’s consistent sloping smooth sides and added height, this pyramid was fit for any king.
What I thought about, as you might have guessed by now, was the methodology used to construct each of these pyramids. In the case of the Step Pyramid form the 3rd Dynasty, it would be easy to see a king proclaim, “I want this kind of pyramid and I want it ready by the time I die.” And he’ll get something twenty years later that resembles what he wanted twenty years ago.
Snofru had a different plan. He was going to shake up the way people think about royal tombs. He was going to do something no one else had done. This man of foresight said to his people, “We will release early, and we will release often. We will have meetings along the way to reflect on how we have progressed, and we will then plan ahead only as far as is practical, adjusting as we go. Most importantly, we will trust each other such that mistakes can be made without causing great harm to the project.” It was with this spirit that Snofru began work on the Bent Pyramid.
He didn’t know what he was doing, there was no model to copy, no patterns to solve this problem. So he guessed 54°. And they built, and they talked and said “Hey Snofru, this looks great, but it won’t be stable if we continue at this angle.” To which Snofru probably replied, “I will trust your expertise here, can we make the angle less steep and see how that works out?” So 45° it was.
When the finished, they stood back and looked at the work they had done. It was taller, and it did have smooth sides, but it wasn’t quite right. Snofru, being a wise king, knew that this was a great success.
- A taller pyramid could indeed be built.
- 45° was a sustainable angle
- He was still drawing breath and now had the knowledge to fully realize his goal.
Snofru still had plenty of time on his hands, and because he released early instead of letting construction drag on, he had new insight from the retrospective that he wouldn’t possess otherwise. So he built the Red Pyramid with complete success.
Snofru was, in my opinion, the first adopter of Agile principles. His “employees” trusted him enough to be able to push back when the plan needed revising. He trusted them right back not to yank his chain about things being too steep. He made his mistakes up front, which taught him lessons about his domain, and it enabled him to be successful in the long run.
Bundler 0.8.1 is out. There have been some significant changes around how the vendor directory is organized, so you’ll want to rm -rf vendor/gems and re-run bundle.
A team tried swapping in Sqlite to see if it made any difference in test suite runtime. It was actually slightly slower than MySql. In-memory Sqlite didn’t help either.
At stand-up this past Monday after the whirlwind of the holidays, our team anchor encouraged us to pair with somebody we hadn’t paired with much recently. While this was good advice I really didn’t have a clear idea in my mind of how often I’d been pairing with specific developers on the team.
On our project we use Bryan Helmkamp’s pair script to change our git author name to a pair of developers joined with “and.” Since both developer names are included on a commit in a consistent way, I was able to get a vague idea of with whom I’d been paired over the past couple of weeks with a quick git log | grep Pignata | less but I couldn’t really see any patterns from this output.
To better keep track I threw together a quick script to quickly parse out these results and show me at a glance what my pairing habits have been over the past 30 days. Now I can check every couple of weeks to see if I’ve inadvertently become part of a sticky pair or if there’s a developer I haven’t been pairing with often enough:
jp@populuxe:~/Projects/lorem(master)$ ./pair-stats Habitasse Platea Pairing stats for Habitasse Platea since 2009-12-06 Developer Days Commits % --------------- ---- ------- --- Tincidunt Nec 8 55 33% Congue vel Mauris 5 40 20% Sed Facilisis 4 21 16% Etiam Blandit 3 16 12% Solo 1 2 1% ...
You can find the script on GitHub.
What tricks does your team have to ensure proper circulation of developer pairs?
A gotcha when using cap and bundler:
“If deploy.rb does a require ‘auto_tagger’ and the auto_tagger gem is in the app’s bundle but not the system, running the system cap won’t find the auto_tagger gem. Using bin/cap runs the bundled cap and thus has access to all the gems in the bundle.”