<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:blogger='http://schemas.google.com/blogger/2008' xmlns:georss='http://www.georss.org/georss' xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-5159103</id><updated>2024-09-02T02:45:06.715-05:00</updated><category term="ruby"/><category term="tools"/><category term="rails"/><category term="communication"/><category term="tdd"/><category term="tips"/><category term="agile"/><category term="active record"/><category term="bash"/><category term="book"/><category term="conference"/><category term="example"/><category term="excel"/><category term="git"/><category term="interaction design"/><category term="power query"/><category term="productivity"/><category term="project management"/><category term="rake"/><category term="review"/><category term="satir"/><category term="test/spec"/><category term="testing"/><category term="tripit"/><category term="unit testing"/><category term="2007"/><category term="Notes"/><category term="Seminar"/><category term="Seth Godin"/><category term="alan cooper"/><category term="art"/><category term="aye"/><category term="bdd"/><category term="book review"/><category term="build"/><category term="campfire"/><category term="change"/><category term="code"/><category term="collaboration"/><category term="continuous improvement"/><category term="etl"/><category term="extreme programming"/><category term="fixtures"/><category term="flog"/><category term="hr"/><category term="iTerm"/><category term="jerryweinberg"/><category term="lean"/><category term="learning"/><category term="mac"/><category term="management"/><category term="meeting"/><category term="mini"/><category term="model"/><category term="open source"/><category term="patch"/><category term="powerbi"/><category term="practices"/><category term="presentation"/><category term="pretty print"/><category term="quote"/><category term="rails plugin sql migrations"/><category term="retrospectives"/><category term="rules"/><category term="sci fi"/><category term="scm"/><category term="spreadsheets"/><category term="svn"/><category term="tax"/><category term="team"/><category term="test case"/><category term="textmate"/><category term="tom robbins"/><category term="tutorial"/><category term="vmware"/><category term="waste"/><title type='text'>Mundane Essays</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://muness.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default?redirect=false'/><link rel='alternate' type='text/html' href='http://muness.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default?start-index=26&amp;max-results=25&amp;redirect=false'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/13080591937269765506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>659</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-5159103.post-2056258714468960740</id><published>2015-03-15T18:09:00.001-05:00</published><updated>2015-03-15T18:22:29.561-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="power query"/><category scheme="http://www.blogger.com/atom/ns#" term="powerbi"/><title type='text'>Power BI tip: Parameters table for Power Query queries to connect to different databases</title><content type='html'>I&#39;ve been using Microsoft&#39;s Power BI to help a wonderful client of mine build self-service BI for their analysts and away from a system where they generate csv data dumps out of a Mainframe and then load them manually into Excel spreadsheets.
&lt;p&gt;
Most analysts love to use Excel, so Power BI is a no-brainer for getting them their data but &lt;a href=&quot;https://support.office.com/en-us/article/Introduction-to-Microsoft-Power-Query-for-Excel-6E92E2F4-2079-4E1F-BAD5-89F6269CD605&quot;&gt;Power Query&lt;/a&gt; M, though incredibly powerful, doesn&#39;t have a way to switch which DB to use. As a developer who develops against my local machine, tests in a test environment and only then promotes my code to production and gives it to the users, is unacceptable.
&lt;p&gt;
Here&#39;s a workaround I came up for specifying which Server and DB to use through parameters. It&#39;s based on &lt;a href=&quot;http://blog.oraylis.de/2013/05/using-dynamic-parameter-values-in-power-query-queries/&quot;&gt;Using dynamic parameter values in Power Query Queries&lt;/a&gt;. That approach doesn&#39;t explain how to use it to connect to different Servers or DBs, which it turns out is trivial, but it also expects the order of parameters not to change, something that strikes me as much too fragile.
&lt;p&gt;
First, create a new Sheet and in it, create a table in Excel. You&#39;ll need to columns, &lt;code&gt;Name of Parameter&lt;/code&gt; and &lt;code&gt;Value&lt;/code&gt;. For dynamically specifying the server and database to use, add two rows, with the &lt;code&gt;Name of Parameter&lt;/code&gt; of &lt;code&gt;DB Server&lt;/code&gt; and &lt;code&gt;DB&lt;/code&gt;. Under the &lt;code&gt;Value&lt;/code&gt; enter the server and database you want to use.
&lt;br/&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhXkq8M-HUv6Oj5uJTMj-mS2J5dH9d_hXDSZnUzDmnOiTQf3wa___y97gWjMWR2u6Ijyx6MaSuVjzP0PIaNM5rOf_VOEhrUgPm9-W8bwKJ6wjUInFdldGEzbEVI60YCKIVCm3SoOg/s1600/Parameters+table.PNG&quot; imageanchor=&quot;1&quot; &gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhXkq8M-HUv6Oj5uJTMj-mS2J5dH9d_hXDSZnUzDmnOiTQf3wa___y97gWjMWR2u6Ijyx6MaSuVjzP0PIaNM5rOf_VOEhrUgPm9-W8bwKJ6wjUInFdldGEzbEVI60YCKIVCm3SoOg/s400/Parameters+table.PNG&quot; /&gt;&lt;/a&gt;
&lt;br/&gt;
Don&#39;t forget to (Home ribbon) &quot;Format As Table&quot; then (Table Tools / Design ribbon) set the table name to what you want to call it, Parameters, in this example.
&lt;p&gt;Now, we&#39;ll use these parameters in our Power Query queries to the DB. To edit your query: (Power Query ribbon) Show Pane, Right click on the query. Then, in the Query Editor that pops up, (in the Home ribbon), click &quot;Advanced Editor&quot;. This will show you the source code of the in &lt;a href=&quot;https://support.office.com/en-us/article/Learn-about-Power-Query-formulas-6BC50988-022B-4799-A709-F8AAFDEE2B2F&quot;&gt;Power Query Formula language&lt;/a&gt; (usually called &quot;M&quot; by Power BI users). Here&#39;s an example query:
&lt;pre&gt;&lt;code&gt;
let
    Source = Sql.Database(&quot;devserver&quot;, &quot;maindb&quot;, [Query=&quot;SELECT * FROM [Reporting].[Users]&quot;])
in
    Source
&lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;We&#39;re going to change the query to load the parameters and use them instead of the hardcoded &quot;dbserver&quot; and &quot;maindb&quot; in Sql.Database function call.

&lt;pre&gt;&lt;code&gt;
let
    Parameters = Excel.CurrentWorkbook(){[Name=&quot;Parameters&quot;]}[Content],
    DBServer = Table.SelectRows(Parameters, each [Name of Parameter] = &quot;DB Server&quot;){0}[Value],
    DB = Table.SelectRows(Parameters, each [Name of Parameter] = &quot;DB&quot;){0}[Value],

    Source = Sql.Database(DBServer, DB, [Query=&quot;SELECT * FROM [Reporting].[Users]&quot;])
in
    Source
&lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;You&#39;ll then change every query that uses this DB to similarly get those parameters first and then use them in the Sql.Database call. The code duplication is unfortunate. I tried a couple of other approaches that take advantage of Power Query&#39;s (creating a power query for each of the parameters and creating a function that passes the parameter name) but Power Query&#39;s security model doesn&#39;t (at least of March 2015) allow another Power Query query or function when retrieving data from an external source.

&lt;p&gt;We&#39;re done. To try it out and use a different Server or DB, modify the Parameters table as necessary then refresh the individual query (right click on the query and refresh) or all your queries (Data ribbon then Refresh All).

&lt;p&gt;If you&#39;ve never connected to the new server via power query, you&#39;ll get a popup asking for them as usual when first connecting to a new database. That popup only works properly when you refresh a single query, so you&#39;ll want to do that if it looks like your Refresh All is hanging.
</content><link rel='replies' type='application/atom+xml' href='http://muness.blogspot.com/feeds/2056258714468960740/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5159103/2056258714468960740' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/2056258714468960740'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/2056258714468960740'/><link rel='alternate' type='text/html' href='http://muness.blogspot.com/2015/03/power-bi-tip-parameters-table-for-power.html' title='Power BI tip: Parameters table for Power Query queries to connect to different databases'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/13080591937269765506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhXkq8M-HUv6Oj5uJTMj-mS2J5dH9d_hXDSZnUzDmnOiTQf3wa___y97gWjMWR2u6Ijyx6MaSuVjzP0PIaNM5rOf_VOEhrUgPm9-W8bwKJ6wjUInFdldGEzbEVI60YCKIVCm3SoOg/s72-c/Parameters+table.PNG" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5159103.post-228605834023822207</id><published>2014-08-22T16:07:00.002-05:00</published><updated>2014-08-22T16:11:51.704-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="agile"/><category scheme="http://www.blogger.com/atom/ns#" term="change"/><category scheme="http://www.blogger.com/atom/ns#" term="continuous improvement"/><category scheme="http://www.blogger.com/atom/ns#" term="extreme programming"/><category scheme="http://www.blogger.com/atom/ns#" term="lean"/><category scheme="http://www.blogger.com/atom/ns#" term="practices"/><title type='text'>Not a software delivery maturity model</title><content type='html'>&lt;h3&gt;Not a what?&lt;/h3&gt;
&lt;p/&gt;
Over years of building software, I&#39;ve found that when there&#39;s a performance problem, I usually jump to conclusions, put in a crazy fix and observe that it makes no real difference. I then remember what I&#39;ve learned a dozen times, step back, instrument the systems involved, collect and analyze performance data. Lo and behold, the solution put in place usually has a significant impact.
&lt;p/&gt;
&lt;blockquote&gt;Optimization matters only when it matters. When it matters, it matters a lot, but &lt;a href=&quot;http://www.flounder.com/optimization.htm&quot;&gt;until you know that it matters, don&#39;t waste a lot of time doing it&lt;/a&gt;.&lt;/blockquote&gt;
&lt;p/&gt;
In my experience, the same tendency to guess at problems and apply fixes randomly applies to improving software teams. Here&#39;s a process I use to avoid such unilateral process changes and instead talk openly about issues and create consensus before acting.
&lt;h3&gt;But we follow [insert methodology here]. Why would we change anything?&lt;/h3&gt;
At OOPSLA 2005, &lt;a href=&quot;http://www.vanderburg.org/&quot;&gt;Glenn Vanderburg&lt;/a&gt; presented a paper, &lt;a href=&quot;http://www.vanderburg.org/Writing/xpannealed.pdf&quot;&gt;A Simple Model of Agile Software Processes – or – Extreme Programming Annealed (PDF)&lt;/a&gt; that I&#39;ve thought about every few months since.
&lt;p/&gt;
My takeaway: XP practices are a coherent set of practices that support the success factor whose health is necessary for software. Embracing the individual practices isn&#39;t the point. What matters is ensuring that those success factor are healthy. If you&#39;re aware and mature enough to swap a practice out while maintaining the health of that area, go for it; by doing so, over time you can create a process that better suits your needs than the generic one.
&lt;h3&gt;Let&#39;s get to it...&lt;/h3&gt;
For a software delivery organization, here&#39;s the list of those success factor that I start with:
&lt;ul&gt;
&lt;li&gt;Asthetic design: Does it hurt my eyes to use this app?&lt;/li&gt;
&lt;li&gt;Company direction: What are our goals? What do we do? What are our values? Does everyone understand them?&lt;/li&gt;
&lt;li&gt;Design and architecture: Is your system robust? Is the emergent architecture you have today what you&#39;d use if you were designing the system today? Can you diagnose issues across systems?&lt;/li&gt;
&lt;li&gt;Dev: Are the individual systems easy to change? Do they fail too often?&lt;/li&gt;
&lt;li&gt;Inter-team collaboration: Do teams work well together?&lt;/li&gt;
&lt;li&gt;Intra-team collaboration: Do members of a team feel like they have common goals and priorities?&lt;/li&gt;
&lt;li&gt;People: Are we supporting people? Are we giving people information they need? How do well do we make decisions?&lt;/li&gt;
&lt;li&gt;Portfolio management: Are we focusing our teams on the right projects?&lt;/li&gt;
&lt;li&gt;Product vision: Are we building a system that makes for &lt;a href=&quot;http://vimeo.com/theblnbusinessofsoftware/review/54469442/9e94db785d&quot;&gt;badass users&lt;/a&gt;?&lt;/li&gt;
&lt;li&gt;Reliability: Can we run this? Can we recover quickly enough when we fail?&lt;/li&gt;
&lt;li&gt;Scalability: Do we understand the scaling characterstics of this system and are they appropriate for what we&#39;re doing?&lt;/li&gt;
&lt;li&gt;Security: Do we understand the security risks and are they appropriate for this system?&lt;/li&gt;
&lt;li&gt;Usability: Are we building a system people enjoy using?&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Now what?&lt;/h3&gt;
As a team, review the list of success factors and come up with ones that work for you. Then use interviews and &lt;a href=&quot;http://en.wikipedia.org/wiki/Blink_(book)&quot;&gt;expert intuition&lt;/a&gt; to assess which of the success factors matter most right now and how the team thinks you&#39;re doing. Here&#39;s an example:&lt;br/&gt;
&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEht6ZU1tuok3YJ7ihYRwjWCTn66SS96sEo8Mq3Euxpqdy5cwjeQadxlVfT_YDP9cEgPGL8x5DQ7kGb7wa34juBsM7yg1Avc3H_qgro7e2gkQrFsiexZmP8mTleTEm1qKadKUXKt7w/s600/Capture.PNG&quot; /&gt;&lt;br/&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJEDPweeGmUGG6xPqWnWD7MRegnzEo2TclC2AnPRr4ue81BYKAsfo55dpuE6TnL93rT1MZq8O2wqmJoBoGnXHhzZt1A2BY2V4uppFDJ9CIuw8bzpl6lqS3P-mk86wOlWosj33eQg/s600/Capture-2.PNG&quot; /&gt;
&lt;p/&gt;
Note that I sorted the list by ratio of competence relative to perceived importance. That gives you a suggested order of what you should tackle first. For the first 3 factors, come up with a list of practices that people on the team has used, seen used or have heard or read about that may help you improve it. With the people who will be impacted by any changes, review current practices and potential practices you could introduce. Finally, pick one that you can try quickly at a small scale. Now &lt;a href=&quot;http://leanchange.org/2014/06/why-changes-should-be-called-experiments/&quot;&gt;experiment&lt;/a&gt;.
&lt;p/&gt;
For example, here are some practices I&#39;d consider implementing when looking for ways to improve code quality:
&lt;ul&gt;
&lt;li&gt;CI&lt;/li&gt;
&lt;li&gt;Code reviews&lt;/li&gt;
&lt;li&gt;Coding standard&lt;/li&gt;
&lt;li&gt;Pair programming&lt;/li&gt;
&lt;li&gt;Pull requests&lt;/li&gt;
&lt;li&gt;QA by developers who didn’t develop the code&lt;/li&gt;
&lt;li&gt;Refactoring as an explicit, expected step during development&lt;/li&gt;
&lt;li&gt;Static code analysis&lt;/li&gt;
&lt;li&gt;TDD&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;What about me?&lt;/h3&gt;
If you&#39;re assessing a software delivery organization, formally or not, come up with your own &lt;em&gt;not a software delivery maturity model&lt;/em&gt; success factors list and practice list. It should be a list that you can relate to your own past experiences as a team.
&lt;p/&gt;
Most importantly, remember that improvement isn&#39;t something you do in isolation but is rather a team activity. With the whole team working together, you&#39;ll gather better information, have a more nuanced analysis, a better chance of consensus and ownership of the improvement process.
&lt;h3&gt;You just don&#39;t get us: we&#39;re a special snowflake!&lt;/h3&gt;
I concede that you are unique. You should think of other people&#39;s practices as tools in a global toolkit. You get to pick which ones are relevant to you. But don&#39;t create all of your practices from scratch or assume that other people&#39;s experiences are irrelevant to you.
&lt;p/&gt;
&lt;blockquote&gt;Immature poets imitate; mature poets steal; bad poets deface what they take, and good poets make it into something better, or at least something different. &lt;em&gt;The good poet welds his theft into a whole of feeling which is unique, utterly different from that from which it was torn&lt;/em&gt;; the bad poet throws it into something which has no cohesion. A good poet will usually borrow from authors remote in time, or alien in language, or diverse in interest. -- &lt;a href=&quot;http://www.bartleby.com/200/sw11.html&quot;&gt;T.S. Elliot&lt;/a&gt;&lt;/blockquote&gt; 
&lt;h3&gt;Doesn&#39;t this model imply I may not need some practices already in place?&lt;/h3&gt;
Absolutely.&lt;p/&gt;
But you may not want to stop them: You don&#39;t want to kill a practice that people value or enjoy. If you see a conflict between an existing practice and a potential one, be honest; explain to the team where you see the conflict and work with them to resolve it.
&lt;h3&gt;Should I &lt;em&gt;only&lt;/em&gt; implement changes that improve the weakest factor?&lt;/h3&gt;
No! Limiting the number of improvement projects your team is implementing indicates that you have a bottleneck in your improvement process, probably a manager. If so, you&#39;re &lt;a href=&quot;http://www.leanblog.org/2014/07/picking-on-the-pick-chart/&quot;&gt;going about it the wrong way&lt;/a&gt;.&lt;p/&gt;
Improvement processes can be going on at once improving various factors.
&lt;h3&gt;What if the improvement process takes a long time to implement?&lt;/h3&gt;
Then implement a different practice. Or find a way to chop it up into smaller chunks. You need &lt;a href=&quot;http://progressprinciple.com/books/single/the_progress_principle&quot;&gt;lots of small wins&lt;/a&gt;, at least at first.
&lt;p/&gt;Or do it anyway. The key is to find a way to measure results along the way so you can adapt or abort it if it isn&#39;t working.
&lt;h3&gt;Why do you say this &lt;em&gt;not&lt;/em&gt; a software delivery maturity model?&lt;/h3&gt;
It isn&#39;t &lt;em&gt;a&lt;/em&gt; model but rather &lt;em&gt;your&lt;/em&gt; model: It only works in context; customize it before using.
&lt;h3&gt;Now what?&lt;/h3&gt;
If you think this might help, use it! Over the next week, draft a version of the table for your team. Share it with your team members and incorporate their feedback. Pick one of the red areas in the table, and introduce one practice that could help improve that area. Set a timeframe for the experiment and iterate.
&lt;h3&gt;Shameless plug&lt;/h3&gt;
I help software delivery organizations &lt;a href=&quot;http://217castle.com&quot;&gt;get more&lt;/a&gt; out of their teams. &lt;a href=&quot;http://www.217castle.com/contact/&quot;&gt;Drop me&lt;/a&gt; a line if you&#39;re thinking about those kinds of issues. I&#39;d love to help you figure out how to use this process and otherwise work with your agile software delivery organization for improved results.
&lt;p/&gt;
&lt;hr&gt;
&lt;p/&gt;Thanks to &lt;a href=&quot;https://twitter.com/bendycode&quot;&gt;Stephen Anderson&lt;/a&gt; for discussing the process and &lt;a href=&quot;http://jasonrudolph.com/&quot;&gt;Jason Rudolph&lt;/a&gt; for feedback on making the post more relevant and actionable.</content><link rel='replies' type='application/atom+xml' href='http://muness.blogspot.com/feeds/228605834023822207/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5159103/228605834023822207' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/228605834023822207'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/228605834023822207'/><link rel='alternate' type='text/html' href='http://muness.blogspot.com/2014/08/not-software-delivery-maturity-model.html' title='Not a software delivery maturity model'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/13080591937269765506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEht6ZU1tuok3YJ7ihYRwjWCTn66SS96sEo8Mq3Euxpqdy5cwjeQadxlVfT_YDP9cEgPGL8x5DQ7kGb7wa34juBsM7yg1Avc3H_qgro7e2gkQrFsiexZmP8mTleTEm1qKadKUXKt7w/s72-c/Capture.PNG" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5159103.post-3898531418333711111</id><published>2014-07-01T10:30:00.000-05:00</published><updated>2014-07-01T10:30:00.526-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="etl"/><category scheme="http://www.blogger.com/atom/ns#" term="excel"/><category scheme="http://www.blogger.com/atom/ns#" term="power query"/><category scheme="http://www.blogger.com/atom/ns#" term="ruby"/><category scheme="http://www.blogger.com/atom/ns#" term="tripit"/><title type='text'>It&#39;s your data, not TripIt&#39;s, take 2</title><content type='html'>&lt;h2&gt;Minimal viable products are sometimes too minimal&lt;/h2&gt;A couple of weeks ago, I posted &lt;a href=&quot;http://muness.blogspot.com/2014/06/its-your-data-not-tripits-example-of.html&quot;&gt;a blog post&lt;/a&gt;&amp;nbsp;on how I extracted my trip information from TripIt to calculate the number of days I traveled last year. Being the second iteration of the process (the first one was: open the page in TripIt, copy and paste to a text file, calculate manually), it worked, but I knew I could make it less crappy: introducing OpenFlights.org wasn&#39;t great: every time I fly, I would have to go back there and reimport the new records. The process included manual work to identify trip start and end dates and to distinguish between business and personal trips. Also, I didn&#39;t like that only trips with flights were included as some of my trips were&lt;br /&gt;
&lt;h2&gt;Let&#39;s automate some things&lt;/h2&gt;Tonight, I looked a into TripIt&#39;s API and once I figured out &lt;i&gt;&lt;a href=&quot;http://blog.andydenmark.com/2009/03/how-to-build-oauth-consumer.html&quot;&gt;their&lt;/a&gt; &lt;/i&gt;OAuth scheme, I had a&amp;nbsp;&lt;a href=&quot;http://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop&quot;&gt;REPL&lt;/a&gt;&amp;nbsp;session working, getting data from TripIt&#39;s servers. After a spike, processing a page of the data, I decided not to change the data. I then wrote a script that grabbed all the pages of my trips data, combined all &lt;i&gt;Trip&lt;/i&gt;&amp;nbsp;nodes and saved them all to an XML file for &amp;nbsp;&lt;a href=&quot;http://www.microsoft.com/en-us/download/details.aspx?id=39379&quot;&gt;import&lt;/a&gt; and &lt;a href=&quot;http://office.microsoft.com/en-us/excel/&quot;&gt;analysis&lt;/a&gt;.&lt;br /&gt;
&lt;h2&gt;Some code...&lt;/h2&gt;My script for exporting TripIt data is over in a &lt;a href=&quot;https://github.com/muness/tripit-export-trips-to-xml&quot;&gt;github repo&lt;/a&gt;. Enjoy! Aside from the authentication, there are only a &lt;a href=&quot;https://gist.github.com/muness/f771e4eee45b6b801e06#file-gistfile1-rb&quot;&gt;few of lines code&lt;/a&gt;:&lt;br /&gt;
&lt;code&gt;&lt;br /&gt;
&lt;pre&gt;xml_out.Trips do # build a Trips node
  for page in (1..pages) # defeat pagination...
    past_trips = t.list.trip({&#39;past&#39; =&amp;gt; &#39;true&#39;, &#39;include_objects&#39; =&amp;gt; &#39;false&#39;, &#39;traveler&#39; =&amp;gt; &#39;true&#39;, &quot;page_size&quot; =&amp;gt; &quot;30&quot;, &quot;page_num&quot; =&amp;gt; page})
    node = Nokogiri::XML(past_trips.to_xml.to_s)
    xml_out &amp;lt;&amp;lt; node.xpath(&quot;//Trip&quot;).to_xml # extract each Trip node and add it to the Trips node built above
  end
end
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;I can haz xml... now what?&lt;/h2&gt;Caveat: the data format from TripIt&#39;s API isn&#39;t pleasant to work with. Use your ETL skills to make it useful.  I used Power Query because I want to get better at using it. With Power Query, you define your data source and apply filters, schema manipulations and value manipulations.  I did several manipulations to make the data useful: I expanded some XML nodes, moved some columns, removed others and renamed some. I also manipulated the data by defining data types, doing some string manipulations so that partial URIs like /trip/show/id/xxxx would become full trip it URLs I can follow and changing values that match &quot;Raleigh, NC&quot; to &quot;Durham, NC&quot;. &lt;br /&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhF0Nuxs9T8sd13Ws7gwyqwotwZ8_v2jFUTCd1VZ74eccRIQlF_PNi93pjRM78UgUmZ_lPDo_IemX2XPqqmOCL1An54BbTEfYh9Go4b1km7dzyfQh5TdZUm-lv4uPJYfQC3Ag9Pg/s1600/Power+Query+steps.PNG&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhF0Nuxs9T8sd13Ws7gwyqwotwZ8_v2jFUTCd1VZ74eccRIQlF_PNi93pjRM78UgUmZ_lPDo_IemX2XPqqmOCL1An54BbTEfYh9Go4b1km7dzyfQh5TdZUm-lv4uPJYfQC3Ag9Pg/s1600/Power+Query+steps.PNG&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Power Query steps&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;On loading this data to &lt;a href=&quot;http://office.microsoft.com/en-us/excel-help/create-a-data-model-in-excel-HA102923361.aspx&quot;&gt;the data model&lt;/a&gt;, I added a calculated field (trip duration) and a couple of clicks later, the report I wanted was ready. To update the report, I rerun the script, launch Excel and press Refresh All.  &lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiF32yK6gsKEvqD7Kx-0FUxnVqbjdlnAF7VkLuxf-QLH3REwurUHPO7nbLPG7q532BvjupfblR250hPvvVIkCrZPeOW29oWlSa8YFr_qoptrgdzOkl5tjMxxZpyhyphenhyphene_nlDqfdGsdA/s1600/imageedit_7_9857635915.jpg&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiF32yK6gsKEvqD7Kx-0FUxnVqbjdlnAF7VkLuxf-QLH3REwurUHPO7nbLPG7q532BvjupfblR250hPvvVIkCrZPeOW29oWlSa8YFr_qoptrgdzOkl5tjMxxZpyhyphenhyphene_nlDqfdGsdA/s1600/imageedit_7_9857635915.jpg&quot; height=&quot;320&quot; width=&quot;252&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
What are you going to do with your TripIt data?&lt;br /&gt;
&lt;br /&gt;
[Self promotion ahead!]&lt;br /&gt;
&lt;br /&gt;
I&#39;m a developer who uses whatever tools I have to quickly produce results. These days, I regularly write scripts to load, manipulate, correlate and analyze data to make financial or operational recommendations based on real data.  &lt;a href=&quot;http://www.217castle.com/&quot;&gt;consulting site&lt;/a&gt; for a sample of things I&#39;ve done recently then give me a &lt;a href=&quot;http://www.217castle.com/contact/&quot;&gt;call&lt;/a&gt; if you have data of your own to wrestle or understand.</content><link rel='replies' type='application/atom+xml' href='http://muness.blogspot.com/feeds/3898531418333711111/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5159103/3898531418333711111' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/3898531418333711111'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/3898531418333711111'/><link rel='alternate' type='text/html' href='http://muness.blogspot.com/2014/07/its-your-data-not-tripits-take-2.html' title='It&#39;s your data, not TripIt&#39;s, take 2'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/13080591937269765506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhF0Nuxs9T8sd13Ws7gwyqwotwZ8_v2jFUTCd1VZ74eccRIQlF_PNi93pjRM78UgUmZ_lPDo_IemX2XPqqmOCL1An54BbTEfYh9Go4b1km7dzyfQh5TdZUm-lv4uPJYfQC3Ag9Pg/s72-c/Power+Query+steps.PNG" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5159103.post-8575712502939183990</id><published>2014-06-17T22:14:00.001-05:00</published><updated>2014-06-30T13:07:50.212-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="excel"/><category scheme="http://www.blogger.com/atom/ns#" term="hr"/><category scheme="http://www.blogger.com/atom/ns#" term="management"/><category scheme="http://www.blogger.com/atom/ns#" term="model"/><title type='text'>Fully loaded what?</title><content type='html'>Over my years as a hiring manager I&#39;ve found that I really like working with contractors. Some of the reasons are what you&#39;d expect: it&#39;s nice to work together on a temporary basis on things we both find explicitly and mutually beneficial, the contractors I work with are usually experts on the things I hire them for and can hit the ground running.&lt;br /&gt;
&lt;br /&gt;
One operational benefit that I didn&#39;t value until recently was the clarity of their hourly rate: when you work with a contractor, you specifically agree on an hourly rate with them. At least in the US, that kind of cost clarity is non-existent for employees.&lt;br /&gt;
&lt;br /&gt;
Especially in the context of professional services, where you&#39;re selling someone&#39;s time, knowing someone&#39;s hourly cost can is quite relevant in setting the price of their services. In that vein, I&#39;ve seen people use a rule of thumb to approximate hourly cost: divide their annual salary by 2000. For example, for someone making $120,000, the rule of thumb would say the equivalent hourly cost is $60.&lt;br /&gt;
&lt;br /&gt;
This rule of thumb is utterly wrong. The first page of results for a Google search on calculating the &quot;fully loaded cost&quot; of an employee has a telling example. &lt;a href=&quot;http://research.unc.edu/offices/sponsored-research/policies-procedures/section-300/policy-7/procedure-5/&quot;&gt;UNC Research provides a formula&lt;/a&gt; to help those submitting budget proposals calculate fully loaded hourly salaries. And the example they use is telling: for a full time EPA employee with a $150,000 in salary, their formula comes up with a very different $157.36/hr rate. Remember, the naive rule of thumb I see used all the time would result in an hourly rate of only $75/hr.&lt;br /&gt;
&lt;br /&gt;
Where does this difference come from? As a sometimes hiring manager, what rate should I use when I am thinking through the difference in cost from hiring an employee or a contractor? I really wasn&#39;t sure. And many fellow hiring managers weren&#39;t sure either. So I came up with &lt;a href=&quot;https://docs.google.com/spreadsheets/d/1xncCxeKsmEfgUDZpyTHE8le9SUn0m0RvoOWqBdLEF1Q/edit#gid=826308895&quot;&gt;a model&lt;/a&gt; to help me think through the costs.&lt;br /&gt;
&lt;br /&gt;
Before I go any further, I&#39;d like to remind you of the &lt;a href=&quot;http://en.wikiquote.org/wiki/George_E._P._Box&quot;&gt;quote&lt;/a&gt;: &quot;Essentially, all models are wrong, but some are useful.&quot; &lt;b&gt;This model is wrong&lt;/b&gt;. Every business will incur different costs for benefits. You&#39;ll have different time off policies. It&#39;s a model that I&#39;ve used to estimate employees&#39; fully loaded cost. If it seems generous, that is because I&#39;ve been lucky to have been working in tech where the benefits are incredible. But I find it useful, and think you will too.&lt;br /&gt;
&lt;br /&gt;
The model is based on a few assumptions:&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;US based.&lt;/li&gt;
&lt;li&gt;2014 &lt;a href=&quot;http://www.ssa.gov/policy/docs/quickfacts/prog_highlights/RatesLimits2014.html&quot;&gt;FICA rates&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Paid time off is based on &lt;a href=&quot;http://www.bls.gov/news.release/ebs.t05.htm&quot;&gt;BLS 1996 averages&lt;/a&gt;&amp;nbsp;and updated based on my personal experience.&lt;/li&gt;
&lt;li&gt;401(k), state unemployment insurance costs and health care insurance based on a &lt;a href=&quot;http://money.cnn.com/2013/02/28/smallbusiness/salary-benefits/&quot;&gt;2013 CNN Money article&lt;/a&gt; and updated based on my personal experience.&lt;/li&gt;
&lt;li&gt;No &lt;a href=&quot;http://thenextweb.com/insider/2012/04/09/12-startups-that-offer-their-employees-the-coolest-perks/&quot;&gt;superawesome&lt;/a&gt; &lt;a href=&quot;http://www.businessinsider.com/everyone-wants-to-work-at-tech-companies-2013-1&quot;&gt;mega&lt;/a&gt;&amp;nbsp;&lt;a href=&quot;http://bendyworks.com/growth-day-happenings-friday-the-13th/&quot;&gt;wowza&lt;/a&gt; &lt;a href=&quot;http://mashable.com/2011/10/17/google-facebook-twitter-linkedin-perks-infographic/&quot;&gt;perks&lt;/a&gt;&amp;nbsp;such as lunch, massages, car washes. Instead I&#39;ve only &amp;nbsp;included&amp;nbsp;the &quot;normal&quot; ones I&#39;ve come to expect including a conference budget and a new laptops every couple of years.&lt;/li&gt;
&lt;li&gt;No other indirect costs like rent.&lt;/li&gt;
&lt;li&gt;I factored in a utilization rate (85%) to reflect the fact that even for a billable person, not every hour of the day will go towards billing. Things that I have seen taking up time of delivery personnel include travel, context switch time, internal meetings, reporting hours and expenses and sales support. I expect that even in a product company you&#39;ll have time lost to these sorts of things, though the utilization rate may be higher.&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;With the model a $150,000 full time salaried employee will cost approximately $181,000 a year. With an expected 1530 utilized hours, that works out to $118/hr.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;If you&#39;re curious about your own fully loaded costs, make a copy of the model and tweak away.&lt;br /&gt;
&lt;br /&gt;
[Self promotion ahead!]&lt;br /&gt;
&lt;br /&gt;
I was a developer and architect. I wrote Java, JavaScript, Perl, Ruby, bash and even VB, C# and Python once in a while. Then I did agile coaching, people management, project and/or product management and as an executive helped grow a software services firm.&lt;br /&gt;
&lt;br /&gt;
Now I use the experience and my passion for lean and data driven decisions to help software delivery teams (companies or departments) run better.&amp;nbsp;If you&#39;d like help understanding your own costs, utilization rates&amp;nbsp;&lt;a href=&quot;http://www.217castle.com/services/&quot;&gt;or otherwise&lt;/a&gt;, &lt;a href=&quot;http://www.217castle.com/contact/&quot;&gt;drop me a line&lt;/a&gt;.&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://muness.blogspot.com/feeds/8575712502939183990/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5159103/8575712502939183990' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/8575712502939183990'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/8575712502939183990'/><link rel='alternate' type='text/html' href='http://muness.blogspot.com/2014/06/fully-loaded-what.html' title='Fully loaded what?'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/13080591937269765506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5159103.post-6420856257636301423</id><published>2014-06-12T22:43:00.002-05:00</published><updated>2014-08-26T10:36:24.424-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="spreadsheets"/><category scheme="http://www.blogger.com/atom/ns#" term="tax"/><category scheme="http://www.blogger.com/atom/ns#" term="tripit"/><title type='text'>It&#39;s your data, not TripIt&#39;s... an example of dragging information out of your buried data</title><content type='html'>&lt;em&gt;Update&lt;/em&gt;: I have another &lt;a href=&quot;http://muness.blogspot.com/2014/07/its-your-data-not-tripits-take-2.html&quot;&gt;blogpost&lt;/a&gt; with an example of how to pull your trip it data (using a Ruby library and OAuth) into an XML file.

&lt;hr/&gt;

My tax accountant recently asked me for a rundown of days I had traveled for work in 2013. I use TripIt pro to track my travel, this should be trivial. Hooray for SaaS!&lt;br /&gt;
&lt;br /&gt;
Alas, I log in, and look around... nah, no reports here. The closest thing is the &lt;a href=&quot;https://www.tripit.com/trip/listPast&quot;&gt;past trips list&lt;/a&gt;. Alas I can&#39;t filter by a date range or destination or purpose. (Aside: the &quot;Traveler&quot; filter is implemented client side and takes down the 10 listed to 0-10 entries. A minimal viable version of the feature. And now we can ignore it. Bah humbug, I say!) No matter, I can do this myself in a spreadsheet, I&#39;ll just export my trips out to a csv...&lt;br /&gt;
&lt;br /&gt;
Thwarted again! TripIt doesn&#39;t provide an export from what I can tell. A &lt;a href=&quot;http://bit.ly/1q9FrQn&quot;&gt;google search leads me in the right direction&lt;/a&gt;. The first link is to a discussion about moving data to other systems for analysis of one&#39;s own flight data. It refers to &lt;a href=&quot;http://openflights.org/&quot;&gt;OpenFlights.org&lt;/a&gt; being able to &lt;a href=&quot;http://openflights.org/blog/2012/03/16/import-flights-from-tripit/&quot;&gt;import TripIt&lt;/a&gt; flight information. A couple of clicks later and yet another sign up to yet another site and a dozen more click I had my TripIt data imported to Openflight.org. And another click later I had it exported to a &lt;a href=&quot;https://docs.google.com/spreadsheets/d/16f1fgjvuyW3vvCHQHe6PCfoTSHT0U6ToZ_-Z0H28W4E/edit#gid=0&quot;&gt;csv&lt;/a&gt;. Finally, my - flight - data is mine!&lt;br /&gt;
&lt;br /&gt;
As I am sure you&#39;ve guessed, I am not really done shaving yaks. Along the way the data got stripped down to flight information, not trip information. So I have to &lt;a href=&quot;https://docs.google.com/spreadsheets/d/16f1fgjvuyW3vvCHQHe6PCfoTSHT0U6ToZ_-Z0H28W4E/edit#gid=1888096476&quot;&gt;recreate that&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Finally I have the data my accountant needs. But I don&#39;t want to send him csv, but rather just the info he wants: which days did I not work in Ohio, &lt;a href=&quot;https://docs.google.com/spreadsheets/d/16f1fgjvuyW3vvCHQHe6PCfoTSHT0U6ToZ_-Z0H28W4E/edit#gid=2024031244&quot;&gt;a pivot table later, and I have what he wants&lt;/a&gt;. There&#39;s actually a bug in this calculation, but I&#39;ll leave grumbling about the issues I have with Google Spreadsheets vis a vis Excel for another post.&lt;br /&gt;
&lt;br /&gt;
Unlike my previous post, I didn&#39;t write any Python or Ruby to solve my problem though I &lt;a href=&quot;https://github.com/ianalexander/tripit-python&quot;&gt;was&lt;/a&gt; &lt;a href=&quot;https://github.com/flextrip/tripit&quot;&gt;tempted&lt;/a&gt; (and still am: when I do this next time I&#39;ll probably use the ruby library to generate the csv in hopes that I don&#39;t have to fill in trip purpose, fill in return automatically and get non-flight trip data in). Using spreadsheets in lieu of real programming, I&#39;ve been able to tackle a surprising number of problems over the last couple of years. Little ones like the one presented, but also more interesting problems like analyzing sales trends for a client and ball-parking the completion date for a 6+ month data center migration so we could schedule with vendors, business teams and engineering teams. Or tracking down why Akamai CDN usage had gone up over a couple of quarters and prioritizing which technical fixes to put in place first. Or calculating billable utilization rates monthly by team over a couple of years.&lt;br /&gt;
&lt;br /&gt;
If you want to hear more about one of those cases &lt;a href=&quot;http://twitter.com/muness&quot;&gt;let me know&lt;/a&gt;. For now, I encourage you to put your data in spreadsheets and play around: they&#39;re good for a lot more than laying out your data into a grid.&lt;br /&gt;
&lt;br /&gt;
[Self promotion ahead!]&lt;br /&gt;
&lt;br /&gt;
I used to be a developer and architect. I wrote Java, JavaScript, Perl, Ruby, bash and even VB, C# and Python once in a while. Then I did agile coaching, people management, project and/or product management and as an executive helped grow a pretty awesome software services firm.&lt;br /&gt;
&lt;br /&gt;
Now I use the experience and my passion for lean and data driven decisions to help software delivery teams (companies or departments) run better. See my &lt;a href=&quot;http://www.217castle.com/&quot;&gt;consulting site&lt;/a&gt; for more, and &lt;a href=&quot;http://www.217castle.com/contact/&quot;&gt;drop me a line&lt;/a&gt; if you&#39;d like to chat about a problem you want to tackle.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
</content><link rel='replies' type='application/atom+xml' href='http://muness.blogspot.com/feeds/6420856257636301423/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5159103/6420856257636301423' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/6420856257636301423'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/6420856257636301423'/><link rel='alternate' type='text/html' href='http://muness.blogspot.com/2014/06/its-your-data-not-tripits-example-of.html' title='It&#39;s your data, not TripIt&#39;s... an example of dragging information out of your buried data'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/13080591937269765506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5159103.post-673246798284190580</id><published>2013-06-13T19:03:00.001-05:00</published><updated>2013-06-14T08:54:08.662-05:00</updated><title type='text'>Life hack: GCal IM reminders</title><content type='html'>&lt;br /&gt;
I am in a lot of meetings. I am also on IM much if not all the time. Despite all the hoopla around Google Hangouts, Google Calendar doesn&#39;t support hangout/google talk reminders.&lt;br /&gt;
&lt;br /&gt;
The chrome desktop notifications aren&#39;t quite what I need either: they don&#39;t give me a link straight into the hangout I am supposed to be in. Instead, when I click the link in them, it opens up Google calendar in the default week view, I have to scan to right now, find the meeting, click it and then click on the &quot;Join hangout&quot; link therein.&lt;br /&gt;
&lt;br /&gt;
A couple of weeks ago I got fed up with this and decided to hack something together to make life easier. I added hangout link support to gcalcli (&lt;a href=&quot;https://github.com/muness/gcalcli&quot;&gt;see my fork&lt;/a&gt;), wrote &lt;a href=&quot;https://gist.github.com/muness/5778339#file-gcalcli_parse_tsv&quot;&gt;a script that parses the TSV output&lt;/a&gt; finding the links I care about in events coming up in the next 10 minutes, and then wired it up with &lt;a href=&quot;http://sendxmpp.hostname.sk/&quot;&gt;sendxmpp &lt;/a&gt;and cron &lt;a href=&quot;https://gist.github.com/muness/5778339#file-cal_im_reminder&quot;&gt;to send me an IM&lt;/a&gt; with the meeting title, start time and either a hangout link or a deep link to the event in google calendar.&lt;br /&gt;
&lt;br /&gt;
(While I was at it, I also put together a mini life hack: a &lt;a href=&quot;https://gist.github.com/muness/5778339#file-soon&quot;&gt;script&lt;/a&gt;&amp;nbsp;that shows me all the events I have coming up in the next 4 hours.)&lt;br /&gt;
&lt;br /&gt;
If you too want to get your calendar reminders via IM:&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;Install &lt;a href=&quot;https://github.com/muness/gcalcli&quot;&gt;my fork of gcalcli&lt;/a&gt;&amp;nbsp;(master branch)&lt;/li&gt;
&lt;li&gt;Authorize gcalcli to your account by launching it -- it&#39;ll walk you through the oauth2 stuff including launching your browser&lt;/li&gt;
&lt;li&gt;Install sendxmpp&lt;/li&gt;
&lt;li&gt;Make your local install of sendxmpp work with the SSL requirements from google by editing and &lt;a href=&quot;https://gist.github.com/muness/5778339#file-gistfile1-pl&quot;&gt;forcing RC4-MD5&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Download/tweak to suit your needs: &lt;a href=&quot;https://gist.github.com/muness/5778339#file-gcalcli_parse_tsv&quot;&gt;gcalcli_parse_tsv&lt;/a&gt;, &lt;a href=&quot;https://gist.github.com/muness/5778339#file-cal_im_reminder&quot;&gt;cal_im_reminder&lt;/a&gt;&amp;nbsp;and install &lt;a href=&quot;https://gist.github.com/muness/5778339#file-cal_im_reminder-L4&quot;&gt;a cron job&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;
If you enjoy this life hack or have a better way to quickly jump into that hangout, chime in with a comment!&lt;/div&gt;
</content><link rel='replies' type='application/atom+xml' href='http://muness.blogspot.com/feeds/673246798284190580/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5159103/673246798284190580' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/673246798284190580'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/673246798284190580'/><link rel='alternate' type='text/html' href='http://muness.blogspot.com/2013/06/life-hack-gcal-im-reminders.html' title='Life hack: GCal IM reminders'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/13080591937269765506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5159103.post-2319817875162591337</id><published>2009-02-20T13:07:00.005-05:00</published><updated>2009-02-26T12:29:29.301-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="rails plugin sql migrations"/><title type='text'>Generate SQL from your migrations</title><content type='html'>&lt;p&gt;
On one of the projects &lt;a href=&quot;http://thinkrelevance.com&quot;&gt;we&lt;/a&gt;&#39;re working on, we needed to occassionaly generate SQL from our migrations.  Déjà vu, I thought.  A few minutes of Googling later, I &lt;a href=&quot;http://blog.jayfields.com/2006/11/rails-generate-sql-from-migrations.html&quot;&gt;remembered why&lt;/a&gt;: Jay and I had been on a project a couple of years ago whence we had the same need.  Jay&#39;s code didn&#39;t quite work any more due to some ActiveRecord changes, and a search for an alternative implementation turned up nothing.
&lt;/p&gt;
&lt;p&gt;
I took his code and modified it to our needs.  A short time later, I had the code pulled out into a rails plugin, &lt;a href=&quot;http://github.com/muness/migration_sql_generator&quot;&gt;migration_sql_generator&lt;/a&gt;.  Install it (&lt;code&gt;script/plugin install git://github.com/muness/migration_sql_generator.git&lt;/code&gt;) and then run the rake task:
&lt;code&gt;
&lt;pre&gt;
rake db:generate:migration_sql
&lt;/pre&gt;
&lt;/code&gt;
&lt;/p&gt;
&lt;p&gt;Running this task generates two sql files per migration in &lt;code&gt;db/migration_sql&lt;/code&gt; in the form &lt;code&gt;20090216224354_add_users.sql&lt;/code&gt; and &lt;code&gt;20090216224354_add_users_down.sql&lt;/code&gt;.
&lt;/p&gt;
&lt;p&gt;
I&#39;ve used the plugin with success using the sqlserver adapter, less luck with the mysql adapter (change_column and rename_column blow up because the mysql adapter checks for the presence of a column first) and no luck with the sqlite adapter.  Haven&#39;t tried it with the postgres adapter.
&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://muness.blogspot.com/feeds/2319817875162591337/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5159103/2319817875162591337' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/2319817875162591337'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/2319817875162591337'/><link rel='alternate' type='text/html' href='http://muness.blogspot.com/2009/02/generate-sql-from-your-migrations.html' title='Generate SQL from your migrations'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/13080591937269765506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5159103.post-7663891767652584690</id><published>2009-02-20T12:05:00.011-05:00</published><updated>2011-05-23T08:48:36.833-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="ruby"/><category scheme="http://www.blogger.com/atom/ns#" term="tips"/><title type='text'>Easily switch between Ruby 1.8.6 and 1.9.1</title><content type='html'>&lt;p&gt;
&lt;strong&gt;NOTE: I don&#39;t keep Ruby Switcher updated anymore.  Use &lt;a href=&quot;https://rvm.beginrescueend.com/&quot;&gt;RVM&lt;/a&gt; instead.&lt;/strong&gt;
&lt;/p&gt;

&lt;p&gt;
&lt;a href=&quot;http://spicycode.com/&quot;&gt;Chad&lt;/a&gt; mentioned that he&#39;d gotten ruby 1.9.1 and 1.8.6 side by side on his workstation (his &lt;a href=&quot;http://github.com/spicycode/spicy-config/commit/aa264bb6e809af6f669a3678dc1f13c6579732a7&quot;&gt;code&lt;/a&gt; for this is in his awesome spicy-config repo).
&lt;/p&gt;
&lt;p&gt;
I took some time this morning to get a &lt;a href=&quot;http://github.com/relevance/etc/blob/c0f4ad613c208eb80a239e1c986ecc9e29aa1d9e/bash/ruby_switcher.sh&quot;&gt;similar setup&lt;/a&gt; working.  Now, on my prompt, I type, &lt;code&gt;use_ruby_186&lt;/code&gt; or &lt;code&gt;use_ruby_191&lt;/code&gt; to go back and forth between the Ruby 1.8.6 shipped with Leopard and a self-compiled install of ruby 1.9.1.
&lt;/p&gt;
&lt;p&gt;Steps for you to get there:
&lt;ol&gt;
&lt;li&gt;Compile and install ruby 1.9.1:
&lt;code&gt;
&lt;pre&gt;
mkdir -p ~/tmp
cd ~/tmp
curl -O ftp://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.1-p0.tar.gz
tar xzf ruby-1.9.1-p0.tar.gz
cd ruby-1.9.1-p0
./configure --prefix=$HOME/.ruby_versions/ruby_191
make
make install
&lt;/pre&gt;
&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Install the ruby switcher:
&lt;code&gt;
&lt;pre&gt;
curl -L http://github.com/relevance/etc/tree/master%2Fbash%2Fruby_switcher.sh?raw=true?raw=true &gt; ~/ruby_switcher.sh
echo &quot;source ~/ruby_switcher.sh&quot; &gt;&gt; ~/.bash_profile
&lt;/pre&gt;
&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;
Note that I use ~/.gem for my gems.  If you don&#39;t do that, you&#39;ll have to modify the switcher to specify your &lt;code&gt;GEM_HOME&lt;/code&gt;.  Type &lt;code&gt;gem env&lt;/code&gt; and look for GEM PATHS to figure out what you should set it to.
&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://muness.blogspot.com/feeds/7663891767652584690/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5159103/7663891767652584690' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/7663891767652584690'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/7663891767652584690'/><link rel='alternate' type='text/html' href='http://muness.blogspot.com/2009/02/easily-switch-between-ruby-186-and-191.html' title='Easily switch between Ruby 1.8.6 and 1.9.1'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/13080591937269765506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5159103.post-2506006018434173045</id><published>2009-02-03T01:10:00.002-05:00</published><updated>2009-02-03T01:28:32.518-05:00</updated><title type='text'>Maatkit installation: the concise guide</title><content type='html'>If you use MySQL, odds are you need &lt;a href=&quot;http://www.maatkit.org/doc/&quot;&gt;Maatkit&lt;/a&gt; whether you know it or not.  It&#39;s &lt;strong&gt;the&lt;/strong&gt; Swiss army knife: I mostly use it to parallelize backup and restore of huge databases on multi-core machines, but it&#39;s also handy for &lt;a href=&quot;http://www.maatkit.org/doc/mk-fifo-split.html&quot;&gt;fake splitting large files&lt;/a&gt;, &lt;a href=&quot;http://www.maatkit.org/doc/mk-find.html&quot;&gt;executing sql on multiple tables&lt;/a&gt; and a whole lot more.  (You also want &lt;a href=&quot;http://jeremy.zawodny.com/mysql/mytop/&quot;&gt;mytop&lt;/a&gt; if you&#39;re wondering what&#39;s up with MySQL connections.)
&lt;p&gt;
Here&#39;s the install script if you too want Maatkit at your fingertips (and you do.  trust me.):
&lt;pre&gt;
&lt;code&gt;
#!/bin/sh
# tested on Leopard with MySQL installed using the &lt;a href=&quot;http://dev.mysql.com/downloads/mysql/5.1.html#macosx-dmg&quot;&gt;package installer&lt;/a&gt;

sudo perl -MCPAN -e &#39;install DBI::Bundle&#39;
sudo perl -MCPAN -e &#39;install DBD::mysql&#39;
# you may need to force the install as follows:
# sudo perl -MCPAN -e &#39;force install DBD::mysql&#39;

mkdir -p ~/tmp
cd ~/tmp
curl -O http://maatkit.googlecode.com/files/maatkit-2725.tar.gz
tar xzvf maatkit-2725.tar.gz
cd maatkit-2725
perl Makefile.PL
sudo make install
&lt;/code&gt;
&lt;/pre&gt;
&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://muness.blogspot.com/feeds/2506006018434173045/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5159103/2506006018434173045' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/2506006018434173045'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/2506006018434173045'/><link rel='alternate' type='text/html' href='http://muness.blogspot.com/2009/02/maatkit-installation-concise-guide.html' title='Maatkit installation: the concise guide'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/13080591937269765506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5159103.post-5758646917993489524</id><published>2009-01-22T09:24:00.003-05:00</published><updated>2013-06-25T21:40:23.661-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="tips"/><category scheme="http://www.blogger.com/atom/ns#" term="vmware"/><title type='text'>No network after copying an Ubuntu VMWare image</title><content type='html'>After you copy an Ubuntu (or any Debian based) image, you&#39;ll probably lose your network connectivity.  After a little bit of &lt;a href=&quot;http://communities.vmware.com/message/521604#521604&quot;&gt;digging&lt;/a&gt;, it turns out that udev persists the MAC address of the network device in &lt;code&gt;/etc/udev/rules.d/*net.rules&lt;/code&gt; .  The fix: &lt;pre&gt;&lt;code&gt;sudo rm /etc/udev/rules.d/*net.rules&lt;/code&gt;
  &lt;code&gt;sudo shutdown -r now #to reboot&lt;/code&gt;
&lt;/pre&gt;&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://muness.blogspot.com/feeds/5758646917993489524/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5159103/5758646917993489524' title='20 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/5758646917993489524'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/5758646917993489524'/><link rel='alternate' type='text/html' href='http://muness.blogspot.com/2009/01/no-network-after-copying-ubuntu-vmware.html' title='No network after copying an Ubuntu VMWare image'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/13080591937269765506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>20</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5159103.post-7565609565025339031</id><published>2008-11-14T09:44:00.008-05:00</published><updated>2008-11-14T12:47:40.762-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="git"/><category scheme="http://www.blogger.com/atom/ns#" term="scm"/><category scheme="http://www.blogger.com/atom/ns#" term="tips"/><category scheme="http://www.blogger.com/atom/ns#" term="tools"/><title type='text'>Git: things I love and manipulating remote branches</title><content type='html'>&lt;p&gt;I&#39;ve been happily using &lt;a href=http://git.or.cz/&gt;Git&lt;/a&gt; for months now.  The primary benefit for me has been the ability to work offline without messing with &lt;a href=http://svk.bestpractical.com/view/HomePage&gt;SVK&lt;/a&gt;, not having to use the &lt;a href=http://subversion.tigris.org/&gt;svn&lt;/a&gt; commands for renames and moves, the ability to use multiple remote servers and better tools to look at branch level history (think gitk).&lt;/p&gt;

&lt;p&gt;Branching is much improved, besides.  In Subversion, to branch, you copy the current tree elsewhere and then you work in this branch and finally, merge your changes manually back into trunk (to wit, you have to keep track of what was already merged and not reapply those commits).  In git, though, that&#39;s all handled for you.  Another difference is best illustrated by example: you&#39;ve started a new feature.  Several file modifications later, you look at your diff and realize, &quot;I should have started a new branch for this.  doh.&quot;  With Subversion, there&#39;s not much you can do automatically to get these changes into a branch.  You&#39;d typically now:
&lt;ol&gt;
&lt;li&gt;generate a diff&lt;/li&gt;
&lt;li&gt;undo your changes&lt;/li&gt;
&lt;li&gt;create a branch&lt;/li&gt;
&lt;li&gt;switch to the branch&lt;/li&gt;
&lt;li&gt;apply the diff&lt;/li&gt;
&lt;li&gt;back to work&lt;/li&gt;
&lt;/ol&gt;
In git on the other hand, you type &lt;code&gt;git branch feature_22&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;
Sweet, huh?
&lt;/p&gt;
&lt;p&gt;Well, not altogether.  Pushing this local branch to a remote server is altogether too complicated:
&lt;pre&gt;
&lt;code&gt;
git push origin feature_22:refs/heads/feature_22
git fetch origin
git config branch.feature_22.remote origin
git config branch.feature_22.merge refs/heads/feature_22
git checkout feature_22
&lt;/code&gt;
&lt;/pre&gt;
&lt;/p&gt;
&lt;p&gt;Sure, you could have created the branch on the remote server first and saved yourself some hassle:
&lt;pre&gt;
&lt;code&gt;
git push origin master:refs/heads/feature_22
git fetch origin
git branch --track feature_22 origin/feature_22
git checkout feature_22
&lt;/code&gt;
&lt;/pre&gt;
Not exactly straight forward either.  Neither are other standard branch related activities.  Here&#39;s how you delete that remote branch:
&lt;pre&gt;
&lt;code&gt;
git push origin :refs/heads/feature_22 # yeah, that&#39;s how you delete a remote branch.  :(
git branch -d feature_22 # delete it locally too
&lt;/code&gt;
&lt;/pre&gt;
&lt;/p&gt;
&lt;p&gt;Fortunately, Git has a vibrant community that work to abstract away some of these complications.  One of my favorite Git tools is &lt;a href=http://github.com/webmat/git_remote_branch/tree/master&gt;git_remote_branch&lt;/a&gt;.  By using it, working with remote git branches becomes a breeze:
&lt;pre&gt;
&lt;code&gt;
grb publish feature_22 # publish a local branch
grb create feature_22 # create a remote branch and local branch to track it
grb delete feature_22 # delete the remote and local tracking branch
&lt;/code&gt;
&lt;/pre&gt;
&lt;/p&gt;
&lt;p&gt;
Ah, that&#39;s better!
&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://muness.blogspot.com/feeds/7565609565025339031/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5159103/7565609565025339031' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/7565609565025339031'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/7565609565025339031'/><link rel='alternate' type='text/html' href='http://muness.blogspot.com/2008/11/git-things-i-love-and-manipulating.html' title='Git: things I love and manipulating remote branches'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/13080591937269765506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5159103.post-4900468041711873007</id><published>2008-09-21T12:12:00.005-05:00</published><updated>2008-11-14T10:32:58.042-05:00</updated><title type='text'>Mingle, meet Git</title><content type='html'>&lt;p&gt;
Several weeks back, &lt;a href=&quot;http://www.adammonago.com/blog/blog.html&quot;&gt;Adam Monago&lt;/a&gt;, &lt;a href=&quot;http://studios.thoughtworks.com/mingle-project-intelligence&quot;&gt;Mingle&lt;/a&gt;&#39;s product manager was visiting our office in Chapel Hill.  One of the topics that came up was Git integration with Mingle. Alas, it sounded like it was a ways out.  But he explained that the SCM integration was pluggable and that we could write the code and drop it in place, echoing things I&#39;d heard from other ThoughtWorkers.
&lt;/p&gt;
&lt;p&gt;A couple of days later, &lt;a href=&quot;http://www.donmullen.net/&quot;&gt;Don&lt;/a&gt; and I were curious as to just what it&#39;d take to implement the integration.  Reverse engineering the interface we had to implement from the Subversion and Perforce plug-ins was anything but fun, but before the day was over, we had rudimentary integration working: we could see commit messages and the list of files modified per check-in.  Since then, Don implemented the rest of it including Mingle-based source code browsing.&lt;/p&gt;

&lt;p&gt;We&#39;ve been using the &lt;a href=&quot;http://github.com/donmullen/mingle_git/&quot;&gt;mingle_git&lt;/a&gt; plugin for a little while with no problems.  For performance reasons, we&#39;re not using the source code browsing from inside Mingle.  Instead, we use GitHub&#39;s source browsing: see the &lt;a href=&quot;http://github.com/donmullen/mingle_git/tree/master%2FREADME.rdoc?raw=true&quot;&gt;README&lt;/a&gt; for instructions on wiring things that way too.&lt;/p&gt;
&lt;p&gt;
Installation is documented in the README.  We&#39;ve only used this on Mac OS, FreeBSD and Linux.  Windows users, you may have luck by using &lt;a href=&quot;http://code.google.com/p/msysgit/&quot;&gt;msysgit&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;
&lt;strong&gt;Caveat emptor&lt;/strong&gt;: if you browse around the code, you&#39;ll quickly conclude that it is a &lt;strong&gt;spike&lt;/strong&gt;: we started with the subversion plugin and evolved it to this.  Since the SCM integration API is not documented, we can&#39;t even be positive that we implemented it correctly (though the evidence suggests that we have).  Also note that Mingle makes the assumption that check-in numbers are sequential, and that&#39;s an assumption that is not Git-friendly and it shows in the code.
&lt;/p&gt;
&lt;p&gt;
Enjoy, but keep in mind that this is unsupported, use-at-your-own-risk software.  For me, that&#39;s better than no Git integration.  ;)
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Mingle&#39;s APIs changed from 2.0 to 2.1 and we haven&#39;t had a chance to update this plugin yet.  If you installed this plugin, and are ready to upgrade to 2.1, you&#39;ll want to remove the plugin and then execute this SQL on your Mingle database:
&lt;code&gt;
delete from plugin_schema_info where plugin_name = &#39;mingle_git&#39;;
&lt;/code&gt;
&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://muness.blogspot.com/feeds/4900468041711873007/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5159103/4900468041711873007' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/4900468041711873007'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/4900468041711873007'/><link rel='alternate' type='text/html' href='http://muness.blogspot.com/2008/09/mingle-meet-git.html' title='Mingle, meet Git'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/13080591937269765506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5159103.post-3148383727785714020</id><published>2008-08-17T11:35:00.007-05:00</published><updated>2008-08-19T07:22:04.212-05:00</updated><title type='text'>Encrypt your client data in 53 minutes</title><content type='html'>&lt;p&gt;Thanks to &lt;a href=&quot;http://aaronbedra.com&quot;&gt;Aaron&lt;/a&gt;&#39;s continued efforts to make us ever more security conscious, I&#39;ve been encrypting client data on my laptop.  This came up in conversation during &lt;a href=&quot;http://erubycon.com&quot;&gt;e&lt;strong&gt;ruby&lt;/strong&gt;con&lt;/a&gt; (thanks &lt;a href=&quot;http://theedgecase.com&quot;&gt;EdgeCase&lt;/a&gt; for a fun conference with engaging &lt;a href=&quot;http://twitter.com/muncman/statuses/889085361&quot;&gt;evening events&lt;/a&gt;).  A couple of people asked me to get them started on encrypting their data too.

&lt;p&gt;Here are instructions.  They&#39;re written for Mac users, but they&#39;d be nearly identical for Linux and Windows users since TrueCrypt is available there too.
&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install &lt;a href=http://www.truecrypt.org/downloads.php&gt;TrueCrypt&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Create a container based encrypted file.  Pick FAT as the file system.  I use both keyfiles and a password.&lt;/li&gt;
&lt;li&gt;Erase the FAT partition and then format as HFS/ext2/[fs of choice] using Disk Utility/[partition manager of choice].  Name it &lt;code&gt;client_data&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Mount the newly created partition.&lt;/li&gt;
&lt;li&gt;Move your sensitive data to &lt;code&gt;/Volumes/client_data&lt;/code&gt;.  This is by far the slowest part of the process.  While you wait, watch &lt;a href=&quot;http://video.google.com/videoplay?docid=-2293483151556804649&quot;&gt;The Enemies of Reason&lt;/a&gt; (48 minutes -- everything else should take 5 minutes or less).&lt;/li&gt;
&lt;li&gt;Modify TrueCrypt preferences to suit your needs.  Some suggestions: leave encrypted volumes mounted when quitting TrueCrypt, set Auto-dismount volume after 30 minutes of inactivity in the partition.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As mentioned in &lt;a href=&quot;http://muness.blogspot.com/2008/06/lazy-bash-cd-aliaes.html&quot;&gt;my previous blog entry&lt;/a&gt;, I use a script to automatically create aliases to allow easy switching to project directories.  All I had to do was switch that entry to point to &lt;code&gt;/Volumes/client_data&lt;/code&gt; instead of the old &lt;code&gt;~/work/client_data&lt;/code&gt;.  Now, when I want to work on client projects or contracts, I launch TrueCrypt, mount the container and open a new terminal window.  All my old aliases work just like they used to.&lt;/p&gt;

&lt;p&gt;Mac users: if you don&#39;t care about the cross-platform compatibility that TrueCrypt offers, and trust Apple to encrypt your data, you  could use Disk Utility to &lt;a href=&quot;http://support.apple.com/kb/HT1578&quot;&gt;create encrypted partitions&lt;/a&gt; instead.  Some people insist it&#39;s more convenient (&lt;a href=&quot;http://jasonrudolph.com/blog&quot;&gt;You&lt;/a&gt; &lt;a href=&quot;http://tech.hickorywind.org/&quot;&gt;know&lt;/a&gt; who you are!).&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://muness.blogspot.com/feeds/3148383727785714020/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5159103/3148383727785714020' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/3148383727785714020'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/3148383727785714020'/><link rel='alternate' type='text/html' href='http://muness.blogspot.com/2008/08/encrypt-your-client-data-in-53-minutes.html' title='Encrypt your client data in 53 minutes'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/13080591937269765506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5159103.post-7821339018035122458</id><published>2008-06-29T13:04:00.011-05:00</published><updated>2009-09-02T13:12:44.946-05:00</updated><title type='text'>Lazy bash cd aliases</title><content type='html'>Those of you who&#39;ve found value in my last couple of bash specific posts may also like the &lt;a href=&quot;http://github.com/relevance/etc/tree/master/bash/project_aliases.sh&quot;&gt;latest addition&lt;/a&gt; to my &lt;code&gt;~/.bash_profile&lt;/code&gt;.
&lt;p&gt;
This one iterates through the one or more directories and creates aliases to the subdirectories so I don&#39;t have to.  Here&#39;s the scenario: I&#39;ve got a directory ~/work/ where I keep work projects, a ~/writings/ where I keep all the writing projects and so on.  I used to have aliases to each subdirectory.  e.g. &lt;code&gt;alias project1=&quot;cd /Users/muness/work/project1&quot;&lt;/code&gt;.  With shame, I admit that I maintained each of these manually.  No more!&lt;/p&gt;
&lt;p&gt;
Install instructions:
&lt;code&gt;
&lt;pre&gt;
curl -L http://github.com/relevance/etc/tree/master%2Fbash%2Fproject_aliases.sh?raw=true?raw=true &gt; ~/.project_aliases.sh
echo &quot;source ~/. project_aliases.sh&quot; &gt;&gt; ~/.bash_profile
&lt;/pre&gt;
&lt;/code&gt;
&lt;/p&gt;
&lt;p&gt;
Usage instructions:
&lt;ul&gt;
&lt;li&gt;Move your work projects to &lt;code&gt;~/work&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Above the &lt;code&gt;source ~/. project_aliases.sh&lt;/code&gt; add &lt;code&gt;PROJECT_PARENT_DIRS[0]=&quot;$HOME/work&quot;&lt;/code&gt;.
&lt;/ul&gt;
&lt;/p&gt;
&lt;p&gt;You may be interested in my blog post over at &lt;a href=&quot;http://pragmactic-osxer.blogspot.com/2008/06/pimp-my-shell.html&quot;&gt;PragMactic OS-Xer&lt;/a&gt; where I describe my motivation for these recent shell scripts.</content><link rel='replies' type='application/atom+xml' href='http://muness.blogspot.com/feeds/7821339018035122458/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5159103/7821339018035122458' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/7821339018035122458'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/7821339018035122458'/><link rel='alternate' type='text/html' href='http://muness.blogspot.com/2008/06/lazy-bash-cd-aliaes.html' title='Lazy bash cd aliases'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/13080591937269765506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5159103.post-5915576812994224892</id><published>2008-06-16T19:56:00.008-05:00</published><updated>2009-02-03T01:30:46.188-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="bash"/><category scheme="http://www.blogger.com/atom/ns#" term="interaction design"/><category scheme="http://www.blogger.com/atom/ns#" term="iTerm"/><title type='text'>bash: don&#39;t make me think</title><content type='html'>&lt;p&gt;
&lt;a href=&quot;http://muness.blogspot.com/2008/06/stop-presses-bash-said-to-embrace.html&quot;&gt;Last week&lt;/a&gt; I figured out a way to make my life a little bit easier  by abstracting the scm I was using and having the prompt indicate whether I was in a Subversion or Git.  Thanks to Mike Hommey whose &lt;A HREF=http://glandium.org/blog/?p=170&gt;script&lt;/A&gt; I tweaked for my needs.
&lt;/p&gt;
&lt;p&gt;
For a long time, I&#39;ve wanted my iTerm tab title to be more useful.  I started by having it display the last process I executed in that tab (this also shows the full path in iTerm&#39;s window title bar):
&lt;pre&gt;
&lt;code&gt;
PS1=&#39;\[\e]2;\h::\]${PWD/$HOME/~}\[\a\]\[\e]1;\]$(history 1 | sed -e &quot;s/^[ ]*[0-9]*[ ]*//g&quot;)\a\]\$ &#39;
&lt;/code&gt;
&lt;/pre&gt;
&lt;/p&gt;
&lt;p&gt;
Next up I wanted to show the currently running command.  Google showed &lt;a href=&quot;http://www.davidpashley.com/articles/xterm-titles-with-bash.html&quot;&gt;me how&lt;/a&gt;:
&lt;pre&gt;
&lt;code&gt;
trap &#39;echo -e &quot;\e]1;$BASH_COMMAND\007\c&quot;&#39; DEBUG
&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;Instead of trying to explain this code I&#39;ll quote the Bash man page:
&lt;blockquote&gt;
BASH_COMMAND&lt;br/&gt;
The  command  currently being executed or about to be executed, unless the shell is executing a command as the result of a trap, in which case it is the command executing at the time of the trap.
&lt;/blockquote&gt;
&lt;/p&gt;
&lt;p&gt;
Some more tweaks followed: 
&lt;ul&gt;
&lt;li&gt;Distinguish between a previously executed command and a currently executing command by decorating them (I chose surrounding the former with braces and the latter with &amp;gt;/&amp;lt;. e.g. &lt;code&gt;[ls] and &amp;gt;vi&amp;lt;&lt;/code&gt;).
&lt;li&gt;Show the context of the executing command in the tab. This is just the repository where I executed the command.  
&lt;li&gt;Make this coexist with TextMate.
&lt;/ul&gt;
&lt;/p&gt;
&lt;p&gt;
And a screenshot to summarize:
&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQRESfjyhyphenhyphenU1d87sH1vLmIclonlKtnE11uw24uvs2Ujup8Mdph8yQS2u83xflBqJs0t6Zp2SVG-NmY1A_olozKbYTPN7DJS61AjCgdyPGlCo-9nSoaOzkxOnZoBhh0ds-hVEZf-A/s1600-h/dont-make-me-think-iterm.jpg&quot;&gt;&lt;img style=&quot;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQRESfjyhyphenhyphenU1d87sH1vLmIclonlKtnE11uw24uvs2Ujup8Mdph8yQS2u83xflBqJs0t6Zp2SVG-NmY1A_olozKbYTPN7DJS61AjCgdyPGlCo-9nSoaOzkxOnZoBhh0ds-hVEZf-A/s800/dont-make-me-think-iterm.jpg&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5212652034953613778&quot; /&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;The code is in github&lt;a href=&quot;http://github.com/relevance/etc/tree/master/bash/bash_vcs.sh&quot;&gt;/relevance/etc/tree/master/bash/bash_vcs.sh&lt;/a&gt;.  
&lt;/p&gt;
&lt;p&gt;
Install instructions:
&lt;pre&gt;
&lt;code&gt;
curl -L http://github.com/relevance/etc/tree/master%2Fbash%2Fbash_vcs.sh?raw=true &gt; ~/.bash_dont_think.sh
echo &quot;source ~/.bash_dont_think.sh&quot; &gt;&gt; ~/.bash_profile
&lt;/code&gt;
&lt;/pre&gt;
&lt;/p&gt;
&lt;p&gt;Enjoy!  Tested with iTerm on Leopard.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://muness.blogspot.com/feeds/5915576812994224892/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5159103/5915576812994224892' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/5915576812994224892'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/5915576812994224892'/><link rel='alternate' type='text/html' href='http://muness.blogspot.com/2008/06/bash-dont-make-me-think.html' title='bash: don&#39;t make me think'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/13080591937269765506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQRESfjyhyphenhyphenU1d87sH1vLmIclonlKtnE11uw24uvs2Ujup8Mdph8yQS2u83xflBqJs0t6Zp2SVG-NmY1A_olozKbYTPN7DJS61AjCgdyPGlCo-9nSoaOzkxOnZoBhh0ds-hVEZf-A/s72-c/dont-make-me-think-iterm.jpg" height="72" width="72"/><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5159103.post-8830395601576300730</id><published>2008-06-10T19:41:00.009-05:00</published><updated>2008-12-09T01:17:42.208-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="bash"/><category scheme="http://www.blogger.com/atom/ns#" term="git"/><category scheme="http://www.blogger.com/atom/ns#" term="svn"/><category scheme="http://www.blogger.com/atom/ns#" term="tips"/><title type='text'>Stop the presses: bash said to embrace subversion and git</title><content type='html'>&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; I&#39;ve since updated and moved this script.  See &lt;a href=&quot;http://muness.blogspot.com/2008/06/bash-dont-make-me-think.html&quot;&gt;my new blog post&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;I had a chance to pair with &lt;a href=&quot;http://robsanheim.com&quot;&gt;Rob&lt;/a&gt; today.  On his console window, I noticed something I wanted: his bash prompt had an indication that he was in a  Git repository along with the branch he was on.  Later, after he&#39;d left, I found myself wondering how that worked and that I simply had to have it, and I wasn&#39;t willing to wait a whole day to figure out how he&#39;d done it.  Patience is &lt;em&gt;not&lt;/em&gt; a virtue.  (In that spirit, click &lt;a href=&quot;http://github.com/relevance/etc/tree/master%2Fbash%2Fbash_vcs.sh?raw=true&quot;&gt;here&lt;/a&gt; for the code if you&#39;re too impatient to read the rest of this blog entry which I spent countless hours writing.  Uphill both ways.  In the snow.)&lt;/p&gt;
&lt;p&gt;
A few minutes of googling turned up a blog entry with relevant &lt;a href=&quot;http://glandium.org/blog/?p=170&quot;&gt;bash fu&lt;/a&gt;.  The code there was for changing the prompt such that it indicated whether you were using svn, git, svk or Mercurial along with some repository metadata.  A couple of minutes later (it took a while to copy and paste the code since it was interweaved with comments), I found out it didn&#39;t work on Leopard.  &lt;code&gt;readlink&lt;/code&gt; turned out to be the culprit.  Instead of figuring out why it wasn&#39;t working, I replaced it with something simpler (specifically I used: &lt;code&gt;base_dir=`cd $base_dir; pwd`&lt;/code&gt;).  Voila, things worked: I now had a hella cool prompt that showed me if I was in a Subversion or a Git repository - I never used Mercurial, and left SVK for Git a couple of months ago.  Yay.
&lt;/p&gt;
&lt;p&gt;
Being who I am I had to do something more with this new ability to distinguish between Subversion and Git.  And I knew exactly which itch to scratch: 90% of the time I use the same scm commands.  I even had shell aliases for them so I wouldn&#39;t have to type them.  A few minutes later I&#39;d converted my previously Subversion specific aliases to generic ones that worked with Git too.  Screenshot says it all:
&lt;/p&gt;
&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCidHr0EwIgs-5YDKLSfWEsnv-GLtOcGP4XjNpj1mKRKL4CKQVgVCuwcnGjRLWmnkewE7CrMzXXh9LVj3hkDJcxNMfSroxa3t9ukkOHp_ru-gTyVIdvJR2Tz0NW_GX-JMEieVf8A/s1600-h/screen-capture.png&quot;&gt;&lt;img style=&quot;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCidHr0EwIgs-5YDKLSfWEsnv-GLtOcGP4XjNpj1mKRKL4CKQVgVCuwcnGjRLWmnkewE7CrMzXXh9LVj3hkDJcxNMfSroxa3t9ukkOHp_ru-gTyVIdvJR2Tz0NW_GX-JMEieVf8A/s800/screen-capture.png&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5210423379061142514&quot; /&gt;&lt;/a&gt;
&lt;p&gt;
If you want the same, download &lt;a href=&quot;http://github.com/relevance/etc/tree/master%2Fbash%2Fbash_vcs.sh?raw=true&quot;&gt;the script&lt;/a&gt; as &lt;code&gt;~/.bash_vcs&lt;/code&gt; and add &lt;code&gt;source ~/.bash_vcs&lt;/code&gt; at the end of your &lt;code&gt;~/.bash_profile&lt;/code&gt;.
&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://muness.blogspot.com/feeds/8830395601576300730/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5159103/8830395601576300730' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/8830395601576300730'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/8830395601576300730'/><link rel='alternate' type='text/html' href='http://muness.blogspot.com/2008/06/stop-presses-bash-said-to-embrace.html' title='Stop the presses: bash said to embrace subversion and git'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/13080591937269765506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCidHr0EwIgs-5YDKLSfWEsnv-GLtOcGP4XjNpj1mKRKL4CKQVgVCuwcnGjRLWmnkewE7CrMzXXh9LVj3hkDJcxNMfSroxa3t9ukkOHp_ru-gTyVIdvJR2Tz0NW_GX-JMEieVf8A/s72-c/screen-capture.png" height="72" width="72"/><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5159103.post-5242540846920615639</id><published>2008-04-25T10:23:00.012-05:00</published><updated>2008-12-09T01:17:43.486-05:00</updated><title type='text'>Buckets of mice</title><content type='html'>&lt;center&gt;
&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhAkO-TgNcehTC6VZQ-87WNSb__6U3B-upqiLye4q7VYHCLVXdxp0WzoVlslmsPhzKB1pnc790JhN3uDcpmRuvstQuXkafQIWkvgKDnZYhNWX8Hw8RfJYUgVFO1b5d2E7c7EVwnkg/s1600-h/20080418-P1000587-1.jpg&quot;&gt;&lt;img style=&quot;cursor:pointer; cursor:hand;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhAkO-TgNcehTC6VZQ-87WNSb__6U3B-upqiLye4q7VYHCLVXdxp0WzoVlslmsPhzKB1pnc790JhN3uDcpmRuvstQuXkafQIWkvgKDnZYhNWX8Hw8RfJYUgVFO1b5d2E7c7EVwnkg/s800/20080418-P1000587-1.jpg&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5193204587172036610&quot; /&gt;&lt;/a&gt;
&lt;/center&gt;
&lt;p&gt;
&lt;a href=&quot;http://thinkrelevance.com&quot;&gt;We&lt;/a&gt; have 24&quot; and 30&quot; pairing stations, an intern, &lt;a href=&quot;http://3cups.net/&quot;&gt;freshly roasted coffee&lt;/a&gt; delivered weekly, a fridge stocked with caffeine, more caffeine, beer, and &lt;a href=&quot;http://en.wikipedia.org/wiki/Dave&#39;s_Gourmet&quot;&gt;Dave&#39;s Insanity sauce&lt;/a&gt;.  We have cool &lt;a href=&quot;http://store.xkcd.com/&quot;&gt;t-shirts&lt;/a&gt; and all manner of radiators up on the walls, whiteboards and the &lt;a href=&quot;http://www.somethingnimble.com/bliki/radiator&quot;&gt;Beta Brite&lt;/a&gt;.  Music &lt;a href=&quot;http://www.alteclansing.com/index.php?file=north_product_detail&amp;iproduct_id=57&quot;&gt;is served&lt;/a&gt; by our CI/Music server.  
&lt;/p&gt;
&lt;center&gt;
&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqBp2CEzy6Ikqa5FgP8Q4vC67WfXN7q0dqZ4IWHs5l6lQ_3qOWmL7O7KsQfRPCqJw1wMes-PG7Huqwj2_lQFg4fUQq8xDbfXQ-uku-dMK_9Dycihv21HKwQcbVoA1yFhXRVUzT6g/s1600-h/20080418-P1000588.jpg&quot;&gt;&lt;img style=&quot;cursor:pointer; cursor:hand;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqBp2CEzy6Ikqa5FgP8Q4vC67WfXN7q0dqZ4IWHs5l6lQ_3qOWmL7O7KsQfRPCqJw1wMes-PG7Huqwj2_lQFg4fUQq8xDbfXQ-uku-dMK_9Dycihv21HKwQcbVoA1yFhXRVUzT6g/s200/20080418-P1000588.jpg&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5193223845805391906&quot; /&gt;&lt;/a&gt;
&lt;/center&gt;
&lt;p&gt;
The bookshelves are stocked with reference materials, the occasional bottle of scotch, and recommended readings.  On the list of recommended readings we&#39;ve got The Carpet Makers, The Company, The Omnivore&#39;s Dilemma, The Eyre Affair, Behind Closed Doors, Bloodsucking Fiends and The Deadline, Men In Hats, Volume I, Don&#39;t Make me Think, The Insane are Running the Asylum.
&lt;/p&gt;


&lt;p&gt;
Our favorite radiator is the Friday radiator.  &lt;a name=&quot;fridays&quot;&gt;Fridays&lt;/a&gt; are special: we don&#39;t do billable work.  Instead we open source, blog (self reference makes this blog entry extra cool), watch &lt;a href=&quot;http://opuszine.com/blog/entry/cinematic_titanic_vs_the_oozing_skull/&quot;&gt;educational movies&lt;/a&gt;, play Wii on the projector.   Here&#39;s today&#39;s:
&lt;/p&gt;
&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhiUoe1B0Gy1TU_ntNdMKwwIqJdsQ_P0k75xqE_AGV2x4ZMkz8uTKn7rgumAEvLrFvoNIJObNOAi9gWPiqMcEOlQIbm4iqNqi9Nq45iA-gkLnUUi1E44GNFMlQiZy_pLXGW0nB9Yg/s1600-h/20080425-P1000606.jpg&quot;&gt;&lt;img style=&quot;cursor:pointer; cursor:hand;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhiUoe1B0Gy1TU_ntNdMKwwIqJdsQ_P0k75xqE_AGV2x4ZMkz8uTKn7rgumAEvLrFvoNIJObNOAi9gWPiqMcEOlQIbm4iqNqi9Nq45iA-gkLnUUi1E44GNFMlQiZy_pLXGW0nB9Yg/s800/20080425-P1000606.jpg&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5193204857754976274&quot; /&gt;&lt;/a&gt;

&lt;p&gt;Oh, let&#39;s not forget the buckets of mice.&lt;/p&gt;
&lt;center&gt;
&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtmW76MpjCx5nGoC6L_Es95mwq1Vb6ewAnDYdLl-d9fydhchQG2SmS6kNCxf4iCQGFlvI9aDFUfxx35MwoakxbfXo3IjQPnxnv0FVnBdVGsr7PwLpHPjSjF-77bAkG-4nWNnki7Q/s1600-h/20080425-P1000603.jpg&quot;&gt;&lt;img style=&quot;cursor:pointer; cursor:hand;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtmW76MpjCx5nGoC6L_Es95mwq1Vb6ewAnDYdLl-d9fydhchQG2SmS6kNCxf4iCQGFlvI9aDFUfxx35MwoakxbfXo3IjQPnxnv0FVnBdVGsr7PwLpHPjSjF-77bAkG-4nWNnki7Q/s400/20080425-P1000603.jpg&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5193203990171582450&quot; /&gt;&lt;/a&gt;
&lt;/center&gt;
&lt;p&gt;
This blog entry inspired by &lt;a href=&quot;http://www.gapingvoid.com/Moveable_Type/archives/004493.html&quot;&gt;another excellent business card cartoon&lt;/a&gt; from Hugh Macleod.
&lt;/p&gt;

&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhkQBmhinZ5r6rowcZ8WO-OzWqswbHB6WdQHDLsqAlGAD-AoFMJBzm_WEASU1jZkrw5qEm0kQx-kIXbJR08EUPhNmVt1qzOzY0Im6g8mUrhcjCGMlhjJY0ax3wrNNtbZnQAJzYjzQ/s1600-h/20080418-P1000597.jpg&quot;&gt;&lt;img style=&quot;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhkQBmhinZ5r6rowcZ8WO-OzWqswbHB6WdQHDLsqAlGAD-AoFMJBzm_WEASU1jZkrw5qEm0kQx-kIXbJR08EUPhNmVt1qzOzY0Im6g8mUrhcjCGMlhjJY0ax3wrNNtbZnQAJzYjzQ/s800/20080418-P1000597.jpg&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5193233247488802914&quot; /&gt;&lt;/a&gt;
&lt;br/&gt;

&lt;center&gt;
&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIYcjt7SB4a9vAZsHf29FSQChV2wZazIONDmRyV5JkMxYmiVkQgcy3QkMMOhexkFnCeKAPrBmXrRcXjsE4K8B5ML61ewy487HvFCWqsrz9pOp0TU1NM4XzLZg8kdbwbWqkcRN_dQ/s1600-h/20080418-P1000577.jpg&quot;&gt;&lt;img style=&quot;cursor:pointer; cursor:hand;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIYcjt7SB4a9vAZsHf29FSQChV2wZazIONDmRyV5JkMxYmiVkQgcy3QkMMOhexkFnCeKAPrBmXrRcXjsE4K8B5ML61ewy487HvFCWqsrz9pOp0TU1NM4XzLZg8kdbwbWqkcRN_dQ/s200/20080418-P1000577.jpg&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5193224236647415874&quot; /&gt;&lt;/a&gt;
&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_psIc999ohSLp5ye8c-0RhNmIoVS7QLLJvgF6Czdc7rXSFiHeWEGY9zgkkjuoPDhaOqbAcVP_rnV9QtxB_dEftzKgSqZ66VdjqS9J0n4dYfnlPL1IXXboSgfrTqFgXUZaU23OgA/s1600-h/20080418-P1000575.jpg&quot;&gt;&lt;img style=&quot;cursor:pointer; cursor:hand;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_psIc999ohSLp5ye8c-0RhNmIoVS7QLLJvgF6Czdc7rXSFiHeWEGY9zgkkjuoPDhaOqbAcVP_rnV9QtxB_dEftzKgSqZ66VdjqS9J0n4dYfnlPL1IXXboSgfrTqFgXUZaU23OgA/s200/20080418-P1000575.jpg&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5193224082028593202&quot; /&gt;&lt;/a&gt;

&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSZJBQ4Dbzxe3npgxCN_9rZ524-AYDEay1BBhfThCNjegYeeOGmprshGBj8wrbx3adtJDIInrxUDjHQ0o8CJ5OEr8qAEZroiuROxMtBh7FrsHowfzKSJEWc9bJ-h4kaEliHshHmw/s1600-h/20080418-P1000573.jpg&quot;&gt;&lt;img style=&quot;cursor:pointer; cursor:hand;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSZJBQ4Dbzxe3npgxCN_9rZ524-AYDEay1BBhfThCNjegYeeOGmprshGBj8wrbx3adtJDIInrxUDjHQ0o8CJ5OEr8qAEZroiuROxMtBh7FrsHowfzKSJEWc9bJ-h4kaEliHshHmw/s200/20080418-P1000573.jpg&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5193225688346361938&quot; /&gt;&lt;/a&gt;

&lt;/center&gt;</content><link rel='replies' type='application/atom+xml' href='http://muness.blogspot.com/feeds/5242540846920615639/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5159103/5242540846920615639' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/5242540846920615639'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/5242540846920615639'/><link rel='alternate' type='text/html' href='http://muness.blogspot.com/2008/04/buckets-of-mice.html' title='Buckets of mice'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/13080591937269765506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhAkO-TgNcehTC6VZQ-87WNSb__6U3B-upqiLye4q7VYHCLVXdxp0WzoVlslmsPhzKB1pnc790JhN3uDcpmRuvstQuXkafQIWkvgKDnZYhNWX8Hw8RfJYUgVFO1b5d2E7c7EVwnkg/s72-c/20080418-P1000587-1.jpg" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5159103.post-6421023039744662815</id><published>2008-04-16T09:06:00.002-05:00</published><updated>2008-04-16T09:08:57.216-05:00</updated><title type='text'>cmd line history meme</title><content type='html'>&lt;p&gt;&lt;a href=http://robsanheim.com/2008/04/16/history-meme-onwards/&gt;Rob tagged me&lt;/a&gt;.  So:
&lt;code&gt;
&lt;pre&gt;
muness$ history 1000 | awk &#39;{a[$2]++}END{for(i in a){print a[i] &quot; &quot; i}}&#39; | sort -rn | head
102 git
72 ls
63 cd
40 ss
35 rake
24 m
17 up
14 st
12 sc
11 ri
&lt;/pre&gt;
&lt;/code&gt;
&lt;/p&gt;
&lt;p&gt;Tag, you&#39;re it.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://muness.blogspot.com/feeds/6421023039744662815/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5159103/6421023039744662815' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/6421023039744662815'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/6421023039744662815'/><link rel='alternate' type='text/html' href='http://muness.blogspot.com/2008/04/cmd-line-history-meme.html' title='cmd line history meme'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/13080591937269765506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5159103.post-5204672793446017781</id><published>2008-04-11T12:10:00.012-05:00</published><updated>2008-04-21T14:08:35.499-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="agile"/><category scheme="http://www.blogger.com/atom/ns#" term="example"/><category scheme="http://www.blogger.com/atom/ns#" term="retrospectives"/><title type='text'>Distributed Retrospectives</title><content type='html'>&lt;p&gt;
&lt;em&gt;&lt;a href=&quot;http://www.amazon.com/gp/redirect.html?ie=UTF8&amp;location=http%3A%2F%2Fwww.amazon.com%2FAgile-Retrospectives-Making-Teams-Great%2Fdp%2F0977616649%3Fie%3DUTF8%26s%3Dbooks%26qid%3D1208008472%26sr%3D8-1&amp;tag=mundaneessays-20&amp;linkCode=ur2&amp;camp=1789&amp;creative=9325&quot;&gt;Agile Retrospectives&lt;/a&gt;&lt;img src=&quot;http://www.assoc-amazon.com/e/ir?t=mundaneessays-20&amp;amp;l=ur2&amp;amp;o=1&quot; width=&quot;1&quot; height=&quot;1&quot; border=&quot;0&quot; alt=&quot;&quot; style=&quot;border:none !important; margin:0px !important;&quot; /&gt;&lt;/em&gt; (Esther Derby, Diana Larsen) and &lt;em&gt;&lt;a href=&quot;http://www.amazon.com/gp/redirect.html?ie=UTF8&amp;location=http%3A%2F%2Fwww.amazon.com%2FProject-Retrospectives-Handbook-Team-Reviews%2Fdp%2F0932633447%3Fie%3DUTF8%26s%3Dbooks%26qid%3D1208008482%26sr%3D8-1&amp;tag=mundaneessays-20&amp;linkCode=ur2&amp;camp=1789&amp;creative=9325&quot;&gt;Project Retrospectives&lt;/a&gt;&lt;img src=&quot;http://www.assoc-amazon.com/e/ir?t=mundaneessays-20&amp;amp;l=ur2&amp;amp;o=1&quot; width=&quot;1&quot; height=&quot;1&quot; border=&quot;0&quot; alt=&quot;&quot; style=&quot;border:none !important; margin:0px !important;&quot; /&gt;&lt;/em&gt; (Norman L. Kerth) have been a great resource in facilitating - or helping others facilitate - retrospectives.  I&#39;ll often leaf through them a day or two before a retrospective to remind myself of the framework (more on that in an forthcoming post), and to introduce new activities or create my own.
&lt;/p&gt;
&lt;p&gt;
Something that neither of those books tackle directly is how to facilitate a distributed retrospective.  Given that &lt;a href=&quot;http://thinkrelevance.com&quot;&gt;we&#39;ve&lt;/a&gt; been running a few of those lately, I wanted to share some tips:
&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;Your equipment matters&lt;/strong&gt;.  It took us a while to find a speaker phone that worked well for those in the office and those on the other end.  It&#39;s hard enough to communicate effectively without being in the same room, without muffled voices.  We use a Polycom unit with two extensions for a 20x16 room.&lt;/li&gt;
 &lt;li&gt;Prepare.  There&#39;s a lot of preparation that goes into running a retrospective in person.  You have to &lt;strong&gt;familiarize yourself with the project, look for a theme, send out invitations, pick activities (and backup activities in case one isn&#39;t working), and collect the tools/resources you need for those activities.&lt;/strong&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href=&quot;http://docs.google.com&quot;&gt;Google Docs&lt;/a&gt; is your friend.  With its near real time updates for viewers and collaborators, it&#39;s been an indispensable tool to take notes.  The facilitator or a scribe can update the document and others can see what&#39;s happening, on the fly.&lt;/li&gt;
 &lt;li&gt;It&#39;s not enough to use a Google Doc, you need to turn it into an online workspace that makes others feel like they&#39;re part of the process.  For example, I prepare a google document with activities scheduled for the retrospective.  I take care to:
 &lt;ul&gt;
  &lt;li&gt;Clearly state activity names.  When we move to the next one, it&#39;s enough to say its name and everyone can easily find the appropriate section.&lt;/li&gt;
  &lt;li&gt;Describe the activity.  &lt;strong&gt;Having a written description allows people who miss the spoken description to follow the intent as well as the process involved&lt;/strong&gt;.  Missing a description over a conference call can happen for all sorts of reasons, from people being more easily distracted, to phone problems.&lt;/li&gt;
  &lt;li&gt;State the targeted time for the activity.  People are even more restless on a conference call.  This gives them a sense of the overall meeting&#39;s timeline.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Copy or summarize information from previous retrospective documents&lt;/strong&gt; for ease of reference.&lt;/li&gt;
 &lt;/ul&gt;
        &lt;li&gt;Share with others.  Sharing the document with others as collaborators (rather than just viewers) gives them the confidence that they can correct mistakes.&lt;/li&gt;
        &lt;li&gt;Don&#39;t forget to &lt;strong&gt;send an email shortly before the meeting that serves as a reminder, repeats the phone number/conference line details and includes a link to the Google Doc&lt;/strong&gt; for that meeting.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;

&lt;p&gt;
I&#39;ve shared a &lt;a href=&quot;http://docs.google.com/Doc?id=dchgzgwd_20cnnwffgs&quot;&gt;Google Doc template&lt;/a&gt; for a 2 hour iteration retrospective for 4 to 8 people you can refer to or use as a starting point for your own living retrospective document.  Credit goes to Agile Retrospectives for the activities.
&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://muness.blogspot.com/feeds/5204672793446017781/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5159103/5204672793446017781' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/5204672793446017781'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/5204672793446017781'/><link rel='alternate' type='text/html' href='http://muness.blogspot.com/2008/04/distributed-retrospectives.html' title='Distributed Retrospectives'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/13080591937269765506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5159103.post-19428114268638310</id><published>2008-04-11T09:20:00.007-05:00</published><updated>2008-04-12T09:00:29.348-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="bdd"/><category scheme="http://www.blogger.com/atom/ns#" term="tdd"/><category scheme="http://www.blogger.com/atom/ns#" term="waste"/><title type='text'>Testing declarative code</title><content type='html'>&lt;p&gt;For a while now, I&#39;ve had my doubts about the usefulness of one aspect of test driven development (which applies equally well to behavior driven development): that it should be done 100% of the time.&lt;/p&gt;

&lt;p&gt;The first time I came across this, was at ThoughtWorks.  The question was whether we should TDD declarative delegations.  I remember a several hour long conversation about the merits of tests for Forwardable based delegations.
&lt;code&gt;
&lt;pre&gt;
class A
  extend Forwardable
  def initialize(b)
    @b = b
  end
  def_delegator :@b, :some_method  # Should I write a test before writing this declaration?!
end
&lt;/pre&gt;
&lt;/code&gt;
&lt;/p&gt;

&lt;p&gt;
A specification for this declaration would be:
&lt;code&gt;
&lt;pre&gt;
describe &quot;A&quot; do
  it &quot;delegates some_method to b&quot; do
    a = A.new(stub(:some_method =&gt; :x))
    a.some_method.should == :x
    # or you could use the &lt;a href=http://handoff.rubyforge.org/&gt;Handoff&lt;/a&gt; assertion:
    assert_handoff.from(A.new).to(:@b).for_method(:some_method)
  end
end
&lt;/pre&gt;
&lt;/code&gt;
&lt;/p&gt;

&lt;p&gt;I didn&#39;t get the fuss -- it was clear to me that testing such delegations was wasteful and a maintenance hassle.  But I didn&#39;t realize that this was part of a larger argument.&lt;/p&gt;

&lt;h1&gt;Not an isolated case&lt;/h1&gt;
&lt;p&gt;Since then, I&#39;ve learned that to some developers this is a general rule.  Here&#39;s another example, re testing stock ActiveRecord validation declarations, e.g.:
&lt;code&gt;
&lt;pre&gt;
class Model &lt; ActiveRecord::Base
  validates_presence_of :attr_1
end
&lt;/pre&gt;
&lt;/code&gt;
&lt;/p&gt;

&lt;p&gt;
The test for this would be:
&lt;code&gt;
&lt;pre&gt;
describe &quot;Model&quot; do
  it &quot;validates_presence_of :attr_1&quot; do
    model = Model.new
    model.valid?
    model.errors.on(:attr_1).should == &quot;must not be blank&quot;
  end
  # or you might write a helper that reflects on validates_presence_of declarations
  test_model_validates_presence_of Model, :attr_1
end
&lt;/pre&gt;
&lt;/code&gt;
&lt;/p&gt;

&lt;p&gt;It took me a while (too long) to get the general pattern: these discussions I kept finding myself in all had to do with testing declarations.&lt;/p&gt;

&lt;h1&gt;How did we get here?&lt;/h1&gt;

&lt;p&gt;Test driven development as best as I can tell is a - pragmatic - descendant of formal specifications:
&lt;blockquote&gt;
&quot;A formal specification is a mathematical description of software or hardware that may be used to develop an implementation. It describes what the system should do, not how the system should do it. Given such a specification, it is possible to use formal verification techniques to demonstrate that a candidate system design is correct with respect to the specification.&quot;
&lt;/blockquote&gt;
&lt;/p&gt;

&lt;p&gt;
&lt;strong&gt;Writing formal specifications for the majority of the software isn&#39;t practical.  Writing tests first provides us a simple, practical way to write executable specifications.&lt;/strong&gt;  True, they don&#39;t give us the ability to use &quot;formal verification techniques&quot; to prove anything about our code, but they do give us confidence that our code does what our tests say the code does (as much confidence as we have in our tests).
&lt;/p&gt;

&lt;h1&gt;The rule&lt;/h1&gt;

&lt;p&gt;The rule regarding TDD is: If you can TDD it and it&#39;s destined to be production code, you should TDD it.&lt;/p&gt;

&lt;p&gt;
The problem I find with this rule is that it causes us to write tests like those above that duplicates the declarative code.  At best case it goes like this:
&lt;code&gt;
&lt;pre&gt;
  # We write this code first in the spec:
  test_model_validates_presence_of Model, :attr_1 
  # we run it and watch it fail...
  # and then we write this in the model class:
  validates_presence_of :attr_1 
  # rerun the test and watch it pass...
&lt;/pre&gt;
&lt;/code&gt;
&lt;/p&gt;

&lt;p&gt;
I find this wasteful:  We first implement the &lt;code&gt;test_declaration_is_called&lt;/code&gt; (in this case, &lt;code&gt;test_model_validates_presence_of&lt;/code&gt;) for every declaration.  After all that work (and not to mention code that we have to maintain) we end up with a test that tells us what the declaration itself does.  But with less readability and intent.  Indeed, &lt;strong&gt;declarative code is in fact formal specification&lt;/strong&gt;.
&lt;/p&gt;

&lt;h1&gt;Guidelines not rules&lt;/h1&gt;
&lt;p&gt;Writing tests first is a great guideline.  I do it &lt;strong&gt;almost&lt;/strong&gt; all the time.  But I don&#39;t do it all the time.  It makes me &lt;em&gt;angry&lt;/em&gt; when I change declarative code only to have a test that looks exactly the same fail as well.&lt;/p&gt;

&lt;p&gt;OK, tests for declarations don&#39;t really make me angry.  But they do make me wonder if the developer who wrote the test really likes to answer the question, &quot;are you sure that you&#39;re sure that you want to do what you just said you want to do?&quot;&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://muness.blogspot.com/feeds/19428114268638310/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5159103/19428114268638310' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/19428114268638310'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/19428114268638310'/><link rel='alternate' type='text/html' href='http://muness.blogspot.com/2008/04/testing-declarative-code.html' title='Testing declarative code'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/13080591937269765506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5159103.post-7750504015905358521</id><published>2008-03-14T10:59:00.005-05:00</published><updated>2008-03-14T12:37:03.839-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="build"/><category scheme="http://www.blogger.com/atom/ns#" term="campfire"/><category scheme="http://www.blogger.com/atom/ns#" term="communication"/><category scheme="http://www.blogger.com/atom/ns#" term="ruby"/><category scheme="http://www.blogger.com/atom/ns#" term="tools"/><title type='text'>Where there&#39;s smoke signals...</title><content type='html'>In a &lt;a href=http://muness.blogspot.com/2008/02/ccrb-campfire-notifier-released.html&gt;previous post&lt;/a&gt;, I briefly described how we were using cc_campfire_notifier to get build success/failure notifications in campfire.  cc_campfire_notifier has lost momentum over the last couple of months.  Since we actively rely on the notifier for several projects, we&#39;ve now forked it as &lt;A HREF=http://opensource.thinkrelevance.com/wiki/smoke_signals&gt;smoke_signals&lt;/a&gt;.
&lt;p&gt;The first release offers the following improvements:
&lt;ul&gt;
&lt;li&gt;Each notification includes a link back to the CruiseControl.rb build (so you have one-click access to the full details for each build).
&lt;li&gt;Recognizes apr_error errors as SVN errors (so you&#39;ll spend less time scratching your head over generic build failure messages).
&lt;li&gt;Speaks once into the campfire room whenever an unexpected error occurs (as opposed to speaking once per line in the backtrace).
&lt;/ul&gt; 
&lt;p&gt;Check out the notifier&#39;s &lt;a href=http://opensource.thinkrelevance.com/wiki/smoke_signals&gt;home page&lt;/a&gt; for installation instructions or head on over to &lt;a href=http://github.com/relevance/smoke_signals/tree&gt;GitHub to browse the code&lt;/a&gt;.</content><link rel='replies' type='application/atom+xml' href='http://muness.blogspot.com/feeds/7750504015905358521/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5159103/7750504015905358521' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/7750504015905358521'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/7750504015905358521'/><link rel='alternate' type='text/html' href='http://muness.blogspot.com/2008/03/where-theres-smoke-signals.html' title='Where there&#39;s smoke signals...'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/13080591937269765506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5159103.post-3266222172370567112</id><published>2008-03-06T08:46:00.003-05:00</published><updated>2008-03-06T09:44:29.245-05:00</updated><title type='text'>Release early, release often, business edition</title><content type='html'>One of the things we preach as software developers is &lt;a href=http://catb.org/~esr/writings/cathedral-bazaar/cathedral-bazaar/ar01s04.html&gt;release early, release often&lt;/a&gt;.  I think ESR is right in that our primary motivation for this is often related to quality:
&lt;blockquote&gt;
In the bazaar view, on the other hand, you assume that bugs are generally shallow phenomena—or, at least, that they turn shallow pretty quickly when exposed to a thousand eager co-developers pounding on every single new release. &lt;strong&gt;Accordingly you release often in order to get more corrections&lt;/strong&gt;, and as a beneficial side effect you have less to lose if an occasional botch gets out the door.
&lt;/blockquote&gt;
&lt;p&gt;
As I&#39;ve done more project consulting and management, I&#39;ve realized that the benefits extend well beyond the technical ones.  But I&#39;ve had a difficult time articulating the business benefits to incremental, frequent releases.  

&lt;p&gt;Last week, Ross posted an article that describes one of the primary business benefits to frequent, early releases, &lt;a href=http://agilemanager.blogspot.com/2008/02/minimising-speculative-risk-of-it.html&gt;Minimising the Speculative Risk of IT Investments&lt;/a&gt;:
&lt;blockquote&gt;
With each incremental delivery, and every increase in tangible value, the intangible or speculative value decreases. &lt;strong&gt;The reduction in speculative value at risk represents a reduction in the total value that can be depressed through delays in delivery&lt;/strong&gt;. Thus, early delivery reduces the risk of speculative value not being realised. Simultaneously, it reduces the volatility of returns.
&lt;/blockquote&gt;
&lt;p&gt;What other benefits do you find to the business from releasing early, releasing often?</content><link rel='replies' type='application/atom+xml' href='http://muness.blogspot.com/feeds/3266222172370567112/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5159103/3266222172370567112' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/3266222172370567112'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/3266222172370567112'/><link rel='alternate' type='text/html' href='http://muness.blogspot.com/2008/03/release-early-release-often-business.html' title='Release early, release often, business edition'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/13080591937269765506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5159103.post-4193223504556281643</id><published>2008-02-07T18:19:00.001-05:00</published><updated>2008-04-11T15:03:40.175-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="communication"/><category scheme="http://www.blogger.com/atom/ns#" term="ruby"/><category scheme="http://www.blogger.com/atom/ns#" term="tools"/><title type='text'>CC.rb Campfire Notifier Released</title><content type='html'>&lt;strong&gt;Note&lt;/strong&gt;: I&#39;ve forked this notifier to add features and fix some bugs at in &lt;a href=http://muness.blogspot.com/2008/03/where-theres-smoke-signals.html&gt;Smoke Signals&lt;/a&gt;.

At Relevance, we make heavy use of Campfire for our distributed projects.  One room per project, and we expect everyone to &quot;be in&quot; the relevant room associated with the project they&#39;re working on at the time.  We use it to communicate: to ask for a pair (we&#39;ve tried Leopard&#39;s Remote Desktop Sharing, but have no settled mostly on emacs + screen), ask questions, request code reviews (we&#39;ve taken to using the topic for posting ad hoc requests like that), and make announcements.

&lt;p&gt;
This virtual space has many disadvantages over being in the same room as I&#39;d been used to on my previous couple of projects, but it did have a nice perk: SVN commit notifications were right there alongside our own discussions.  The notices were more useful than I expected, a quick, clear indicator of our pace on a given day.

&lt;p&gt;
Naturally, I wanted our other major automated task to notify us of 
&lt;A HREF=http://ccmenu.sourceforge.net/&gt;CCMenu&lt;/A&gt; is OK, but it&#39;s disconnected from our primary virtual space (Campfire, as discussed above).  Being &lt;a href=http://undefined.com/ia/2006/10/24/the-fourteen-types-of-programmers-type-4-lazy-ones/&gt;lazy&lt;/a&gt;, I didn&#39;t want to build it myself, so I first looked around for tools that suited my needs.  Much to my chagrin, none did, so I took the one that was closest and added to it what I needed: &lt;a href=http://campfire-ccrb.rubyforge.org/svn/test/campfire_notifier_test.rb&gt;specs&lt;/a&gt;.  Then came the stuff that was missing: ssl support and support for one room per project.  And last but not least, to help deal with configuration hassles and debugging, I added logging.

&lt;p&gt;
If you&#39;re using CruiseControl.rb and Campfire, give it a whirl.  Instructions are in the &lt;a href=http://campfire-ccrb.rubyforge.org/svn/CampfireNotifier.README&gt;README&lt;/A&gt;.</content><link rel='replies' type='application/atom+xml' href='http://muness.blogspot.com/feeds/4193223504556281643/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5159103/4193223504556281643' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/4193223504556281643'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/4193223504556281643'/><link rel='alternate' type='text/html' href='http://muness.blogspot.com/2008/02/ccrb-campfire-notifier-released.html' title='CC.rb Campfire Notifier Released'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/13080591937269765506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5159103.post-3824245386675763112</id><published>2008-02-05T16:53:00.000-05:00</published><updated>2008-12-09T01:17:43.551-05:00</updated><title type='text'>Pi</title><content type='html'>&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;http://www.wired.com/science/discoveries/news/2008/02/dayintech_0205&quot;&gt;&lt;img style=&quot;cursor:pointer; cursor:hand;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKzHtM0IeDWuKMHlLvVCNIpiID8AI9-Si901PyaQSIBKrfPatkfDtUjew1RK-JWp0STewIk3tbyCJahTNVoP6dfR05QnLVQwfLsaoedwWgeQ0nBcIDb25ojFQ5iYEV1RpjurXdzA/s640/TextMateScreenSnapz002.jpg&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5163627177850672770&quot; /&gt;&lt;/a&gt;</content><link rel='replies' type='application/atom+xml' href='http://muness.blogspot.com/feeds/3824245386675763112/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5159103/3824245386675763112' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/3824245386675763112'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/3824245386675763112'/><link rel='alternate' type='text/html' href='http://muness.blogspot.com/2008/02/pi.html' title='Pi'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/13080591937269765506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKzHtM0IeDWuKMHlLvVCNIpiID8AI9-Si901PyaQSIBKrfPatkfDtUjew1RK-JWp0STewIk3tbyCJahTNVoP6dfR05QnLVQwfLsaoedwWgeQ0nBcIDb25ojFQ5iYEV1RpjurXdzA/s72-c/TextMateScreenSnapz002.jpg" height="72" width="72"/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5159103.post-2158564186679554778</id><published>2008-01-20T18:46:00.001-05:00</published><updated>2008-04-14T10:34:39.128-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="communication"/><category scheme="http://www.blogger.com/atom/ns#" term="jerryweinberg"/><category scheme="http://www.blogger.com/atom/ns#" term="project management"/><category scheme="http://www.blogger.com/atom/ns#" term="rules"/><category scheme="http://www.blogger.com/atom/ns#" term="satir"/><category scheme="http://www.blogger.com/atom/ns#" term="team"/><title type='text'>Managers and rules</title><content type='html'>&lt;A HREF=http://www.geraldmweinberg.com/BIOStuff/EachBIO/bio.Jerry.html&gt;Jerry Weinberg&lt;/A&gt;, if you don&#39;t know him, is the author of more than a dozen books on software development and memorable laws such as the &lt;A HREF=http://en.wikipedia.org/wiki/Weinberg%27s_Law_of_Twins&gt;Law of Twins&lt;/A&gt;.  In Quality Software Management: Systems Thinking, published in &#39;92, he described what we might today call an agile team.  (He lists several levels of software team cultures, ranging from Oblivious to Congruent.  Along that spectrum, an agile team would fall somewhere along Steering, Anticipating and Congruent.)

&lt;p&gt;
At &lt;a href=http://www.ayeconference.com/&gt;AYE&lt;/A&gt; 2007, I had the pleasure of meeting Jerry in person.  Something that I was not aware of was his remarkable skill at helping others.  Instead of answering a request for advice directly, he&#39;d delve into the details, applying something like the &lt;A HREF=http://en.wikipedia.org/wiki/5_Whys&gt;5 Whys&lt;/A&gt;.  Within minutes, he would hone in on a rule that the person had imposed on themselves that was at the root of the problem.  After expressing the problematic rule, he&#39;d help them relax the rule into guidelines.  Several people who&#39;ve spent more time with him have echoed my gut feel that they too found this ability to help others rare and invaluable.

&lt;p&gt;
His approach to helping individuals is based on &lt;A HREF=http://en.wikipedia.org/wiki/Virginia_Satir&gt;Virginia Satir&lt;/A&gt;&#39;s work in family therapy on survival rule transformation.  An excerpt from The New People Making:
&lt;blockquote&gt;
After you have written down all the rules your family thinks exist and cleared up any misunderstanding about them, go on to the next phase.  Try to discover which of your rules are still up to date and which are not.  As fast as the world changes, it is easy to have out-of-date rules.  Are you driving a modern car with Model T rules?  Many families are doing just this.  If you find that you are, can you bring your rules up to date and throw away the old ones?  One characteristic of a nurturing family is the ability to keep its rules up to date.

&lt;p&gt;
Now ask yourself if your rules are helping or obstructing.  What do you want them to accomplish?  Good rules facilitate instead of limit.
&lt;/blockquote&gt;

&lt;p&gt;
Last weekend, while attending a Satir workshop I was struck by the applicability of Satir&#39;s ideas on rules to teams at work.  There too, rules can be unstated, ambiguous or inapplicable sometimes hampering our ability to get things done, other times making everyone miserable.

&lt;p&gt;
(For a valuable discussion on rules suitable for software development teams, check out the section on Rules in chapter 21 of Software Teamwork:Taking Ownership for Success.  To summarize, the rules should be focused on ensuring that a team has &quot;the capabilities and resources to do the right things, rather than on stepwise procedures to be dogmatically followed&quot;.) 

&lt;p&gt;
In big companies the problems are magnified as fully connected networks of people become prohibitively expensive.  For example, it&#39;d take each person over a day out of every week for 25 people to stay in meaningful weekly one-on-one contact.  (Indeed, the few who take the time to build an extensive network can become &lt;a href=http://blog.splitbody.com/2007/6/27/social-networks-in-large-companies&gt;stars&lt;/a&gt; capable of getting things done faster than their peers could.)  Additionally, in a big organization, it&#39;s likely that a rule that works for one group will be applied to ones where it doesn&#39;t fit.

&lt;p&gt;
The role of a manager is to engender an enabling environment.  But how?  Virginia Satir&#39;s approach to rule transformation provides one way.  One which fits in well with the management advice in many of the management books I am familiar with, including Behind Closed Doors, Peopleware and Slack.

&lt;p&gt;
Here&#39;s how I describe the process:
&lt;ol&gt;
&lt;li&gt;Identify implicit rules and restate the explicit ones.
&lt;li&gt;Explore how they came about.
&lt;li&gt;Express the rules explicitly.
&lt;li&gt;Clear up ambiguities in the team&#39;s understanding of each rule.
&lt;li&gt;Evaluate each rule&#39;s applicability.
&lt;/ol&gt;

&lt;p&gt;
Having done this, you might find that a rule is:
&lt;ul&gt;
&lt;li&gt;Helpful and reasonable.  So, apply it!
&lt;li&gt;Helpful but impossible or untenable.  Then transform it into one or more guidelines that aren&#39;t prohibitive.
&lt;li&gt;Limiting or obstructing your team.  If so, the rule should be challenged, publicly.
&lt;/ul&gt;


&lt;p&gt;
Again, quoting The New People Making:
&lt;blockquote&gt;
What do you think about your rules?  Are they overt, human [i.e. reasonable] and up to date?  Or are they covert, inhuman [i.e. prohibitive, impossible] and out of date?  If your rules are mostly of the second variety, I think you realize that you and your family [or team] have some important and necessary work to do.  If your rules are of the first category, you are probably all having a ball.
&lt;/blockquote&gt;</content><link rel='replies' type='application/atom+xml' href='http://muness.blogspot.com/feeds/2158564186679554778/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5159103/2158564186679554778' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/2158564186679554778'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5159103/posts/default/2158564186679554778'/><link rel='alternate' type='text/html' href='http://muness.blogspot.com/2008/01/managers-and-rules.html' title='Managers and rules'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/13080591937269765506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry></feed>