<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Robby on Rails</title>
    <description>Robby Russell writes about Ruby on Rails, software maintainability, and the craft of building long-lived applications. Creator of Oh My Zsh.</description>
    <link>https://robbyonrails.com/</link>
    <atom:link href="https://robbyonrails.com/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Mon, 27 Apr 2026 20:03:19 +0000</pubDate>
    <lastBuildDate>Mon, 27 Apr 2026 20:03:19 +0000</lastBuildDate>
    <generator>Jekyll v4.3.3</generator>
    
      <item>
        <title>On ShopTalk Show: Rails in 2026, Dependencies, and the Long Tail of Decisions</title>
        <description>&lt;p&gt;I recently joined &lt;a href=&quot;https://shoptalkshow.com/&quot;&gt;ShopTalk Show&lt;/a&gt;… &lt;a href=&quot;https://shoptalkshow.com/711/&quot;&gt;episode 711&lt;/a&gt; to be exact. Which… now has me thinking I should probably go get myself a Slurpee.&lt;/p&gt;

&lt;div class=&quot;responsive-video-container&quot;&gt;
  &lt;iframe src=&quot;https://www.youtube.com/embed/VZtq-zLkOQM&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;p&gt;A few things we got into…&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://ohmyz.sh/&quot;&gt;Oh My Zsh&lt;/a&gt;&lt;/strong&gt; started as shared aliases for teammates… and turned into a plugin ecosystem that keeps growing.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://rubyonrails.org/&quot;&gt;Ruby on Rails&lt;/a&gt;&lt;/strong&gt; in 2026… scaling isn’t the question anymore. Teams are wrestling with restraint and keeping systems understandable.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;LLMs and contribution&lt;/strong&gt;… more people can open meaningful PRs now. Conventions and structure matter more than ever.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Dependencies&lt;/strong&gt;… every one feels small when added. Over time… they stack up and stick around longer than expected.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you haven’t listened to ShopTalk Show… go &lt;a href=&quot;https://shoptalkshow.com/&quot;&gt;subscribe&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Mon, 27 Apr 2026 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2026/04/27/on-shoptalk-show-rails-in-2026-dependencies-and-the-long-tail-of-decisions/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2026/04/27/on-shoptalk-show-rails-in-2026-dependencies-and-the-long-tail-of-decisions/</guid>
        
        <category>rubyonrails</category>
        
        <category>dependencies</category>
        
        <category>podcasts</category>
        
        <category>ohmyzsh</category>
        
        
      </item>
    
      <item>
        <title>Less Opinions. More Data. The 2026 Rails Survey</title>
        <description>&lt;p&gt;Every two years, the team at Planet Argon runs the Rails Developer Survey. And every two years, I go through the same process… a long list of questions, a hard look at how much time we’re asking of respondents, and a lot of cutting.&lt;/p&gt;

&lt;p&gt;If you’ve filled it out before, thank you. If this is new to you, here’s the short version: it’s an open survey for Rails developers at all experience levels and across all kinds of organizations. We compile the results and share everything publicly. &lt;strong&gt;No paywalls. No vendor spin.&lt;/strong&gt; Just a snapshot of what the community is actually doing.&lt;/p&gt;

&lt;p&gt;We first ran this in &lt;a href=&quot;https://railsdeveloper.com/survey/2009/index.html?utm_source=robbyonrails&amp;amp;utm_medium=blog&amp;amp;utm_campaign=2026-rails-survey&amp;amp;utm_content=2009-reference&quot;&gt;2009&lt;/a&gt;… back when &lt;em&gt;”should I use Rails?”&lt;/em&gt; was still a serious question… and have been doing it every two years since &lt;a href=&quot;https://railsdeveloper.com/survey/2012/index.html?utm_source=robbyonrails&amp;amp;utm_medium=blog&amp;amp;utm_campaign=2026-rails-survey&amp;amp;utm_content=2012-reference&quot;&gt;2012&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It’s become something we rely on heavily at Planet Argon. In client conversations. In blog posts. On podcasts. In talks. When someone asks, &lt;em&gt;“what are other teams doing with X?”&lt;/em&gt;… we’d rather point to data than speculate.&lt;/p&gt;

&lt;p&gt;And then there’s the less practical reason. We’ve been in this community a long time. &lt;em&gt;Someone should be asking these questions.&lt;/em&gt; We’re fine being the ones who do.&lt;/p&gt;

&lt;h2 id=&quot;whats-in-the-2026-survey&quot;&gt;What’s in the 2026 survey&lt;/h2&gt;

&lt;p&gt;I’ll be upfront… I have a heavy hand in what makes it in. It’s a team effort with plenty of discussion and refinement, but the final call usually comes down to me.&lt;/p&gt;

&lt;p&gt;The filter isn’t just curiosity. It’s closer to: &lt;strong&gt;what reflects real decisions teams are making right now?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Those aren’t always the same thing.&lt;/p&gt;

&lt;p&gt;This year, a couple of areas got expanded.&lt;/p&gt;

&lt;p&gt;One is project types.&lt;/p&gt;

&lt;p&gt;I want a clearer picture of what Rails teams have actually shipped… and &lt;strong&gt;what they’re actively changing right now.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not just &lt;em&gt;“we build SaaS”&lt;/em&gt; or &lt;em&gt;“we have an API”…&lt;/em&gt; but the actual decisions showing up in roadmaps and backlogs.&lt;/p&gt;

&lt;p&gt;Things like:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Upgrading Rails or Ruby versions&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Moving off Heroku or changing hosting providers&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Consolidating services… or breaking things apart&lt;/strong&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Reworking CI pipelines or adopting self-hosted runners&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Switching testing approaches between RSpec and Minitest&lt;/strong&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Rethinking background jobs or authentication systems&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Overhauling the frontend layer&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Introducing tools like Kamal, Packwerk, or Rails Engines&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Experimenting with AI agents in development workflows&lt;/strong&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s the level of detail I care about.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“What is Rails being used for?”&lt;/em&gt; is still interesting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What teams are actively changing&lt;/strong&gt; is where things get useful.&lt;/p&gt;

&lt;p&gt;The other is AI.&lt;/p&gt;

&lt;p&gt;Leaving it out would’ve been an obvious miss.&lt;/p&gt;

&lt;p&gt;We added questions based on input from folks in the community to get a clearer picture of what’s actually happening… how teams are (or aren’t) using AI day-to-day, where it’s showing up in real workflows, and how that compares to what we all see on LinkedIn and social feeds.&lt;/p&gt;

&lt;p&gt;I care more about &lt;strong&gt;what’s making it into production&lt;/strong&gt; than what people say they’re excited about.&lt;/p&gt;

&lt;h2 id=&quot;a-few-things-ive-left-out&quot;&gt;A few things I’ve left out&lt;/h2&gt;

&lt;p&gt;Some people asked if we’d include questions about specific organizations or happenings in the Ruby and Rails ecosystem.&lt;/p&gt;

&lt;p&gt;We won’t.&lt;/p&gt;

&lt;p&gt;There are plenty of places for those conversations. This survey isn’t a proxy for grievances or a feedback channel aimed at anyone.&lt;/p&gt;

&lt;p&gt;If something is mostly discourse, I’d rather wait and see if it turns into actual decisions teams have to make.&lt;/p&gt;

&lt;p&gt;We do include a &lt;em&gt;“what questions do you wish we had asked?”&lt;/em&gt; prompt at the end. People use it… sparingly… which I take as a decent signal we’re not wildly off.&lt;/p&gt;

&lt;h2 id=&quot;how-to-participate&quot;&gt;How to participate&lt;/h2&gt;

&lt;p&gt;The survey takes about 10–15 minutes. It’s open to anyone working with Rails… solo developers, agencies, large teams… all experience levels.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://railsdeveloper.com/survey/?utm_source=robbyonrails&amp;amp;utm_medium=blog&amp;amp;utm_campaign=2026-rails-survey&amp;amp;utm_content=cta&quot;&gt;Take the 2026 Rails Developer Survey&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We’ll publish results shortly after it closes… and before Rails World 2026. As always, everything will be freely available.&lt;/p&gt;

&lt;p&gt;If you want to share it with your team or community… do it. More responses means better data.&lt;/p&gt;

&lt;p&gt;Thanks for being part of it.&lt;/p&gt;
</description>
        <pubDate>Mon, 27 Apr 2026 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2026/04/27/less-opinions-more-data-the-2026-rails-survey/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2026/04/27/less-opinions-more-data-the-2026-rails-survey/</guid>
        
        <category>ai</category>
        
        <category>survey</category>
        
        <category>rails</category>
        
        
      </item>
    
      <item>
        <title>AI Increased Your Output. It Also Increased Your Variance.</title>
        <description>&lt;p&gt;You gave a handful of your devs access to Claude Code Max… and things moved fast. Output went up. Then things started to drift. Not just formatting or naming. Structure. Approach. Assumptions. One developer builds guardrails around it. Another uses it like autocomplete. A third treats it like a thinking partner. Same tool, same codebase… different results. The code starts to reflect how each person uses the tool, not how the team builds software together.&lt;/p&gt;

&lt;p&gt;There’s a tension here that most teams are sidestepping. You can feel the inconsistency creeping in, but saying &lt;em&gt;“we should align on how we use this”&lt;/em&gt; sounds heavy-handed. So it gets framed as experimentation. Let people figure it out. Give it space. But this isn’t a side project. This is production work. Treating AI like open-ended R&amp;amp;D means everyone is running their own experiment… and nobody is learning together.&lt;/p&gt;

&lt;p&gt;Inside the team, the gap gets wider.&lt;/p&gt;

&lt;p&gt;A few engineers find a rhythm with these tools and start moving faster.&lt;/p&gt;

&lt;p&gt;Others don’t hit the same stride.&lt;/p&gt;

&lt;p&gt;That difference starts getting interpreted as a performance gap… even though the underlying workflows are all over the place. From the outside, leadership sees a few big wins and starts asking uncomfortable questions about why the rest of the team isn’t there yet.&lt;/p&gt;

&lt;p&gt;A recent &lt;em&gt;On Rails&lt;/em&gt; guest, &lt;a href=&quot;https://podcast.rubyonrails.org/2462975/episodes/19060786-brian-scanlan-building-ai-first-at-intercom&quot;&gt;Brian Scanlan&lt;/a&gt;, shared a metaphor that stuck with me. Commercial pilots often land planes on autopilot. They’re still paying close attention. Monitoring, inspecting, managing the system. They’re tested without it. They take control in unfamiliar scenarios. Sometimes they turn it off on purpose just to stay sharp. There’s pride in the craft. That’s the shift for engineers right now. The tool can handle more of the execution, but someone still owns the outcome… and knows when to step in, or step away from it entirely.&lt;/p&gt;

&lt;p&gt;There’s another tension coming into focus.&lt;/p&gt;

&lt;p&gt;Some engineers see this as survival.&lt;/p&gt;

&lt;p&gt;A way to level up quickly and stay relevant.&lt;/p&gt;

&lt;p&gt;That’s not irrational. But these tools pull a lot of the work into the CLI tooling away from your coworkers and into a more private loop. As a result, the learning also fragments. You start hearing more &lt;em&gt;“look what I figured out”&lt;/em&gt; and less &lt;em&gt;“look what &lt;strong&gt;we&lt;/strong&gt; figured out.”&lt;/em&gt; I’m not immune to this either. It’s easy to optimize your own workflow and forget that the team needs a shared one.&lt;/p&gt;

&lt;p&gt;Most teams don’t have a dedicated group thinking about how this should work. So the problem doesn’t wait its turn. It shows up in code reviews, in Slack threads where people share what worked or vent about what didn’t, in 1:1s, and eventually in performance reviews. It shows up when leadership is staring at a list of names and trying to make sense of who is rising to the moment and who seems to be falling behind (regardless of whether that is actually the case).&lt;/p&gt;

&lt;p&gt;Engineering leaders already have the authority to shape how work gets done. That hasn’t changed. How your team uses AI is now part of your system. If you don’t define it, it still exists but in a fragmented and difficult to improve manner. None of this works if people are each operating in their own setup, with no shared baseline to compare notes against.&lt;/p&gt;

&lt;p&gt;Treat your shared AI conventions like a style guide… &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLAUDE.md&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AGENTS.md&lt;/code&gt; or whatever it’s called by the time you’re reading this… prompt rules, shared skills – versioned, reviewed, owned by the team.&lt;/p&gt;

&lt;p&gt;Otherwise, you’re scaling your inconsistencies.&lt;/p&gt;
</description>
        <pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2026/04/23/ai-increased-your-output-it-also-increased-your-variance/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2026/04/23/ai-increased-your-output-it-also-increased-your-variance/</guid>
        
        <category>ai</category>
        
        <category>engineering</category>
        
        <category>leadership</category>
        
        
      </item>
    
      <item>
        <title>Your codebase doesn&apos;t care how it got written</title>
        <description>&lt;p&gt;Every design team I’ve worked with has had the same complaint about clients.&lt;/p&gt;

&lt;p&gt;“They sent over a PowerPoint with mockups.” Eye rolls. Crossed arms. The unspoken accusation: &lt;em&gt;you’re stepping on my domain.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I got it. The client would shortcut the process. Fall in love with their own wireframes. Hand the designer a solution instead of a problem.&lt;/p&gt;

&lt;p&gt;That friction was real.&lt;/p&gt;

&lt;p&gt;But here’s what nobody said out loud… the designers were doing the same thing to the developers.&lt;/p&gt;

&lt;p&gt;A designer would disappear for a few weeks. Come back with approved comps. Hand them off to a dev team with zero context on the constraints they’d just inherited. The developers would look at the designs and realize that one “simple” interaction was going to blow the budget.&lt;/p&gt;

&lt;p&gt;But the client already signed off. The battle wasn’t worth the effort.&lt;/p&gt;

&lt;p&gt;So the devs would swallow it. Build it. Ask to be looped in earlier next time.&lt;/p&gt;

&lt;p&gt;Next time, they’d ask again.&lt;/p&gt;

&lt;h2 id=&quot;nobody-was-tracking-any-of-this&quot;&gt;Nobody was tracking any of this&lt;/h2&gt;

&lt;p&gt;Here’s what we glossed over in all of that role-boundary drama… every person in every one of those conversations costs money.&lt;/p&gt;

&lt;p&gt;Which developers sit in ideation meetings? Which designers join the earliest planning calls? How much does all that collaboration cost before anyone builds anything?&lt;/p&gt;

&lt;p&gt;Debate has a dollar amount. So does prototyping. So does the politics of navigating who gets a voice and when.&lt;/p&gt;

&lt;p&gt;That overhead was always part of the model. We just built it into the timeline and called it process.&lt;/p&gt;

&lt;h2 id=&quot;the-client-just-got-a-promotion&quot;&gt;The client just got a promotion&lt;/h2&gt;

&lt;p&gt;Now look at what happened.&lt;/p&gt;

&lt;p&gt;That client who used to send over clumsy PowerPoint mockups? They can prototype at a fidelity that would have required a designer and a developer just a few years ago. They can ideate. Get feedback. Iterate. Evolve their thinking… before they ever pick up the phone to call someone like me.&lt;/p&gt;

&lt;p&gt;All of those “my role is very critical” layers? Easy to gloss over when someone can test an idea faster than it used to take to schedule the kickoff meeting.&lt;/p&gt;

&lt;p&gt;I’m not going to pretend that doesn’t threaten my business model. My entire consultancy is built around being involved in the strategy and implementation of those types of client ideas.&lt;/p&gt;

&lt;p&gt;Despite that… I don’t blame people for wanting to see what they can accomplish on their own.&lt;/p&gt;

&lt;h2 id=&quot;ive-seen-this-movie-before&quot;&gt;I’ve seen this movie before&lt;/h2&gt;

&lt;p&gt;In the late ’90s and early 2000s, some entrepreneurial founder would build an elaborate system in Microsoft Access or FileMaker Pro.&lt;/p&gt;

&lt;p&gt;And it worked. It actually worked.&lt;/p&gt;

&lt;p&gt;They ran meaningful parts of their business on something they stitched together with enough technical curiosity and sheer stubbornness to pull it off.&lt;/p&gt;

&lt;p&gt;Here’s the thing people in our industry like to gloss over… a lot of those systems are &lt;em&gt;still running&lt;/em&gt;. In production. Right now. Doing the thing they were built to do.&lt;/p&gt;

&lt;p&gt;Sometimes because the system genuinely still meets the need. Sometimes because the person who built it never quite saw the value in investing in something nicer for the team of people required to interface with it every day.&lt;/p&gt;

&lt;p&gt;We literally just replaced an Access-based system this past year with a Rails app. Not because Access stopped working. Because someone finally retired and nobody else wanted to own the damn thing they’d built several decades ago. I’ve &lt;a href=&quot;https://robbyonrails.com/articles/2025/07/16/stop-pretending-youre-the-last-developer/&quot;&gt;written about that pattern before&lt;/a&gt;. It never stops showing up.&lt;/p&gt;

&lt;p&gt;Eventually, some of those systems hit walls. They got complicated. Organizations needed real UX. Architecture. Scaling. Integration. Tech support. Operational resilience.&lt;/p&gt;

&lt;p&gt;So they brought in people like us.&lt;/p&gt;

&lt;p&gt;Those DIY systems birthed a whole industry. Our industry. Much of what we’ve built our careers on exists because someone who was &lt;em&gt;dangerously just-enough&lt;/em&gt; motivated built something with the tools available to them.&lt;/p&gt;

&lt;p&gt;I don’t know if we need all of those same steps now. The ceiling is a lot higher than FileMaker ever was.&lt;/p&gt;

&lt;p&gt;But the pattern feels familiar.&lt;/p&gt;

&lt;p&gt;I want to be careful here… because I hear people raising real concerns about AI and I don’t think those concerns are silly.&lt;/p&gt;

&lt;p&gt;Quality. Accountability. Labor displacement. Environmental cost. The question of whether we’re building on a foundation that exploits creative work without consent.&lt;/p&gt;

&lt;p&gt;These are legitimate things to wrestle with.&lt;/p&gt;

&lt;p&gt;I’ve had to wrestle with versions of that question my entire career. And I mean my &lt;em&gt;entire&lt;/em&gt; career.&lt;/p&gt;

&lt;p&gt;When I was an employee, someone else was making those calls. I didn’t control the tools. I didn’t choose the clients. I just had to decide whether the work was worth the compromise.&lt;/p&gt;

&lt;p&gt;There was a whole period where I was being asked to work with Micro&lt;strong&gt;$&lt;/strong&gt;oft tooling and all I wanted was open source. Microsoft was the evil monopoly of the era. That wasn’t a casual opinion. It was a real position, held by a lot of people I respected.&lt;/p&gt;

&lt;p&gt;I eventually quit that job specifically to go work with open source tooling. I genuinely believed it was better for small businesses. And in a weird way, I still think I was right about that.&lt;/p&gt;

&lt;p&gt;Open source was built mostly by volunteers and contributors to a movement.&lt;/p&gt;

&lt;p&gt;But now we have tooling that can leverage all of that work… and it feels like it’s at odds with what that movement stood for. There’s a part of me that wonders if I’ve ended up on the wrong side of this one. Whether I’m advocating for the dark side and just can’t see it from where I’m standing.&lt;/p&gt;

&lt;p&gt;But then I think about open source a little more honestly. Were we not heavily borrowing on the ideas of commercial products the whole time? “Open source clone of Microsoft Project” was a real search term. So was “open source version of Oracle.” Legitimate things people were actively building toward. Open source didn’t emerge from a vacuum. It emerged partly in opposition to commercial software, sure. But also in imitation of it.&lt;/p&gt;

&lt;p&gt;The ethical lines were blurry then too. I just wasn’t paying close enough attention to notice. I’m not smart enough to have that philosophical debate now. I definitely wasn’t back then.&lt;/p&gt;

&lt;p&gt;I’ve worked with clients whose labor practices abroad I used to actively protest. Built software for companies that produce products I would never put in my body. Worked with organizations that preach things I don’t believe in and don’t support.&lt;/p&gt;

&lt;p&gt;Because there was an exchange of services rendered. Because I had a team to feed.&lt;/p&gt;

&lt;p&gt;Because the alternative was… what, exactly? Holding out for clients whose values perfectly aligned with mine on every axis?&lt;/p&gt;

&lt;p&gt;That’s a nice story to tell yourself at twenty. It’s also bullshit if you’re trying to make payroll.&lt;/p&gt;

&lt;p&gt;Twenty-year-old Robby would have called me a sellout.&lt;/p&gt;

&lt;p&gt;Forty-six-year-old Robby is a little more open to the idea that you can do meaningful work within systems you didn’t design and don’t fully endorse. That the money I earn lets me invest in things I actually care about. That the good I can do with the work produced matters alongside the work itself.&lt;/p&gt;

&lt;p&gt;This isn’t unique to software. Every carpenter, every accountant, every nurse has had some version of this negotiation with modern capitalism. It’s a matter of what good you can do with the resources you earn.&lt;/p&gt;

&lt;p&gt;Not a clean equation. Never has been.&lt;/p&gt;

&lt;h2 id=&quot;full-disclosure&quot;&gt;Full disclosure&lt;/h2&gt;

&lt;p&gt;I should be honest about something. Six months ago, I was closer to the skeptics than I’d like to admit.&lt;/p&gt;

&lt;p&gt;In January, I wrote about &lt;a href=&quot;https://robbyonrails.com/articles/2026/01/20/humans-in-the-loop/&quot;&gt;humans in the loop&lt;/a&gt; after the Oh My Zsh team spent real time figuring out how to handle AI-assisted contributions. We weren’t anti-AI. But we were grappling with the cost of reviewing work that &lt;em&gt;looked&lt;/em&gt; plausible without being deeply understood.&lt;/p&gt;

&lt;p&gt;That’s a legitimate concern. It still is.&lt;/p&gt;

&lt;p&gt;A few weeks later, I wrote something more personal: &lt;a href=&quot;https://robbyonrails.com/articles/2026/02/12/i-didnt-want-ai-to-be-good-at-this/&quot;&gt;I Didn’t Want AI to Be Good at This&lt;/a&gt;. The title says it all. I was coming around begrudgingly. I didn’t want the calculus to change.&lt;/p&gt;

&lt;p&gt;But it changed anyway.&lt;/p&gt;

&lt;p&gt;I’m still adjusting. I don’t have this figured out. I’m just further along than I was in January.&lt;/p&gt;

&lt;h2 id=&quot;the-job-description-changed&quot;&gt;The job description changed&lt;/h2&gt;

&lt;p&gt;I’m hearing firsthand from peers who run small software teams. They’ve got engineers who aren’t just reluctant about AI. They’re not cautiously skeptical. They are entirely, firmly against it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“Am I being forced to adopt this, or do I need to go somewhere else?”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That’s not a hypothetical. That’s a real conversation happening between developers and their managers at real companies. It’s being discussed in leadership meetings by people trying to figure out how to catch up with adoption. Some of those companies are also struggling to keep the business afloat right now.&lt;/p&gt;

&lt;p&gt;And honestly? A lot of us &lt;em&gt;were&lt;/em&gt; hired to write code. That’s not revisionist. That was the job description. We were brought in to produce code the same way a writer is brought in to produce words.&lt;/p&gt;

&lt;p&gt;And let’s fact-check ourselves for just a moment here… because I can already picture people reading this and thinking they disagree.&lt;/p&gt;

&lt;p&gt;We asked candidates for coding samples. We put them through live coding exercises. We talked about coding standards. Style guides. Naming conventions. We’ve all wished for… and sometimes had… the chance to reject someone’s code because it didn’t meet our conventions. Didn’t match our writing style.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Writing&lt;/em&gt; style.&lt;/p&gt;

&lt;p&gt;Let’s not kid ourselves that we were entirely hiring for “outcomes” all along. We built the whole hiring apparatus around the artifact. Whether that was framed right or not… that’s what we did.&lt;/p&gt;

&lt;p&gt;The codebase doesn’t care how it got written. It cares whether it works, whether it can be maintained, and whether it helps the business do what it needs to do.&lt;/p&gt;

&lt;p&gt;That’s not a bait and switch. That’s what happens when your organization gets access to new tools and the economics shift underneath everyone’s feet.&lt;/p&gt;

&lt;p&gt;What we have now is a training problem. A reclassification problem. And I’m not sure what the best HR-friendly way to frame this is… but here’s a serious question:&lt;/p&gt;

&lt;p&gt;Would you rather your company post a new job ad for what they actually need going forward and let you compete with people from the outside? Or would you rather have the insider track to try out the evolving role before you decide to move on to some idealized version of what you were originally hired to do?&lt;/p&gt;

&lt;p&gt;That’s not rhetorical. I think teams need to commit to a course of action here. If there are new roles to be defined… define them. Post the ad. Phase out what’s no longer needed as originally written.&lt;/p&gt;

&lt;p&gt;Because “overseeing agents on my laptop produce code wasn’t on my job description” is going to be an exhausting fucking conversation for every human involved.&lt;/p&gt;

&lt;p&gt;Most of us at smaller companies don’t have much of a voice in the big industry narrative. We’re not fielding hundreds of inbound inquiries and cherry-picking dream clients. We service the clients we can attract.&lt;/p&gt;

&lt;p&gt;If you think now is the right time to turn away work because you disagree with whether AI-generated code is ethical… I understand the impulse. I do.&lt;/p&gt;

&lt;p&gt;But I also think you might be asking the wrong people the wrong question.&lt;/p&gt;

&lt;p&gt;What you might need to be asking is whether you need to find a different industry entirely.&lt;/p&gt;

&lt;p&gt;Or… maybe you brand yourself as an artisanal coder. Find the organizations actively resisting LLM adoption. Carve out that niche.&lt;/p&gt;

&lt;p&gt;I think that path can work for an individual. I’m skeptical it can sustain a company for long.&lt;/p&gt;

&lt;p&gt;I don’t know what my own success looks like in five years. I don’t think anyone does.&lt;/p&gt;

&lt;p&gt;It’s kind of scary. Nobody is going to tell you otherwise with a straight face.&lt;/p&gt;

&lt;p&gt;But I know that the people who thrived after the FileMaker era weren’t the ones who resented the founders for building those systems. They were the ones who understood &lt;em&gt;why&lt;/em&gt; the founders built them… respected what they accomplished… and then made the case for what comes next.&lt;/p&gt;

</description>
        <pubDate>Tue, 14 Apr 2026 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2026/04/14/your-codebase-doesnt-care-how-it-got-written/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2026/04/14/your-codebase-doesnt-care-how-it-got-written/</guid>
        
        <category>ai</category>
        
        <category>consulting</category>
        
        <category>careers</category>
        
        <category>opensource</category>
        
        
      </item>
    
      <item>
        <title>Using Claude Code with Ruby on Rails: Field Notes</title>
        <description>&lt;p&gt;Over the past few months, a few of us at Planet Argon have been experimenting with &lt;a href=&quot;https://docs.anthropic.com/en/docs/claude-code/overview&quot;&gt;Claude Code&lt;/a&gt; on real client Ruby on Rails projects. Not demos or greenfield apps, but the kind of long-lived codebases where a backtrace can eat forty-five minutes before you even understand the call chain.&lt;/p&gt;

&lt;p&gt;The part that surprised us is how often some of those debugging sessions now wrap up closer to ten minutes.&lt;/p&gt;

&lt;p&gt;We’re not ready to make big claims. But we’ve developed enough of a feel for when to reach for it, which model to use, and how to keep it from making a mess of your codebase that it felt worth writing down.&lt;/p&gt;

&lt;p&gt;The guide covers things like model selection (Sonnet vs Opus and when to switch mid-session), RSpec and Minitest workflows, debugging with error monitoring payloads, and how we’re structuring &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLAUDE.md&lt;/code&gt; without turning it into a dumping ground. It’s written for developers who are skeptical of the hype but curious enough to run an experiment.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href=&quot;https://robbyonrails.com/claude-code-curious-rails-developers/&quot;&gt;Claude Code for the Semi-Reluctant, Somewhat Curious Ruby on Rails Developer&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The engineers on your team who’ve already started experimenting with tools like this probably have a small head start. The gap closes quickly once you see where they actually help.&lt;/p&gt;
</description>
        <pubDate>Thu, 12 Mar 2026 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2026/03/12/field-notes-claude-code-in-a-rails-codebase/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2026/03/12/field-notes-claude-code-in-a-rails-codebase/</guid>
        
        <category>claude-code</category>
        
        <category>rubyonrails</category>
        
        <category>ai</category>
        
        <category>llm</category>
        
        <category>claude</category>
        
        
      </item>
    
      <item>
        <title>From AppSignal Alert to Fix to Deploy — Without Leaving the Terminal</title>
        <description>&lt;p&gt;I’ve been running a few experiments to streamline our development workflow, and I keep talking with folks on teams who haven’t been granted the budget or access to this kind of tooling yet. So I’m writing some of these up to help demystify what the experience is actually like.&lt;/p&gt;

&lt;p&gt;For this one.. I wanted to see if I could take an AppSignal error and go from &lt;strong&gt;alert → fix → deploy&lt;/strong&gt; without leaving the terminal.&lt;/p&gt;

&lt;p&gt;Using Claude Code and AppSignal’s MCP integration, the whole process took under five minutes.&lt;/p&gt;

&lt;p&gt;For context, our Rails app is already configured with &lt;a href=&quot;https://www.appsignal.com/&quot;&gt;AppSignal&lt;/a&gt; and has been running in production for a while. Like many teams, there are a few low-priority errors that pop up from time to time, but aren’t mission-critical enough to demand immediate attention. The kind of thing where you’d normally open AppSignal in a browser, copy the stack trace, switch to your editor, dig around, and by the time you’ve context-switched three times you’ve lost 30 minutes and your train of thought.&lt;/p&gt;

&lt;p&gt;Here’s how I was able to handle one of those lingering errors without leaving the terminal.&lt;/p&gt;

&lt;h2 id=&quot;step-1-install-appsignals-mcp&quot;&gt;Step 1: Install AppSignal’s MCP&lt;/h2&gt;

&lt;p&gt;I popped over to &lt;a href=&quot;https://docs.appsignal.com/mcp.html&quot;&gt;AppSignal’s MCP docs&lt;/a&gt;. Okay, I used a browser for this part.&lt;/p&gt;

&lt;p&gt;I’m familiar enough with installing MCPs, but noticed that AppSignal’s MCP requires Docker to run it. I don’t know if this will be the case long-term, but I already had Docker running on my laptop so it wasn’t an issue.&lt;/p&gt;

&lt;p&gt;I jumped down to their “Claude Code” instructions… which proposed:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;% claude mcp add appsignal &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;APPSIGNAL_API_KEY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;your_api_key_here &lt;span class=&quot;nt&quot;&gt;--&lt;/span&gt; docker run &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; APPSIGNAL_API_KEY appsignal/mcp
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I didn’t like the idea of adding the API key into my zsh command history, so I opted to add the API key to my project’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; file, which is already being used by the Rails app, and then referenced it like so:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;% claude mcp add appsignal &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;APPSIGNAL_API_KEY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$APPSIGNAL_API_KEY&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--&lt;/span&gt; docker run &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; APPSIGNAL_API_KEY appsignal/mcp

Added stdio MCP server appsignal with &lt;span class=&quot;nb&quot;&gt;command&lt;/span&gt;: docker run &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; APPSIGNAL_API_KEY appsignal/mcp to &lt;span class=&quot;nb&quot;&gt;local &lt;/span&gt;config
File modified: /Users/robbyrussell/.claude.json &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;project: /Users/robbyrussell/projects/project-name]

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;step-2-test-the-mcp-connection&quot;&gt;Step 2: Test the MCP connection&lt;/h2&gt;

&lt;p&gt;Next, I opened up Claude Code and ran &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/mcp&lt;/code&gt; to see if the connection was working. I was able to see the AppSignal MCP listed and was connected.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; ❯ appsignal · ✔ connected
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Instead of digging through the docs, I tried the obvious command:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  Fetch details on AppSignal error 342
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;(Note: my first attempt was “Error #3342” — a typo that returned no results. Being precise with incident numbers saves a roundtrip.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To my delight, the MCP integration retrieved the full error details from AppSignal, including the stack trace, exception type, and timeline of when it first and most recently occurred. A positive starting point.&lt;/p&gt;

&lt;p&gt;For some context, this looked related to some bot-crawler behavior. Something related to authenticity tokens. Again, it didn’t feel like a critical error, hence why it had been lingering for a while. But I was curious to understand the root cause and potential fixes.&lt;/p&gt;

&lt;h2 id=&quot;step-3-collaborate-on-a-solution&quot;&gt;Step 3: Collaborate on a solution&lt;/h2&gt;

&lt;p&gt;Next, I was eager to task Claude with helping me understand the severity and potential fixes for this error. I prompted Claude with the following:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;How can we handle this gracefully in Rails?
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Claude provided four potential solutions. Some were minor code tweaks, one was to just set it to ignored in AppSignal, another was to modify our middleware or explore something on the Web Application Firewall (WAF) side of things.&lt;/p&gt;

&lt;p&gt;Once we narrowed down a solution, I asked Claude to write the code change, run our test suite, commit with a descriptive message (including a link to the AppSignal incident for context), and submit a pull request to our GitHub repository.&lt;/p&gt;

&lt;p&gt;My prompt for the first few steps was something like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;please go ahead and implement Option 1 for now and then mark the issue as closed for now in AppSignal.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;step-4-deploy-the-fix&quot;&gt;Step 4: Deploy the fix&lt;/h2&gt;

&lt;p&gt;Given the non-critical nature of this particular error, I opted to use one of my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/review-pr&lt;/code&gt; skills to run a fresh PR review on the change that Claude had made. I asked for a summary of the change, then approved the PR and let our pipelines run as normal.&lt;/p&gt;

&lt;p&gt;While that was deploying, I logged into the AppSignal dashboard to confirm that particular error was now marked as closed. That way, if it popped up again, it would reopen and alert us again.&lt;/p&gt;

&lt;h2 id=&quot;no-performance-help-yet&quot;&gt;No Performance Help… yet&lt;/h2&gt;

&lt;p&gt;Once this was sorted out, I wanted to see if it could look into performance issues captured by AppSignal, too. Sadly, that doesn’t exist in the MCP as of March 2026.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
❯ https://appsignal.com/planet-argon/sites/655d333b83eb671c877e2948/performance/incidents/2

⏺ appsignal - Check &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;additional tools &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;MCP&lt;span class=&quot;o&quot;&gt;)(&lt;/span&gt;context: &lt;span class=&quot;s2&quot;&gt;&quot;User wants to view a performance incident from AppSignal. The existing
      get_incident tool doesn&apos;t seem to support performance incidents (only
      exceptions/anomalies). Need to find a tool that can fetch performance incident
      details.&quot;&lt;/span&gt;, description: &lt;span class=&quot;s2&quot;&gt;&quot;I need to fetch performance incident details from AppSignal.
      The URL is for a performance incident (not an exception). I need a tool that can
      retrieve performance/slow request incident information by incident number.&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  ⎿  # Tool Request Logged
     We&lt;span class=&quot;s1&quot;&gt;&apos;ve recorded your request for additional tooling capabilities. Your feedback helps us prioritize new tool development.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I’ll be sure to keep an eye on that feature request and update this post if/when it becomes available.&lt;/p&gt;

&lt;h2 id=&quot;final-thoughts&quot;&gt;Final thoughts&lt;/h2&gt;

&lt;p&gt;The interesting part here isn’t this particular error.&lt;/p&gt;

&lt;p&gt;It’s the workflow.&lt;/p&gt;

&lt;p&gt;By exposing monitoring tools like AppSignal through MCP, tools like Claude Code can retrieve incidents, analyze stack traces, propose fixes, and help ship patches without ever leaving the terminal.&lt;/p&gt;

&lt;p&gt;In this case the flow looked like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AppSignal alert → investigate → implement fix → open PR → deploy&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Five minutes. No browser tabs. No losing my train of thought.&lt;/p&gt;

&lt;p&gt;This is still early territory, but the building blocks are starting to line up. If you’re already using tools like AppSignal, it’s worth experimenting with MCP integrations and seeing what workflows you can streamline.&lt;/p&gt;

</description>
        <pubDate>Tue, 03 Mar 2026 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2026/03/03/from-appsignal-alert-to-fix-to-deploy/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2026/03/03/from-appsignal-alert-to-fix-to-deploy/</guid>
        
        <category>appsignal</category>
        
        <category>mcp</category>
        
        <category>claude-code</category>
        
        <category>monitoring</category>
        
        <category>devops</category>
        
        
      </item>
    
      <item>
        <title>Building a RAG Tool in Ruby 4: What Actually Happened</title>
        <description>&lt;p&gt;One of my priorities this quarter was running a few AI pilot experiments. This was one of them.&lt;/p&gt;

&lt;p&gt;When I mentioned the project to a teammate, he said, “You should write this up.” So here we are.&lt;/p&gt;

&lt;p&gt;Others on our team had already been exploring embeddings, vector databases, and RAG. I’d been watching from the sidelines… until it was time to roll up my sleeves and build something myself.&lt;/p&gt;

&lt;h2 id=&quot;the-problem&quot;&gt;The Problem&lt;/h2&gt;

&lt;p&gt;At &lt;a href=&quot;https://www.planetargon.com/?utm_source=robbyonrails.com&amp;amp;utm_medium=referral&amp;amp;utm_campaign=rag_tool_in_ruby&quot;&gt;Planet Argon&lt;/a&gt;, we manage several client projects. We live in Jira (I know… I know…). We keep decisions in Confluence. We ship code from GitHub. Over years a lot of institutional knowledge piles up across those systems… past bugs, old tradeoffs, and the “we tried that once” stories.&lt;/p&gt;

&lt;p&gt;The problem is that nobody remembers all of it. A new ticket comes in: “users can’t export reports to PDF”. Somewhere in Jira there’s a ticket from eight months ago where we debugged a Safari-specific PDF export issue. Of course it was Safari. Somewhere in Confluence there’s a permissions matrix that’s suddenly relevant. If you weren’t assigned to the project back then, you would never know to look.&lt;/p&gt;

&lt;p&gt;So we start over. We ask clarifying questions from scratch. We search Slack to ask if anyone has asked something like this before. Tickets go into development with vague acceptance criteria, and the back-and-forth that should have happened before coding shows up during code review and/or when we’re QAing on staging instead.&lt;/p&gt;

&lt;p&gt;A vague ticket is a polite way to ask engineers to guess.
Guessing can be expensive.&lt;/p&gt;

&lt;p&gt;I wanted to build something that could surface that historical context automatically. Point it at a ticket and get suggested clarifying questions grounded in what we actually &lt;del&gt;know&lt;/del&gt; “remember” about this project.&lt;/p&gt;

&lt;h2 id=&quot;why-ruby-why-minimal-dependencies&quot;&gt;Why Ruby, Why Minimal Dependencies&lt;/h2&gt;

&lt;p&gt;Ruby is what our team loves working in. If we were going to learn embeddings, vector search, and LLM integration, I wanted everything around those ideas to feel familiar.&lt;/p&gt;

&lt;p&gt;I also wanted to keep the dependency footprint deliberately small. This is an internal tool for a small team. Every Ruby gem you add is a gem you maintain. I’ve watched too many internal tools rot after someone pulled in thirty dependencies for a weekend project, then nobody wanted to deal with the upgrade treadmill six months later.&lt;/p&gt;

&lt;p&gt;Rather than listing the full &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile&lt;/code&gt; here, I’ll call out the gems that aren’t obvious. You already know &lt;a href=&quot;https://github.com/rails/thor&quot;&gt;thor&lt;/a&gt;, &lt;a href=&quot;https://github.com/lostisland/faraday&quot;&gt;faraday&lt;/a&gt;, &lt;a href=&quot;https://nokogiri.org/&quot;&gt;nokogiri&lt;/a&gt;, and &lt;a href=&quot;https://github.com/ruby-concurrency/concurrent-ruby&quot;&gt;concurrent-ruby&lt;/a&gt;. The interesting ones:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/alexrudall/ruby-openai&quot;&gt;ruby-openai&lt;/a&gt;&lt;/strong&gt;: handles both embedding generation and LLM completions. One gem, two jobs.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/ScotterC/pinecone&quot;&gt;pinecone&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href=&quot;https://rubygems.org/gems/chroma-db/&quot;&gt;chroma-db&lt;/a&gt;&lt;/strong&gt;: Pinecone for the shared production index, Chroma for local development via Docker. More on this below.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/modelcontextprotocol/ruby-sdk&quot;&gt;mcp&lt;/a&gt;&lt;/strong&gt;: &lt;a href=&quot;https://modelcontextprotocol.io/&quot;&gt;Model Context Protocol&lt;/a&gt; server for Claude Code integration. This came later, and it changed everything.&lt;/li&gt;
  &lt;li&gt;The &lt;strong&gt;&lt;a href=&quot;https://ttytoolkit.org/&quot;&gt;tty-*&lt;/a&gt;&lt;/strong&gt; family: not necessary… but nicer when you’re watching a 20-minute ingestion run.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Beyond that, Ruby’s standard library handled most of the rest. The instinct to reach for a gem is strong, but for most things the stdlib is genuinely sufficient.&lt;/p&gt;

&lt;h2 id=&quot;why-not-a-server&quot;&gt;Why Not a Server&lt;/h2&gt;

&lt;p&gt;Early on I made a decision that shaped the whole architecture: &lt;strong&gt;no running HTTP server with an endpoint.&lt;/strong&gt; (at least, not yet).&lt;/p&gt;

&lt;p&gt;A server is a commitment. Hosting. VPNs. Monitoring. Security reviews. Someone eventually asking, “who owns this?”. For an internal experiment that might not pan out, that felt like a lot of ceremony up front.&lt;/p&gt;

&lt;p&gt;So I built it as a CLI tool. Each engineer runs it locally on their own machine. The only shared infrastructure is &lt;a href=&quot;https://www.pinecone.io/&quot;&gt;Pinecone&lt;/a&gt;, a cloud-hosted vector database. Everyone gets API keys to the same Pinecone index, but each client’s data lives in its own namespace. Engineers use their own Atlassian and GitHub API tokens when they want to run an ingestion.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Related: &lt;a href=&quot;https://robbyonrails.com/articles/2025/08/13/internal-tooling-maturity-ladder/&quot;&gt;Internal Tooling Maturity Ladder&lt;/a&gt; is an approach that I’ve been exploring with our internal tools. The idea is to start with the simplest possible implementation (a script that solves the problem for one person), then evolve it through stages of maturity (CLI tool, shared server, versioned gem) as the need becomes clearer and the team is ready to invest more.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here’s what the environment setup looks like:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# .env: each engineer has their own copy&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# OpenAI (for embeddings and analysis)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;sk-...
&lt;span class=&quot;nv&quot;&gt;OPENAI_MODEL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;gpt-4o-mini

&lt;span class=&quot;c&quot;&gt;# Atlassian (shared instance, individual tokens)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;ATLASSIAN_BASE_URL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;https://planetargon.atlassian.net
&lt;span class=&quot;nv&quot;&gt;ATLASSIAN_EMAIL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;you@planetargon.com
&lt;span class=&quot;nv&quot;&gt;ATLASSIAN_API_TOKEN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;ATATT3x...

&lt;span class=&quot;c&quot;&gt;# GitHub (individual tokens)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;ghp_...

&lt;span class=&quot;c&quot;&gt;# Pinecone (shared index, namespaces isolate client data)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;PINECONE_API_KEY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;...
&lt;span class=&quot;nv&quot;&gt;PINECONE_INDEX_NAME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;clarion
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This kept the experiment low-stakes. No deployment pipeline, no server to maintain, no VPN to configure. If it didn’t work out, there was nothing to decommission. Engineers pull updates from the main branch, run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundle install&lt;/code&gt;, and they’re current. It’ll likely become a proper gem we version at some point, but for now the simplicity of “pull main and go” is working fine.&lt;/p&gt;

&lt;h2 id=&quot;what-the-cli-looks-like&quot;&gt;What the CLI Looks Like&lt;/h2&gt;

&lt;p&gt;We called it Clarion. A clarion is a signal… felt right for a tool whose whole job is to surface things worth paying attention to.&lt;/p&gt;

&lt;p&gt;The entrypoint is dead simple:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;#!/usr/bin/env ruby&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;bundler/setup&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require_relative&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;../lib/clarion&quot;&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;Clarion&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;CLI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ARGV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here’s the help output:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;bin/clarion &lt;span class=&quot;nb&quot;&gt;help

&lt;/span&gt;Commands:
  clarion analyze TICKET_ID    &lt;span class=&quot;c&quot;&gt;# Analyze a Jira ticket and suggest clarifications&lt;/span&gt;
  clarion &lt;span class=&quot;nb&quot;&gt;help&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;COMMAND]       &lt;span class=&quot;c&quot;&gt;# Describe available commands or one specific command&lt;/span&gt;
  clarion ingest SUBCOMMAND    &lt;span class=&quot;c&quot;&gt;# Ingest data from various sources&lt;/span&gt;
  clarion ingest_all CLIENT    &lt;span class=&quot;c&quot;&gt;# Ingest Jira, Confluence, and GitHub data for a client&lt;/span&gt;
  clarion mcp                  &lt;span class=&quot;c&quot;&gt;# Start MCP server (for Claude Code integration)&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;bin/clarion &lt;span class=&quot;nb&quot;&gt;help &lt;/span&gt;ingest

Commands:
  clarion ingest confluence    &lt;span class=&quot;c&quot;&gt;# Ingest Confluence pages for a specific space&lt;/span&gt;
  clarion ingest github        &lt;span class=&quot;c&quot;&gt;# Ingest GitHub repository data&lt;/span&gt;
  clarion ingest &lt;span class=&quot;nb&quot;&gt;help&lt;/span&gt;          &lt;span class=&quot;c&quot;&gt;# Describe subcommands or one specific subcommand&lt;/span&gt;
  clarion ingest jira          &lt;span class=&quot;c&quot;&gt;# Ingest Jira tickets for a specific project&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;the-cli-thor-subcommands&quot;&gt;The CLI: Thor Subcommands&lt;/h2&gt;

&lt;p&gt;Thor for the CLI, nothing exotic. Here’s the skeleton:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Clarion&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CLI&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Thor&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;desc&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;analyze TICKET_ID&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Analyze a Jira ticket and suggest clarifications&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;option&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:verbose&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;type: :boolean&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;desc: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Enable verbose output&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;analyze&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ticket_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;validate_ticket_id!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ticket_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;analyzer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Clarion&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Analyzer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ticket_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;verbose: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:verbose&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
      &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;analyzer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;analyze&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;desc&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ingest_all CLIENT&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Ingest Jira, Confluence, and GitHub data for a client&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;option&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:limit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;type: :numeric&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;option&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:parallel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;type: :boolean&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ingest_all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# Looks up client config, dispatches to parallel ingestion&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;desc&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;mcp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Start MCP server (for Claude Code integration)&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;option&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:namespace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;type: :string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;desc: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Client namespace (auto-detected if omitted)&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;mcp&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;Clarion&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;McpServer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;namespace: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:namespace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Nested subcommand for individual ingestion&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;desc&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ingest SUBCOMMAND&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Ingest data from various sources&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;subcommand&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ingest&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Ingest&lt;/span&gt;

    &lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;validate_ticket_id!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ticket_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ticket_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=~&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;/^[A-Z]+-\d+$/&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Thor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Invalid ticket ID format. Expected: PROJECT-123&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ingest&lt;/code&gt; subcommand is its own Thor class, giving us scoped commands for each data source:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Analyze a ticket&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;bin/clarion analyze WR-123

&lt;span class=&quot;c&quot;&gt;# Ingest everything for a client (parallel by default)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;bin/clarion ingest_all waystar &lt;span class=&quot;nt&quot;&gt;--limit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;500

&lt;span class=&quot;c&quot;&gt;# Or ingest individual sources&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;bin/clarion ingest jira &lt;span class=&quot;nt&quot;&gt;--namespace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;waystar &lt;span class=&quot;nt&quot;&gt;--project&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;WR &lt;span class=&quot;nt&quot;&gt;--limit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;500
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;bin/clarion ingest confluence &lt;span class=&quot;nt&quot;&gt;--namespace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;waystar &lt;span class=&quot;nt&quot;&gt;--space&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;WR
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;bin/clarion ingest github &lt;span class=&quot;nt&quot;&gt;--namespace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;waystar &lt;span class=&quot;nt&quot;&gt;--repo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;planetargon/waystar-web

&lt;span class=&quot;c&quot;&gt;# Start an MCP server for Claude Code&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;bin/clarion mcp &lt;span class=&quot;nt&quot;&gt;--namespace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;waystar
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Every ingest command requires explicit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--namespace&lt;/code&gt; and source-specific scoping flags (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--project&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--space&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--repo&lt;/code&gt;). This is deliberate. Operations should never run without explicit client scope.&lt;/p&gt;

&lt;h2 id=&quot;client-configuration&quot;&gt;Client Configuration&lt;/h2&gt;

&lt;p&gt;Each client maps to a namespace, a Jira project, a Confluence space, and optionally GitHub repos:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# config/clients.yml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;clients&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;waystar&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;waystar&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;jira_project&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;WR&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;confluence_space&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;WR&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;vector_store&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;pinecone&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;github_repos&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;planetargon/waystar-web&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;planetargon/waystar-api&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;piedpiper&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;piedpiper&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;jira_project&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;PP&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;confluence_space&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;PP&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;vector_store&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;pinecone&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;github_repos&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;planetargon/piedpiper-app&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;pierpoint&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;pierpoint&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;jira_project&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;PPC&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;confluence_space&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;PPC&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;vector_store&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;chroma&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# Local Chroma for testing&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note the per-client &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vector_store&lt;/code&gt; setting. One client can use Pinecone (shared, cloud-hosted) while another uses Chroma (local Docker instance) for development. The tool doesn’t care. The vector store abstraction handles it.&lt;/p&gt;

&lt;h2 id=&quot;embeddings-simpler-than-i-expected-until-they-werent&quot;&gt;Embeddings: Simpler Than I Expected, Until They Weren’t&lt;/h2&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/alexrudall/ruby-openai&quot;&gt;ruby-openai&lt;/a&gt; gem makes the embedding call straightforward:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;EMBEDDING_MODEL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;text-embedding-3-small&quot;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;EMBEDDING_DIMENSION&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1536&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;generate_embedding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EMBEDDING_DIMENSION&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;nil?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;strip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;empty?&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@openai&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;embeddings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;parameters: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;model: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EMBEDDING_MODEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;input: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;strip&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;data&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;embedding&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;One thing I didn’t appreciate initially is that every embedding call costs money and adds latency. My early version used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;search&lt;/code&gt;, which takes a text string and internally calls OpenAI to generate an embedding before querying Pinecone:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Before: each search() call generates its own embedding internally&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;similar&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@vector_store&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;filter: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;source: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;jira&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;docs&lt;/span&gt;     &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@vector_store&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;filter: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;source: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;confluence&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;github&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;resolved&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@vector_store&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;filter: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resolved_filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s three sequential calls to OpenAI’s embedding API for the exact same text, followed by three sequential calls to Pinecone. Six network round-trips, all in series.&lt;/p&gt;

&lt;p&gt;Looking at the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;search&lt;/code&gt; method, you can see why. It generates a fresh embedding every time:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;filter: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;top_k: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;query_embedding&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;generate_embedding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# Hits OpenAI every call&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;search_by_vector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query_embedding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;filter: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;top_k: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;top_k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The fix was two things at once: generate the embedding once, then pass that vector directly to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;search_by_vector&lt;/code&gt; (which skips the embedding step). Then run those three Pinecone queries concurrently:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# After: one embedding, three parallel vector searches&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;query_vector&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;embed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;similar&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;search_by_vector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query_vector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;source: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;jira&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;docs&lt;/span&gt;     &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;search_by_vector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query_vector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;source: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;confluence&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;github&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;resolved&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;search_by_vector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query_vector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resolved_filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The OpenAI embedding calls went from 3 to 1. The Pinecone queries stayed at 3 but now run concurrently instead of sequentially. Two wins from a small refactor.&lt;/p&gt;

&lt;p&gt;I also learned about truncation the hard way. Some Jira tickets are enormous… long comment threads, embedded images described in markup, and extensive acceptance criteria. The embedding model has a token limit. We now truncate text at 30,000 characters before sending it for embedding.&lt;/p&gt;

&lt;p&gt;Would’ve been nice to learn that from documentation rather than from a production error. Oh well.&lt;/p&gt;

&lt;h2 id=&quot;the-vector-store-abstraction&quot;&gt;The Vector Store Abstraction&lt;/h2&gt;

&lt;p&gt;I didn’t want to be locked into a single vector database, especially early on when I wasn’t sure which one would work best for us. So I built a simple abstraction layer. It’s a factory that returns different backends behind the same interface:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;VectorStore&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;backend: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;backend&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;VECTOR_STORE_BACKEND&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;memory&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;backend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;downcase&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;pinecone&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;VectorStores&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Pinecone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;namespace: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;chroma&quot;&lt;/span&gt;   &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;VectorStores&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Chroma&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;namespace: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;memory&quot;&lt;/span&gt;   &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;VectorStores&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Memory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;namespace: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;All three backends implement the same base contract:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;VectorStores&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Base&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;attr_reader&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:namespace&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;namespace: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;vi&quot;&gt;@namespace&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;namespace&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;upsert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;documents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NotImplementedError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;#upsert must be implemented&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;filter: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;top_k: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NotImplementedError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;#search must be implemented&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;search_by_vector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;filter: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;top_k: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NotImplementedError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;#search_by_vector must be implemented&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;embed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NotImplementedError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;#embed must be implemented&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;delete_all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;namespace: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NotImplementedError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;#delete_all must be implemented&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;stats&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NotImplementedError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;#stats must be implemented&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Callers just use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;upsert&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;search&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;search_by_vector&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stats&lt;/code&gt;. They never know or care whether they’re talking to &lt;a href=&quot;https://www.pinecone.io/&quot;&gt;Pinecone&lt;/a&gt;, &lt;a href=&quot;https://www.trychroma.com/&quot;&gt;Chroma&lt;/a&gt;, or an in-memory hash.&lt;/p&gt;

&lt;p&gt;The Pinecone backend stores document text inside the metadata (Pinecone doesn’t have a native text field), then strips it back out on retrieval:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# During upsert: embed text into metadata&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;metadata&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;doc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:metadata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;merge&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;text: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;doc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;doc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;values: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;embedding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;metadata: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;metadata&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# During search: extract text back out, unescape newlines&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;matches&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dup&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;metadata&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;metadata&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;metadata&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;is_a?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;gsub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;\\n&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;metadata&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;metadata&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;except&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This paid off quickly. We started with the in-memory backend (pure Ruby cosine similarity, persists to a JSON file) just to prove the concept worked at all. Then Chroma for local development. You can run it in Docker. No cloud account needed. Then Pinecone for the shared production dataset that the whole team can access.&lt;/p&gt;

&lt;h2 id=&quot;ingesting-messy-real-world-data&quot;&gt;Ingesting Messy Real-World Data&lt;/h2&gt;

&lt;p&gt;This is where things got messy.&lt;/p&gt;

&lt;h3 id=&quot;jira-flattening-the-ticket&quot;&gt;Jira: Flattening the Ticket&lt;/h3&gt;

&lt;p&gt;Each Jira ticket gets transformed into a document with an ID, a text blob, and structured metadata:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ticket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ticket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;key&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;fields&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ticket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;fields&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;

  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;id: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;jira_&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@namespace&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;# e.g., &quot;jira_waystar_WR-123&quot;&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;text: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;build_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fields&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;metadata: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;build_metadata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fields&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The text blob concatenates everything meaningful about the ticket: the key, summary, description, comments (with author tags), labels, parent/subtask relationships, and any embedded Confluence links.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Jira’s rich text format is a nested JSON tree.&lt;/strong&gt; Jira uses something called Atlassian Document Format (ADF) for ticket descriptions and comments. It’s not HTML. It’s not Markdown. It’s a deeply nested JSON structure with node types like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;paragraph&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bulletList&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;taskItem&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mention&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inlineCard&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;emoji&lt;/code&gt;. I had to write a recursive parser to walk that tree and flatten it into plain text:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AdfParser&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;extract_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;adf_doc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;adf_doc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;is_a?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;extract_blocks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;adf_doc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot; &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;strip&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;extract_blocks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;adf_doc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;adf_doc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;is_a?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;adf_doc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;format_block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;format_block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;is_a?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;taskList&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;format_task_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;bulletList&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;orderedList&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;format_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;extract_from_node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;extract_from_node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;text&quot;&lt;/span&gt;      &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;hardBreak&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;mention&quot;&lt;/span&gt;   &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;@&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;attrs&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;text&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;user&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;emoji&quot;&lt;/span&gt;     &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;attrs&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;shortName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;inlineCard&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;blockCard&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;attrs&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;inline_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Not complex, but the kind of thing you don’t anticipate until you see your first embedding full of raw JSON nodes. Thankfully, we can task Claude Code with figuring out some of this chaos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Comment authors matter.&lt;/strong&gt; We tag each Jira comment as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[Team]&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[Client]&lt;/code&gt; based on the commenter’s email domain:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;determine_author_type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;include?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;@planetargon.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;[Team]&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;elsif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;empty?&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;[Client]&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This matters more than I thought it would. The LLM can distinguish between internal engineering discussion and client-facing conversation when generating suggested questions.&lt;/p&gt;

&lt;h3 id=&quot;github-prs-issues-docs-and-code&quot;&gt;GitHub: PRs, Issues, Docs, and Code&lt;/h3&gt;

&lt;p&gt;We pull READMEs, docs, PRs, issues, and source files. Honestly, the source code has been the least useful of the bunch. PRs and issues have the “why”… the discussion, the tradeoffs, the things that almost shipped but didn’t. Source files have the “what,” but without the surrounding conversation the embedding doesn’t give you much you couldn’t get from grep.&lt;/p&gt;

&lt;h3 id=&quot;batch-uploads-and-deterministic-ids&quot;&gt;Batch Uploads and Deterministic IDs&lt;/h3&gt;

&lt;p&gt;Documents get uploaded to the vector store in batches of 20. Errors in one batch don’t abort subsequent batches:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BatchUploader&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;BATCH_SIZE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;upload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;documents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;documents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each_slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;BATCH_SIZE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;batch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
      &lt;span class=&quot;vi&quot;&gt;@vector_store&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;upsert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;batch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;vi&quot;&gt;@processed_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;batch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;length&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;StandardError&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;
      &lt;span class=&quot;vi&quot;&gt;@error_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;batch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;length&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Every document gets a deterministic ID based on its source: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jira_waystar_WR-123&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;confluence_waystar_12345_chunk_2&lt;/code&gt;, or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github_waystar_waystar-web_pr_47&lt;/code&gt;. This means re-running ingestion overwrites old documents instead of creating duplicates. Engineers can re-ingest anytime without polluting the dataset.&lt;/p&gt;

&lt;p&gt;When a Jira ticket updates, the next ingestion run replaces the old embedding with the new one. Same with Confluence pages and GitHub content. The vector store stays in sync with reality without complex change detection or deletion logic.&lt;/p&gt;

&lt;p&gt;One gap we haven’t closed yet: deletion. When a Jira ticket gets deleted or a Confluence page is removed, the old embedding stays in the index. We have no cleanup strategy right now. It hasn’t caused real problems, but it’s on the list.&lt;/p&gt;

&lt;p&gt;The tradeoff: someone needs to remember to run ingestion periodically. But the simplicity is worth it.&lt;/p&gt;

&lt;h3 id=&quot;parallel-ingestion-with-concurrent-ruby&quot;&gt;Parallel Ingestion with concurrent-ruby&lt;/h3&gt;

&lt;p&gt;When ingesting all sources for a client, the tool uses &lt;a href=&quot;https://github.com/ruby-concurrency/concurrent-ruby&quot;&gt;concurrent-ruby&lt;/a&gt; to run Jira, Confluence, and GitHub ingestions in parallel:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;pool&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Concurrent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;FixedThreadPool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;futures&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;futures&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Concurrent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Future&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;executor: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ingest_jira&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;futures&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Concurrent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Future&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;executor: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ingest_confluence&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;github_repos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;repo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;futures&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Concurrent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Future&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;executor: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ingest_github&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;repo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Wait for all to complete&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;futures&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:wait&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Thread-safe state tracking uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Concurrent::Hash&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;vi&quot;&gt;@results&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Concurrent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;
&lt;span class=&quot;vi&quot;&gt;@timings&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Concurrent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;
&lt;span class=&quot;vi&quot;&gt;@status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Concurrent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After completion, the tool calculates time saved versus sequential execution and reports a speedup factor. In practice, parallel ingestion typically finishes in about 60% of the time sequential would take, since the API calls to Jira, Confluence, and GitHub can overlap.&lt;/p&gt;

&lt;p&gt;Running an ingestion looks like this:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;bin/clarion ingest_all waystar &lt;span class=&quot;nt&quot;&gt;--limit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;500

════════════════════════════════════════════════════════════
                  COMBINED DATA INGESTION
════════════════════════════════════════════════════════════

ℹ Client: waystar
ℹ Namespace: waystar
ℹ Vector store: pinecone
ℹ Jira project: WR
ℹ Confluence space: WR
ℹ GitHub repos: planetargon/waystar-web
ℹ Limit: 500 items per &lt;span class=&quot;nb&quot;&gt;source
&lt;/span&gt;ℹ Mode: Parallel

  ✓ Jira &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;WR&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;                     Complete &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;487/500 processed&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  ✓ Confluence &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;WR&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;               Complete &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;245/500 processed&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  ✓ GitHub: waystar-web            Complete &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;498/500 processed&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

════════════════════════════════════════════════════════════
                    INGESTION RESULTS
════════════════════════════════════════════════════════════

ℹ ✓ Jira: 487 processed, 0 errors &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;45.2s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
ℹ ✓ Confluence: 245 processed, 0 errors &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;38.1s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
ℹ ✓ Github Waystar Web: 498 processed, 0 errors &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;52.7s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

════════════════════════════════════════════════════════════
                   PERFORMANCE SUMMARY
════════════════════════════════════════════════════════════

ℹ Total documents processed: 1230
✓ Total &lt;span class=&quot;nb&quot;&gt;time&lt;/span&gt;: 58.3s
ℹ Time saved vs sequential: 77.7s &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;2.3x speedup&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

✓ Client &lt;span class=&quot;s1&quot;&gt;&apos;waystar&apos;&lt;/span&gt; is ready &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;analysis!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;retrieval-and-re-ranking&quot;&gt;Retrieval and Re-Ranking&lt;/h2&gt;

&lt;p&gt;Raw cosine similarity gets you most of the way there, but not all the way. The vector search returns the 40 most similar Jira tickets, and some of them are similar for the wrong reasons… same boilerplate language, same component name, but not actually useful context.&lt;/p&gt;

&lt;p&gt;The context builder generates one embedding, then runs three concurrent searches. Similar tickets. Resolved tickets filtered by component. Documentation from Confluence and GitHub.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;gather_all_context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ticket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ticket_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current_key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;created_time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;query&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;build_query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ticket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;query_vector&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;embed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;similar_thread&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;search_by_vector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query_vector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;source: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;jira&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;score_and_limit_results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ticket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current_key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;created_time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;resolved_thread&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;search_by_vector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query_vector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resolved_filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;format_resolved_tickets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;docs_thread&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;search_by_vector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query_vector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;source: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;confluence&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;github&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;process_and_limit_docs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ticket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ticket_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;created_time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;similar_tickets:  &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;similar_thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;related_resolved: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resolved_thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;documentation:    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;docs_thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;value&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After retrieval, I added two scoring tweaks that made a noticeable difference:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Relationship boost.&lt;/strong&gt; If a retrieved ticket is a parent or subtask of the ticket being analyzed, its score gets a 1.5x multiplier:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;apply_relationship_boost&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ticket_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;relationship_type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ticket_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:relationship&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;relationship_type&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ticket_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:score&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.5&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Temporal decay.&lt;/strong&gt; Tickets older than 7 days get a 0.7x multiplier. Older than 30 days, 0.3x:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;age_adjustment_params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;days_before&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Created &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;days_before&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; days before ticket&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;days_before&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.7&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;days_before&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;These aren’t machine learning models. They’re just multipliers applied after retrieval. I was surprised how much difference they made. A few lines of Ruby math moved the output from “interesting but noisy” to something I’d actually act on.&lt;/p&gt;

&lt;p&gt;It’s still early days, I expect that we’ll likely need to tweak this a bunch as we see more real-world queries and get feedback from engineers.&lt;/p&gt;

&lt;h2 id=&quot;the-prompting-side&quot;&gt;The Prompting Side&lt;/h2&gt;

&lt;p&gt;Two things surprised me here.&lt;/p&gt;

&lt;p&gt;First: &lt;strong&gt;structured JSON output.&lt;/strong&gt; Huge deal. We set &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;response_format: { type: &quot;json_object&quot; }&lt;/code&gt; on the LLM call, which means the response is always valid JSON. No regex parsing, no hoping the model follows your format instructions. The response comes back with a defined structure:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;ticket_type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;feature&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;clarity_assessment&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;needs_clarification&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;clarifying_questions&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;question&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;The question to ask the client&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;rationale&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Why this matters for implementation&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;reference&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;WR-892: similar issue last quarter&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;suggested_acceptance_criteria&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;User can export all report types to PDF&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Export completes within 30 seconds&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Error message displays if export fails&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;potential_edge_cases&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Special characters in report data&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Very large reports (&amp;gt;10,000 rows)&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;implementation_notes&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Brief notes on approach&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Once you have reliable structure, everything downstream gets simpler.&lt;/p&gt;

&lt;p&gt;Second: &lt;strong&gt;the prompt is where your actual communication style lives.&lt;/strong&gt; Ours doesn’t just say “generate clarifying questions”… it encodes how we actually talk to clients:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Instead of asking open-ended technical questions, frame them as confirmations:&lt;/p&gt;

  &lt;p&gt;“It sounds like this needs to work in Chrome. Should we also make sure it works in Safari and Firefox?”&lt;/p&gt;

  &lt;p&gt;Rather than:&lt;/p&gt;

  &lt;p&gt;“What browsers need to be supported?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The prompt covers dozens of specific communication scenarios. A few examples from the actual prompt file:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When clients apologize for not being technical:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;“No need to apologize. You’re describing exactly what we need to know. The ‘what’s broken’ is your expertise; the ‘why it’s broken’ is ours.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;When scope is creeping:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;“There’s a lot of good stuff here. To make sure nothing gets lost, would it help to break this into separate tickets? That way we can track the export fix and the new filter feature independently.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;When clients describe workarounds they’re using:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;“Good thinking on the CSV workaround. That’ll keep things moving. We’ll fix the PDF export so you don’t have to keep doing that extra step.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;When something is working as designed:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;“So it turns out the system is doing what it was originally built to do, but I hear you that it’s not what you need it to do. Want us to write up a feature request to change this behavior?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The prompt took real care to get right, but not because it was technically hard. As a client-services company we put a lot of thought into our communication style… how we ask questions, how we handle scope creep, how we respond when something is working as designed but not as the client expected. Baking that into the prompt mattered. We also have engineers from different regions and backgrounds, and anything that helps guide everyone toward a consistent, confident tone with clients is worth the effort.&lt;/p&gt;

&lt;p&gt;This is the part that makes it &lt;em&gt;ours&lt;/em&gt; and not just another RAG wrapper. The vector search finds the history. The prompt makes it sound like us.&lt;/p&gt;

&lt;p&gt;We also maintain two separate prompt files. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prompts/analyzer_default.md&lt;/code&gt; is for open tickets (“what’s unclear?”). &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prompts/analyzer_completed.md&lt;/code&gt; is for closed tickets (retrospective analysis). The tool detects the ticket’s status and selects the right prompt automatically. It’s a small touch, but it means the output is always contextually appropriate.&lt;/p&gt;

&lt;p&gt;I expected engineers to be the primary users. The first people to actually build it into their workflow were our PMs. They’re in tickets all day, working with clients on acceptance criteria… and having suggested clarifying questions grounded in project history turns out to be directly useful for that conversation, not just for development. I should have seen that coming.&lt;/p&gt;

&lt;h2 id=&quot;the-mcp-surprise&quot;&gt;The MCP Surprise&lt;/h2&gt;

&lt;p&gt;I didn’t expect this part to become the most useful thing in the whole project.&lt;/p&gt;

&lt;p&gt;The tool started as a CLI experiment. Run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/clarion analyze WR-123&lt;/code&gt; in your terminal, get output, copy what’s useful. It worked, but there was friction. You had to switch contexts, looking at a Jira ticket, looking at and jumping away from your editor, and remember the command syntax.&lt;/p&gt;

&lt;p&gt;Having spent a bunch of time recently in Claude code, I wondered… could we bring this analysis directly into the editor? I think this took me less than two hours from “I wonder if this could be an MCP server” to “oh wow, it’s actually working”.&lt;/p&gt;

&lt;p&gt;I quickly found the &lt;a href=&quot;https://github.com/anthropics/ruby-sdk/tree/main/gems/mcp&quot;&gt;mcp gem&lt;/a&gt;, which implements Anthropic’s &lt;a href=&quot;https://modelcontextprotocol.io/&quot;&gt;Model Context Protocol&lt;/a&gt;. MCP lets you expose a tool as a server that &lt;a href=&quot;https://docs.anthropic.com/en/docs/claude-code&quot;&gt;Claude Code&lt;/a&gt; can call directly. Here’s what the server looks like:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;McpServer&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;namespace: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;working_directory: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@namespace&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;namespace&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@working_directory&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;working_directory&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resolve_client&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;build_server&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;transport&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MCP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Server&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Transports&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;StdioTransport&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;transport&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;build_server&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;MCP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;clarion&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;version: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Clarion&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;VERSION&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;tools: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Mcp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;AnalyzeTool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The MCP tool itself is built dynamically. The tool description is baked in with the client’s namespace and ticket prefix at startup time, so Claude Code knows exactly what it can do:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;AnalyzeTool&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tool&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;MCP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Tool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;tool_name&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;analyze_ticket&quot;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;description&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Analyze a Jira ticket and suggest clarifying questions &quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;\&lt;/span&gt;
                  &lt;span class=&quot;s2&quot;&gt;&quot;and acceptance criteria. Scoped to client &apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&apos; &quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;\&lt;/span&gt;
                  &lt;span class=&quot;s2&quot;&gt;&quot;(ticket prefix: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ticket_prefix&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;).&quot;&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;input_schema&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;properties: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;ss&quot;&gt;ticket_key: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;ss&quot;&gt;type: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;string&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;ss&quot;&gt;description: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Jira ticket ID (e.g., &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ticket_prefix&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;-123)&quot;&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;required: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ticket_key&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# ... wire up call, validation, and analysis methods&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tool&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Each MCP server instance is scoped to a single client namespace. When an engineer is working in a client’s repository, they drop a small JSON config file at the repo root:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;mcpServers&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;clarion&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;command&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/path/to/clarion/bin/clarion-mcp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;args&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;--namespace=waystar&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Getting set up isn’t quite drag-and-drop. A new engineer needs to clone the repo, run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundle install&lt;/code&gt;, configure their own &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; with API keys for OpenAI, Atlassian, GitHub, and Pinecone, then drop the right config file into whichever client repo they’re working in. Our team is used to juggling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; files across multiple projects, so this didn’t feel like a big ask… but it’s worth knowing upfront if you’re thinking about something similar.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/clarion-mcp&lt;/code&gt; wrapper is a one-liner. It sets the working directory, then delegates:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/bash&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;dirname&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$0&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/..&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;ruby &lt;span class=&quot;nt&quot;&gt;-Ilib&lt;/span&gt; bin/clarion mcp &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$@&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now they can ask Claude Code to “analyze WR-123” and get the full analysis inline. Clarifying questions. Suggested acceptance criteria. Edge cases. Implementation notes. All without leaving their editor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auto-detection from git remote.&lt;/strong&gt; If the client’s repo is configured in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;clients.yml&lt;/code&gt; with its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github_repos&lt;/code&gt;, you can even skip the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--namespace&lt;/code&gt; flag. The server shells out to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git remote get-url origin&lt;/code&gt;, parses the owner/repo slug, and looks it up automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One gotcha worth mentioning:&lt;/strong&gt; TTY output breaks MCP’s stdio transport. All those nice spinners and progress bars and colored output that make the CLI experience polished? They corrupt the MCP response stream. I had to suppress stdout during MCP calls:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run_analysis&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;AnalyzerConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;result_formatter: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;PlainTextFormatter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;original_stdout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$stdout&lt;/span&gt;
  &lt;span class=&quot;vg&quot;&gt;$stdout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;w&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;begin&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Analyzer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;analyze&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;ensure&lt;/span&gt;
    &lt;span class=&quot;vg&quot;&gt;$stdout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;original_stdout&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Small thing, but it would have been confusing to debug without knowing to look for it. We also have a separate &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PlainTextFormatter&lt;/code&gt; that outputs clean text for MCP, versus the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ResultFormatter&lt;/code&gt; that uses colored boxes and unicode for the CLI.&lt;/p&gt;

&lt;h2 id=&quot;where-it-gets-really-interesting-mcp-in-combination&quot;&gt;Where It Gets Really Interesting: MCP in Combination&lt;/h2&gt;

&lt;p&gt;Clarion as an MCP server is useful on its own. But the thing that got me excited was running it &lt;strong&gt;alongside&lt;/strong&gt; other MCP servers in the same Claude Code session.&lt;/p&gt;

&lt;p&gt;Our engineers can have Clarion (our embedded project history), the &lt;a href=&quot;https://www.atlassian.com/blog/announcements/remote-mcp-server&quot;&gt;Atlassian MCP&lt;/a&gt; (live read/write access to Jira and Confluence), and the &lt;a href=&quot;https://github.com/github/github-mcp-server&quot;&gt;GitHub MCP&lt;/a&gt; all connected at once.&lt;/p&gt;

&lt;p&gt;The combination we’ve found most useful: analyze a ticket with Clarion, review the suggested clarifying questions, adjust the wording, then use the Atlassian MCP to post the comment directly on the Jira ticket. The whole loop closes without leaving Claude Code.&lt;/p&gt;

&lt;p&gt;Cross-source research has been useful too. Ask “what do we know about how authentication works in this project?” and get results from Jira tickets where auth bugs were fixed, Confluence pages documenting the auth flow, and GitHub PRs where the auth code was changed. All from one query, scoped to that client. With GitHub MCP connected, you can then check whether the docs still match the actual code.&lt;/p&gt;

&lt;p&gt;The underlying idea is that before anyone starts building, the ticket should be clear. Clarion sits at that boundary. The suggested questions aren’t generic… they’re informed by the specific history of this project. “Last time we did a PDF export on this project, Safari caused problems” is more useful than “have you considered browser compatibility?”.&lt;/p&gt;

&lt;h2 id=&quot;multi-tenant-scoping-the-hard-constraint&quot;&gt;Multi-Tenant Scoping: The Hard Constraint&lt;/h2&gt;

&lt;p&gt;One constraint that shaped everything: Planet Argon uses a single Atlassian account across most of our client projects (some clients own their own Atlassian accounts). Same Jira instance, same Confluence instance, one set of API credentials.&lt;/p&gt;

&lt;p&gt;That means &lt;strong&gt;data isolation has to be enforced in our code&lt;/strong&gt;, not by infrastructure boundaries. Every operation requires an explicit client namespace. The vector store uses that namespace to partition data. One Pinecone index. Many isolated namespaces. Ticket IDs are validated against the expected prefix before any analysis runs.&lt;/p&gt;

&lt;p&gt;Granted, our engineers do have access to reference different clients at the same time in their Atlassian account, but the tool itself is always scoped to one client per run. That’s the important part.&lt;/p&gt;

&lt;p&gt;The validation happens at multiple layers. In the CLI:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;validate_ticket_id!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ticket_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ticket_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=~&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;/^[A-Z]+-\d+$/&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Thor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Invalid ticket ID format. Expected: PROJECT-123&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And again in the MCP tool, where it also checks the prefix matches the scoped client:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;validate_ticket_prefix!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;match?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/^[A-Z]+-\d+$/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ArgumentError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Invalid ticket ID format: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;. Expected: PROJECT-123&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;-&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scoped_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ticket_prefix&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ClientScopeError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;Ticket &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; does not belong to client &quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;\&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;scoped_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&apos; (expected prefix: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;scoped_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ticket_prefix&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;)&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you’re working in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;waystar&lt;/code&gt; namespace and try to analyze &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PP-123&lt;/code&gt;, you get a clear error: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;Ticket PP-123 does not belong to client &apos;waystar&apos; (expected prefix: WR)&quot;&lt;/code&gt;. Not results from the wrong client.&lt;/p&gt;

&lt;p&gt;It’s a simple system. Namespaces and prefix checks. Again, engineers technically have access to all clients’ data in Atlassian, but the tool enforces discipline. You have to be intentional about which client’s context you’re working in. We don’t want someone accidentally running an analysis against the wrong client’s project and making assumptions based on irrelevant history.&lt;/p&gt;

&lt;h2 id=&quot;whats-next&quot;&gt;What’s Next&lt;/h2&gt;

&lt;p&gt;A few people on the team are using it regularly. I’m hoping that spreads in the coming weeks. Whether this becomes a genuine workflow change or an interesting experiment we eventually deprioritize… I genuinely don’t know yet. I’ll write a follow-up when there’s something real to report.&lt;/p&gt;

&lt;p&gt;We’re using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gpt-4o-mini&lt;/code&gt; for now because it was the easiest thing to get running and the output has been good enough that we haven’t felt pressure to switch.&lt;/p&gt;

&lt;p&gt;Atlassian is building AI features into Jira and Confluence, and some of that will overlap with what we’ve built. But Atlassian’s tooling only knows about what’s inside Atlassian. It can’t see GitHub repos, PR histories, or how past implementations actually played out in code. Our tool bridges that gap… context across all three systems, shaped by how &lt;em&gt;we&lt;/em&gt; work.&lt;/p&gt;

&lt;p&gt;Our team is also experimenting more with LLM-assisted code generation. But this tool sits deliberately upstream of that. It’s about the collaboration layer. Making sure what we’re about to build is well-understood before anyone writes code. A perfectly generated pull request against a vague ticket is still a miss.&lt;/p&gt;

&lt;p&gt;We’ll probably open source this eventually, but the codebase is full of references to real client projects in tests and config. Scrubbing that is on the list. Not the priority right now.&lt;/p&gt;

&lt;p&gt;If you’re thinking about building something like this… just start. Ruby has what you need. The gems are there. It’s more approachable than it looks from the outside.&lt;/p&gt;

&lt;p&gt;p.s. &lt;em&gt;Oh, and did you notice I never mentioned Ruby 4 anywhere in this post? That’s because there’s nothing to mention.&lt;/em&gt; &lt;strong&gt;It just worked.&lt;/strong&gt;&lt;/p&gt;
</description>
        <pubDate>Thu, 26 Feb 2026 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2026/02/26/building-a-rag-tool-in-ruby/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2026/02/26/building-a-rag-tool-in-ruby/</guid>
        
        <category>ruby</category>
        
        <category>openai</category>
        
        <category>claude-code</category>
        
        <category>jira</category>
        
        <category>atlassian</category>
        
        <category>llms</category>
        
        <category>rag</category>
        
        
      </item>
    
      <item>
        <title>Most Developers Don’t Build New Things</title>
        <description>&lt;p&gt;The industry tends to celebrate beginnings.&lt;/p&gt;

&lt;p&gt;New repositories. Clean architecture diagrams. The excitement of choosing tools before real constraints show up. First commits get attention because they feel like authorship.&lt;/p&gt;

&lt;p&gt;Most developers, though, don’t spend their careers starting from scratch. They spend them stepping into systems that already exist, already have users, and already have decisions embedded in them.&lt;/p&gt;

&lt;p&gt;You open the repository and there are hundreds of thousands of lines of code waiting for you. Patterns layered over time. Workarounds that solved real problems in earlier moments. Comments that hint at context you weren’t there for. You didn’t choose the framework version. You didn’t pick the database. You didn’t design the way billing logic is structured.&lt;/p&gt;

&lt;p&gt;You inherited it.&lt;/p&gt;

&lt;p&gt;That inheritance is the work.&lt;/p&gt;

&lt;h2 id=&quot;the-rails-default-debate&quot;&gt;The Rails Default Debate&lt;/h2&gt;

&lt;p&gt;This is why debates about what DHH changed in the default &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rails new&lt;/code&gt; template sometimes feel slightly disconnected from day-to-day reality.&lt;/p&gt;

&lt;p&gt;Threads fill up with opinions about JavaScript bundling, database adapters, testing philosophy. The conversation centers on defaults and direction.&lt;/p&gt;

&lt;p&gt;Meanwhile, most developers working with Ruby on Rails haven’t typed &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rails new&lt;/code&gt; in years, at least not on anything beyond a side project or experiment.&lt;/p&gt;

&lt;p&gt;They’re inside a twelve year old system that has been upgraded across versions, shaped by product demands, team turnover, and business pressure. Their attention is on stability. On how to change something without breaking adjacent workflows. On whether a refactor will surface an edge case that only appears in production.&lt;/p&gt;

&lt;p&gt;The generator shapes the beginning.&lt;/p&gt;

&lt;p&gt;Maintenance shapes everything after that.&lt;/p&gt;

&lt;h2 id=&quot;software-has-second-acts&quot;&gt;Software Has Second Acts&lt;/h2&gt;

&lt;p&gt;We often talk about software in terms of launch and decline, as if those are the only meaningful chapters. In practice, most valuable products spend the majority of their life in a long second act.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.planetargon.com/second-act&quot;&gt;The second act&lt;/a&gt; is what happens after product-market fit, after early growth, after the architecture has already been bent once or twice. It’s when the team has turned over. When new engineers are maintaining code they didn’t write. When uptime and predictability matter more than novelty.&lt;/p&gt;

&lt;p&gt;It’s the stage where you stop asking, “What should we build?” and start asking, “How do we keep this adaptable without destabilizing it?”&lt;/p&gt;

&lt;p&gt;That’s the environment most engineers operate in.&lt;/p&gt;

&lt;p&gt;Not at the starting line, but in the middle of a system that already has gravity.&lt;/p&gt;

&lt;h2 id=&quot;legacy-code-is-accumulated-context&quot;&gt;Legacy Code Is Accumulated Context&lt;/h2&gt;

&lt;p&gt;A mature codebase is accumulated decision-making. Each file reflects what someone believed was reasonable under the constraints they faced at the time.&lt;/p&gt;

&lt;p&gt;A couple of years ago you introduced an abstraction that felt thoughtful and clean. It simplified things. It made sense with what you knew then. Today it may feel slightly overbuilt or misaligned with how the system evolved. The code you once defended now requires explanation.&lt;/p&gt;

&lt;p&gt;Legacy code is not a moral category.&lt;/p&gt;

&lt;p&gt;It is accumulated context.&lt;/p&gt;

&lt;p&gt;Most legacy systems were built by people responding to real constraints. That includes you. The second act is less about erasing that history and more about working within it deliberately.&lt;/p&gt;

&lt;h2 id=&quot;the-rewrite-fantasy&quot;&gt;The Rewrite Fantasy&lt;/h2&gt;

&lt;p&gt;The urge to rewrite usually comes from frustration. We could design this better. We understand the domain more clearly now. The stack feels dated.&lt;/p&gt;

&lt;p&gt;The real cost of a rewrite isn’t just risk or budget. It’s attention.&lt;/p&gt;

&lt;p&gt;The moment a team starts to think “we’re rewriting this anyway,” the current system quietly becomes second-class. Tests stop improving. Refactors are postponed. Documentation is deferred. People stop investing in clarity because the future version will “fix it.” I’ve written more about this dynamic in &lt;a href=&quot;https://blog.planetargon.com/blog/entries/the-cost-of-leaving-a-software-rewrite-on-the-table&quot;&gt;The Cost of Leaving a Software Rewrite on the Table&lt;/a&gt;, because it shows up more often than teams expect.&lt;/p&gt;

&lt;p&gt;Months pass. Sometimes years.&lt;/p&gt;

&lt;p&gt;When the rewrite finally ships, it inherits the same domain complexity, the same business pressures, and often a team that hasn’t practiced maintaining what they already had.&lt;/p&gt;

&lt;p&gt;Rewrites don’t just reset code. They reset discipline.&lt;/p&gt;

&lt;p&gt;That’s the cost teams underestimate.&lt;/p&gt;

&lt;h2 id=&quot;stewardship-is-the-craft&quot;&gt;Stewardship Is the Craft&lt;/h2&gt;

&lt;p&gt;Most careers unfold inside inherited systems. The skill is learning how to move those systems forward without pretending they began today.&lt;/p&gt;

&lt;p&gt;We inherit. We understand. We stabilize. We extend. We improve what we can without destabilizing what already works.&lt;/p&gt;

&lt;p&gt;This kind of work rarely attracts attention. It looks like incremental improvement and steady compounding over time.&lt;/p&gt;

&lt;p&gt;But if most of your career is going to be spent in the second act, then the real question isn’t whether you get to start something new.&lt;/p&gt;

&lt;p&gt;It’s whether what you inherit gets better because you were there.&lt;/p&gt;

</description>
        <pubDate>Wed, 18 Feb 2026 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2026/02/18/most-developers-dont-build-new-things/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2026/02/18/most-developers-dont-build-new-things/</guid>
        
        <category>software</category>
        
        <category>maintainability</category>
        
        <category>careers</category>
        
        
      </item>
    
      <item>
        <title>The Handoff Test</title>
        <description>&lt;p&gt;I keep hearing the same story in conference hallway tracks. An engineer leans in and tells me their old boss or client still texts them every four to six months with a “quick question.” There’s usually a slight eye roll. They’ve moved on. They’re not being paid anymore. It feels like a boundary issue.&lt;/p&gt;

&lt;p&gt;Then I ask what the question was.&lt;/p&gt;

&lt;p&gt;It’s rarely random. It’s about the custom annual report they used to run before board meetings… whether it’s safe to delete a specific SPF value in DNS… why three staging hostnames are still sitting in the load balancer and the new DevOps person is afraid to touch them. Sometimes it’s a background job chain that behaves strangely under load. Sometimes it’s a migration that looked simple but has sharp edges nobody remembers.&lt;/p&gt;

&lt;p&gt;These aren’t casual interruptions. They’re loose threads that never got tied off.&lt;/p&gt;

&lt;p&gt;And that’s when the uncomfortable question shows up…&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Did we fully hand off the system before we left?&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;what-we-really-mean-by-handoff&quot;&gt;What We Really Mean by Handoff&lt;/h2&gt;

&lt;p&gt;In long-lived systems, knowledge doesn’t live only in the codebase. It lives in decisions, tradeoffs, and context. The repository might show what the system does, but it rarely captures why it ended up there or what constraints shaped it.&lt;/p&gt;

&lt;p&gt;While you’re still inside the system, none of this feels fragile. You answer quickly. You remember the nuance. You act as the compression layer between complexity and the rest of the team. It feels efficient and, if we’re honest, a little flattering. Being the one who “just knows” carries quiet status.&lt;/p&gt;

&lt;p&gt;The risk appears later.&lt;/p&gt;

&lt;p&gt;If only one person can safely interpret parts of the system, succession hasn’t happened. When that person leaves, the system doesn’t just lose a contributor. It loses confidence. The code still runs. The infrastructure still exists. But people hesitate before touching it. That hesitation is usually what triggers the text.&lt;/p&gt;

&lt;h2 id=&quot;the-house-you-sold&quot;&gt;The House You Sold&lt;/h2&gt;

&lt;p&gt;Think about selling a house you’ve lived in for years. You know which breaker controls the backyard outlet. You know the upstairs shower takes a minute before it gets hot. You know where the sprinkler shutoff is hidden and which switch looks functional but does nothing.&lt;/p&gt;

&lt;p&gt;You move out without leaving notes.&lt;/p&gt;

&lt;p&gt;Three months later, the new owner texts asking about a breaker or a pipe noise. They aren’t trying to pull you back into ownership. They inherited something that works but feels uncertain because the context behind it is invisible.&lt;/p&gt;

&lt;p&gt;I’ve bought a house from people who were excellent stewards. They labeled panels, left manuals, and wrote down the quirks. It made a meaningful difference. When I did text them once or twice, I had already flipped switches, checked breakers, and read the binder before reaching out. The message wasn’t laziness. It was caution.&lt;/p&gt;

&lt;p&gt;You don’t owe infinite support after you sell a house, but you do owe a clean handoff while you still own it.&lt;/p&gt;

&lt;p&gt;Software systems aren’t any different. If someone has to text you to feel safe deleting a DNS record or modifying a report query, the system may function technically… but it wasn’t fully transferred operationally.&lt;/p&gt;

&lt;h2 id=&quot;the-part-thats-hard-to-admit&quot;&gt;The Part That’s Hard to Admit&lt;/h2&gt;

&lt;p&gt;There’s a pattern underneath those hallway conversations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You can be the one they still text, or the one they never have to.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Being the one they still text can feel good. It confirms that you were critical. It reinforces the idea that you were the person who truly understood the system. There’s status in being indispensable, even if we don’t consciously chase it.&lt;/p&gt;

&lt;p&gt;But indispensability is often just concentrated context, and concentrated context is fragile.&lt;/p&gt;

&lt;p&gt;If your absence creates anxiety, you didn’t build resilience. If your absence creates confidence, you did. That difference has nothing to do with generosity after you leave. It has everything to do with whether you treated knowledge as something to hold or something to distribute.&lt;/p&gt;

&lt;p&gt;There’s also no harm in changing your phone number. That’s definitely cheaper. It just doesn’t fix the underlying issue.&lt;/p&gt;

&lt;h2 id=&quot;run-the-handoff-test&quot;&gt;Run the Handoff Test&lt;/h2&gt;

&lt;p&gt;Open a blank document and write: &lt;em&gt;“If I left tomorrow…”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Then list what would likely trigger a text four to six months later. Be specific. Would someone hesitate to run the annual board report without your guidance? Would they avoid touching a background job because they don’t fully understand its retry behavior? Would they pause before cleaning up infrastructure because they can’t see what depends on it? Are production accounts or key decisions still too closely tied to your memory?&lt;/p&gt;

&lt;p&gt;This list isn’t an accusation. It’s a map of concentrated knowledge.&lt;/p&gt;

&lt;p&gt;Each item marks a place where the system depends more on familiarity than on structure. It shows you where succession planning for code ownership hasn’t happened yet. Many engineers quietly wonder how things are going after they leave… did the migration succeed… did the architecture hold up… did the team refactor the risky part? When I ask whether they’ve reached out to find out, most haven’t.&lt;/p&gt;

&lt;p&gt;That hesitation tells you something.&lt;/p&gt;

&lt;h2 id=&quot;this-is-structural-not-just-personal&quot;&gt;This Is Structural, Not Just Personal&lt;/h2&gt;

&lt;p&gt;Handoff isn’t just an individual virtue. It’s an organizational expectation. Engineering leaders should design for it, and teams should normalize it as part of professional completion. If your culture rewards heroics but never budgets time for transfer, you’ll recreate the same dependency patterns over and over. Engineers will leave, and the same operational uncertainty will resurface.&lt;/p&gt;

&lt;p&gt;Succession planning for code ownership isn’t documentation theater. It’s risk management. It ensures systems can evolve without leaning on someone who no longer works there. Engineers need to externalize context. Organizations need to create space for that work.&lt;/p&gt;

&lt;p&gt;Replaceability isn’t weakness. It’s maturity.&lt;/p&gt;

&lt;h2 id=&quot;redefining-done&quot;&gt;Redefining Done&lt;/h2&gt;

&lt;p&gt;Most teams define done as merged, tested, and deployed. That keeps velocity high. For long-lived systems, done should also include transferability. Someone else can run the board report without fear. Someone else can rotate keys confidently. Someone else can remove a staging hostname or refactor a risky job without Slack archaeology.&lt;/p&gt;

&lt;p&gt;The real question isn’t whether you’ll ever get that text.&lt;/p&gt;

&lt;p&gt;It’s whether you left the system in a state where they needed to send it.&lt;/p&gt;
</description>
        <pubDate>Mon, 16 Feb 2026 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2026/02/16/the-handoff-test/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2026/02/16/the-handoff-test/</guid>
        
        <category>software</category>
        
        <category>careers</category>
        
        
      </item>
    
      <item>
        <title>I Didn’t Want AI to Be Good at This</title>
        <description>&lt;p&gt;Over the past few months, I’ve been begrudgingly coming around to something I didn’t expect to admit publicly: AI is getting legitimately useful at building software. Not magical. Not autonomous. Not “paste in requirements and press BUILD.” We’re far from that. But the tooling has crossed a threshold where it meaningfully lowers friction in ways I can no longer dismiss.&lt;/p&gt;

&lt;p&gt;What surprised me most isn’t that it can generate code. It’s that the cost of context has dropped. Exploring an idea. Scaffolding a feature. Writing tests. Refactoring awkward logic. Documenting decisions. Iterating without feeling like you just signed a six-month commitment.&lt;/p&gt;

&lt;p&gt;For someone who has spent over two decades maintaining real systems, that shift isn’t abstract.&lt;/p&gt;

&lt;p&gt;It changes the calculus.&lt;/p&gt;

&lt;h2 id=&quot;weve-been-here-before&quot;&gt;We’ve Been Here Before&lt;/h2&gt;

&lt;p&gt;When I first started building web applications in the early 2000s, nearly everything was custom. If your company wanted software that matched how you actually worked, you built it. That was the default.&lt;/p&gt;

&lt;p&gt;Then SaaS matured.&lt;/p&gt;

&lt;p&gt;Suddenly you didn’t have to maintain your own systems. You could buy something that handled seventy to eighty percent of what you needed. On paper, it was obvious. Lower upfront cost. Someone else handles upgrades. Predictable pricing. Fewer late nights worrying about infrastructure.&lt;/p&gt;

&lt;p&gt;For many organizations, that was the right move.&lt;/p&gt;

&lt;p&gt;But over time, something subtle happened. Companies began shaping themselves around generic tools instead of shaping tools around what made them different. The software worked… mostly. But it wasn’t built around their secret sauce. It was built for a “company like yours.”&lt;/p&gt;

&lt;p&gt;That tradeoff made sense when custom felt heavy and risky.&lt;/p&gt;

&lt;p&gt;It feels different now.&lt;/p&gt;

&lt;h2 id=&quot;the-economics-of-context-just-changed&quot;&gt;The Economics of Context Just Changed&lt;/h2&gt;

&lt;p&gt;I’ve built a consultancy around the idea that software is heavy. Context is fragile. Continuity takes intention. We’ve helped organizations maintain and evolve applications that other teams walked away from.&lt;/p&gt;

&lt;p&gt;So when the cost of building drops, an uncomfortable question shows up: if rebuilding gets cheaper, what does that mean for everything we optimized around maintaining?&lt;/p&gt;

&lt;p&gt;Recently, I replaced a small SaaS subscription we were paying about $80 per month for. It was a Slackbot that tracked birthdays and anniversaries for our team and clients. Quiet. Useful. Forgettable.&lt;/p&gt;

&lt;p&gt;Rebuilt it in half a day.&lt;/p&gt;

&lt;p&gt;It’s small. Focused. It does exactly one thing. The maintenance footprint is tiny. No sprawling feature roadmap. No abstraction layers for hypothetical users we’ll never have.&lt;/p&gt;

&lt;p&gt;That constraint is the feature.&lt;/p&gt;

&lt;p&gt;And it got me thinking about something bigger.&lt;/p&gt;

&lt;h2 id=&quot;build-around-your-secret-sauce&quot;&gt;Build Around Your Secret Sauce&lt;/h2&gt;

&lt;p&gt;The most interesting software I’ve worked on over the years wasn’t generic. It wasn’t interchangeable. It was built around what made a particular organization different.&lt;/p&gt;

&lt;p&gt;Their workflows.&lt;br /&gt;
Their judgment.&lt;br /&gt;
Their weird edge cases.&lt;br /&gt;
Their advantage.&lt;/p&gt;

&lt;p&gt;…their secret sauce.&lt;/p&gt;

&lt;p&gt;Recently, I’ve been building a personal project for my band. It’s a custom CRM built for a user base of… me and a few of my bandmates.&lt;/p&gt;

&lt;p&gt;(Yes, &lt;a href=&quot;https://mightymissoula.com&quot;&gt;The Mighty Missoula&lt;/a&gt;. Shameless, I know.)&lt;/p&gt;

&lt;p&gt;The tool tracks relationships with other musicians in the Pacific Northwest, surfaces shows we might want to attend, and nudges us when we haven’t connected in a while. Under the hood, it ingests messy event data from APIs and scraped sites where nothing is structured the same way twice.&lt;/p&gt;

&lt;p&gt;Band names aren’t consistent. Sometimes “The” matters. Sometimes it doesn’t. Sometimes a band in Bolivia shares a name with a group we’ve played with a few miles from home. Event data is messy because humans are messy.&lt;/p&gt;

&lt;p&gt;That mess is not a bug.&lt;/p&gt;

&lt;p&gt;It’s reality.&lt;/p&gt;

&lt;p&gt;A few years ago, I would have assumed weeks of plumbing before getting anything usable. Instead, I’ve been iterating quickly on a Rails 8 app, hanging out inside some of my newer CLI workflows, refining heuristics and tightening assumptions as I go.&lt;/p&gt;

&lt;p&gt;It isn’t perfect. There are bugs. There are mismatches. But the code being produced and then reviewed through my own workflows is better than a lot of code I’ve seen over the years.&lt;/p&gt;

&lt;p&gt;Not the best I’ve ever seen.&lt;/p&gt;

&lt;p&gt;But better than the code written by people who cared “just enough” to get it working and move on.&lt;/p&gt;

&lt;p&gt;The cost of caring more is cheaper now.&lt;/p&gt;

&lt;p&gt;And that changes what’s possible when you’re building around something that actually matters to you.&lt;/p&gt;

&lt;h2 id=&quot;the-seventy-percent-trap&quot;&gt;The Seventy Percent Trap&lt;/h2&gt;

&lt;p&gt;We’ve been paying for a CRM for years. Thousands per year. And if I’m honest, nobody loves using it. We scatter information across other tools anyway. Conversations live in email. Notes in docs. Context in Slack. The CRM becomes the place we update because we’re supposed to.&lt;/p&gt;

&lt;p&gt;It covers maybe seventy percent of what we need.&lt;/p&gt;

&lt;p&gt;But our relationships, our way of thinking about clients, our actual differentiators… they don’t live comfortably inside its abstractions.&lt;/p&gt;

&lt;p&gt;Do we really need to keep spending that money?&lt;/p&gt;

&lt;p&gt;Or have we been tolerating software that flattens our secret sauce because rebuilding felt too heavy?&lt;/p&gt;

&lt;p&gt;This isn’t an argument for rewriting everything. Rebuilding generic systems just because you can is ego. Reclaiming the parts of your workflow that actually define you is strategy.&lt;/p&gt;

&lt;p&gt;SaaS still makes sense for commoditized systems. Payroll. Accounting. Infrastructure. The boring parts that don’t differentiate you.&lt;/p&gt;

&lt;p&gt;But when the workflow itself is your advantage… when it shapes how you think, decide, and relate… settling for seventy percent might be more expensive than it looks.&lt;/p&gt;

&lt;p&gt;Maybe this moment isn’t about replacing everything.&lt;/p&gt;

&lt;p&gt;Maybe it’s about selectively reclaiming the parts that make you different.&lt;/p&gt;
</description>
        <pubDate>Thu, 12 Feb 2026 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2026/02/12/i-didnt-want-ai-to-be-good-at-this/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2026/02/12/i-didnt-want-ai-to-be-good-at-this/</guid>
        
        <category>ai</category>
        
        <category>llms</category>
        
        <category>ruby on rails</category>
        
        <category>saas</category>
        
        <category>software maintenance</category>
        
        <category>startups</category>
        
        <category>consulting</category>
        
        
      </item>
    
      <item>
        <title>Humans in the Loop</title>
        <description>&lt;p&gt;The Oh My Zsh core team recently met up in person at &lt;a href=&quot;https://githubuniverse.com/&quot;&gt;GitHub Universe&lt;/a&gt; in San Francisco. Getting the maintainers into the same room matters more than most people realize. It strips away the abstractions and forces the conversation back to reality… what’s working, what’s breaking, what’s quietly draining energy, and what’s worth protecting long term.&lt;/p&gt;

&lt;p&gt;We spent a good amount of time talking about AI. Not as a culture war or a prediction exercise, but as something already embedded in our day-to-day work. It shows up in our jobs, our creative projects, our personal workflows, and increasingly, in open source contributions to projects like &lt;a href=&quot;https://ohmyz.sh&quot;&gt;Oh My Zsh&lt;/a&gt;. We don’t all share the same worldview on AI usage. That’s fine. Alignment on outcomes matters more than agreement on philosophy.&lt;/p&gt;

&lt;p&gt;What we &lt;em&gt;do&lt;/em&gt; share is stewardship of a project that millions of people rely on. And stewardship means occasionally slowing down long enough to name a problem instead of pretending it’ll sort itself out.&lt;/p&gt;

&lt;h2 id=&quot;the-pattern-we-couldnt-ignore&quot;&gt;The pattern we couldn’t ignore&lt;/h2&gt;

&lt;p&gt;Over the past year, we’ve seen a noticeable increase in contributions that appear to lean heavily on AI tooling. That, by itself, is not a problem. People should use whatever tools help them learn and build. Forks are a playground. Experimentation is healthy.&lt;/p&gt;

&lt;p&gt;What changed was the shape and cost of the work landing on maintainers’ desks.&lt;/p&gt;

&lt;p&gt;We’re seeing larger pull requests from first-time contributors. Broader scope than necessary. Changes that touch parts of the codebase unrelated to the stated goal. PR descriptions and follow-up comments that feel polished but oddly disconnected from the actual implementation. In some cases, we genuinely can’t tell whether the contributor understands the changes they’re proposing.&lt;/p&gt;

&lt;p&gt;That uncertainty matters because review is the bottleneck. Not code generation. Review. When a PR is sprawling, optimistic, and hard to reason about, it doesn’t matter how fast it was produced. It consumes volunteer time. AI doesn’t remove that constraint. In many cases, it amplifies it.&lt;/p&gt;

&lt;h2 id=&quot;we-needed-clarity-not-vibes&quot;&gt;We needed clarity, not vibes&lt;/h2&gt;

&lt;p&gt;At some point, “we’ll handle it case by case” stops being fair to contributors and exhausting for maintainers. We needed something explicit we could point to. Not a moral stance. Not a ban. Just clarity around expectations and accountability.&lt;/p&gt;

&lt;p&gt;The team agreed that I’d take the first pass at researching how other open source projects are approaching AI usage and propose a path forward. We already maintain a private GitHub project where we collaborate on behind-the-scenes decisions… security considerations, moderation questions, and process changes that don’t belong in public threads. That gave us space to pressure-test ideas before bringing anything to the community.&lt;/p&gt;

&lt;p&gt;What I found was interesting. Many projects treat AI as a separate category entirely, with standalone policies layered on top of CONTRIBUTING.md. Others get extremely prescriptive, trying to enumerate when AI is allowed, how much is allowed, and under what circumstances.&lt;/p&gt;

&lt;p&gt;I understand the impulse. But it also felt like a distraction.&lt;/p&gt;

&lt;p&gt;CONTRIBUTING.md already exists to describe how humans contribute responsibly. Tools change. Responsibility doesn’t. Treating AI as something fundamentally different risks avoiding the harder conversation… ownership.&lt;/p&gt;

&lt;h2 id=&quot;where-does-ai-start-and-end-anyway&quot;&gt;Where does AI start and end anyway?&lt;/h2&gt;

&lt;p&gt;If your editor suggests a line of code, is that AI? If autocomplete finishes a function, does it count? If a tool rewrites your PR description, is that different from asking a colleague to proofread it? If a Copilot agent updates a handful of links, is that categorically different from doing a global search-and-replace by hand?&lt;/p&gt;

&lt;p&gt;Bright lines fall apart quickly. In reality, maintainers rely on judgment, pattern recognition, and experience. We always have. AI just makes it cheaper to submit work that &lt;em&gt;looks&lt;/em&gt; plausible without being deeply understood. Volume goes up. Review cost goes up with it.&lt;/p&gt;

&lt;p&gt;So we didn’t try to play detective. We’re not interested in policing tooling. We’re interested in accountability.&lt;/p&gt;

&lt;h2 id=&quot;the-approach-we-took&quot;&gt;The approach we took&lt;/h2&gt;

&lt;p&gt;We chose to integrate guidance about AI-assisted contributions directly into our existing contribution guidelines, instead of treating it as a separate class of work. Concretely, that meant adding a “Working with AI tools” section to &lt;a href=&quot;https://github.com/ohmyzsh/ohmyzsh/blob/master/CONTRIBUTING.md&quot;&gt;our CONTRIBUTING.md&lt;/a&gt; and updating our PR templates so that contributors can disclose how they used AI when it’s relevant to their work.&lt;/p&gt;

&lt;p&gt;The standard is straightforward: if you submit code to core, you own it. You must understand every line. You must be able to explain what changed and why. You must test what you touched and keep the scope focused. And if something breaks, you should be able to debug it without regenerating your way out of the problem.&lt;/p&gt;

&lt;p&gt;That’s not an “AI policy.” That’s basic stewardship.&lt;/p&gt;

&lt;p&gt;If you’re curious, the pull request where we proposed and merged these changes is &lt;a href=&quot;https://github.com/ohmyzsh/ohmyzsh/pull/13520&quot;&gt;ohmyzsh/ohmyzsh#13520&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;a-quick-story-because-this-matters&quot;&gt;A quick story, because this matters&lt;/h2&gt;

&lt;p&gt;We’ve already had people reach out to say they’re done using Oh My Zsh because I once used GitHub Copilot to help update a few links in the codebase and referred to a class of low-effort contributions as “slop.”&lt;/p&gt;

&lt;p&gt;Yes, I could have done those updates manually. I’ve been writing shell scripts for decades. The point wasn’t capability. The point was experimentation.&lt;/p&gt;

&lt;p&gt;I opened a GitHub issue, let the Copilot agent propose a change, and then did what maintainers always do: we reviewed it. A human noticed a problem. We modified the PR. We merged it.&lt;/p&gt;

&lt;p&gt;Exactly like we always have.&lt;/p&gt;

&lt;p&gt;The only difference is that I didn’t need to fire up my editor to replace a handful of URLs. The human checkpoints didn’t disappear. Responsibility didn’t disappear. Review didn’t disappear.&lt;/p&gt;

&lt;p&gt;That’s the entire point.&lt;/p&gt;

&lt;h2 id=&quot;what-hasnt-changed&quot;&gt;What hasn’t changed&lt;/h2&gt;

&lt;p&gt;Nothing gets merged without human review. Every approval still represents a maintainer making an informed decision on behalf of the community. AI doesn’t remove that responsibility. It increases it.&lt;/p&gt;

&lt;p&gt;We reserve the right to ask contributors to explain their code. To show how they tested it. To narrow scope. To revise. To collaborate. And yes… to decline changes that don’t meet the bar.&lt;/p&gt;

&lt;p&gt;Not because we’re precious about the code. Because volunteer time is the most finite resource in open source.&lt;/p&gt;

&lt;p&gt;Oh My Zsh exists to make the terminal a little more delightful for humans, keystroke by keystroke. If your contribution moves us in that direction, we’re excited to review it. If it reads like output optimized for confidence instead of clarity, we’ll say no.&lt;/p&gt;

&lt;p&gt;We’ll be friendly about it. But we’ll say no.&lt;/p&gt;

&lt;p&gt;If you want to follow along, the project lives at &lt;a href=&quot;https://github.com/ohmyzsh/ohmyzsh&quot;&gt;github.com/ohmyzsh/ohmyzsh&lt;/a&gt;, and the public-facing home for documentation and installation is &lt;a href=&quot;https://ohmyz.sh&quot;&gt;ohmyz.sh&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Tools evolve. Stewardship remains a human job.&lt;/p&gt;
</description>
        <pubDate>Tue, 20 Jan 2026 20:03:36 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2026/01/20/humans-in-the-loop/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2026/01/20/humans-in-the-loop/</guid>
        
        <category>opensource</category>
        
        <category>ai</category>
        
        <category>ohmyzsh</category>
        
        
      </item>
    
      <item>
        <title>Why So Serious?</title>
        <description>&lt;p&gt;The question &lt;strong&gt;&lt;a href=&quot;https://www.wired.com/story/ruby-is-not-a-serious-programming-language/&quot;&gt;Sheon Han&lt;/a&gt;&lt;/strong&gt; poses — &lt;em&gt;“Is Ruby a serious programming language?”&lt;/em&gt; — says a lot about what someone thinks programming is supposed to feel like. For some folks, if a tool feels good to use… that must mean it isn’t “serious.”&lt;/p&gt;

&lt;p&gt;Ruby never agreed to that definition. If it did, I missed the memo.&lt;/p&gt;

&lt;p&gt;If you arrived late, you missed a chapter when the language felt like a quiet rebellion. The community was small. The energy was playful. Ruby tapped you on the shoulder and asked what would happen if programming didn’t have to feel intimidating… what might be possible if clarity and joy were allowed.&lt;/p&gt;

&lt;p&gt;The early skeptics were predictable. Java architects. Enterprise traditionalists. Anyone whose identity depended on programming being a stern activity. They said Ruby was unserious. And the community mostly shrugged… because we were busy building things.&lt;/p&gt;

&lt;p&gt;Ruby made programming approachable. Not simplistic… approachable. That distinction matters. It helped beginners see the path forward. It helped small teams build momentum before anxiety caught up. It helped experienced developers rediscover a sense of lightness in their work.&lt;/p&gt;

&lt;p&gt;This is why bootcamps embraced it. Why tiny startups found traction with it. Ruby wasn’t trying to win benchmarks… it was trying to keep you moving. When you’re creating something new, that matters far more than the theoretical purity of your type system.&lt;/p&gt;

&lt;p&gt;And yes… critics love the Twitter example. But look closer. Ruby carried them further than most companies will ever reach. They outgrew their shoes. That’s not an indictment… that’s success.&lt;/p&gt;

&lt;p&gt;In my world… running a software consultancy for a few decades… I’ve never seen a team fail because they chose Ruby. I have seen them fail because they chose complexity. Because they chose indecision. Because they chose “seriousness” over momentum. Ruby just needed to stay out of the way so people could focus on the real work.&lt;/p&gt;

&lt;p&gt;And while folks keep debating its “credibility,” the receipts are plain. Shopify moves billions through Ruby. Doximity supports most physicians in the US with Ruby. GitHub held the world’s source code together for years using Ruby. This isn’t sentiment. This is proof.&lt;/p&gt;

&lt;p&gt;What outsiders often miss is the culture. Ruby attracts people who care how code feels to write and read. Not because of nostalgia… but because most of our careers are spent living inside someone else’s decisions. Joy isn’t a luxury. It’s how sustainable software gets made.&lt;/p&gt;

&lt;p&gt;I don’t know Sheon personally, but I’m guessing we have as much in common about music tastes as we do whether _why’s &lt;em&gt;Poignant Guide to Ruby&lt;/em&gt; made any sense to them. And that’s fine. That’s actually the point.&lt;/p&gt;

&lt;p&gt;And on that note… there’s one thing I genuinely agree with Sheon about. Ruby doesn’t seem to be for them. That’s not a failure of the language. That’s just taste. Some people like jazz. Some like metal. Some prefer the comfort of ceremony. Ruby has never tried to convert anyone. It simply resonates with the people it resonates with.&lt;/p&gt;

&lt;p&gt;Since we’re noting taste, I’ll add something of my own. As an atheist, it feels oddly appropriate to mention my lack of religion here… mostly because it mirrors how strangely irrelevant it was for the article to bring up Matz’s religion at all. It didn’t add context. It didn’t deepen the argument. It was just… there. A detail reaching for meaning that wasn’t actually connected to the point.&lt;/p&gt;

&lt;p&gt;Sheon mentions approaching Ruby without “the forgiving haze of sentimentality.” Fair enough. But the sentiment wasn’t nostalgia. It was gratitude. Gratitude for a language that centers the human being. Gratitude for a community that believes programming can be expressive. Gratitude for a tool that makes the work feel lighter without making it careless.&lt;/p&gt;

&lt;p&gt;But here’s the part the discourse keeps missing… this isn’t just about the past.&lt;/p&gt;

&lt;p&gt;The future of programming is fuzzy for everyone. Anyone claiming to have the master recipe for what’s coming is bullshitting you. The future won’t be owned by one paradigm or one language or one ideology. It’ll be a blend… a messy collage of ideas, old and new, borrowed and rediscovered.&lt;/p&gt;

&lt;p&gt;And in that future… Ruby’s values aren’t relics. They’re an anchor. Readability will matter more as AI writes more code. Maintainability will matter more as products live longer. Joy will matter more as burnout becomes the default state.&lt;/p&gt;

&lt;p&gt;And if you need a reminder that seriousness isn’t the reliable signal people wish it were…&lt;/p&gt;

&lt;p&gt;The serious candidate doesn’t always get elected.&lt;br /&gt;
The serious musician doesn’t always get signed.&lt;br /&gt;
The serious artist doesn’t always sell.&lt;br /&gt;
The serious man doesn’t always find a serious relationship.&lt;br /&gt;
The serious startup doesn’t always find product-market fit.&lt;br /&gt;
The serious engineer doesn’t always write the code that lasts.&lt;br /&gt;
The serious rewrite doesn’t always solve the real problem.&lt;/p&gt;

&lt;p&gt;Culture doesn’t reliably reward the serious. Neither does business.&lt;br /&gt;
It rewards the resonant. The clear. The human. The work that connects.&lt;/p&gt;

&lt;p&gt;Ruby has always leaned toward that side of the craft. Toward the part of programming that remembers people are involved. Toward the part that says maybe the code should serve the team… not the other way around.&lt;/p&gt;

&lt;p&gt;And honestly… I think unserious people will play an important role in the future too. The curious ones. The playful ones. The ones who keep the door propped open instead of guarding it. They’ll keep the industry honest. They’ll keep it human.&lt;/p&gt;

&lt;p&gt;So is Ruby “serious”? I still think that’s the wrong question.&lt;/p&gt;

&lt;p&gt;A better one is… does Ruby still have something meaningful to contribute to the next chapter of software?&lt;/p&gt;

&lt;p&gt;It does.&lt;br /&gt;
And if that makes it “unserious”… maybe that’s exactly why it belongs in the conversation.&lt;/p&gt;

</description>
        <pubDate>Mon, 01 Dec 2025 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2025/12/01/why-so-serious/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2025/12/01/why-so-serious/</guid>
        
        <category>ruby</category>
        
        <category>rubyonrails</category>
        
        <category>dx</category>
        
        <category>programming</category>
        
        <category>culture</category>
        
        <category>future</category>
        
        
      </item>
    
      <item>
        <title>Who Keeps the Lights On?</title>
        <description>&lt;p&gt;Every so often, someone in the Ruby community will ask,&lt;br /&gt;
&lt;strong&gt;&lt;em&gt;“So… what does Planet Argon actually do these days?”&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Fair question.&lt;/p&gt;

&lt;p&gt;We’re not a startup factory.&lt;br /&gt;
We don’t parachute in to build a shiny MVP, disappear, and leave you with a maintenance headache.&lt;br /&gt;
Most of our work begins &lt;em&gt;after&lt;/em&gt; the launch party.&lt;/p&gt;

&lt;p&gt;We get the call when the freelancer moves on.&lt;br /&gt;
When the agency shifts its focus.&lt;br /&gt;
When the last in-house developer, the one who knows every corner of the codebase, decides it’s time to retire.&lt;/p&gt;

&lt;p&gt;It’s rarely a crisis.&lt;br /&gt;
It’s usually a quiet realization…&lt;br /&gt;
“This system runs part of our business. We can’t afford for it to fail… but we don’t need a full-time team to babysit it.”&lt;/p&gt;

&lt;p&gt;That’s where we come in.&lt;/p&gt;

&lt;p&gt;Maybe it’s a nonprofit whose original freelancer built their Rails app years ago and has since moved on. The app still runs their core operations, but no one has touched dependencies or upgrades in ages. They just need a steady hand to bring it up to date and keep it safe.&lt;/p&gt;

&lt;p&gt;Or it’s a company that hired a creative agency to build a beautiful customer portal. The agency has moved on to new projects, and now the client just needs someone dependable to keep it healthy, fix bugs, and plan for what’s next.&lt;/p&gt;

&lt;p&gt;Sometimes it’s a long-time engineer getting ready to retire… the one who built and maintained an internal system that quietly powers part of the business. Leadership doesn’t want to rebuild a whole dev team. They just want to make sure the software ends up in good hands.&lt;/p&gt;

&lt;p&gt;These are the calls we get.&lt;/p&gt;

&lt;p&gt;We take over the systems that keep organizations moving… the internal tools, customer portals, and business-critical Rails apps that quietly power something larger. They’re not &lt;em&gt;the&lt;/em&gt; business, but they’re essential to it.&lt;/p&gt;

&lt;p&gt;We step in, steady the ground, document what exists, and help plan what’s next.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Here’s the part most people miss:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maintaining software isn’t “keeping the lights on.”&lt;br /&gt;
It’s innovation… the quiet kind.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It’s the discipline of protecting momentum instead of resetting it.&lt;br /&gt;
The craft of helping teams evolve what they already have instead of pretending a rewrite will fix their habits.&lt;/p&gt;

&lt;p&gt;Rewrites aren’t always progress.&lt;br /&gt;
Sometimes they’re just an expensive way to forget what you’ve already learned.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;So we focus on the in-between years… the seasons where your software still works but needs care, clarity, and a path forward.&lt;/p&gt;

&lt;p&gt;We modernize what matters, archive what doesn’t, and give teams confidence that their systems will keep running while they decide the bigger questions:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Should we extend this?&lt;/li&gt;
  &lt;li&gt;Replace it?&lt;/li&gt;
  &lt;li&gt;Retire it?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s the second act we specialize in.&lt;br /&gt;
Not the spotlight moment… the sustained performance that follows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Because good software deserves a second act.&lt;/strong&gt;&lt;br /&gt;
And someone dependable to keep the lights on when everyone else chases the next big thing.&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;-about-planet-argon&quot;&gt;🌍 About Planet Argon&lt;/h3&gt;

&lt;p&gt;For the past 20+ years, &lt;a href=&quot;https://www.planetargon.com/&quot;&gt;&lt;strong&gt;Planet Argon&lt;/strong&gt;&lt;/a&gt; has helped organizations sustain and evolve the software they depend on, often Ruby on Rails applications that have outlasted the people who built them.&lt;/p&gt;

&lt;p&gt;We help teams reduce risk, modernize their systems, and build a plan for what’s next.&lt;br /&gt;
If that sounds like a challenge your organization is facing, &lt;a href=&quot;https://www.planetargon.com/contact&quot;&gt;we’d love to hear from you&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Mon, 20 Oct 2025 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2025/10/20/who-keeps-the-lights-on/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2025/10/20/who-keeps-the-lights-on/</guid>
        
        <category>rubyonrails</category>
        
        <category>modernization</category>
        
        <category>maintenance</category>
        
        <category>technicaldebt</category>
        
        
      </item>
    
      <item>
        <title>Architecture for Contraction</title>
        <description>&lt;p&gt;We’ve spent the last decade optimizing for scale. How do we handle more traffic? More users? More engineers? The assumptions were baked in: Growth is coming. Prepare accordingly.&lt;/p&gt;

&lt;p&gt;So we split things apart. We mapped services to teams. We built for the org chart we were &lt;em&gt;about&lt;/em&gt; to have.&lt;/p&gt;

&lt;p&gt;Then 2023 happened. And 2024. And now 2025.&lt;/p&gt;

&lt;p&gt;Turns out, the future isn’t always bigger.&lt;/p&gt;

&lt;h2 id=&quot;the-question-nobodys-asking&quot;&gt;The Question Nobody’s Asking&lt;/h2&gt;

&lt;p&gt;What if your team gets smaller? Not as a failure scenario. As a design constraint.&lt;/p&gt;

&lt;p&gt;What if you architect with the assumption that in three years, you’ll have &lt;em&gt;less&lt;/em&gt; capacity than you do today? Less engineers. Less time. Less runway to maintain complexity.&lt;/p&gt;

&lt;p&gt;How would that change what you build right now?&lt;/p&gt;

&lt;h2 id=&quot;the-playbook-we-borrowed-doesnt-fit&quot;&gt;The Playbook We Borrowed Doesn’t Fit&lt;/h2&gt;

&lt;p&gt;A lot of engineering leadership came from places where growth was a given. Amazon. Google. Netflix. Facebook. They brought their playbooks with them. Microservices. Team autonomy. Service boundaries.&lt;/p&gt;

&lt;p&gt;Great ideas. When you have the teams to staff them.&lt;/p&gt;

&lt;p&gt;The rationale made sense: Big teams step on each other’s toes. You need boundaries so people can move fast without coordination overhead. But most companies don’t grow like FAANG. Most companies hire in waves and contract in cycles.&lt;/p&gt;

&lt;p&gt;Yet we’re still designing like growth is inevitable.&lt;/p&gt;

&lt;p&gt;Most of the splitting we do isn’t for compliance or security. It’s organizational. We split things because we have teams. And teams need ownership. Fair enough. But then what happens when the teams go away and the repos stay?&lt;/p&gt;

&lt;h2 id=&quot;the-repo-to-engineer-ratio-nobody-tracks&quot;&gt;The Repo-to-Engineer Ratio Nobody Tracks&lt;/h2&gt;

&lt;p&gt;Here’s a question I keep coming back to: What’s a healthy ratio of engineers to repos?&lt;/p&gt;

&lt;p&gt;If you have 15 engineers responsible for 50 repos, something’s wrong. Not touching them. Just… responsible for them. Who’s keeping those repos updated? Who’s patching dependencies? Who’s maintaining CI/CD pipelines across all of them?&lt;/p&gt;

&lt;p&gt;Here’s what usually happens: You have a team of 15 engineers and 14 of them aren’t even thinking about those 50 repos. It’s that one hyper-motivated engineer who created them all. The one who knew where everything was. The one who kept mental track of dependencies and deployment quirks.&lt;/p&gt;

&lt;p&gt;Then they got a new job last year.&lt;/p&gt;

&lt;p&gt;Now you have 50 repos. And nobody who really owns them.&lt;/p&gt;

&lt;p&gt;Some companies solve this with platform teams. Dedicated engineers who keep the infrastructure healthy so product teams can focus on features. Great strategy. If you can afford it.&lt;/p&gt;

&lt;p&gt;But what about the teams with 10-15 engineers who already have 30-50 repos and no platform team? They don’t have a platform team. They &lt;em&gt;are&lt;/em&gt; the platform team. And the product team. And the infrastructure team.&lt;/p&gt;

&lt;p&gt;Every repo is another thing to maintain. Another place for secrets to live. Another pipeline to keep green. And when that one person who understood how it all fit together leaves? You’re left holding the bag.&lt;/p&gt;

&lt;h2 id=&quot;redundancy-vs-excess&quot;&gt;Redundancy vs. Excess&lt;/h2&gt;

&lt;p&gt;A resilient team isn’t one that can do everything. It’s one that can absorb change.&lt;/p&gt;

&lt;p&gt;Redundancy is about people, not systems. It means two people can deploy. Three people understand the payment flow. Someone can go on parental leave and the app doesn’t fall apart. It means you’re not hit by the bus factor. That dreaded moment when the one person who knows how something works leaves and you’re stuck reverse-engineering their decisions.&lt;/p&gt;

&lt;p&gt;Redundancy is good. It’s insurance against reality.&lt;/p&gt;

&lt;p&gt;Excess is different. Excess is ten services that could’ve been three. Four repos when one would do. Coordination overhead that only makes sense if you have twice the people.&lt;/p&gt;

&lt;p&gt;We need redundancy. We can’t afford excess.&lt;/p&gt;

&lt;h2 id=&quot;design-for-the-team-you-might-have&quot;&gt;Design for the Team You Might Have&lt;/h2&gt;

&lt;p&gt;Here’s a thought experiment: What if you designed your architecture assuming your team would be &lt;em&gt;half&lt;/em&gt; its current size in two years? Not because you’re pessimistic. Because you’re realistic.&lt;/p&gt;

&lt;p&gt;Would you still split that app into twelve services? Would you still introduce that new framework? Would you still custom-build that thing you could’ve used a library for?&lt;/p&gt;

&lt;p&gt;Maybe. But you’d think harder about it.&lt;/p&gt;

&lt;h2 id=&quot;what-this-looks-like-in-practice&quot;&gt;What This Looks Like in Practice&lt;/h2&gt;

&lt;p&gt;Architecture for contraction means:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prefer monoliths until you have a really good reason not to.&lt;/strong&gt; Splitting things up creates coordination costs. Make sure you can afford them long-term. And “we might hire more engineers” isn’t a good enough reason.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Count your repos.&lt;/strong&gt; Seriously. How many do you have? How many engineers touch them regularly? Is that ratio sustainable if three people leave?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Optimize for fewer moving parts.&lt;/strong&gt; Every service is another thing to deploy, monitor, and maintain. Every repo is another place things can break.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build for handoffs.&lt;/strong&gt; People leave. They go on vacation. They take parental leave. Your architecture should assume the person who built it won’t always be around to maintain it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reduce cognitive overhead.&lt;/strong&gt; If it takes three weeks to onboard someone, your architecture is too complex. Simplify until it takes three days.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resist the urge to “get ahead of it.”&lt;/strong&gt; You don’t need the architecture for 100 engineers when you have 20. You really don’t.&lt;/p&gt;

&lt;h2 id=&quot;so-what-do-we-do&quot;&gt;So What Do We Do?&lt;/h2&gt;

&lt;p&gt;Start asking different questions:&lt;/p&gt;

&lt;p&gt;Not “how do we scale this?” but “how do we keep this simple?”&lt;/p&gt;

&lt;p&gt;Not “what does this look like at 10x?” but “what does this look like at 0.5x?”&lt;/p&gt;

&lt;p&gt;The playbook for growth is everywhere. It’s well-documented. It’s what got funded for a decade.&lt;/p&gt;

&lt;p&gt;But we need a different playbook now. One that optimizes for durability, not growth. One that assumes less, not more. One that doesn’t mistake complexity for sophistication.&lt;/p&gt;

&lt;p&gt;Because the team that can do more with less isn’t just efficient. It’s resilient.&lt;/p&gt;

&lt;p&gt;And resilience is what we need right now.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;need-help-getting-there&quot;&gt;Need Help Getting There?&lt;/h2&gt;

&lt;p&gt;If you’re reading this and thinking “we have 50 repos and the person who understood them all left last year,” you’re not alone.&lt;/p&gt;

&lt;p&gt;A lot of teams find themselves maintaining complexity they didn’t choose and can’t afford to rewrite. You don’t need a full platform team. You need someone who can help you consolidate, simplify, and build redundancy into what you actually have.&lt;/p&gt;

&lt;p&gt;That’s what we do at &lt;a href=&quot;https://www.planetargon.com&quot;&gt;Planet Argon&lt;/a&gt;. We work as a fractional platform team—helping Rails teams reduce complexity, improve maintainability, and build systems that don’t require heroics to keep running.&lt;/p&gt;

&lt;p&gt;If that sounds like something your team could use, &lt;a href=&quot;https://www.planetargon.com/contact&quot;&gt;let’s talk&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Mon, 13 Oct 2025 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2025/10/13/architecture-for-contraction/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2025/10/13/architecture-for-contraction/</guid>
        
        <category>rubyonrails</category>
        
        <category>architecture</category>
        
        <category>teams</category>
        
        
      </item>
    
      <item>
        <title>Organizations, Like Code, Deserve Refactoring</title>
        <description>&lt;p&gt;I’ve been thinking about what happens when open source organizations hit their breaking point… when funding dries up, relationships fracture, and everyone’s scrambling to make sense of what went wrong.&lt;/p&gt;

&lt;p&gt;It turns out, the patterns look familiar.&lt;/p&gt;

&lt;h2 id=&quot;the-organic-growth-problem&quot;&gt;The Organic Growth Problem&lt;/h2&gt;

&lt;p&gt;Open source projects rarely start with governance documents. They start with people solving problems. Infrastructure gets donated. Roles emerge organically. Someone’s AWS account becomes the infrastructure. Someone’s time becomes the bottleneck. Someone’s relationships become the organization.&lt;/p&gt;

&lt;p&gt;This works remarkably well… right up until it doesn’t.&lt;/p&gt;

&lt;p&gt;The moment you try to formalize what’s been organic, you discover all the implicit assumptions. Who actually owns this? Who’s been paying for it? What happens when the donor stops donating? When organizations merge, do they write divorce papers just in case?&lt;/p&gt;

&lt;p&gt;Usually not. Because that feels pessimistic. And besides, everyone’s too busy keeping things running to plan for things falling apart.&lt;/p&gt;

&lt;p&gt;This whole situation has left me thinking about Oh My Zsh. We’ve established some levels of governance, but we have such a simpler situation today than this one. It kind of makes me glad that I get to keep things lean. But we’ve also been participating in open source security trainings, and they’re highlighting a lot of policies we need to get in place. Things that I don’t think most of us ever should worry about until we need to worry about them in our projects.&lt;/p&gt;

&lt;p&gt;Much in the same way that I don’t think a brand new startup should be worrying about their employee handbook before their first few hires. Just enough works enough until it just doesn’t. Most of us haven’t had to cross that divide ourselves.&lt;/p&gt;

&lt;h2 id=&quot;when-multiple-pressures-converge&quot;&gt;When Multiple Pressures Converge&lt;/h2&gt;

&lt;p&gt;Here’s what makes these situations messy: everything happens at once.&lt;/p&gt;

&lt;p&gt;Funding pressures don’t arrive neatly sequenced. They pile up. Employment situations change. Personal relationships deteriorate. Governance questions that seemed theoretical become urgent. And everyone’s making decisions with incomplete information, under time pressure, believing they’re doing the right thing.&lt;/p&gt;

&lt;p&gt;No one’s operating with the full picture. Not the board. Not the staff. Not the donors. Not the community. Everyone’s got fragments and they’re trying to construct meaning from partial data.&lt;/p&gt;

&lt;h2 id=&quot;okay-quick-side-quest&quot;&gt;Okay, Quick Side Quest.&lt;/h2&gt;

&lt;p&gt;At RailsConf 2024, Ruby Central &lt;a href=&quot;https://rubycentral.org/news/anewearforrubycentralevents/&quot;&gt;announced the end of the conference&lt;/a&gt;, then committed to one final edition in 2025. They reached out about sponsorship from Planet Argon.&lt;/p&gt;

&lt;p&gt;I had questions. They were committing significant resources to one more event… at a to-be-determined location on a to-be-determined date. If they were focusing on RubyConf going forward, this felt like an opportunity to refocus sooner rather than later.&lt;/p&gt;

&lt;p&gt;I explained we were exploring ways to contribute more in the next fiscal year, but this particular decision didn’t resonate from a sponsor perspective. As a Rails developer, I appreciated the gesture. As a potential contributor, I saw resources going toward something undefined when regional conferences were already filling the void.&lt;/p&gt;

&lt;p&gt;They explained they hoped the event would help recover losses through sponsorships and ticket sales. It was a bet that doesn’t appear to have paid off.&lt;/p&gt;

&lt;p&gt;(I was honored to be invited to &lt;a href=&quot;https://robbyonrails.com/articles/2025/07/25/railsconf-2025-talk/&quot;&gt;speak at the very last RailsConf&lt;/a&gt; and glad it happened!)&lt;/p&gt;

&lt;p&gt;Here’s what’s interesting: I’m defending Ruby Central’s right to continue existing despite my questions about that decision. Maybe that reveals something about residual trust. About believing in a mission even when you question specific tactical choices.&lt;/p&gt;

&lt;p&gt;Let’s be honest about sponsorships, too. They’re marketing. Logo placement has value… access to an audience, visibility on event pages, search engine rankings. When people call for sponsors to demand their logos be removed, they’re obviously not accounting for the downsides of not getting what you paid for. But I guess if you think Ruby Central is Goliath and irredeemable, then sure, I understand how your logic would arrive at that.&lt;/p&gt;

&lt;p&gt;I just don’t think it’s that simple.&lt;/p&gt;

&lt;p&gt;Anyway, that’s my commit to this story. Let’s get back to the main branch.&lt;/p&gt;

&lt;h2 id=&quot;the-rewrite-temptation&quot;&gt;The Rewrite Temptation&lt;/h2&gt;

&lt;p&gt;When code gets messy, there’s always someone who wants to start over. Throw it all out. Rebuild from scratch. This time we’ll do it right.&lt;/p&gt;

&lt;p&gt;I’ve spent years arguing against the big rewrite. I’m (almost always) firmly &lt;strong&gt;Team Refactor&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Why? Because rewrites discard something precious: the accumulated wisdom embedded in the system. Every line of code represents decisions made, problems solved, edge cases discovered. Someone spent time… their creativity, their care, their effort… building solutions that work.&lt;/p&gt;

&lt;p&gt;Organizations are the same. Years of relationship-building. Trust earned through reliability. Lessons learned from mistakes. Systems developed through trial and error. That institutional knowledge has real value, even when the current state feels messy.&lt;/p&gt;

&lt;p&gt;The rewrite impulse assumes the problem is the thing itself. But usually, the problem is that we’ve outgrown the original structure without evolving it. &lt;strong&gt;The answer isn’t demolition. It’s thoughtful renovation.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Organizations face the same temptation. When things get complicated, there’s a seductive simplicity to &lt;em&gt;“defund and rebuild.”&lt;/em&gt; Start fresh. New governance. New people. This time with clear rules.&lt;/p&gt;

&lt;p&gt;But that’s disconnected from how organizations actually improve. &lt;a href=&quot;https://www.citizensforethics.org/reports-investigations/crew-reports/doges-big-illusion-the-heavy-costs-of-the-trump-administrations-so-called-efficiency/&quot;&gt;You don’t defund your way to better governance&lt;/a&gt;. You iterate. You refactor. You acknowledge the human effort that built what exists, even as you work to make it better.&lt;/p&gt;

&lt;h2 id=&quot;the-stories-we-tell&quot;&gt;The Stories We Tell&lt;/h2&gt;

&lt;p&gt;We love a good David versus Goliath story. But sometimes it’s just a bunch of Davids looking for a Goliath. Sometimes everyone thinks they’re the underdog.&lt;/p&gt;

&lt;p&gt;I’ve been observing this whole situation unfold, and I’ve noticed something: some accounts leave certain details out. Others fill them in. Some people fact-check and never include those details in their telling. It’s a reminder of how often humans… consciously or unconsciously… leave things out that would help inform the rest of us.&lt;/p&gt;

&lt;p&gt;I &lt;a href=&quot;https://bsky.app/profile/robbyonrails.com/post/3lzovmabwd226&quot;&gt;voiced this at the time during the fact-checking&lt;/a&gt;: there are employment policies and laws that mean some things can’t be disclosed. We’ll likely never be able to get the full story. And whether we like it or not… that’s just the world we live in when we’re dealing with organizations and people who are liable for what they disclose to the public.&lt;/p&gt;

&lt;p&gt;Here’s what I think actually happened: funding got uncertain. Decisions got made under pressure. Different people reached different conclusions about the right path forward. Some of those decisions look questionable in retrospect. Some may have been mistakes. None of it required villainy.&lt;/p&gt;

&lt;p&gt;As we seem to be learning, I’m not even convinced each of the actors understood the full context. When you’re dealing with funding pressures, employment changes, infrastructure ownership questions, and relationship breakdowns all at once… it’s entirely possible that no single person had the complete picture.&lt;/p&gt;

&lt;p&gt;It’s healthy to ask ourselves, &lt;em&gt;“what would I do in that situation?”&lt;/em&gt; But we don’t actually know what the situation was. And we probably never will.&lt;/p&gt;

&lt;h2 id=&quot;on-new-organizations&quot;&gt;On New Organizations&lt;/h2&gt;

&lt;p&gt;Someone might ask: if I’m against the rewrite, why do &lt;a href=&quot;https://rubyonrails.org/foundation/planet-argon&quot;&gt;I support the Rails Foundation&lt;/a&gt;? Wasn’t it created in reaction to Ruby Central not inviting DHH to keynote one year?&lt;/p&gt;

&lt;p&gt;That might have been the instigator. The catalyst that prompted DHH to step back and look at the bigger picture again.&lt;/p&gt;

&lt;p&gt;But here’s the thing: the Rails Foundation focuses specifically on promoting and adopting Ruby on Rails itself… educational content, revamping public-facing resources, supporting Rails events, highlighting work within the community. That’s a different mission than Ruby Central’s stewardship of RubyGems, Bundler, and the broader Ruby ecosystem.&lt;/p&gt;

&lt;p&gt;Planet Argon is a contributing member because that clarity of purpose resonates. I can see where our sponsorship dollars are going.&lt;/p&gt;

&lt;p&gt;New organizations can emerge to fill genuine gaps. That’s not a rewrite… it’s recognizing that one organization can’t do everything, and sometimes focused missions serve the community better than trying to be all things to all people.&lt;/p&gt;

&lt;p&gt;The question isn’t whether new organizations should exist. It’s whether we burn down what’s working to build them.&lt;/p&gt;

&lt;h2 id=&quot;what-most-developers-actually-care-about&quot;&gt;What Most Developers Actually Care About&lt;/h2&gt;

&lt;p&gt;Here’s something important to remember: the overwhelming majority of Ruby developers hardly know what to make of this situation. They’re focused on getting their work done. They’re going to stick with what feels familiar to them… until there’s a pain point that directly impacts them.&lt;/p&gt;

&lt;p&gt;And here’s the thing: there are plenty of problems with dependency management. But the source of where we install our gems? That’s far from top of mind. It’s updating the dependencies themselves that we’re all struggling with. That’s where the opportunity is to get people to change how they think about dependency management.&lt;/p&gt;

&lt;p&gt;Infrastructure drama doesn’t drive adoption. Solving real problems does.&lt;/p&gt;

&lt;h2 id=&quot;why-second-acts-matter&quot;&gt;Why Second Acts Matter&lt;/h2&gt;

&lt;p&gt;At Planet Argon, we talk a lot about how good software deserves a second act. Not because change is bad, but because smart, sustainable change respects what came before while building toward what’s next.&lt;/p&gt;

&lt;p&gt;This isn’t nostalgia. It’s not clinging to the past. It’s recognizing that the systems we have… code or organizations… were built with real human effort. People put their creativity and care into making them work. That investment deserves respect, even when the result needs improvement.&lt;/p&gt;

&lt;p&gt;Ruby Central has made decisions I don’t understand. But their track record, across their entire history, trends toward doing good. They’ve stewarded critical infrastructure that millions depend on. They’ve run events that brought the community together. They’ve invested in educational content and tooling.&lt;/p&gt;

&lt;p&gt;They deserve accountability. They deserve tough questions. They deserve pressure to improve governance, communication, and decision-making.&lt;/p&gt;

&lt;p&gt;But they also deserve a second act.&lt;/p&gt;

&lt;p&gt;Not because they’ve been perfect. Because they’ve been consistently trying to do good, and that effort… that accumulated institutional knowledge, those relationships, that trust built over years… has value worth preserving and building on.&lt;/p&gt;

&lt;p&gt;This isn’t about resisting change. It’s about making change that’s sustainable. That learns from what worked. That fixes what didn’t. That acknowledges complexity instead of pretending it away.&lt;/p&gt;

&lt;p&gt;Splinter factions will emerge. New organizations will form with clearer missions. Some of those might be worth supporting too. That’s healthy. That’s how ecosystems evolve.&lt;/p&gt;

&lt;p&gt;But I prefer the refactor. I prefer the second act.&lt;/p&gt;

&lt;h2 id=&quot;sitting-with-uncertainty&quot;&gt;Sitting With Uncertainty&lt;/h2&gt;

&lt;p&gt;Can we resist the clean narrative? Can we sit with not knowing the full story… and acknowledging that nobody involved probably knew it either?&lt;/p&gt;

&lt;p&gt;Can we hold space for multiple people with legitimate grievances, reasonable disagreement about direction, financial pressures creating impossible choices, and decisions made with incomplete information by people acting in good faith?&lt;/p&gt;

&lt;p&gt;This isn’t moral relativism. It’s recognizing that organizational complexity, like code complexity, doesn’t yield to simple explanations.&lt;/p&gt;

&lt;p&gt;Let me be clear: I don’t think Ruby Central handled this well from a public communications perspective. But as a community member, I can accept &lt;a href=&quot;https://rubycentral.org/news/our-stewardship-where-we-are-whats-changing-and-how-well-engage/&quot;&gt;their apology&lt;/a&gt; and move forward. I completely understand if those who had their access revoked don’t feel that way. They have every right not to.&lt;/p&gt;

&lt;p&gt;And I can’t help but wonder: sometimes we make public statements, take positions, feel like we’ve burned bridges that can’t ever be repaired. But in the spirit of honoring where we’ve been… is there a path forward that doesn’t splinter so much?&lt;/p&gt;

&lt;p&gt;Maybe there is. Maybe there isn’t.&lt;/p&gt;

&lt;p&gt;I also understand if some people read this and think, &lt;em&gt;“Robby, you’re a fucking coward.”&lt;/em&gt; Maybe sitting with uncertainty feels like fence-sitting. Maybe nuance feels like avoiding taking a stand.&lt;/p&gt;

&lt;p&gt;But as just a Rails developer… I’ll applaud the effort and cheer them on to find their path. And hope the paths meet again.&lt;/p&gt;

&lt;p&gt;The infrastructure we depend on was built by humans. Maintained by humans. Those humans put real effort… real creativity, real care… into systems that work, even imperfectly.&lt;/p&gt;

&lt;p&gt;Maybe the most honest thing we can do is resist the rewrite impulse. For code and for organizations. Focus instead on thoughtful refactoring that respects what was built while making it better.&lt;/p&gt;

&lt;p&gt;Give good things their second act.&lt;/p&gt;

&lt;p&gt;Complicated things are complicated.&lt;/p&gt;

&lt;p&gt;And that’s okay…&lt;/p&gt;

&lt;p&gt;But what the fuck do I know.&lt;/p&gt;
</description>
        <pubDate>Thu, 09 Oct 2025 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2025/10/09/organizations-like-code-deserve-refactoring/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2025/10/09/organizations-like-code-deserve-refactoring/</guid>
        
        <category>opensource</category>
        
        <category>ruby</category>
        
        <category>rubycentral</category>
        
        <category>governance</category>
        
        <category>refactoring</category>
        
        <category>community</category>
        
        
      </item>
    
      <item>
        <title>Talking Shop with Ruby &amp; Rails Maintainers at Rails World 2025</title>
        <description>&lt;p&gt;As the opening keynote on &lt;a href=&quot;https://rubyonrails.org/world/2025/day-2/opening-keynote&quot;&gt;Day 2 of Rails World 2025&lt;/a&gt;, I had the chance to host a panel with three people who’ve been shaping the direction of both Ruby and Rails from deep within the internals.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Aaron Patterson&lt;/strong&gt; (&lt;a href=&quot;https://github.com/tenderlove&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@tenderlove&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Hiroshi Shibata&lt;/strong&gt; (&lt;a href=&quot;https://github.com/hsbt&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@hsbt&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Jean Boussier&lt;/strong&gt; (&lt;a href=&quot;https://github.com/byroot&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@byroot&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We covered a lot in an hour:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;What they’ve been working on behind the scenes&lt;/li&gt;
  &lt;li&gt;Which areas of &lt;a href=&quot;https://www.ruby-lang.org&quot;&gt;Ruby&lt;/a&gt; and Rails could use more community support&lt;/li&gt;
  &lt;li&gt;The evolving release process for the language&lt;/li&gt;
  &lt;li&gt;Why Hiroshi’s focused on improving the experience for developers on Windows&lt;/li&gt;
  &lt;li&gt;How security fixes are coordinated across multiple versions&lt;/li&gt;
  &lt;li&gt;Performance work related to &lt;a href=&quot;https://github.com/Shopify/yjit&quot;&gt;YJIT&lt;/a&gt; and &lt;a href=&quot;https://github.com/ruby/ruby/tree/master/zjit&quot;&gt;ZJIT&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;JSON parsing performance and compatibility&lt;/li&gt;
  &lt;li&gt;What keeps them motivated to continue maintaining the ecosystem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There’s even a moment where Aaron and Jean get into a friendly disagreement about performance and priorities. If you enjoy technical nuance and sharp perspectives, you’ll appreciate that exchange.&lt;/p&gt;

&lt;p&gt;And yes… I asked Aaron about his favorite &lt;a href=&quot;https://ruby-doc.org/core/Regexp.html&quot;&gt;Regular Expression&lt;/a&gt;. His response did not disappoint.&lt;/p&gt;

&lt;p&gt;It was a fun, thoughtful, and occasionally surprising conversation — and a reminder that Ruby and Rails continue to evolve in the hands of people who care deeply about their future.&lt;/p&gt;

&lt;p&gt;If you weren’t in Amsterdam or want to revisit it, the full panel is now available:&lt;/p&gt;

&lt;div class=&quot;responsive-video-container&quot;&gt;
  &lt;iframe src=&quot;https://www.youtube.com/embed/QaQ9rF9sYHQ&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;p&gt;Also worth pairing with this &lt;a href=&quot;https://podcast.rubyonrails.org/2462975/episodes/17496711-jean-boussier-io-bound-misconceptions&quot;&gt;interview with Jean on the &lt;em&gt;On Rails&lt;/em&gt; podcast&lt;/a&gt;, where we dig into IO-bound workloads, misconceptions, and what it’s like maintaining Rails at scale.&lt;/p&gt;

&lt;p&gt;A solid pairing if you’re curious where the ecosystem is headed next.&lt;/p&gt;

</description>
        <pubDate>Mon, 22 Sep 2025 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2025/09/22/ruby-rails-panel-rails-world-2025/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2025/09/22/ruby-rails-panel-rails-world-2025/</guid>
        
        <category>rubyonrails</category>
        
        <category>ruby</category>
        
        <category>railsworld</category>
        
        <category>conferences</category>
        
        <category>maintainers</category>
        
        
      </item>
    
      <item>
        <title>7 Stages of Software Tech Stack Adoption (You&apos;re Probably in Stage 5)</title>
        <description>&lt;p&gt;I’ve been part of the Ruby on Rails ecosystem for over two decades. I’ve watched teams adopt Rails with wild enthusiasm… evolve their systems… struggle through growing pains… and eventually find themselves in an uncomfortable position; debating whether to abandon the tools that once brought them so much joy.&lt;/p&gt;

&lt;p&gt;I don’t think that’s necessary… or even wise.&lt;/p&gt;

&lt;p&gt;But I do think it’s understandable.&lt;/p&gt;

&lt;p&gt;After working with and talking to hundreds of teams…  many of them using &lt;a href=&quot;https://rubyonrails.org&quot;&gt;Rails&lt;/a&gt;, &lt;a href=&quot;https://laravel.com&quot;&gt;Laravel&lt;/a&gt;, &lt;a href=&quot;https://emberjs.com&quot;&gt;Ember.js&lt;/a&gt;, or even &lt;a href=&quot;https://react.dev&quot;&gt;React&lt;/a&gt;…  I’ve noticed a pattern. A lifecycle of sorts. The way teams internally adopt and evolve their relationship with a technical stack. I’ve seen it reflected in our consulting clients at Planet Argon, the guests on my podcasts (&lt;a href=&quot;https://maintainable.fm&quot;&gt;Maintainable&lt;/a&gt; and &lt;a href=&quot;https://onrails.fm&quot;&gt;On Rails&lt;/a&gt;), and peers who’ve been part of the various peak waves of these ecosystems.&lt;/p&gt;

&lt;p&gt;And while every team is different, the stages of internal tech stack adoption often follow a similar spiral.&lt;/p&gt;

&lt;p&gt;This post is an attempt to describe that spiral.&lt;/p&gt;

&lt;p&gt;Not as a fully baked theory; but as a conversation starter. A mirror. And maybe a compass.&lt;/p&gt;

&lt;p&gt;Because whether your team is building your core product with Rails, or you’re a non-software company maintaining internal tools on Laravel, understanding where you are in this lifecycle might help you understand what comes next.&lt;/p&gt;

&lt;h2 id=&quot;-the-spiral-of-internal-tech-stack-adoption&quot;&gt;🌀 The Spiral of Internal Tech Stack Adoption&lt;/h2&gt;

&lt;p&gt;Before we go deeper, here’s a quick overview of the seven stages I’ve observed. These aren’t fixed; your team might skip around or revisit them multiple times. But in general, this is the pattern I’ve seen:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Adopting&lt;/strong&gt;: 
A small group of enthusiastic engineers selects and introduces the stack while building a prototype or MVP.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Expanding&lt;/strong&gt;: 
The stack proves useful… so it spreads. More features, more developers, more tooling.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Normalizing&lt;/strong&gt;: 
The stack becomes the default. Teams standardize around it. Hiring pipelines and best practices emerge.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Fragmenting&lt;/strong&gt;:
Pain points surface. Teams bolt on new tools or sidestep old ones. Internal consistency erodes.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Drifting&lt;/strong&gt;: 
The stack feels sluggish. Upgrades are deferred. The excitement is gone.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Debating&lt;/strong&gt;: 
Conversations shift to rewrites or migrations. Confidence is shaken.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Recommitting&lt;/strong&gt;: 
Teams pause, reflect, and decide to reinvest in the stack…  and their shared future with it.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Again, these stages aren’t a ladder; they’re a spiral.&lt;/p&gt;

&lt;p&gt;And the question your team has to ask is: &lt;em&gt;Are we spiraling upward… or downward?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Because while &lt;em&gt;The Downward Spiral&lt;/em&gt; &lt;a href=&quot;https://shop.nin.com/collections/the-downward-spiral&quot;&gt;is a great album&lt;/a&gt;, it doesn’t have to be your trajectory.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;️-its-a-cycle-not-a-ladder&quot;&gt;♻️ It’s a Cycle, Not a Ladder&lt;/h2&gt;

&lt;p&gt;It might be tempting to look at this lifecycle and think, &lt;em&gt;“Our goal is to get to the Recommitting stage and stay there forever.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But that’s not how this works.&lt;/p&gt;

&lt;p&gt;Every team will move through these stages multiple times over the lifespan of their product. Shifting priorities, team turnover, organizational pivots… they all create new dynamics that ripple across your tech stack.&lt;/p&gt;

&lt;p&gt;Recommitting isn’t a finish line. It’s an inflection point.
One that clears the fog, sharpens priorities, and invites your team to move forward with intent.&lt;/p&gt;

&lt;p&gt;Just don’t mistake clarity for comfort… the spiral keeps turning.&lt;/p&gt;

&lt;h3 id=&quot;1-adopting&quot;&gt;1. Adopting&lt;/h3&gt;

&lt;p&gt;This is the &lt;em&gt;“we’re just getting started”&lt;/em&gt; phase.&lt;/p&gt;

&lt;p&gt;You’re building a new product… launching an internal tool… or maybe spinning up a proof of concept for an idea that hasn’t fully proven itself yet. The team is small — maybe even just one or two engineers. The surface area of the application is still manageable. You don’t have years of technical debt slowing you down… yet.&lt;/p&gt;

&lt;p&gt;You might not even be around in six months. You might never hire another engineer. Or you might be laying the foundation for a product that hundreds of people will touch every day. It’s all possibility. And possibility is exciting.&lt;/p&gt;

&lt;p&gt;This is when the technical leaders on your team make a recommendation: &lt;em&gt;“Let’s use Rails”&lt;/em&gt;… or Laravel… or Ember… or React. The pitch sounds familiar:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;“We’ll be able to move quickly…”&lt;/li&gt;
  &lt;li&gt;“There’s a strong community…”&lt;/li&gt;
  &lt;li&gt;“This framework is well documented and tested…”&lt;/li&gt;
  &lt;li&gt;“I’ve used it before and had a great experience…”&lt;/li&gt;
  &lt;li&gt;“There are tons of plugins and libraries…”&lt;/li&gt;
  &lt;li&gt;“Hiring should be relatively easy down the road…”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sometimes these decisions are driven by experience. Other times by curiosity or hope. And occasionally, it’s a compromise between a few competing options.&lt;/p&gt;

&lt;p&gt;But one thing is consistent: this decision is rarely debated at the org-wide level. You’re a small team. You’re trusted to figure it out. And if you’re successful, the rest of the company will inherit whatever you chose.&lt;/p&gt;

&lt;p&gt;That’s a big responsibility. And it’s one that deserves more reflection than it often gets.&lt;/p&gt;

&lt;p&gt;In the Adopting stage, there’s a kind of magic. Things are getting built. Confidence is growing. And there’s a sense of momentum. It’s also the phase when you’re most open to experimentation… and least burdened by legacy code or organizational inertia.&lt;/p&gt;

&lt;p&gt;That freedom can be your greatest asset… or a future liability.&lt;/p&gt;

&lt;p&gt;Because once the rest of the org starts depending on what you’ve built… the clock starts ticking. You’re creating gravity. You’re laying down roots. And without realizing it, you’re beginning to shape the lifecycle that follows.&lt;/p&gt;

&lt;p&gt;In this stage, there aren’t many &lt;em&gt;“symptoms”&lt;/em&gt; of trouble to diagnose. But there is one smell worth watching for:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Are you adopting this stack because you believe in its long-term fit… or because it feels exciting right now?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The best early decisions come from enthusiasm and discernment. Not just &lt;em&gt;what’s cool&lt;/em&gt;… but what’s sustainable for the kind of team you plan to become.&lt;/p&gt;

&lt;h3 id=&quot;2-expanding&quot;&gt;2. Expanding&lt;/h3&gt;

&lt;p&gt;Things are starting to work. You’ve shipped your MVP, and the initial product has shown enough promise to warrant further investment.&lt;/p&gt;

&lt;p&gt;This might be when your startup begins raising money. Or if you’re inside a larger organization, leadership is noticing what your team has been building and wants more. More features, more integrations, more engineers… more everything.&lt;/p&gt;

&lt;p&gt;The engineering team starts to grow. Maybe you’re hiring full-time developers. Maybe you’re working with freelancers or external partners. In some cases, it’s a mix of both.&lt;/p&gt;

&lt;p&gt;Your original technical decisions are still holding up. The discussions about what framework to use have faded into the background. The focus now is on growing a team that can execute within the current stack.&lt;/p&gt;

&lt;p&gt;That means job descriptions begin referencing specific RubyGems or open-source tools your team has already adopted. You start asking candidates what types of third-party systems or infrastructure tools they’ve integrated Rails apps with. You want engineers who can jump in and be productive—because momentum is everything right now.&lt;/p&gt;

&lt;p&gt;Onboarding becomes more frequent. The need for documentation increases. There’s more coordination, more meetings, more product specs… and inevitably, more opinions.&lt;/p&gt;

&lt;p&gt;Meanwhile, the original engineers—the ones who picked the stack—might be shifting roles. Some move into management. Others are pulled into meetings or strategy work. Some get recruited away. This changes the center of gravity on the team and fragments some of the original context.&lt;/p&gt;

&lt;p&gt;That’s not necessarily a red flag. It’s just a natural byproduct of growing quickly.&lt;/p&gt;

&lt;p&gt;To support that growth, many teams begin leaning on outside help, bringing in freelancers or working with firms like &lt;a href=&quot;https://www.planetargon.com/services/staff-augmentation&quot;&gt;Planet Argon&lt;/a&gt; to meet delivery goals or stabilize certain parts of the system.&lt;/p&gt;

&lt;h4 id=&quot;signs-your-team-might-be-in-the-expanding-stage&quot;&gt;Signs your team might be in the Expanding stage:&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;You’re actively hiring engineers familiar with your tech stack&lt;/li&gt;
  &lt;li&gt;Job descriptions reference specific RubyGems, tools, or integrations&lt;/li&gt;
  &lt;li&gt;Original contributors are stepping into new roles or leaving the team&lt;/li&gt;
  &lt;li&gt;There’s increased emphasis on onboarding and documentation&lt;/li&gt;
  &lt;li&gt;External agencies or freelancers are augmenting the team&lt;/li&gt;
  &lt;li&gt;Leadership is asking for more features, faster&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;3-normalizing&quot;&gt;3. Normalizing&lt;/h3&gt;

&lt;p&gt;This is when the technology stack starts to fade into the background a bit.&lt;/p&gt;

&lt;p&gt;The stack is no longer a hot topic in team meetings or strategy sessions. It’s just assumed. New team members are expected to figure it out — or are given onboarding resources to get them up to speed. Hiring patterns shift from “must have experience in [Rails/Laravel/etc.]” to “smart generalists welcome.” There’s a belief that the tooling is mature enough and the documentation strong enough that good engineers can pick it up as they go.&lt;/p&gt;

&lt;p&gt;Some of the original engineers may have moved into different roles — promoted up into management, or recruited away to another opportunity. The institutional memory of why certain decisions were made may start to thin out. That early enthusiasm that helped rally the team around the technology stack isn’t quite as present anymore. It’s not gone… it just doesn’t show up in the same way.&lt;/p&gt;

&lt;p&gt;There’s often more stability in this phase. You have patterns, workflows, and some documented best practices. New features get built. Bugs get fixed. Things are fine.&lt;/p&gt;

&lt;p&gt;But “fine” can be a dangerous place to hang out for too long.&lt;/p&gt;

&lt;p&gt;You may have a team of people using the same framework in slightly different ways. You might have multiple approaches to testing, job queues, or background processing. Your linter rules may vary from one directory to another. CI/CD pipelines might have been built with just-in-time needs rather than a longer-term strategy.&lt;/p&gt;

&lt;p&gt;That early foundation? It’s now several layers buried. It hasn’t been actively questioned in a while.&lt;/p&gt;

&lt;p&gt;And that’s okay… for a while.&lt;/p&gt;

&lt;p&gt;But normalization is often when invisible complexity starts to grow. The decisions made years ago shape the defaults everyone works with today — even if those choices don’t make as much sense anymore.&lt;/p&gt;

&lt;h4 id=&quot;signs-your-team-might-be-in-the-normalizing-stage&quot;&gt;Signs your team might be in the Normalizing stage:&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;Job postings don’t require deep expertise in the stack anymore&lt;/li&gt;
  &lt;li&gt;Onboarding materials are assumed to be “good enough”&lt;/li&gt;
  &lt;li&gt;Few people remember why certain architecture choices were made&lt;/li&gt;
  &lt;li&gt;Conversations about tooling are rare or surface-level&lt;/li&gt;
  &lt;li&gt;“That’s just how we do it here” starts to show up more often&lt;/li&gt;
  &lt;li&gt;Engineers spend more time working around problems than fixing root causes&lt;/li&gt;
  &lt;li&gt;Updating documentation feels like a chore, not a habit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a stage where good habits can take root — or fade away. The stack isn’t the story anymore… but maybe it should be, at least from time to time.&lt;/p&gt;

&lt;h3 id=&quot;4-fragmenting&quot;&gt;4. Fragmenting&lt;/h3&gt;

&lt;p&gt;The team has grown… and not just in headcount. Now there are multiple teams. With multiple managers. And multiple priorities. Boundaries are being drawn — to the best of everyone’s ability.&lt;/p&gt;

&lt;p&gt;To support that, new tooling is introduced. Conversations about architecture become more frequent. Maybe you’ve started spinning up microservices or smaller applications to own specific pieces of the platform. Some teams adopt new JavaScript frameworks. Other teams reach for different patterns. And it’s around this time that the term &lt;em&gt;“legacy code”&lt;/em&gt; starts appearing in planning meetings.&lt;/p&gt;

&lt;p&gt;Your single app is starting to feel more like a web of interconnected services… and very few people have a full sense of the whole. The test suite is getting slower. Teams are optimizing for their portion of the platform. Infra work feels like its own company within a company.&lt;/p&gt;

&lt;p&gt;This isn’t necessarily a problem. These adaptations make sense — especially when the organization is scaling quickly. Teams are doing their best to deliver. To reduce friction. To match the autonomy they were promised.&lt;/p&gt;

&lt;p&gt;But… it’s also where cohesion begins to suffer.&lt;/p&gt;

&lt;p&gt;Some teams start using different programming languages. Different infrastructure platforms. Some of that comes from necessity; some of it comes from preference. You may start to hear things like, &lt;em&gt;“This stack just isn’t a good fit for our part of the product.”&lt;/em&gt; And depending on who’s leading those conversations, it might even sound convincing.&lt;/p&gt;

&lt;p&gt;When teams lack a shared understanding of the primary tech stack — or weren’t around during the earlier stages when that decision was made — they’re less likely to feel a sense of responsibility to carry it forward. That’s not a character flaw. It’s context.&lt;/p&gt;

&lt;p&gt;But when enough teams go their own way, the foundation begins to shift. The very things that made your product cohesive and maintainable start to drift apart. And the assumptions driving today’s decisions may not reflect the needs of tomorrow’s (potentially smaller) team.&lt;/p&gt;

&lt;h4 id=&quot;signs-your-team-might-be-in-the-fragmenting-stage&quot;&gt;Signs your team might be in the Fragmenting stage:&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;Multiple frameworks or languages are used across services or features&lt;/li&gt;
  &lt;li&gt;Different teams own different deployment workflows or CI tools&lt;/li&gt;
  &lt;li&gt;Cross-functional planning requires a lot of explanation or diagrams&lt;/li&gt;
  &lt;li&gt;The core team’s shared standards feel outdated or optional&lt;/li&gt;
  &lt;li&gt;Hiring focuses more on team-specific expertise than core stack familiarity&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;5-drifting&quot;&gt;5. Drifting&lt;/h3&gt;

&lt;p&gt;The product still works. Nobody disputes that.&lt;/p&gt;

&lt;p&gt;From the product side of things, everything seems relatively healthy. Customers are using the app. Revenue is coming in. Features still ship… eventually. But behind the scenes, things feel slower. Heavier. Riskier.&lt;/p&gt;

&lt;p&gt;Technical leadership may have changed a few times. The original advocates of the stack have long since moved on. The current team inherited the system — and while they’re keeping things afloat, they haven’t navigated a major stack upgrade before. They’re not sure what’s lurking in the corners.&lt;/p&gt;

&lt;p&gt;People are referring to “technical debt” more frequently. It shows up in roadmap discussions. Sprint planning. Team retros. But even when it’s acknowledged, it’s hard to act on. There’s always something more urgent.&lt;/p&gt;

&lt;p&gt;Monitoring and observability become focal points. You’re collecting tons of logs and metrics, hoping to make data-informed decisions soon… but those decisions are harder to make when your engineers feel blocked. Or overwhelmed. Or unsure where to start.&lt;/p&gt;

&lt;p&gt;Because while the system isn’t broken… it doesn’t feel sustainable.&lt;/p&gt;

&lt;h4 id=&quot;signs-your-team-might-be-in-the-drifting-stage&quot;&gt;Signs your team might be in the Drifting stage:&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;Deployments are slower and increasingly reliant on manual QA&lt;/li&gt;
  &lt;li&gt;Engineers hesitate to touch parts of the codebase without thorough investigation&lt;/li&gt;
  &lt;li&gt;Test suite is long-running and semi-trusted&lt;/li&gt;
  &lt;li&gt;Technical debt is frequently referenced, but rarely prioritized&lt;/li&gt;
  &lt;li&gt;Tooling and documentation feel out of sync with the current product&lt;/li&gt;
  &lt;li&gt;Leadership is requesting clarity on what’s really blocking progress&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;🧰 Need help navigating this phase?&lt;br /&gt;
Check out &lt;a href=&quot;https://maintainablerails.com/&quot;&gt;Maintainable Rails&lt;/a&gt; — my free email course for teams wrestling with legacy code, slow test suites, and Rails upgrades.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;6-debating&quot;&gt;6. Debating&lt;/h3&gt;

&lt;p&gt;The conversations have shifted.&lt;/p&gt;

&lt;p&gt;You’re not just trying to unblock tickets or shave time off deploys anymore… you’re openly asking if your current stack is still the right long-term choice.&lt;/p&gt;

&lt;p&gt;Leadership wants to know what’s possible; and at what cost. Engineers are drawing diagrams to explain years of decisions. Technical assessments are being scoped. Rewrite proposals are floating around. But nothing feels definitive.&lt;/p&gt;

&lt;p&gt;It’s clear that the team is capable… but tired. The stack feels like a weight rather than a boost. And while nobody wants to jump into a rewrite lightly, it’s starting to feel like you’ve run out of easy paths forward.&lt;/p&gt;

&lt;p&gt;Some teams in this stage start experimenting with other frameworks or stacks. They build new tools in a different language. They reference how other companies are “doing it better.” Consultants are brought in to validate hunches… or challenge assumptions. As more data is collected, a clearer picture begins to form — but agreement doesn’t always follow.&lt;/p&gt;

&lt;p&gt;This stage is emotionally charged. Some team members are fiercely loyal to the existing stack. Others feel it’s holding them back. People are reflecting on the past, imagining the future, and questioning whether the current path is still worth walking.&lt;/p&gt;

&lt;p&gt;These aren’t easy conversations. But they’re necessary.&lt;/p&gt;

&lt;h4 id=&quot;signs-your-team-might-be-in-the-debating-stage&quot;&gt;Signs your team might be in the Debating stage:&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;Leadership is asking for estimates to rewrite “just part” of the system&lt;/li&gt;
  &lt;li&gt;Engineers are referencing success stories from other tech stacks&lt;/li&gt;
  &lt;li&gt;Framework upgrade tasks are scoped but rarely completed&lt;/li&gt;
  &lt;li&gt;Tooling and infrastructure conversations dominate team discussions&lt;/li&gt;
  &lt;li&gt;There’s little consensus on whether to modernize or replace existing systems&lt;/li&gt;
  &lt;li&gt;Consultants or contractors are being brought in to offer a second opinion&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;This is a good time to pause and realign. Before making drastic changes, revisit your product vision, business constraints, and the real reasons things feel hard. Sometimes what’s needed isn’t a new stack… but a recommitment to the one you already have.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;7-recommitting&quot;&gt;7. Recommitting&lt;/h3&gt;

&lt;p&gt;Eventually, your team has to choose: double down or start over.&lt;/p&gt;

&lt;p&gt;Recommitting means you’re sticking with the stack you’ve built your product — and your team — around. Not out of nostalgia. Not because it’s perfect. But because it still works.&lt;/p&gt;

&lt;p&gt;It’s not giving up. It’s opting out of the fantasy that a rewrite will magically fix everything.&lt;/p&gt;

&lt;p&gt;You’re not trapped in sunk costs… you’re investing in the stack you already understand. That’s not weakness. That’s leverage.&lt;/p&gt;

&lt;p&gt;Recommitting isn’t about nostalgia. It’s about making a grounded decision:&lt;br /&gt;
We’ve come this far… and we’re not done yet. So let’s make this better.&lt;/p&gt;

&lt;p&gt;That decision unlocks a new type of energy.&lt;/p&gt;

&lt;p&gt;Teams begin to invest in modernization efforts. Upgrades get planned — and actually delivered. Documentation becomes something people reference and improve. Refactors have purpose again, enabling future work rather than just cleaning up messes from the past.&lt;/p&gt;

&lt;p&gt;Hiring starts to shift, too. You’re not just looking for people who know the stack — you’re looking for those who can help align and grow the team around it. Sometimes that means bringing in new voices. Sometimes it means letting go of folks who aren’t on board anymore.&lt;/p&gt;

&lt;p&gt;Some people will move on. Some of them should.&lt;br /&gt;
And in a few cases, you might need to ask them to.&lt;/p&gt;

&lt;p&gt;Your team might get a little smaller. That can be a really healthy thing.&lt;br /&gt;
Fewer silos. Fewer competing visions. More clarity. More momentum.&lt;/p&gt;

&lt;p&gt;Recommitting is a forcing function. It requires saying “no” to the infinite set of other directions your team could go — and doubling down on the direction you’ve chosen.&lt;/p&gt;

&lt;p&gt;It isn’t the end of the story.&lt;br /&gt;
It’s the start of the next cycle… but with your eyes open this time.&lt;/p&gt;

&lt;h3 id=&quot;-how-to-identify-what-stage-youre-in&quot;&gt;🔍 How to Identify What Stage You’re In&lt;/h3&gt;

&lt;p&gt;Before you can spiral upward, you need to know where you’re standing.&lt;/p&gt;

&lt;p&gt;So… what stage are you in?&lt;/p&gt;

&lt;p&gt;More importantly: would your teammates say the same?&lt;/p&gt;

&lt;p&gt;You don’t need a quiz. You need a conversation.&lt;/p&gt;

&lt;p&gt;Here are a few questions to spark that:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;If a new hire joined last week, how would they describe your team’s tech culture?&lt;/li&gt;
  &lt;li&gt;When’s the last time someone challenged the way you work… and did it land?&lt;/li&gt;
  &lt;li&gt;Are you hiring for what the team is today, or what it needs to become?&lt;/li&gt;
  &lt;li&gt;What’s being quietly avoided in planning meetings?&lt;/li&gt;
  &lt;li&gt;Are you moving with purpose, or waiting for permission?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Talk about it. With a peer. In a 1:1. Maybe even in your next retro.&lt;/p&gt;

&lt;hr /&gt;

&lt;h4 id=&quot;-invite-your-team-in&quot;&gt;💬 Invite Your Team In&lt;/h4&gt;

&lt;p&gt;Feel like this article hit a nerve? You’re not alone.&lt;/p&gt;

&lt;p&gt;Drop the link in Slack or email with something simple:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;“This made me think… curious which stage you think we’re in?”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;“Stage 5 kinda hurt. Just me?”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h4 id=&quot;️-try-a-team-pulse-check&quot;&gt;🗳️ Try a Team Pulse Check&lt;/h4&gt;

&lt;p&gt;Post a quick poll:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;“Which stage in this article feels most like us right now?”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Compare results. Disagreements are good. They reveal where your team’s stories aren’t aligned yet.&lt;/p&gt;

&lt;p&gt;Which might be the most important thing to fix.&lt;/p&gt;

&lt;h3 id=&quot;-what-teams-who-recommit-do-differently&quot;&gt;💡 What Teams Who Recommit Do Differently&lt;/h3&gt;

&lt;p&gt;So if your team has chosen to recommit, what happens next?&lt;/p&gt;

&lt;p&gt;You’re not pivoting to the latest shiny thing. You’re saying:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;We’re going to stick with Ruby on Rails (or insert your stack here).&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And if you make that choice, here’s the work:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Center your stack in your culture.&lt;/strong&gt;&lt;br /&gt;
Mention it in onboarding. Reference it in planning docs. Make it part of the story your team tells itself.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Invest in internal education.&lt;/strong&gt;&lt;br /&gt;
Run workshops. Build an internal playbook. Share tips, tricks, and hard-won lessons.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Send people into the community.&lt;/strong&gt;&lt;br /&gt;
Conferences. Meetups. Slack groups. Encourage curiosity. Normalize outreach.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Sponsor the ecosystem.&lt;/strong&gt;&lt;br /&gt;
Not just because it looks good. Because it gives your team a stake in the future of their tools.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Hire differently.&lt;/strong&gt;&lt;br /&gt;
Look for engineers who align with opinions, not just languages. Prioritize mindset over mastery.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;Be the kind of place that attracts the kind of people who are drawn to strong opinions and clear commitments.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Because when you truly recommit, better questions become possible:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;“If we’re still on Rails ten years from now, what might a healthy version of that look like?”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Maybe:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;You’re running the latest stable release of Ruby and Rails… always&lt;/li&gt;
  &lt;li&gt;Your test suite runs against weekly Ruby and Rails builds to catch regressions early&lt;/li&gt;
  &lt;li&gt;You file upstream bug reports before they hit your production code&lt;/li&gt;
  &lt;li&gt;Rails upgrades happen continuously… not as Q3 projects&lt;/li&gt;
  &lt;li&gt;New hires don’t ask, “What version are we on?” because the answer is always “latest”&lt;/li&gt;
  &lt;li&gt;The phrase ‘legacy code’ stops meaning ‘scary’ — and just means ‘old.’&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don’t need to operate at Shopify scale. You just need a stack your team can rally around.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;-wrapping-up-the-spiral-doesnt-end&quot;&gt;🏁 Wrapping Up: The Spiral Doesn’t End&lt;/h2&gt;

&lt;p&gt;If your team feels stuck somewhere between fragmenting, drifting, and debating… you’re not alone.
You’re also not doomed.&lt;/p&gt;

&lt;p&gt;Maybe… just maybe… you’ll never get out of the spiral.
But you can rethink how you move through it.&lt;/p&gt;

&lt;p&gt;Upward. Intentionally. With less friction and more momentum.&lt;/p&gt;

&lt;p&gt;Because recommitting isn’t waving a white flag.
It’s planting one.&lt;/p&gt;

&lt;p&gt;It’s saying: We’ve been through the hard parts. We know what works. Let’s build on that instead of starting over again and again.&lt;/p&gt;

&lt;p&gt;It’s choosing the long road because that’s where the depth is.&lt;/p&gt;

&lt;p&gt;Your team — whether it’s ten engineers or four hundred — has an opportunity to write the next chapter in your stack’s story. And if you do it well, the next set of new hires won’t just inherit your tools. They’ll inherit your clarity.&lt;/p&gt;
</description>
        <pubDate>Sat, 20 Sep 2025 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2025/09/20/seven-stages-of-tech-stack-adoption/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2025/09/20/seven-stages-of-tech-stack-adoption/</guid>
        
        <category>frameworks</category>
        
        <category>rubyonrails</category>
        
        <category>rewrites</category>
        
        <category>refactoring</category>
        
        
      </item>
    
      <item>
        <title>When Your Cache Has a Bigger Carbon Footprint Than Your Users</title>
        <description>&lt;p&gt;Caching in Rails is like duct tape. Sometimes it saves the day. Sometimes it just makes a sticky mess you’ll regret later.&lt;/p&gt;

&lt;p&gt;Nowhere is this more true than in data-heavy apps… like a custom CRM analytics tool that glues together a few systems. You know the type: dashboards full of metrics, funnel charts, KPIs, and reports that customers swear they need “real-time.”&lt;/p&gt;

&lt;p&gt;And that’s where the caching debates begin.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Do we cache the raw data in &lt;a href=&quot;https://redis.io/&quot;&gt;Redis&lt;/a&gt; or &lt;a href=&quot;https://github.com/basecamp/solid_cache&quot;&gt;Solid Cache&lt;/a&gt;?&lt;/li&gt;
  &lt;li&gt;Do we cache entire report payloads?&lt;/li&gt;
  &lt;li&gt;Do we lean on &lt;a href=&quot;https://aws.amazon.com/cloudfront/&quot;&gt;CloudFront&lt;/a&gt; or &lt;a href=&quot;https://www.cloudflare.com/cdn/&quot;&gt;Cloudflare&lt;/a&gt; to hold onto responses?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All valid. All expensive in their own way.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-dashboard-problem&quot;&gt;The Dashboard Problem&lt;/h2&gt;

&lt;p&gt;Your SaaS app has grown to 2,000 customers, each with multiple users.&lt;/p&gt;

&lt;p&gt;For the overwhelming majority, dashboards load just fine. Nobody complains.&lt;/p&gt;

&lt;p&gt;But then your whales log in. The Fortune 500 accounts your sales reps obsess over. Their dashboards pull data from half a dozen APIs, crunch millions of rows, and stitch together a wall of charts. It’s not just a page. It’s practically a data warehouse in disguise.&lt;/p&gt;

&lt;p&gt;These dashboards are slow. Painfully slow. And you hear about it… through support tickets, account managers, and sometimes even a terse email from someone with “Chief” in their title.&lt;/p&gt;

&lt;p&gt;So your engineering team digs in. You fire up &lt;a href=&quot;https://appsignal.com/&quot;&gt;AppSignal&lt;/a&gt;, &lt;a href=&quot;https://www.datadoghq.com/&quot;&gt;Datadog&lt;/a&gt;, or &lt;a href=&quot;https://sentry.io/&quot;&gt;Sentry&lt;/a&gt; and zero in on the slowest dashboard requests. You look at traces, database query timings, and request logs. You chart out the p95 and p99 response times to understand how bad it gets for the biggest customers.&lt;/p&gt;

&lt;p&gt;From there, you start experimenting:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Are we missing database indexes?&lt;/li&gt;
  &lt;li&gt;Are there N+1 queries lurking in the background?&lt;/li&gt;
  &lt;li&gt;Can we preload or memoize expensive calls?&lt;/li&gt;
  &lt;li&gt;Could a few data points be cached individually, without touching the rest?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You squeeze what you can out of the obvious optimizations. Maybe things improve… but not enough.&lt;/p&gt;

&lt;p&gt;So the conversation shifts.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;negotiating-real-time-expectations&quot;&gt;Negotiating “Real-Time” Expectations&lt;/h2&gt;

&lt;p&gt;When your product team actually sits down with those whale customers, the conversation shifts.&lt;/p&gt;

&lt;p&gt;They start by saying: &lt;em&gt;we need real-time data&lt;/em&gt;. But after a little probing, everyone realizes “real-time” doesn’t always mean &lt;em&gt;right now this second&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Maybe what they really need is a reliable snapshot of activity as of the end of the previous day. That’s good enough for the kinds of decisions their leadership is making in the morning meeting. Nobody is making million dollar calls based on a lead that just landed five minutes ago.&lt;/p&gt;

&lt;p&gt;And your team can remind them: there are &lt;em&gt;other&lt;/em&gt; real-time metrics in the system. For example:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;New leads created today.&lt;/li&gt;
  &lt;li&gt;Active users in the past hour.&lt;/li&gt;
  &lt;li&gt;Pipeline changes as they happen.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So now you’ve reframed the dashboard story. Instead of one giant “real-time” data warehouse, you split it into two categories:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Daily rollups.&lt;/strong&gt; Crunch the heavy stuff once a night. End-of-day data is sufficient, and it’s reliable.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Today’s activity.&lt;/strong&gt; Show a few real-time metrics that &lt;em&gt;are&lt;/em&gt; fast to calculate. Give customers the dopamine hit of “live” data without boiling the ocean.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s usually enough to recalibrate expectations. Customers feel like they’re still getting fresh data, while your app no longer sets itself on fire every time a big account logs in.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-1-am-job-and-its-hidden-cost&quot;&gt;The 1 AM Job (and Its Hidden Cost)&lt;/h2&gt;

&lt;p&gt;Armed with that agreement, your team ships the “reasonable” solution most of us have built at least once:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Every night at 1 AM, loop through &lt;strong&gt;all 2,000 customers&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;Generate every dashboard report.&lt;/li&gt;
  &lt;li&gt;Cache the results somewhere “safe.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The next morning, dashboards are instant. Support tickets quiet down. Account reps breathe easier. Everyone celebrates.&lt;/p&gt;

&lt;p&gt;But here’s the kicker: the whales were the problem. The rest of your customers never needed this optimization in the first place. Their dashboards were already fine.&lt;/p&gt;

&lt;p&gt;So now you’ve turned one customer’s problem into everyone’s nightly job. And under the hood, you’ve cranked through hours of CPU, memory, and database load… just to prepare data for customers who won’t even log in later today.&lt;/p&gt;

&lt;p&gt;Worse, you’ve stuffed your background job queue with 2,000 little tasks every night. Which means your queue system—whether it’s &lt;a href=&quot;https://sidekiq.org/&quot;&gt;Sidekiq&lt;/a&gt;, &lt;a href=&quot;https://github.com/basecamp/solid_queue&quot;&gt;Solid Queue&lt;/a&gt;, or &lt;a href=&quot;https://github.com/bensheldon/good_job&quot;&gt;GoodJob&lt;/a&gt;—is spending precious time juggling busy work instead of focusing on the jobs that actually matter. And when those queues get stuck, or a worker crashes, you’re left wading through a mountain of pending jobs just to catch up.&lt;/p&gt;

&lt;p&gt;This is what I call &lt;strong&gt;Cache Pollution&lt;/strong&gt;: the buildup of unnecessary caching work that bloats your systems, slows down your queues, and leaves your caching strategy with a far bigger carbon footprint than it needs to. Another benefit of tackling Cache Pollution early is future flexibility — you might eventually solve the computation challenges in a different way, and you won’t be anchored to big, scary scheduled tasks that churn through &lt;em&gt;all&lt;/em&gt; of your customers every night.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;frequency-matters-more-than-we-admit&quot;&gt;Frequency Matters More Than We Admit&lt;/h2&gt;

&lt;p&gt;Do these reports need to run every single day… or only on weekdays when your customers actually log in?&lt;/p&gt;

&lt;p&gt;If your traffic drops on Saturdays and Sundays, consider a lighter schedule. Or even none at all. Because “slow” isn’t so slow when almost nobody is around. A BigCorp admin poking the dashboard on Sunday morning might be fine with an on-demand render… especially if the weekday experience is snappy.&lt;/p&gt;

&lt;p&gt;And here’s another angle: if your scheduled job runs at 1 AM, that means when a BigCorp user logs in later that same day, they’re still looking at data that’s less than 24 hours old. For most business use cases, that’s plenty. You don’t need to rerun heavy jobs every few hours just because you &lt;em&gt;can&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;This is all about right-sizing &lt;em&gt;frequency&lt;/em&gt;:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Weekday cadence:&lt;/strong&gt; nightly rollups for whales; maybe twice a day if usage demands it.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Weekend cadence:&lt;/strong&gt; pause, or run a narrower subset.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Holiday mode:&lt;/strong&gt; same idea… different switch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your dashboard code doesn’t rely on the cache to render, you keep the option to &lt;em&gt;not&lt;/em&gt; precompute. That flexibility is where the savings live. As the business grows, the cost of overly eager schedules grows with it… so design for dials, not hard-coded habits.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;other-things-to-consider-with-recurring-tasks&quot;&gt;Other Things to Consider with Recurring Tasks&lt;/h2&gt;

&lt;p&gt;One more question to ask about recurring scheduled jobs: do you really need to iterate through &lt;strong&gt;all users&lt;/strong&gt; or &lt;strong&gt;all organizations&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;In many cases, the answer is no. Most customers don’t trigger the conditions that require a heavy recompute. Yet teams often design jobs to blast across every top-level object in the database, every night, without discrimination.&lt;/p&gt;

&lt;p&gt;Instead, look for signals that help you scope the work down:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Which organizations actually logged in today?&lt;/li&gt;
  &lt;li&gt;Which customers have datasets large enough to need optimization?&lt;/li&gt;
  &lt;li&gt;Which accounts crossed a threshold since the last run?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By narrowing the set of work each job touches, you cut down on wasted compute, reduce queue congestion, and avoid the kind of Cache Pollution that grows silently as your business scales.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;clever-tricks-weve-seen&quot;&gt;Clever Tricks We’ve Seen&lt;/h2&gt;

&lt;p&gt;The trick isn’t just caching everything for everyone. It’s knowing &lt;em&gt;who&lt;/em&gt; to cache for and &lt;em&gt;when&lt;/em&gt;.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Selective pre-caching.&lt;/strong&gt; Only build nightly rollups for your whales. Maybe 50 out of 2,000 customers. Everyone else can render on demand, which was fine all along.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Cache on login.&lt;/strong&gt; If you know a user from BigCorp is signing in, enqueue a background job to warm up their dashboard before they hit it. You can even anticipate who they are based on a cookie value when they land on the Sign In page — before they’ve had a chance to trigger 1Password or type in their credentials, the system is already working behind the scenes to prep their dashboard. Even a 10–20 second head start can smooth the experience.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Cache on demand… with a fallback.&lt;/strong&gt; If cached data is missing, build fresh on the spot. Outages happen when teams assume the cache will always be there.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And here’s a bonus: if your job fails at 1 AM, re-running it for 50 customers is a whole lot faster than crawling through 2,000.&lt;/p&gt;

&lt;p&gt;Extra credit: scope your scheduled tasks so that when a customer crosses a certain threshold—say, user count, dataset size, or request volume—they automatically join the “whale” group. No manual babysitting required.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;other-patterns&quot;&gt;Other Patterns&lt;/h2&gt;

&lt;p&gt;Not all caching challenges look like dashboards.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;h3 id=&quot;case-study-the-press-release-problem&quot;&gt;Case Study: The Press Release Problem&lt;/h3&gt;
  &lt;p&gt;We once managed a public-facing site for a massive brand. Whenever they dropped a big press release, it spread fast across social media. Traffic would spike within minutes.&lt;/p&gt;

  &lt;p&gt;Of course, that’s when the CEO would notice a typo. Or the PR team would need to update a paragraph to reflect a question from the media. Despite their editorial workflows, changes still had to happen &lt;em&gt;after&lt;/em&gt; publication.&lt;/p&gt;

  &lt;p&gt;So we had to get clever. We couldn’t cache those fresh pages for hours. Instead, we used a sliding window approach:&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;First 5 minutes:&lt;/strong&gt; cache for 30 seconds at a time.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;After 5 minutes:&lt;/strong&gt; increase to 1 minute.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;After 10 minutes:&lt;/strong&gt; increase to 2 minutes.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;After 20 minutes:&lt;/strong&gt; increase to 5 minutes.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;After 6 hours:&lt;/strong&gt; safe to cache for an hour.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;After a day:&lt;/strong&gt; cache for a few hours at a time.&lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;This let us protect our Rails servers from massive traffic spikes when a new article was spreading fast, while still giving editors the ability to push corrections through quickly. Older articles, once stable, could safely sit in &lt;a href=&quot;https://www.akamai.com/&quot;&gt;Akamai&lt;/a&gt;’s cache for hours.&lt;/p&gt;

  &lt;p&gt;At the time, Akamai could take up to seven minutes to guarantee a purge across their global network. Not ideal. We had to plan for that lag. Today, most CDNs can purge instantly, but back then… it was a constraint we had to design around.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;a-final-challenge&quot;&gt;A Final Challenge&lt;/h2&gt;

&lt;p&gt;A lot of what we’ve talked about here comes down to avoiding &lt;strong&gt;Cache Pollution&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That’s the unnecessary churn your system takes on when it generates data nobody asked for. It’s the background job queue bloated with thousands of tasks that fight with more important work. It’s the 1 AM process chewing through CPU just to prep dashboards for customers who never log in.&lt;/p&gt;

&lt;p&gt;Cache Pollution looks like optimization on the surface… but underneath it’s just waste.&lt;/p&gt;

&lt;p&gt;So before your team spins up the next caching project, stop and ask:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Who really needs this cache?&lt;/li&gt;
  &lt;li&gt;How fresh does it need to be?&lt;/li&gt;
  &lt;li&gt;What happens if the cache isn’t there?&lt;/li&gt;
  &lt;li&gt;Do we need to run it this often, or for this many customers?&lt;/li&gt;
  &lt;li&gt;Could we scale down the busy work instead of scaling it up?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because the goal isn’t just faster dashboards. The goal is to keep your caching strategy lean, resilient, and focused — instead of leaving behind a trail of Cache Pollution that grows with every new customer you add.&lt;/p&gt;
</description>
        <pubDate>Thu, 28 Aug 2025 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2025/08/28/cache-carbon-footprint/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2025/08/28/cache-carbon-footprint/</guid>
        
        <category>rubyonrails</category>
        
        <category>caching</category>
        
        <category>performance</category>
        
        <category>consulting</category>
        
        
      </item>
    
      <item>
        <title>The Internal Tooling Maturity Ladder</title>
        <description>&lt;p&gt;&lt;em&gt;(or: How I’m Learning to Step Back So We Can Move Forward)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here’s a pattern I’ve been contributing to more than I’d like to admit:&lt;/p&gt;

&lt;p&gt;Someone on the team proposes a new internal tool. There’s a clear need. There’s momentum. Conversations start about how we’ll build it… what tools we’ll use… where it’ll live… whether it might become client-facing someday.&lt;/p&gt;

&lt;p&gt;It hasn’t been built yet, but we’re already architecting the scaffolding.&lt;/p&gt;

&lt;p&gt;And that’s usually when I step in.&lt;/p&gt;

&lt;p&gt;Not with a thumbs-up. Not with funding. But with questions. The kind that start with, &lt;em&gt;“What if we didn’t build this?”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It’s not fun. It’s not fair. And it’s not how I want to lead.&lt;/p&gt;

&lt;p&gt;By that point, people are invested. They’ve done the thinking. They’ve shared the idea. They’ve taken a risk. And now I’m asking them to scale it back—or stop entirely.&lt;/p&gt;

&lt;p&gt;This is me taking responsibility for that pattern.&lt;/p&gt;

&lt;p&gt;So I did the only thing I know to do in moments like this: I wrote it down.&lt;br /&gt;
We now have a model. Internally, we call it &lt;strong&gt;The Internal Tooling Maturity Ladder.&lt;/strong&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;-the-ladder-in-plain-english&quot;&gt;🪜 The Ladder (in Plain English)&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Level 0 – One-off Manual Script&lt;/strong&gt;&lt;br /&gt;
Something one person runs on their own machine to save time or reduce repetitive work.&lt;br /&gt;
Maybe it lives in an unsaved file or as a task in Alfred or Automator.&lt;br /&gt;
It’s not elegant—but it works.&lt;br /&gt;
You run it manually. You copy-paste the result. You feel smug for five minutes.&lt;br /&gt;
No repo. No expectations. No long-term promises.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Example: A quick Ruby or Bash script that tallies something from an API and drops it into Slack.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;strong&gt;Level 1 – Shared Manual Script&lt;/strong&gt;&lt;br /&gt;
You cleaned it up. You wrote a little README. You dropped it in a shared Gist or Google Drive.&lt;br /&gt;
It’s still manually triggered, but now others can use it too—if they read the instructions.&lt;br /&gt;
It’s still lightweight. Still safe.&lt;br /&gt;
And it’s often where great tools should stay.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Example: A command-line tool that a few team members can run locally, maybe to generate a report or fetch usage stats.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;strong&gt;Level 2 – Scheduled Automation&lt;/strong&gt;&lt;br /&gt;
Now we’re automating things.&lt;br /&gt;
It runs on a schedule—maybe through Zapier, a GitHub Action, or a scheduled Rake task.&lt;br /&gt;
No UI. No buttons. Just automated updates that go where we already spend time.&lt;br /&gt;
Slack. Google Sheets. Email.&lt;br /&gt;
These tools hum quietly in the background, doing one job well.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Example: A script that posts weekly project stats to a Slack channel every Monday morning.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;strong&gt;Level 3 – Lightweight Internal Service&lt;/strong&gt;&lt;br /&gt;
Now we’re getting fancy.&lt;br /&gt;
This has a small UI. A form. A dashboard. Maybe some configuration options.&lt;br /&gt;
It needs hosting. Credentials. Some thought about security.&lt;br /&gt;
It’s still simple enough that one person can manage it—but now it’s a thing.&lt;br /&gt;
And it needs some care.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Example: A mini app that lets the team search across client project docs or surface stale Jira tickets.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;strong&gt;Level 4 – Fully Hosted Internal Product&lt;/strong&gt;&lt;br /&gt;
This is a real web app.&lt;br /&gt;
It’s deployed. It has a frontend and a backend. It has users. Sessions. Maybe even tests (hopefully).&lt;br /&gt;
It needs to be maintained. Updated. Monitored.&lt;br /&gt;
It might solve a meaningful problem—but it’s not free.&lt;br /&gt;
This is the top of the ladder for a reason.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Example: A polished internal dashboard that’s become a critical part of day-to-day operations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;start-lower&quot;&gt;Start Lower&lt;/h3&gt;

&lt;p&gt;This isn’t a blueprint. It’s a conversation starter.&lt;/p&gt;

&lt;p&gt;The higher you go, the more you commit—time, infrastructure, expectations.&lt;br /&gt;
So we’re learning to start lower on the ladder.&lt;br /&gt;
To earn our way up.&lt;br /&gt;
To see if people care before we care too much.&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;why-it-matters&quot;&gt;Why It Matters&lt;/h3&gt;

&lt;p&gt;Every internal tool is a promise.&lt;br /&gt;
To support it. To upgrade it. To explain it to the next person who inherits it.&lt;/p&gt;

&lt;p&gt;And sometimes… the smallest version of the tool is all we need.&lt;/p&gt;

&lt;p&gt;A Slack post.&lt;br /&gt;
A spreadsheet.&lt;br /&gt;
A script that helps one person do their job 10% faster.&lt;/p&gt;

&lt;p&gt;Not everything needs a UI.&lt;br /&gt;
Not everything needs a repo.&lt;br /&gt;
And not everything needs me to be the one who calls time on the project two weeks in.&lt;/p&gt;

&lt;p&gt;This post isn’t about our internal model. Not really.&lt;/p&gt;

&lt;p&gt;It’s about building fewer things that trap us.&lt;br /&gt;
And creating more space to experiment without regret.&lt;/p&gt;

&lt;p&gt;If you’ve found yourself playing the role of reluctant gatekeeper… you’re not alone.&lt;br /&gt;
This ladder is helping me find a better way.&lt;/p&gt;

&lt;p&gt;One rung at a time.&lt;/p&gt;
</description>
        <pubDate>Wed, 13 Aug 2025 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2025/08/13/internal-tooling-maturity-ladder/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2025/08/13/internal-tooling-maturity-ladder/</guid>
        
        <category>internaltools</category>
        
        <category>leadership</category>
        
        <category>consulting</category>
        
        
      </item>
    
      <item>
        <title>The Features We Loved, Lost, and Laughed At: My RailsConf 2025 Talk Is Now Online</title>
        <description>&lt;p&gt;If you didn’t make it to &lt;a href=&quot;https://railsconf.org/&quot;&gt;RailsConf&lt;/a&gt; this year…or couldn’t make it to my talk…I’ve got good news: the full video is now live.&lt;/p&gt;

&lt;p&gt;🎥 &lt;a href=&quot;https://www.youtube.com/watch?v=sjvsBvugcf4&quot;&gt;Watch it here&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Preparing for this talk was one of the most nostalgic (and sometimes absurd) research dives I’ve done in years. I pitched &lt;em&gt;The Features We Loved, Lost, and Laughed At&lt;/em&gt; thinking it would be easy to uncover a long list of removed or weird Rails features to poke fun at.&lt;/p&gt;

&lt;p&gt;Turns out? They weren’t so easy to find.&lt;/p&gt;

&lt;p&gt;Rails hasn’t just thrown things away. It’s looped. It’s learned. It’s come back to old ideas and made them better.&lt;/p&gt;

&lt;p&gt;In the talk, I trace that evolution…using code examples and stories from the early days of &lt;a href=&quot;https://guides.rubyonrails.org/active_record_basics.html&quot;&gt;ActiveRecord&lt;/a&gt;, form builders, &lt;a href=&quot;https://apidock.com/rails/ActionView/Helpers/PrototypeHelper/observe_field&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;observe_field&lt;/code&gt;&lt;/a&gt;, semicolon routes, and even a few lesser-known misadventures involving matrix parameters.&lt;/p&gt;

&lt;p&gt;I touch on features like &lt;a href=&quot;https://github.com/rails/rails-observers&quot;&gt;Observers&lt;/a&gt; (invisible glue, invisible bugs) and &lt;a href=&quot;https://github.com/rails/activeresource&quot;&gt;ActiveResource&lt;/a&gt;…which wasn’t confusing so much as it was &lt;em&gt;optimistic&lt;/em&gt;. It assumed the APIs you were consuming were designed with Rails-like conventions in mind. That was rarely the case.&lt;/p&gt;

&lt;p&gt;I also explore what Rails has taught us about developer happiness, what it means to build with care, and what the community keeps refining (and laughing about).&lt;/p&gt;

&lt;p&gt;Here’s a quick example: I once wrote an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;InvoiceObserver&lt;/code&gt; that did four different things silently…and when it broke, it took hours to even figure out where the logic lived. Magical until it wasn’t.&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;why-look-back-now&quot;&gt;Why Look Back Now?&lt;/h3&gt;

&lt;p&gt;With RailsConf coming to a close, it felt like the right moment to reflect not just on the framework…but on how &lt;em&gt;we&lt;/em&gt; evolve alongside it.&lt;/p&gt;

&lt;p&gt;Rails doesn’t just chase trends. It revisits its own decisions and asks: &lt;em&gt;“What still brings us joy?”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That’s a rare trait in software. And it’s why Rails still feels like home for so many of us.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“Rails doesn’t just move forward…it reflects. It loops. It asks: Where’s the friction? What can we make effortless again?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you’re newer to the framework, or just curious what Rails has quietly taught us over the years…I hope you find something here to smile at.&lt;/p&gt;

&lt;div class=&quot;responsive-video-container&quot;&gt;
  &lt;iframe src=&quot;https://www.youtube.com/embed/sjvsBvugcf4&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;hr /&gt;

&lt;p&gt;I’m grateful to my Ruby friends…some old, some new…who shared memories, weird bugs, screenshots, mailing list lore, and just the right amount of healthy skepticism while I was putting this together.&lt;/p&gt;

&lt;p&gt;If you’ve ever written &lt;a href=&quot;https://apidock.com/rails/ActionView/Helpers/JavaScriptHelper/button_to_function&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;button_to_function&lt;/code&gt;&lt;/a&gt; or &lt;a href=&quot;https://guides.rubyonrails.org/active_record_querying.html#dynamic-finders&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;find_by_title_and_category_and_status&lt;/code&gt;&lt;/a&gt;, this one’s for you.&lt;/p&gt;
</description>
        <pubDate>Fri, 25 Jul 2025 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2025/07/25/railsconf-2025-talk/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2025/07/25/railsconf-2025-talk/</guid>
        
        <category>rubyonrails</category>
        
        <category>railsconf</category>
        
        <category>nostalgia</category>
        
        <category>observers</category>
        
        <category>activeresource</category>
        
        
      </item>
    
      <item>
        <title>Stop Pretending You&apos;re the Last Developer</title>
        <description>&lt;p&gt;&lt;a href=&quot;https://rubyonrails.org/&quot;&gt;Ruby on Rails&lt;/a&gt; is often celebrated for how quickly it lets small teams build and ship web applications. I’d go further: it’s the best tool for that job.&lt;/p&gt;

&lt;p&gt;Rails gives solo developers a powerful framework to bring an idea to life—whether it’s a new business venture or a behind-the-scenes app to help a company modernize internal workflows.&lt;/p&gt;

&lt;p&gt;You don’t need a massive team. In many cases, you don’t even need a team.&lt;/p&gt;

&lt;p&gt;That’s the magic of Rails.&lt;/p&gt;

&lt;p&gt;It’s why so many companies have been able to start with just one developer. They might hire a freelancer, a consultancy, or bring on a full-time engineer to get something off the ground. And often, they do.&lt;/p&gt;

&lt;p&gt;Ideas get shipped. The app goes live. People start using it. The team adds features, fixes bugs, tweaks things here and there. Maybe they’ve got a &lt;a href=&quot;https://www.atlassian.com/agile/kanban&quot;&gt;Kanban board&lt;/a&gt; full of tasks and ideas. Maybe they don’t. Either way, the thing mostly works.&lt;/p&gt;

&lt;p&gt;Until something breaks.&lt;/p&gt;

&lt;p&gt;Someone has to redo work. A weird bug eats some data. A quick patch is deployed. Then someone in management asks the timeless question:
&lt;strong&gt;“How do we prevent this from happening again?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Time marches on. Other engineers come and go, but the original developer is still around. Still knows the system inside and out. Still putting out fires.&lt;/p&gt;

&lt;p&gt;Eventually, the company stops backfilling roles. There’s not quite enough in the backlog to justify it. And besides, everything important seems to be in one person’s head. That person becomes both the system’s greatest asset—and its biggest risk.&lt;/p&gt;

&lt;p&gt;This is usually about the time our team at &lt;a href=&quot;https://www.planetargon.com/&quot;&gt;Planet Argon&lt;/a&gt; gets a call.&lt;/p&gt;

&lt;p&gt;Sometimes, it’s the developer who reaches out. They’re burned out. They miss collaborating with others. They’re tired of carrying the whole thing.
Other times, it’s leadership. Things are moving too slowly. Tickets aren’t getting closed. The bugs they reported last quarter still haven’t been addressed. They’re worried about what happens if that one dev goes on vacation. Or leaves.&lt;/p&gt;

&lt;p&gt;They’ve tried bringing in outside help… but nothing sticks. The long-term engineer keeps saying new people “don’t get it.”&lt;/p&gt;

&lt;p&gt;By the time we step in, we’ve seen some version of this story many, many times.&lt;/p&gt;

&lt;p&gt;Documentation? Sparse or outdated.&lt;br /&gt;
Tests? There are &lt;em&gt;some&lt;/em&gt;, but good luck trusting them.&lt;br /&gt;
Git commit messages? A series of “fixes” and “WIP”.&lt;br /&gt;
Hardcoded credentials? Of course.&lt;br /&gt;
Onboarding materials? There’s nobody to onboard.&lt;br /&gt;
Rails upgrades? &lt;a href=&quot;https://www.planetargon.com/services/ruby-on-rails-upgrade&quot;&gt;“We’ll get to it eventually… maybe.”&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;None of this is uncommon. It’s what happens when good engineers work in isolation for too long, often doing their best to keep things afloat without much help or recognition. Rails makes it possible. But that doesn’t make it sustainable.&lt;/p&gt;

&lt;h2 id=&quot;rails-is-a-one-person-framework-but-very-few-apps-are-one-person-apps-forever&quot;&gt;Rails is a one-person framework. But very few apps are one-person apps forever.&lt;/h2&gt;

&lt;p&gt;If your project’s lucky enough to live past MVP…&lt;br /&gt;
If it’s important enough for people to rely on…&lt;br /&gt;
If it’s been around long enough to be worth maintaining…&lt;/p&gt;

&lt;p&gt;…then it’s worth being honest about where things stand.&lt;/p&gt;

&lt;p&gt;Write the commit messages.&lt;br /&gt;
Leave a few breadcrumbs in the README.&lt;br /&gt;
Comment that weird regex.&lt;/p&gt;

&lt;p&gt;And while you’re in there…&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Write a test for that one thing that keeps breaking. You know the one.&lt;/li&gt;
  &lt;li&gt;See if you can finally remove that outdated gem dependency that’s blocking your &lt;a href=&quot;https://www.planetargon.com/services/ruby-on-rails-upgrade&quot;&gt;Rails upgrade&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Strip out that old &lt;a href=&quot;https://backbonejs.org/&quot;&gt;Backbone.js&lt;/a&gt; interface. Nobody’s going to miss it.&lt;/li&gt;
  &lt;li&gt;Delete the dead code you’ve been tiptoeing around. Don’t pass it on like some haunted heirloom.&lt;/li&gt;
  &lt;li&gt;Truncate that giant table full of data nobody’s touched since the Obama administration.&lt;/li&gt;
  &lt;li&gt;Rip out &lt;a href=&quot;https://activeadmin.info/&quot;&gt;ActiveAdmin&lt;/a&gt; and replace it with a few boring CRUD pages. It might actually feel good to write normal Rails again—and if you miss the shortcuts, &lt;a href=&quot;https://generators.dev/&quot;&gt;build your own generators&lt;/a&gt; to spin up the patterns you actually use.&lt;/li&gt;
  &lt;li&gt;And please, &lt;a href=&quot;https://blog.planetargon.com/blog/entries/organizing-the-chaos-why-password-managers-make-developer-onboarding-easier&quot;&gt;use a password manager&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren’t heroic efforts. They’re just little acts of maintenance. Little acts of kindness… for the next person.&lt;/p&gt;

&lt;p&gt;If you’re working in a legacy Rails app—or trying not to create one—you might like &lt;a href=&quot;https://maintainablerails.com/&quot;&gt;Maintainable Rails&lt;/a&gt;. It’s a free, no-fluff email course full of real-world tips for reducing technical debt and taming older Rails code. Sent only when it’s useful. No spam, no guilt.&lt;/p&gt;

&lt;p&gt;And if it already feels like a mess? You’re not alone. There are &lt;a href=&quot;https://www.planetargon.com/services&quot;&gt;teams like ours&lt;/a&gt; who’ve made a business out of helping projects like yours find their second wind.&lt;/p&gt;
</description>
        <pubDate>Wed, 16 Jul 2025 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2025/07/16/stop-pretending-youre-the-last-developer/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2025/07/16/stop-pretending-youre-the-last-developer/</guid>
        
        <category>rubyonrails</category>
        
        <category>technicaldebt</category>
        
        <category>consulting</category>
        
        
      </item>
    
      <item>
        <title>A New Rails Podcast: On Rails</title>
        <description>&lt;p&gt;Today marks the launch of &lt;a href=&quot;https://podcast.rubyonrails.org&quot;&gt;&lt;em&gt;On Rails&lt;/em&gt;&lt;/a&gt;, a new podcast produced by the &lt;a href=&quot;https://rubyonrails.org/foundation&quot;&gt;Rails Foundation&lt;/a&gt; and hosted by yours truly.&lt;/p&gt;

&lt;p&gt;We’ve recorded the first batch of episodes, and Episode 1 is out now: &lt;a href=&quot;https://podcast.rubyonrails.org/2462975/episodes/17305252-rosa-gutierrez-solid-queue&quot;&gt;Rosa Gutiérrez on Solid Queue&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The show dives into technical decision-making in the Ruby on Rails world. Not the shiny trend of the week… but the real conversations teams are having about how to scale, what trade-offs to make, and what long-term maintainability actually looks like.&lt;/p&gt;

&lt;p&gt;You’ll hear from developers running real apps. Some are building internal tools. Others work on products you’ve probably used. A few are out there blogging and tweeting… but many are too deep in the day-to-day to stop and write about it. They’re just doing the work — shipping, fixing, refactoring, and figuring it out as they go.&lt;/p&gt;

&lt;p&gt;The idea for &lt;em&gt;On Rails&lt;/em&gt; started with those hallway conversations at conferences. The ones that don’t make it into keynotes or blog posts. It grew out of the calls I have with clients at &lt;a href=&quot;https://www.planetargon.com&quot;&gt;Planet Argon&lt;/a&gt;. And, of course, out of years of hosting &lt;a href=&quot;https://maintainable.fm&quot;&gt;&lt;em&gt;Maintainable.fm&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You’d think that after recording over 200 episodes of &lt;em&gt;Maintainable&lt;/em&gt;, I wouldn’t be so nervous to hit record on something new… but here we are. New show jitters are real.&lt;/p&gt;

&lt;p&gt;We’re approaching this podcast with depth and focus. Fewer episodes. Longer interviews. Conversations that aim to surface lessons learned…and the thinking behind the decisions that shape real systems.&lt;/p&gt;

&lt;p&gt;If you’re a Rails fan, I hope you’ll give it a listen. If subscribing is your thing, you know what to do. And if you’ve got a story worth sharing — I’d love to hear from you.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;🎧 Listen to Episode 1: &lt;a href=&quot;https://podcast.rubyonrails.org/2462975/episodes/17305252-rosa-gutierrez-solid-queue&quot;&gt;Rosa Gutiérrez: Solid Queue&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🌐 Browse all episodes: &lt;a href=&quot;https://podcast.rubyonrails.org&quot;&gt;podcast.rubyonrails.org&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;📢 Official announcement: &lt;a href=&quot;https://rubyonrails.org/2025/6/25/on-rails-podcast&quot;&gt;Ruby on Rails blog&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;backstory&quot;&gt;Backstory&lt;/h2&gt;

&lt;p&gt;Earlier this year, I dusted off this blog — which I started back in 2005 — and found myself reflecting on &lt;em&gt;Maintainable&lt;/em&gt;, the podcast I’ve hosted for the past few years about long-term software health.&lt;/p&gt;

&lt;p&gt;At the same time, I was toying with the idea of spinning off something more Rails-focused. A show that could spotlight the kinds of conversations I was already having…with &lt;a href=&quot;https://www.planetargon.com/clients&quot;&gt;clients&lt;/a&gt;, with other devs, and in those casual, between-session moments at conferences.&lt;/p&gt;

&lt;p&gt;Right around then, Amanda from the &lt;a href=&quot;https://rubyonrails.org/foundation&quot;&gt;Rails Foundation&lt;/a&gt; reached out &lt;a href=&quot;https://x.com/AmandaBPerino/status/1937829970165788975&quot;&gt;with a prompt&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“A podcast of Rails devs talking about the nitty gritty technical decisions they’ve made along the way.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;…which aligned nicely with what I had been ruminating on. The timing was perfect and we decided to make it happen.&lt;/p&gt;

&lt;p&gt;One of the early shifts for me was adapting to a more collaborative production process. I’ve been running &lt;a href=&quot;https://www.planetargon.com&quot;&gt;Planet Argon&lt;/a&gt; for more than two decades — and I’m used to moving quickly, often without needing to pitch or workshop ideas with others. But with &lt;em&gt;On Rails&lt;/em&gt;, I’ve had the opportunity to work closely with Amanda, the Foundation, and DHH. They’ve all taken an active interest in shaping the vision, the guests, and the format.&lt;/p&gt;

&lt;p&gt;Another early challenge? The first round of guests were pitched to me — which meant jumping into the deep end with folks I hadn’t already spoken with. That raised the bar for prep. On &lt;em&gt;Maintainable&lt;/em&gt;, I’ve occasionally relied on some degree of improvisation. Here, I knew I’d need to come in more prepared…and that’s been a good thing.&lt;/p&gt;

&lt;p&gt;So &lt;em&gt;On Rails&lt;/em&gt; was born.&lt;/p&gt;

&lt;p&gt;I’ll still be hosting &lt;em&gt;Maintainable&lt;/em&gt; (though likely on a slower cadence). And I’m excited to run both of these shows side by side — each with their own tone and focus.&lt;/p&gt;

&lt;p&gt;Hope you get a chance to give it a listen.&lt;/p&gt;
</description>
        <pubDate>Wed, 25 Jun 2025 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2025/06/25/launching-on-rails/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2025/06/25/launching-on-rails/</guid>
        
        <category>podcast</category>
        
        <category>rubyonrails</category>
        
        <category>maintainablefm</category>
        
        <category>onrails</category>
        
        
      </item>
    
      <item>
        <title>Steering Rails Apps Out of Technical Debt - Rails World</title>
        <description>&lt;p&gt;Drowning in technical debt?&lt;/p&gt;

&lt;p&gt;It doesn’t have to be this way.&lt;/p&gt;

&lt;p&gt;Back in September at Rails World 2024, I &lt;a href=&quot;https://rubyonrails.org/world/2024/day-2/robby-russell&quot;&gt;shared what I’ve learned&lt;/a&gt; from helping teams tack their way out of trouble—less theory, more battle-tested strategies. Lessons from Planet Argon’s clients, Maintainable.fm guests, and real-world Rails teams.&lt;/p&gt;

&lt;p&gt;Have 25 minutes? Watch it here:&lt;/p&gt;

&lt;div class=&quot;responsive-video-container&quot;&gt;
  &lt;iframe src=&quot;https://www.youtube.com/embed/eT7hJz_GXGo&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;p&gt;Your future self (and your app) will thank you.&lt;/p&gt;
</description>
        <pubDate>Sat, 01 Feb 2025 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2025/02/01/steering-rails-apps-out-of-technical-debt/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2025/02/01/steering-rails-apps-out-of-technical-debt/</guid>
        
        <category>rubyonrails</category>
        
        <category>railsworld</category>
        
        <category>conference</category>
        
        
      </item>
    
      <item>
        <title>8 things I look for in a Ruby on Rails app</title>
        <description>&lt;p&gt;As a consultant, I’ve looked over a shitload (how many? probably
~150-200) over the last 12 1/2 years in the Ruby on Rails community. I
haven’t worked on most of them, but I do get invited to look over,
review, audit, and provide feedback on a lot of those.&lt;/p&gt;

&lt;p&gt;Over on the Planet Argon blog I’ve shared my quick &lt;a href=&quot;http://blog.planetargon.com/entries/ruby-on-rails-code-audits-8-steps-to-review-your-app&quot;&gt;hit list of a few
initial
things&lt;/a&gt;
that I look for when looking over an existing code base.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://blog.planetargon.com/entries/ruby-on-rails-code-audits-8-steps-to-review-your-app&quot;&gt;Read Ruby on Rails Code Audits: 8 Steps to Review Your
App&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Thu, 06 Jul 2017 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2017/07/06/8-things-i-look-for-in-a-ruby-on-rails-app/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2017/07/06/8-things-i-look-for-in-a-ruby-on-rails-app/</guid>
        
        
      </item>
    
      <item>
        <title>Lessons in Open Source</title>
        <description>&lt;p&gt;A few months ago, I wrote &lt;a href=&quot;https://medium.freecodecamp.com/d-oh-my-zsh-af99ca54212c&quot;&gt;an
article&lt;/a&gt; on
Medium about my experience of starting &lt;a href=&quot;http://ohmyz.sh/&quot;&gt;Oh My Zsh&lt;/a&gt;.
Thought I’d share it for the few lingering readers over here, too.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://medium.freecodecamp.com/d-oh-my-zsh-af99ca54212c&quot;&gt;d’Oh My Zsh: How I unexpectedly built a monster of an open source
project&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Mon, 27 Jun 2016 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2016/06/27/lessons-in-open-source/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2016/06/27/lessons-in-open-source/</guid>
        
        <category>ohmyzsh</category>
        
        <category>zsh</category>
        
        <category>opensource</category>
        
        
      </item>
    
      <item>
        <title>PostgreSQL is the preferred database of Ruby on Rails developers</title>
        <description>&lt;p&gt;The 2016 Rails Hosting community survey results are in. One of the
numbers that I’m tickled with joy about? &lt;a href=&quot;http://blog.planetargon.com/entries/2016/6/2/postgresql-continues-to-win-the-hearts-of-the-rails-community&quot;&gt;84% of Rails developers said
they’d prefer to use
PostgreSQL&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We won!&lt;/p&gt;

&lt;p&gt;Check out all the survey results at &lt;a href=&quot;http://rails-hosting.com/&quot;&gt;http://rails-hosting.com/&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Fri, 03 Jun 2016 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2016/06/03/postgresql-is-the-preferred-database-of-ruby-on-rails-developers/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2016/06/03/postgresql-is-the-preferred-database-of-ruby-on-rails-developers/</guid>
        
        
      </item>
    
      <item>
        <title>2016 Rails hosting survey is open</title>
        <description>&lt;p&gt;If you’ve not taken the 2016 Rails hosting survey, please do so at
&lt;a href=&quot;http://rails-hosting.com/&quot;&gt;http://rails-hosting.com/&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Tue, 12 Apr 2016 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2016/04/12/2016-rails-hosting-survey-is-open/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2016/04/12/2016-rails-hosting-survey-is-open/</guid>
        
        
      </item>
    
      <item>
        <title>Tom on Automatic Differentiation</title>
        <description>&lt;p&gt;Tom Stuart posted up an excellent article on &lt;a href=&quot;http://codon.com/automatic-differentiation-in-ruby&quot;&gt;Automatic Differentiation
in Ruby&lt;/a&gt; with links
to his talk slides and video.&lt;/p&gt;
</description>
        <pubDate>Thu, 11 Feb 2016 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2016/02/11/tom-on-automatic-differentiation/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2016/02/11/tom-on-automatic-differentiation/</guid>
        
        <category>ruby</category>
        
        
      </item>
    
      <item>
        <title>Action Cable demo by DHH in Rails 5</title>
        <description>&lt;p&gt;David was kind enough to put together &lt;a href=&quot;https://twitter.com/dhh/status/678620677889478656&quot;&gt;a
demonstration&lt;/a&gt; of how
to take advantage of Action Cable in Rails 5.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/n0WUjGkDFS0?si=udeOam0_-5malZEp&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;In the video he puts together a small chat application.&lt;/p&gt;
</description>
        <pubDate>Sun, 20 Dec 2015 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2015/12/20/action-cable-demo-by-dhh-in-rails-5/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2015/12/20/action-cable-demo-by-dhh-in-rails-5/</guid>
        
        <category>rails</category>
        
        <category>actioncable</category>
        
        <category>demo</category>
        
        <category>video</category>
        
        
      </item>
    
      <item>
        <title>Ezra Zygmuntowicz -- Farewell, Friend.</title>
        <description>&lt;p&gt;Yesterday, I found out that &lt;a href=&quot;https://twitter.com/ezmobius&quot;&gt;Ezra
Zygmuntowicz&lt;/a&gt; had passed away.&lt;/p&gt;

&lt;p&gt;Ezra and I first met in the #Caboose family. The first time we got to
spend time together, in person, was at RailsConf 2006.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.flickr.com/photos/19932288@N00/177403465&quot;&gt;&lt;img src=&quot;https://monosnap.com/image/HwiOcn9oUFeFFrTWEw2DzvTlrm1d1Q.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(photo credit: Jarkko Laine)&lt;/p&gt;

&lt;p&gt;A few weeks later, he emailed me to ask about getting a job here at
Planet Argon. We couldn’t afford him so he continued to pursue other
paths… and a month later was helping found
&lt;a href=&quot;http://engineyard.com/&quot;&gt;EngineYard&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;(…one of those things that I still find myself asking, &lt;em&gt;“what if we
could have?”&lt;/em&gt;)&lt;/p&gt;

&lt;p&gt;Back when Planet Argon had a hosting department, Ezra and I collaborated
on various deployment best-practice projects. He was always helping our
team figure out how to make deployment easier. Some of us might remember
&lt;a href=&quot;http://pastie.org/pastes/65433&quot;&gt;his famous nginx configuration&lt;/a&gt; (the
closest version I could find). He was an active contributor in the &lt;a href=&quot;https://groups.google.com/forum/#!searchin/rubyonrails-deployment/ezra&quot;&gt;Ruby
on Rails Deployment
group&lt;/a&gt;
that I started. He was always around to help the community.&lt;/p&gt;

&lt;p&gt;Ezra always seemed to find time for the community… whether on mailing
lists, at conferences, commenting on our blogs, or chatting over IRC.&lt;/p&gt;

&lt;p&gt;When Ezra and his family moved to Portland, we made several half-assed
attempts to schedule time to catch up again in person. It never happened
and our interactions were limited to keeping up on Facebook&lt;/p&gt;

&lt;p&gt;His passing is a great loss to our community.&lt;/p&gt;

&lt;p&gt;To his friends and family, I am sorry for your loss.&lt;/p&gt;

&lt;p&gt;To his son, (should you ever discover this), your father helped pave the
way for hundreds of thousands (if not millions?) of software developers
around the globe—whether they know it or not. He was constantly
looking for innovative ways to solve problems. His talks at conferences
were always fascinating… and the rest of us would sit back and think,
“where does he come up with these ideas?!”&lt;/p&gt;

&lt;p&gt;He was a trailblazer.&lt;/p&gt;

&lt;p&gt;He will be missed.&lt;/p&gt;

&lt;p&gt;Thank you for everything, Ezra.&lt;/p&gt;

&lt;p&gt;Update: If you would like to contribute to the memorial fund for Ezra’s
son, Ryland, please &lt;a href=&quot;https://www.indiegogo.com/projects/ezra-zygmuntowicz-memorial-fund-for-his-son-ryland&quot;&gt;visit this campaign on
indigogo&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Mon, 01 Dec 2014 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2014/12/01/ezra-zygmuntowicz-farewell-friend/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2014/12/01/ezra-zygmuntowicz-farewell-friend/</guid>
        
        <category>deployment</category>
        
        <category>ezra</category>
        
        
      </item>
    
      <item>
        <title>Six days to complete the Rails hosting survey</title>
        <description>&lt;p&gt;Attention all Ruby on Rails Developers… you have six days to complete
the &lt;a href=&quot;http://rails-hosting.com&quot;&gt;2014 Rails Hosting Survey&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;All of the results will be shared with the community. Consider this your
civic duty of the day. :-)&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://rails-hosting.com/&quot;&gt;http://rails-hosting.com&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Thu, 24 Apr 2014 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2014/04/24/six-days-to-complete-the-rails-hosting-survey/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2014/04/24/six-days-to-complete-the-rails-hosting-survey/</guid>
        
        <category>hosting</category>
        
        <category>deployment</category>
        
        <category>rubyonrails</category>
        
        <category>rails</category>
        
        <category>survey</category>
        
        
      </item>
    
      <item>
        <title>Ruby on Rails is like IKEA...whaa?</title>
        <description>&lt;p&gt;Recently, I found reading an article by &lt;a href=&quot;http://www.infoworld.com/blogs/paul-venezia&quot;&gt;Paul
Venezia&lt;/a&gt; titled, &lt;a href=&quot;http://www.infoworld.com/d/data-center/fatal-abstraction-bottom-view-of-high-level-languages-238880&quot;&gt;Fatal
abstraction: A bottom-up view of high-level
languages&lt;/a&gt;,
where—if you read between the lines—you can see that Paul just found
himself waking up from a coma and it’s no longer 2004.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“I may have questioned Perl’s future now and then, and Perl certainly
doesn’t have the presence it once enjoyed, but the strength of Perl
has always been its flexibility. You can do pretty much anything with
Perl, and you can do it in a wide variety of ways. Perl’s core
revolves around the idea that there’s always more than one way to do
it. In fact, there may be dozens of ways to do it. PHP shares a
similar trait in that it gives you a large set of tools and leaves the
construction up to you.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Ruby, and especially Rails, is the opposite, and Python definitely
leans more in that direction. Essentially, it’s the difference between
building a chair from raw lumber and assembling one from IKEA. This
isn’t to say there’s anything wrong with assembling from parts, and
clearly Ruby and Python are very capable and strong languages.
However, they’re not my cup of tea.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Admittedly, perhaps I’ve been in drinking the “kool-aid” for far too
long, but I thought this tired argument has run it’s course.&lt;/p&gt;

&lt;p&gt;I take huge offense to comparing Ruby on Rails to IKEA furniture. It’s
far easier to build a web application with Ruby on Rails than it is to
&lt;a href=&quot;http://www.buzzfeed.com/ariellecalderon/why-building-ikea-furniture-is-probably-satans-fa&quot;&gt;build an IKEA
bookshelf&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“When it comes right down to it, I need to know exactly what my code
is doing. I’m going to keep an open mind and spend more time on the
other side of the fence in the short term. Perhaps I’ll be won over,
but it won’t be easy. Trust issues are complicated.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Paul, I completely understand where you’re coming from. It sounds like
you’re dealing with &lt;a href=&quot;http://www.robbyonrails.com/articles/2005/08/18/active-record-i-3-u-but-i-still-trust-my-database-server-a-tiny-bit-more&quot;&gt;similar trust
issues&lt;/a&gt;
that I had nearly a decade ago. Trust me, it will be okay.&lt;/p&gt;

&lt;p&gt;Ruby on Rails isn’t magic. &lt;em&gt;Behind the curtain&lt;/em&gt; you’ll find a collection
of &lt;a href=&quot;https://github.com/rails/rails&quot;&gt;object-oriented code&lt;/a&gt; written in one
of the most readable languages in existence.&lt;/p&gt;
</description>
        <pubDate>Wed, 02 Apr 2014 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2014/04/02/ruby-on-rails-is-like-ikea-whaa/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2014/04/02/ruby-on-rails-is-like-ikea-whaa/</guid>
        
        <category>development</category>
        
        <category>programming</category>
        
        <category>rubyonrails</category>
        
        <category>perl</category>
        
        
      </item>
    
      <item>
        <title>2014 Rails Hosting Survey is now open</title>
        <description>&lt;p&gt;Hello! It’s been two years since &lt;a href=&quot;http://planetargon.com&quot;&gt;Planet Argon&lt;/a&gt;
last asked the &lt;a href=&quot;http://rubyonrails.org/&quot;&gt;Ruby on Rails&lt;/a&gt; community how
they were deploying and hosting their applications and we thought it was
time to checkin with people.&lt;/p&gt;

&lt;p&gt;Until April 30th, you can &lt;a href=&quot;http://rails-hosting.com/&quot;&gt;take the survey
here&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Fri, 28 Mar 2014 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2014/03/28/2014-rails-hosting-survey-is-now-open/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2014/03/28/2014-rails-hosting-survey-is-now-open/</guid>
        
        <category>survey</category>
        
        <category>rubyonrails</category>
        
        <category>hosting</category>
        
        <category>deployment</category>
        
        <category>community</category>
        
        
      </item>
    
      <item>
        <title>Oh My Zsh gets a proper web page</title>
        <description>&lt;p&gt;It was a long time coming… but we finally got a web page designed and
deployed for &lt;a href=&quot;http://ohmyz.sh/&quot;&gt;Oh My Zsh&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://ohmyz.sh/&quot;&gt;Check it out!&lt;/a&gt; (and tell your friends to ditch bash)&lt;/p&gt;
</description>
        <pubDate>Sat, 22 Mar 2014 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2014/03/22/oh-my-zsh-gets-a-proper-web-page/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2014/03/22/oh-my-zsh-gets-a-proper-web-page/</guid>
        
        <category>ohmyzsh</category>
        
        
      </item>
    
      <item>
        <title>Jr. Ruby on Rails developers</title>
        <description>&lt;p&gt;Over on the &lt;a href=&quot;http://planetargon.com&quot;&gt;Planet Argon&lt;/a&gt; blog, you can read an
enlightening interview with two of our Jr. Ruby on Rails developers.
Both of them made a career change in their 30s and went through a coding
academy here in Portland, Oregon.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://blog.planetargon.com/entries/2014/2/27/lessons-from-planet-argon-junior-developers&quot;&gt;Check it
out…&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Thu, 27 Feb 2014 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2014/02/27/jr-ruby-on-rails-developers/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2014/02/27/jr-ruby-on-rails-developers/</guid>
        
        <category>rubyonrails</category>
        
        <category>programming</category>
        
        
      </item>
    
      <item>
        <title>8 Things Github’s Atom Editor is not going to solve for you</title>
        <description>&lt;p&gt;&lt;img src=&quot;http://robbyonrails.com/files/atom_logo.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We’re all excited about the prospect of a new code editor (&lt;a href=&quot;http://atom.io&quot;&gt;Atom
Editor&lt;/a&gt;). We all love what &lt;a href=&quot;http://github.com&quot;&gt;Github&lt;/a&gt;
has produced so far and our expectations for anything new are going to
be quite high.&lt;/p&gt;

&lt;p&gt;Do we know exactly what it’ll be yet? Not quite. We have some hints…
but until it’s released… &lt;a href=&quot;http://www.robbyonrails.com/articles/2014/02/25/github-prepares-to-release-atom&quot;&gt;we’ll continue to
speculate&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;While I am not one to make predictions—I do have a few theories about
what Atom will &lt;em&gt;not&lt;/em&gt; do for us. (if you’re looking for a new business
idea… feel free to snag any of these)&lt;/p&gt;

&lt;p&gt;1. &lt;strong&gt;Atom Editor will not make it easier to code while in the shower.&lt;/strong&gt;
While I would love to take advantage of putting my thoughts to code
while letting my conditioner do it’s thing… I don’t believe they’re
trying to solve this problem (yet).&lt;/p&gt;

&lt;p&gt;2. &lt;strong&gt;Atom Editor is not going to make it difficult for me to produce
shitty code.&lt;/strong&gt; To date, nearly every code editor on the market is
focused on making it easier to produce code…. good AND/OR bad. Where
is the editor that tells us to quit while we’re head. “Are you serious,
Robby? Have you seen what you’ve been writing today? Just stop. Go
outside. Take a break and try again tomorrow.”&lt;/p&gt;

&lt;p&gt;3. &lt;strong&gt;Atom Editor will not bring synergy to developers.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;4. &lt;strong&gt;Atom Editor will not change the music playing&lt;/strong&gt; to compliment the
coding problem that I’m faced with. If my tests aren’t passing… I wish
my music would keep me calm and focused. This is not a time to start
playing WHAM! (…or maybe it is)&lt;/p&gt;

&lt;p&gt;5. &lt;strong&gt;Atom Editor will not bring about peace in the Emacs vs Vim wars.&lt;/strong&gt;
We are going to have to let them sort a peace deal on their own.&lt;/p&gt;

&lt;p&gt;6. &lt;strong&gt;Atom Editor will not have integrated CVS or Subversion support&lt;/strong&gt;
when it is released.&lt;/p&gt;

&lt;p&gt;7. &lt;strong&gt;Atom Editor will not promise the world to you&lt;/strong&gt; like Visual
Studio.NET did back in 2002. I remember their demo videos and it seemed
like the development world was about to change! I never would have
guessed that come 2005, I’d be in love with something as simple and
fancy-feature-less as TextMate.&lt;/p&gt;

&lt;p&gt;8. &lt;strong&gt;Atom Editor will not just be a clone of Sublime Editor&lt;/strong&gt;. Github
has too clever a team for that objective.&lt;/p&gt;

&lt;p&gt;What are you confident that Atom Editor will &lt;em&gt;not&lt;/em&gt; be?&lt;/p&gt;

&lt;h2 id=&quot;update&quot;&gt;Update&lt;/h2&gt;

&lt;p&gt;8 for 8!&lt;/p&gt;
</description>
        <pubDate>Wed, 26 Feb 2014 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2014/02/26/8-things-github-s-atom-editor-is-not-going-to-solve-for-you/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2014/02/26/8-things-github-s-atom-editor-is-not-going-to-solve-for-you/</guid>
        
        <category>github</category>
        
        <category>atom</category>
        
        <category>editor</category>
        
        <category>coding</category>
        
        <category>programming</category>
        
        
      </item>
    
      <item>
        <title>GitHub prepares to release Atom</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;funny how Twitter warned me that a bunch of my friends at GitHub are
all a sudden interested in &lt;a href=&quot;https://t.co/cb52btuPZe&quot;&gt;https://t.co/cb52btuPZe&lt;/a&gt;&lt;/p&gt;

  &lt;p&gt;— Matt Aimonetti (@mattetti) &lt;a href=&quot;https://twitter.com/mattetti/statuses/438494095506280448&quot;&gt;February 26,
2014&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;…which leads us to&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://robbyonrails.com/files/Atom-1.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Initial sources suggest that will be one of the biggest app releases of
the year.&lt;/p&gt;

&lt;p&gt;It’ll be loved by many. Hated by some.&lt;/p&gt;

&lt;p&gt;For more details… visit &lt;a href=&quot;http://atom.io/&quot;&gt;Atom.io&lt;/a&gt; or &lt;a href=&quot;https://github.com/atom/welcome/blob/master/lib/welcome.md&quot;&gt;Atom
Beta&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Dear &lt;a href=&quot;https://github.com/robbyrussell/oh-my-zsh&quot;&gt;\@ohmyzsh&lt;/a&gt; users… &lt;a href=&quot;https://github.com/robbyrussell/oh-my-zsh/commit/6cc240811fd5756a7e2fd1a79ac529ca5f284e1d&quot;&gt;we
are
ready!&lt;/a&gt;
;-)&lt;/p&gt;

&lt;h2 id=&quot;updates&quot;&gt;Updates&lt;/h2&gt;

&lt;p&gt;As we learn more… I will try to keep this updated. Here are a few
“screenshots” people have posted on twitter.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://robbyonrails.com/files/Atom-3.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://robbyonrails.com/files/Atom-4.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Some animated gifs:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://f.cloud.github.com/assets/2988/1796891/85e69ff2-6a93-11e3-89ac-31927f604592.gif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://atom.io/assets/demo@2x-134fd657ddb7b6f143f1cec11d03e368.gif&quot;&gt;&lt;img src=&quot;https://atom.io/assets/demo@2x-134fd657ddb7b6f143f1cec11d03e368.gif&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Stay tuned…&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;SOON.&lt;/p&gt;

  &lt;p&gt;— Tom Preston-Werner (@mojombo) &lt;a href=&quot;https://twitter.com/mojombo/statuses/438731623526182912&quot;&gt;February 26,
2014&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You might consider following @AtomEditor on twitter.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://twitter.com/AtomEditor&quot;&gt;Follow @AtomEditor&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Tue, 25 Feb 2014 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2014/02/25/github-prepares-to-release-atom/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2014/02/25/github-prepares-to-release-atom/</guid>
        
        <category>github</category>
        
        <category>atom</category>
        
        <category>opensource</category>
        
        
      </item>
    
      <item>
        <title>Jim Weirich -- Farewell, Friend. </title>
        <description>&lt;p&gt;It’s been an odd day. The sort of day where you really don’t know what
to say. The only thing you can manage to get out is, “Sigh. I’m going to
miss him.”&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/jimweirich&quot;&gt;Jim Weirich&lt;/a&gt; was building interesting
stuff with Ruby several years before I was introduced to it. Tools that
most of us have taken for granted. Tools that were just there.&lt;/p&gt;

&lt;p&gt;Before Jim came along… they didn’t exist.&lt;/p&gt;

&lt;p&gt;Back in the early Ruby on Rails explosion era (circa 2004-2006), it was
much easier to get to know the great Rubyists. I remember finally
getting a chance to meet Jim (and _why) at FOSCON here in Portland,
which still goes down as one of the best “conferences” I have ever
attended.&lt;/p&gt;

&lt;p&gt;(I think we all knew something special was happening.)&lt;/p&gt;

&lt;p&gt;Jim spoke at a ton of conferences. At any conference that I seemed to
get invited to speak at… Jim seemed to always be on the speaker list
too. We’d end up meeting up on the conference circuit a several times
over the coming years. It was always a delight to catchup.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/obie/3761683282/&quot;&gt;&lt;img src=&quot;http://farm3.staticflickr.com/2530/3761683282_3cdd620427.jpg&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;http://www.flickr.com/photos/obie/3761683282/&quot;&gt;Photo by Obie from Rails Underground
2009&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I believe the last one was in 2009 at Rails Underground in London. I
remember walking in one of the rooms and spotting Jim. There he was…
waiting patiently for his time slot… sitting by the wall in another
horribly uncomfortable conference chair… hacking away on his laptop as
if he was on a mission to save the human race. In reality, he was
probably toying around with some new idea.&lt;/p&gt;

&lt;p&gt;As I walked towards him… my red hair must have caught the corner of
his eyes… because he looked up and with the warmest of smiles and
kindest of voices said, “Robby!”&lt;/p&gt;

&lt;p&gt;It’s people like Jim that helped me feel like I had something valuable
to contribute to the community. The mere fact that he knew who I was,
that he commented on my silly blog posts, referred potential customers
to me, showed up for and complimented me on my talks, asked ME for
advice on IRC, wished me a happy birthday on Facebook, responded to my
lazy tweets… made me feel like I was welcome to (and part of) the
party.&lt;/p&gt;

&lt;p&gt;A party that started a number of years before I showed up.&lt;/p&gt;

&lt;p&gt;Let us raise our glasses high and thank our host for the pleasure of
being amidst his most generous company.&lt;/p&gt;

&lt;p&gt;Thank you, Jim, for helping me learn more about myself. I only wish I
had gotten to know &lt;strong&gt;you&lt;/strong&gt; more.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/robbyrussell/oh-my-zsh/commit/598a9c6f990756386517d66b6bcf77e53791e905&quot;&gt;Adieu
l’ami&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Thu, 20 Feb 2014 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2014/02/20/jim-weirich/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2014/02/20/jim-weirich/</guid>
        
        <category>ruby</category>
        
        <category>rake</category>
        
        <category>rubyonrails</category>
        
        <category>jimweirich</category>
        
        <category>programming</category>
        
        <category>memories</category>
        
        
      </item>
    
      <item>
        <title>Ruby on Rails developers in New York</title>
        <description>&lt;p&gt;We’ve had a number of clients in New York over the years. After a recent
trip this last fall to visit clients and attend “Cultivate”:, we decided
that we should spend more of our time in the city.&lt;/p&gt;

&lt;p&gt;In 2014, we’re looking to expand our client base there. If you’re in the
NYC area and are looking for an agency that has great Ruby on Rails
developers… &lt;a href=&quot;http://planetargon.com/new-york-ruby-on-rails-development&quot;&gt;get in
touch&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Fri, 10 Jan 2014 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2014/01/10/ruby-on-rails-developers-in-new-york/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2014/01/10/ruby-on-rails-developers-in-new-york/</guid>
        
        <category>newyork</category>
        
        <category>rubyonrails</category>
        
        <category>development</category>
        
        <category>team</category>
        
        
      </item>
    
      <item>
        <title>oh-my-zsh reaches over 500 contributors</title>
        <description>&lt;p&gt;Earlier today, I noticed that we now have over 500 developers from
around the globe who I have accepted pull-requests
from[^1^](#fn1){#fnref1 .footnote-ref role=”doc-noteref”}. That is so
fantastic.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://robbyonrails.com/files/505contributors-1.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Thanks to each and every one of you who has helped make this project so
wonderful for others. :-)&lt;/p&gt;

&lt;hr /&gt;

&lt;ol&gt;
  &lt;li&gt;::: {#fn1}
This number could be a lot higher if I spent more than a hour or two
a week on this, but I’m a big fan of slow and steady… a good
number of the open pull-requests are themes at the
moment.&lt;a href=&quot;#fnref1&quot;&gt;↩︎&lt;/a&gt;{.footnote-back role=”doc-backlink”}
:::&lt;/li&gt;
&lt;/ol&gt;
</description>
        <pubDate>Sun, 01 Dec 2013 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2013/12/01/oh-my-zsh-reaches-over-500-contributors/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2013/12/01/oh-my-zsh-reaches-over-500-contributors/</guid>
        
        <category>ohmyzsh</category>
        
        <category>zsh</category>
        
        <category>development</category>
        
        <category>github</category>
        
        <category>opensource</category>
        
        
      </item>
    
      <item>
        <title>Reducing MySQL&apos;s memory usage on OS X Mavericks</title>
        <description>&lt;p&gt;Recently, I found myself re-installing everything from Homebrew and
began to notice that MySQL was consuming nearly half a gig of memory.
Given that I don’t do too much with MySQL on a regular basis, I opted to
override a handful of default configuration options to reduce the memory
footprint.&lt;/p&gt;

&lt;p&gt;As you can see, a fresh MySQL install via homebrew was consuming over
400mb of memory.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://robbyonrails.com/files/mysqld-before.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Here is how I reduced my memory footprint:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$ mkdir -p /usr/local/etc&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Unless you already have a custom MySQL config file, you will want to add
one into this directory.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$ vim /usr/local/etc/my.cnf&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We’ll then paste in the following options into our file… and save it.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;
# Robby’s MySQL overrides&lt;br /&gt;
[mysqld]&lt;br /&gt;
max_connections = 10&lt;/p&gt;

&lt;p&gt;key_buffer_size = 16K&lt;br /&gt;
max_allowed_packet = 1M&lt;br /&gt;
table_open_cache = 4&lt;br /&gt;
sort_buffer_size = 64K&lt;br /&gt;
read_buffer_size = 256K&lt;br /&gt;
read_rnd_buffer_size = 256K&lt;br /&gt;
net_buffer_length = 2K&lt;br /&gt;
thread_stack = 128K\&lt;/p&gt;

&lt;p&gt;Finally, we’ll restart MySQL.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$ mysql.server stop&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If you have MySQL setup in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launchctl&lt;/code&gt;, it should restart automatically.
After I did this, my MySQL instance was now closer to 80mb.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://robbyonrails.com/files/mysqld-after.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;So far, this has worked out quite well for my local Ruby on Rails
development. Mileage may vary…&lt;/p&gt;

&lt;p&gt;Having said that, how much memory are you now saving?&lt;/p&gt;
</description>
        <pubDate>Sun, 24 Nov 2013 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2013/11/24/reducing-mysqls-memory-usage-on-os-x-mavericks/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2013/11/24/reducing-mysqls-memory-usage-on-os-x-mavericks/</guid>
        
        <category>homebrew</category>
        
        <category>mavericks</category>
        
        <category>osx</category>
        
        <category>memory</category>
        
        <category>performance</category>
        
        <category>development</category>
        
        <category>mysql</category>
        
        
      </item>
    
      <item>
        <title>See you at LessConf and RailsConf?</title>
        <description>&lt;p&gt;Hey all,&lt;/p&gt;

&lt;p&gt;It’s been a while (most of my blogging is over on the &lt;a href=&quot;http://blog.planetargon.com/&quot;&gt;Planet Argon
blog&lt;/a&gt;..). but I’m hoping to have some
technical-related posts coming in the near future.&lt;/p&gt;

&lt;p&gt;If you’ll be at &lt;a href=&quot;http://lessconf.com&quot;&gt;LessConf&lt;/a&gt; in Florida this week,
I’ll be there. Do I owe you a drink?&lt;/p&gt;

&lt;p&gt;Also, if you’re coming to visit Portland for &lt;a href=&quot;http://www.railsconf.com/&quot;&gt;RailsConf
2013&lt;/a&gt; and will arrive the weekend before, you
should &lt;a href=&quot;http://planetargon.com/railsconf-hike&quot;&gt;join us on a hike&lt;/a&gt;. If
you can’t make it, be sure to say hello at the conference!&lt;/p&gt;

&lt;p&gt;Hope all is well!&lt;/p&gt;

&lt;p&gt;Cheers,&lt;br /&gt;
Robby&lt;/p&gt;
</description>
        <pubDate>Tue, 09 Apr 2013 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2013/04/09/see-you-at-lessconf-and-railsconf/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2013/04/09/see-you-at-lessconf-and-railsconf/</guid>
        
        <category>railsconf</category>
        
        <category>lessconf</category>
        
        <category>portland</category>
        
        <category>travel</category>
        
        <category>conferences</category>
        
        
      </item>
    
      <item>
        <title>2012 Rails Hosting Survey results</title>
        <description>&lt;p&gt;A few months ago we invited the Ruby on Rails community to participate
in our hosting survey. We’ve since &lt;a href=&quot;http://blog.planetargon.com/entries/2012/8/14/rails-hosting-survey-2012-results-are-in&quot;&gt;published the
results&lt;/a&gt;
and created an
&lt;a href=&quot;https://planetargon-blog.s3.amazonaws.com/images/infographic-2012-survey.png&quot;&gt;infographic&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To learn more, check out the &lt;a href=&quot;http://rails-hosting.com/&quot;&gt;Rails Hosting&lt;/a&gt;
site.&lt;/p&gt;
</description>
        <pubDate>Fri, 14 Sep 2012 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2012/09/14/2012-rails-hosting-survey-results/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2012/09/14/2012-rails-hosting-survey-results/</guid>
        
        <category>hosting</category>
        
        <category>survey</category>
        
        <category>deployment</category>
        
        <category>community</category>
        
        
      </item>
    
      <item>
        <title>GoogleAnalyticsProxy - now minified</title>
        <description>&lt;p&gt;It’s been several years since I released
&lt;a href=&quot;https://github.com/robbyrussell/GoogleAnalyticsProxy&quot;&gt;GoogleAnalyticsProxy&lt;/a&gt;,
which allows our team to test their GA event/click/view tracking during
the development phases of our project. Today, I pushed a quick update to
it with a minified version of the JavaScript so that there is a smaller
footprint.&lt;/p&gt;

&lt;p&gt;For more information on how we use it, read my older post, &lt;a href=&quot;http://www.robbyonrails.com/articles/2009/11/01/googleanalyticsproxy-for-development-environment-tracking-events-in-google-analytics&quot;&gt;Tracking
Google Analytics events in development environment with
GoogleAnalyticsProxy&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Tue, 10 Jul 2012 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2012/07/10/googleanalyticsproxy-now-minified/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2012/07/10/googleanalyticsproxy-now-minified/</guid>
        
        <category>googleanalytics</category>
        
        <category>analytics</category>
        
        <category>javascript</category>
        
        <category>development</category>
        
        <category>metrics</category>
        
        
      </item>
    
      <item>
        <title>Planet Argon on social media</title>
        <description>&lt;p&gt;You can follow along with some of my writing at the following sites:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.facebook.com/planetargon&quot;&gt;Planet Argon on Facebook&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://twitter.com/planetargon&quot;&gt;Planet Argon on twitter&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://blog.planetargon.com/&quot;&gt;Planet Argon blog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Thu, 05 Jul 2012 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2012/07/05/planet-argon-on-social-media/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2012/07/05/planet-argon-on-social-media/</guid>
        
        <category>facebook</category>
        
        <category>twitter</category>
        
        <category>planetargon</category>
        
        
      </item>
    
      <item>
        <title>Setting Akamai Edge-Control headers with Ruby on Rails</title>
        <description>&lt;p&gt;Just a short and sweet little tip.&lt;/p&gt;

&lt;p&gt;Several months ago we moved one of our clients over to Akamai’s Content
Delivery Network (CDN). Ww were previously using a combination of Amazon
S3 and CloudFront with &lt;em&gt;some&lt;/em&gt; benefits, but we were finding several key
areas of the world were not s covered by Amazon (yet) for asset
delivery. Along with that, we really wanted to take advantage of the CDN
for more of our HTML content with a lot of complex rules that related to
geo-targeting and regionalization of content.&lt;/p&gt;

&lt;p&gt;I’ll try to cover those topics in another post, but wanted to share a
few tidbits of code that we are using to manage Akamai’s Edge-control
caches from within our Rails application.&lt;/p&gt;

&lt;p&gt;With Akamai, we’re able to tell their Edge servers whether it should
hold on to the response so it can try to avoid an extra request to the
origin (aka our Rails application). From Rails, we just added a few
helper methods to our controllers so that we can litter our application
with various expiration times.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Sets the headers for Akamai&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# acceptable formats include:&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# 1m, 10m, 90m, 2h, 5d&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;set_cache_control_for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;maxage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;20m&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;Edge-control&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;!no-store, max-age=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;maxage&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This allows us to do things like:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ProductsController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationController&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;show&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;set_cache_control_for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;4h&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@product&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Product&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then when Akamai gets a request for
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://domain.com/products/20-foo-bar&lt;/code&gt;, it’ll try to keep a cached copy
around for four hours before it hits our server again.&lt;/p&gt;
</description>
        <pubDate>Tue, 19 Jun 2012 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2012/06/19/setting-akamai-edge-control-headers-with-ruby-on-rails/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2012/06/19/setting-akamai-edge-control-headers-with-ruby-on-rails/</guid>
        
        <category>akamai</category>
        
        <category>rubyonrails</category>
        
        <category>performance</category>
        
        <category>caching</category>
        
        <category>controllers</category>
        
        <category>helpers</category>
        
        
      </item>
    
      <item>
        <title>Announcing the 2012 Rails Hosting Survery</title>
        <description>&lt;p&gt;You might recall that back in 2009, we surveyed the Ruby on Rails
community to gauge how people were deploying and hosting their
applications. It’s been over three years, so we’ve decided to run it
again…. I know… we’re crazy like that!&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://rails-hosting.com/?utm_source=robbyonrails&amp;amp;utm_medium=blog&amp;amp;utm_campaign=announcement&quot;&gt;&lt;img src=&quot;https://www.railsinside.com/wp-content/uploads/2009/03/img-rails-hosting-survey.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So… without further ado… we present &lt;a href=&quot;http://rails-hosting.com/?utm_source=robbyonrails&amp;amp;utm_medium=blog&amp;amp;utm_campaign=announcement&quot;&gt;the 2012 Rails Hosting
Survey&lt;/a&gt;.
We’ll be collecting responses for a month, so be sure to put it on your
todo list.&lt;/p&gt;
</description>
        <pubDate>Thu, 31 May 2012 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2012/05/31/announcing-the-2012-rails-hosting-survery/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2012/05/31/announcing-the-2012-rails-hosting-survery/</guid>
        
        <category>hosting</category>
        
        <category>deployment</category>
        
        <category>rubyonrails</category>
        
        <category>rails</category>
        
        <category>survey</category>
        
        
      </item>
    
      <item>
        <title>A Mobile Site or Responsive Design?</title>
        <description>&lt;p&gt;Our team at &lt;a href=&quot;http://planetargon.com&quot;&gt;Planet Argon&lt;/a&gt; just released a white
paper on this topic. I’d like to invite you to &lt;a href=&quot;http://blog.planetargon.com/entries/2012/5/29/white-paper-responsive-design-vs-mobile-site&quot;&gt;check it
out&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Wed, 30 May 2012 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2012/05/30/a-mobile-site-or-responsive-design/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2012/05/30/a-mobile-site-or-responsive-design/</guid>
        
        <category>mobile</category>
        
        <category>html</category>
        
        <category>css</category>
        
        <category>responsive</category>
        
        
      </item>
    
      <item>
        <title>Planet Argon is hiring</title>
        <description>&lt;p&gt;Hello all!&lt;/p&gt;

&lt;p&gt;I’ve been so busy this year travelling (for pleasure and work) and
helping grow the company that I’ve not had much to say here. Expect some
posts soon!&lt;/p&gt;

&lt;p&gt;In the meantime, if you’re looking for a new challenge… &lt;a href=&quot;http://planetargon.com&quot;&gt;Planet
Argon&lt;/a&gt; is seeking a few more Ruby on Rails
developers. Interested? &lt;a href=&quot;http://planetargon.com/careers&quot;&gt;get in touch&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Mon, 22 Aug 2011 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2011/08/22/planet-argon-is-hiring/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2011/08/22/planet-argon-is-hiring/</guid>
        
        <category>jobs</category>
        
        <category>rubyonrails</category>
        
        <category>planetargon</category>
        
        
      </item>
    
      <item>
        <title>Announcing RailsDeveloper</title>
        <description>&lt;p&gt;Earlier today… our team at &lt;a href=&quot;http://planetargon.com/&quot;&gt;Planet Argon&lt;/a&gt;
launched a new site for the Ruby on Rails community. If you have a few
spare minutes, I’d love it if you’d to head over and &lt;a href=&quot;http://railsdeveloper.com/blog/2010/8/31/we-are-railsdeveloper&quot;&gt;read the
announcement&lt;/a&gt;
on &lt;a href=&quot;http://railsdeveloper.com&quot;&gt;RailsDeveloper&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Enjoy!&lt;/p&gt;
</description>
        <pubDate>Wed, 01 Sep 2010 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2010/09/01/announcing-railsdeveloper/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2010/09/01/announcing-railsdeveloper/</guid>
        
        <category>railsdeveloper</category>
        
        <category>rubyonrails</category>
        
        <category>rails</category>
        
        
      </item>
    
      <item>
        <title>Join the zsh revolution</title>
        <description>&lt;p&gt;I love the OhMyZsh community. Let’s grow it a little more this next
Tuesday.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://twitter.com/ohmyzsh/status/14812098501&quot;&gt;&lt;img src=&quot;http://img.skitch.com/20100527-pnc2xtigq9xerriem7txddwi7s.preview.jpg&quot; alt=&quot;Twitter / Oh My Zsh: Declaring that next Tuesda
...&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://github.com/robbyrussell/oh-my-zsh&quot;&gt;Join the Zsh revolution
here…&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Thu, 27 May 2010 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2010/05/27/join-the-zsh-revolution/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2010/05/27/join-the-zsh-revolution/</guid>
        
        <category>zsh</category>
        
        <category>ohmyzsh</category>
        
        <category>console</category>
        
        <category>terminal</category>
        
        <category>bash</category>
        
        
      </item>
    
      <item>
        <title>Planet Argon Blog</title>
        <description>&lt;p&gt;Just a quick update to let you know that I’ll be moving more of my
business-related topics to the &lt;a href=&quot;http://blog.planetargon.com&quot;&gt;Planet Argon
Blog&lt;/a&gt;. I invite you to &lt;a href=&quot;http://feeds.feedburner.com/planetargon&quot;&gt;subscribe to that
feed&lt;/a&gt; as well. :-)&lt;/p&gt;

&lt;p&gt;s. We just published Episode 4 of the &lt;a href=&quot;http://planetargon.com/podcast&quot;&gt;Planet Argon
Podcast&lt;/a&gt;, which was on the topic of
Hiring designers.&lt;/p&gt;
</description>
        <pubDate>Wed, 17 Feb 2010 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2010/02/17/planet-argon-blog/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2010/02/17/planet-argon-blog/</guid>
        
        <category>planetargon</category>
        
        <category>blog</category>
        
        <category>podcast</category>
        
        
      </item>
    
      <item>
        <title>Installing Ruby on Rails, Passenger, PostgreSQL, MySQL, Oh My Zsh on Snow Leopard, Fourth Edition</title>
        <description>&lt;p&gt;Welcome to what seems like my tenth installment (actually, it’s the
fourth) of showing you how I setup my development environment on a fresh
OSX install. In this case, I’m actually getting a MacBook setup for a
new employee with Snow Leopard.&lt;/p&gt;

&lt;p&gt;Over the years, I’ve evolved these following steps and they’ve helped
our team maintain a consistent and stable envirnment for Ruby on Rails
development. I know that there are a few other ways to approaching this
and I’m sure you’ll get similar results, but this approach has allowed
me to maintain a hassle-free setup for the last five years.&lt;/p&gt;

&lt;p&gt;As with all things… your milage may vary.&lt;/p&gt;

&lt;h2 id=&quot;phase-one&quot;&gt;Phase One&lt;/h2&gt;

&lt;p&gt;During this initial phase, we’re going to install the primary
dependencies and setup our environment.&lt;/p&gt;

&lt;h3 id=&quot;xcode&quot;&gt;XCode&lt;/h3&gt;

&lt;p&gt;The first thing that you’ll need to do is install XCode, which almost
everything depends upon as this will install developer-friendly tools
for you. Apple has been kind enough to ship this on your Snow Leopard
DVD.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://img.skitch.com/20100208-kmamssde4apw1qxcs6u991p518.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Go ahead and install XCode from the &lt;strong&gt;Optional Installs&lt;/strong&gt; folder.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://img.skitch.com/20100208-1up5s7ahybmryrerrxxwn1d7tu.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;(might require a reboot)&lt;/p&gt;

&lt;p&gt;You can also &lt;a href=&quot;http://developer.apple.com/technology/xcode.html&quot;&gt;download
it&lt;/a&gt; online.&lt;/p&gt;

&lt;h3 id=&quot;macports&quot;&gt;MacPorts&lt;/h3&gt;

&lt;p&gt;Now we’ll install MacPorts, which the web site describes itself as, “an
open-source community initiative to design an easy-to-use system for
compiling, installing, and upgrading either command-line, X11 or Aqua
based open-source software on the Mac OS X operating system.”&lt;/p&gt;

&lt;p&gt;As I’ve said in past versions of this guide, this tool is about to
become one of the most important tools on your operating system. It’ll
be used time and time again to maintain your libraries and many of the
Unix tools that you’ll be using. If you’re from the Linux or BSD world,
you are likely familiar with similar tools… such as: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apt-get&lt;/code&gt;,
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;port&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yum&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You’ll want to download the latest stable version from
&lt;a href=&quot;http://www.macports.org&quot;&gt;http://www.macports.org/&lt;/a&gt;. Once downloaded,
you can install it.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://img.skitch.com/20100208-fd4h7rd16437yd63hf45mkn592.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Once this is installed, you’ll be able to use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;port&lt;/code&gt; command from
your console.&lt;/p&gt;

&lt;h3 id=&quot;wget&quot;&gt;Wget&lt;/h3&gt;

&lt;p&gt;Let’s test out your MacPorts install by installing a useful tool called
wget, which we’ll use to install oh-my-zsh.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;port &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;wget
&lt;span class=&quot;sb&quot;&gt;```&lt;/span&gt;bash
&lt;span class=&quot;c&quot;&gt;### Git and Subversion&lt;/span&gt;

Every development environment should have some &lt;span class=&quot;nb&quot;&gt;source &lt;/span&gt;code management
tools available. We&lt;span class=&quot;s1&quot;&gt;&apos;ll install both of these with one command.
```bash
sudo port install git-core +svn
```ruby
This will install git and subversion.

### oh-my-zsh

[Oh My Zsh](http://github.com/robbyrussell/oh-my-zsh) is the most
amazing thing to happen to shells since... well since I said so. It&apos;&lt;/span&gt;s
one of my open &lt;span class=&quot;nb&quot;&gt;source &lt;/span&gt;projects that I encourage you to give a whirl.
&lt;span class=&quot;sb&quot;&gt;```&lt;/span&gt;bash
wget http://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh &lt;span class=&quot;nt&quot;&gt;-O&lt;/span&gt; - | sh
&lt;span class=&quot;sb&quot;&gt;```&lt;/span&gt;ruby
That&lt;span class=&quot;s1&quot;&gt;&apos;s it. The next time you open up your terminal, you&apos;&lt;/span&gt;ll be running
zsh with a bunch of stuff available. For more information, visit
&amp;lt;http://github.com/robbyrussell/oh-my-zsh&amp;gt;.

&lt;span class=&quot;c&quot;&gt;### Terminal theme (optional)&lt;/span&gt;

I never understood why the icon &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;Terminal has a black background but
when you start it up the default theme is black on white.

&lt;span class=&quot;o&quot;&gt;![](&lt;/span&gt;http://img.skitch.com/20100208-1b92cbx8gtuttqsbdd49qspq7f.jpg&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

versus

&lt;span class=&quot;o&quot;&gt;![](&lt;/span&gt;http://img.skitch.com/20100208-8dnmpswyumfk73h942g6u4fih4.jpg&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

Anyhow, I&lt;span class=&quot;s1&quot;&gt;&apos;m a fan of the the dark background. To change this, open up
preferences in Terminal. Select **Pro**, then click on the **Default**
window so that this sticks around.

![](http://img.skitch.com/20100208-mr4q4y5btq7sscuh7ceky3ahgx.jpg)

Let&apos;&lt;/span&gt;s now open up a new Terminal window..

You should be looking at something like this:

&lt;span class=&quot;o&quot;&gt;![](&lt;/span&gt;http://img.skitch.com/20100208-psmj7mqsx38j1i2nj75n6hbcg7.jpg&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

Much better... &lt;span class=&quot;nb&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;s continue.

## Phase Two

We&apos;&lt;/span&gt;re now going to start installing everything we need to get this
running.

&lt;span class=&quot;c&quot;&gt;### Ruby 1.8.7.x&lt;/span&gt;

First up, Ruby.

Snow Leopard includes Ruby and Rails already installed, but we&lt;span class=&quot;s1&quot;&gt;&apos;re going
to back these up for a rainy day. Just issue these commands:
```bash
$ sudo su -
Password:
:~ root# mv /usr/bin/ruby /usr/bin/ruby.orig
:~ root# mv /usr/bin/gem /usr/bin/gem.orig
:~ root# mv /usr/bin/rails /usr/bin/rails.orig
:~ root# logout
```ruby
![](http://img.skitch.com/20100208-b7cxbt8andg3t7ntn17r1wahfg.jpg)

Now we&apos;&lt;/span&gt;ll go ahead and &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;a fresh copy of Ruby and RubyGems via
MacPorts.
&lt;span class=&quot;sb&quot;&gt;```&lt;/span&gt;bash
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;port &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;ruby rb-rubygems
&lt;span class=&quot;sb&quot;&gt;```&lt;/span&gt;ruby
You should now see something like this &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;a bit...

&lt;span class=&quot;o&quot;&gt;![](&lt;/span&gt;http://img.skitch.com/20100208-n9xxiaqbe6bw72qr88dgurxqdp.jpg&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

Let&lt;span class=&quot;s1&quot;&gt;&apos;s watch a video about bumble bees.

When it finishes installing, you should check that Ruby is available to
you and installed in `/opt/local/bin`.

![](http://img.skitch.com/20100208-tfc3ifeaau15295r9sfmfhbnm1.jpg)

We&apos;&lt;/span&gt;ll also take a second to create a symlink &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;this as some tools seem
to rely on &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;/usr/bin/ruby&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt; being there.
&lt;span class=&quot;sb&quot;&gt;```&lt;/span&gt;bash
&lt;span class=&quot;nb&quot;&gt;sudo ln&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; /opt/local/bin/ruby /usr/bin/ruby
&lt;span class=&quot;sb&quot;&gt;```&lt;/span&gt;bash
Great, &lt;span class=&quot;nb&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;s move on.

### Passenger (mod_rails)

Now that we have Ruby installed, we&apos;&lt;/span&gt;re going to take a quick detour to
setup Passenger with the Apache server already available on your
machine. I&lt;span class=&quot;s1&quot;&gt;&apos;ve been a big fan of using Passenger for your development for
over a year now.
```bash
sudo gem install passenger
```ruby
Once the gem is finished installing, you&apos;&lt;/span&gt;ll need to &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;the apache2
module with the following &lt;span class=&quot;nb&quot;&gt;command&lt;/span&gt;:

It&lt;span class=&quot;s1&quot;&gt;&apos;ll ask you to continue by pressing Enter. At this point, it&apos;&lt;/span&gt;ll check
that you have all the necessary dependencies and &lt;span class=&quot;k&quot;&gt;then &lt;/span&gt;compile everything
needed &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;Apache2.

&lt;span class=&quot;o&quot;&gt;![](&lt;/span&gt;http://img.skitch.com/20100208-x4rcmr6m5q44etk7rad2kqpmir.jpg&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

Now I&lt;span class=&quot;s1&quot;&gt;&apos;ll force you to watch a highlights reel of Fernando Torres... *the
best striker in the world!*

The passenger install will then show you this output, which you&apos;&lt;/span&gt;ll want
to stop and &lt;span class=&quot;nb&quot;&gt;read &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;a moment and highlight the following:

&lt;span class=&quot;o&quot;&gt;![](&lt;/span&gt;http://img.skitch.com/20100208-ms13ub2srx2axk6ti43ifiyut4.jpg&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

Then using vi or emacs, you&lt;span class=&quot;s1&quot;&gt;&apos;ll want to create a new file with the
following content:
```bash
vi /etc/apache2/other/passenger.conf
```text
Then paste in the following (what you highlighted and copied above.)
```apache
LoadModule passenger_module /opt/local/lib/ruby/gems/1.8/gems/passenger-2.2.9/ext/apache2/mod_passenger.so
PassengerRoot /opt/local/lib/ruby/gems/1.8/gems/passenger-2.2.9
PassengerRuby /opt/local/bin/ruby
```bash
You&apos;&lt;/span&gt;ll also want to include the following below what you just pasted.
&lt;span class=&quot;sb&quot;&gt;```&lt;/span&gt;apache
&lt;span class=&quot;c&quot;&gt;# Set the default environment to development&lt;/span&gt;
RailsEnv development

&lt;span class=&quot;c&quot;&gt;# Which directory do you want Apache to be able to look into for projects?&lt;/span&gt;
&amp;lt;Directory &lt;span class=&quot;s2&quot;&gt;&quot;/Users/ryangensel/development&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
    Order allow,deny
    Allow from all
&amp;lt;/Directory&amp;gt;
&lt;span class=&quot;sb&quot;&gt;```&lt;/span&gt;ruby
You&lt;span class=&quot;s1&quot;&gt;&apos;ll want to quickly start up your web sharing, which will start
Apache2 up via your System Preferences.

![](http://img.skitch.com/20100208-ekr2g6f5seb6iif35fteyjfi74.jpg)

![](http://img.skitch.com/20100208-rm96gmde4d8ffwjbdyqusiqr8f.jpg)

Simple enough... moving forward.

### Passenger Pref Pane

To make things as simple as possible, I&apos;&lt;/span&gt;d encourage you to &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;the
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Passenger Preference
Pane]&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;http://www.fngtps.com/2009/09/new-os-more-pane-passenger-preference-pane-v1-3&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;view this post &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;a download&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;![](&lt;/span&gt;http://img.skitch.com/20100208-ra8p88abxtnxbq3m9711gbpryb.jpg&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;### Development directory&lt;/span&gt;

I have a directory named &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;development/&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;my home directory, which is
where I end up storing all of my projects. This should match whatever
you put above &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;the apache configuration
&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&amp;lt;Directory &lt;span class=&quot;s2&quot;&gt;&quot;/Users/ryangensel/development&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;sb&quot;&gt;```&lt;/span&gt;bash
&lt;span class=&quot;nb&quot;&gt;mkdir &lt;/span&gt;development&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;development&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;sb&quot;&gt;```&lt;/span&gt;ruby
&lt;span class=&quot;c&quot;&gt;### Installing Ruby on Rails via RubyGems&lt;/span&gt;

Now we&lt;span class=&quot;s1&quot;&gt;&apos;ll use RubyGems to install the latest version of Ruby on Rails
(and all of it&apos;&lt;/span&gt;s dependencies&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;sb&quot;&gt;```&lt;/span&gt;bash
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;gem &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;rails
&lt;span class=&quot;sb&quot;&gt;```&lt;/span&gt;ruby
&lt;span class=&quot;o&quot;&gt;![](&lt;/span&gt;http://img.skitch.com/20100208-eyhg4t9sscbeiccs5rxkb5p35.jpg&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

While this is installing, you can watch a video from my old band that
ended around the &lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;that business started picking up &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Planet
Argon]&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;http://planetargon.com&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;

Great, &lt;span class=&quot;nb&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;s test out the install of Rails...

### Test Rails and Passenger

In your development directory, let&apos;&lt;/span&gt;s quickly a new Rails app...
&lt;span class=&quot;sb&quot;&gt;```&lt;/span&gt;bash
rails testapp
&lt;span class=&quot;sb&quot;&gt;```&lt;/span&gt;ruby
This will generate a new Rails application &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;a &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;testapp/&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt; directory.

Now open up the &lt;span class=&quot;k&quot;&gt;**&lt;/span&gt;Passenger Preferences Pane&lt;span class=&quot;k&quot;&gt;**&lt;/span&gt; and add this directory as
a new application.

&lt;span class=&quot;o&quot;&gt;![](&lt;/span&gt;http://img.skitch.com/20100208-83rjan794eqcmy62e1u4aujtiy.jpg&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;![](&lt;/span&gt;http://img.skitch.com/20100208-tms89ssutthhq8rkpjm392qfpb.jpg&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

Press Apply...

You should now fire up your browser of choice and &lt;span class=&quot;nb&quot;&gt;head &lt;/span&gt;to
&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;http://testapp.local&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; If all has worked, you&lt;span class=&quot;s1&quot;&gt;&apos;ll see a, &quot;Welcome
aboard&quot; screen from the Ruby on Rails application.

![](http://img.skitch.com/20100208-c9phjdfq8ybcu29uc5k9qiuha9.jpg)

Assuming that this worked for you, let&apos;&lt;/span&gt;s take a quick &lt;span class=&quot;nb&quot;&gt;break &lt;/span&gt;to make some
tea...

&lt;span class=&quot;c&quot;&gt;## Phase Three&lt;/span&gt;

In this last phase, we&lt;span class=&quot;s1&quot;&gt;&apos;re going to install a few database servers and
corresponding rubygems so that you can get to work.

### PostgreSQL

At [Planet Argon](http://planetargon.com), we build our web applications
on top of [PostgreSQL](http://postgresql.org). I&apos;&lt;/span&gt;ve been a long-time
advocate of it and hope you consider using it yourself.

At this point &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time&lt;/span&gt;, the current stable version of PostgreSQL via
MacPorts is 8.4.x. Let&lt;span class=&quot;s1&quot;&gt;&apos;s install that now...
```bash
sudo port install postgresql84 postgresql84-server
```text
Once this finishes compiling, you&apos;&lt;/span&gt;ll need to run the following commands
to setup a new PostgreSQL database.
&lt;span class=&quot;sb&quot;&gt;```&lt;/span&gt;bash
&lt;span class=&quot;nb&quot;&gt;sudo mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; /opt/local/var/db/postgresql84/defaultdb
&lt;span class=&quot;nb&quot;&gt;sudo chown &lt;/span&gt;postgres:postgres /opt/local/var/db/postgresql84/defaultdb
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;su postgres &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;/opt/local/lib/postgresql84/bin/initdb -D /opt/local/var/db/postgresql84/defaultdb&apos;&lt;/span&gt;
&lt;span class=&quot;sb&quot;&gt;```&lt;/span&gt;ruby
Assuming that you want PostgreSQL to always be running, you can run:
&lt;span class=&quot;sb&quot;&gt;```&lt;/span&gt;bash
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;launchctl load &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; /Library/LaunchDaemons/org.macports.postgresql84-server.plist
&lt;span class=&quot;sb&quot;&gt;```&lt;/span&gt;ruby
...and to start it right now, run:
&lt;span class=&quot;sb&quot;&gt;```&lt;/span&gt;bash
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;launchctl start org.macports.postgresql84-server
&lt;span class=&quot;sb&quot;&gt;```&lt;/span&gt;ruby
Before you can start using it, we&lt;span class=&quot;s1&quot;&gt;&apos;ll need to make sure that the
PostgreSQL executables are available in your shell path. Since you&apos;&lt;/span&gt;re
now using &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;oh-my-zsh]&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;http://github.com/robbyrussell/oh-my-zsh&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;, you&lt;span class=&quot;s1&quot;&gt;&apos;ll
want to edit `~/.zshrc` with your favorite editor.
```bash
vi ~/.zshrc
```text
Just append this to `export PATH=` line in the file.
```bash
:/opt/local/lib/postgresql84/bin
```ruby
Your PATH might look something like the following now:
```bash
# Customize to your needs... export PATH=/opt/local/bin:/opt/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/X11/bin:/opt/local/lib/postgresql84/bin
```ruby
#### Setup database user

To setup a new database (with superuser credentials), just run:
```bash
createuser --superuser ryangensel -U postgres
```ruby
We&apos;&lt;/span&gt;ll now &lt;span class=&quot;nb&quot;&gt;test &lt;/span&gt;creating a database:
&lt;span class=&quot;sb&quot;&gt;```&lt;/span&gt;bash
createdb test_db
&lt;span class=&quot;sb&quot;&gt;```&lt;/span&gt;text
Let&lt;span class=&quot;s1&quot;&gt;&apos;s test that we can access it...
```bash
➜  ~  psql test_db
psql (8.4.2)
Type &quot;help&quot; for help.

test_db=# \q
```text
Great, let&apos;&lt;/span&gt;s drop it now.
&lt;span class=&quot;sb&quot;&gt;```&lt;/span&gt;bash
➜  ~  dropdb test_db
➜  ~  psql test_db
psql: FATAL:  database &lt;span class=&quot;s2&quot;&gt;&quot;test_db&quot;&lt;/span&gt; does not exist
➜  ~
&lt;span class=&quot;sb&quot;&gt;```&lt;/span&gt;ruby
Okay, we&lt;span class=&quot;s1&quot;&gt;&apos;ll now install the library that will allow Ruby to talk to
PostgreSQL.

Just run:
```bash
sudo gem install pg
```ruby
![](http://img.skitch.com/20100208-b4cf1pm1utkf2ihd9up432etwm.jpg)

Voila... let&apos;&lt;/span&gt;s move on to the inferior database...

&lt;span class=&quot;c&quot;&gt;### MySQL&lt;/span&gt;

We&lt;span class=&quot;s1&quot;&gt;&apos;re going to run through the installation of MySQL really quickly
because you might need it.
```bash
sudo port install mysql5 mysql5-server
```text
This took ages on my machine... so let&apos;&lt;/span&gt;s watch a video.

We&lt;span class=&quot;s1&quot;&gt;&apos;ll now setup the database and make sure it starts on system boot.
```bash
sudo -u _mysql mysql_install_db5
sudo launchctl load -w /Library/LaunchDaemons/org.macports.mysql5.plist
sudo launchctl start org.macports.mysql5
```text
Let&apos;&lt;/span&gt;s &lt;span class=&quot;nb&quot;&gt;test &lt;/span&gt;that we can create a database now &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;and that it&lt;span class=&quot;s1&quot;&gt;&apos;s running.)
```bash
➜  ~  mysql5 -u root
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 3
Server version: 5.1.43 Source distribution

Type &apos;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;help&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos; or &apos;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\h&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos; for help. Type &apos;&lt;/span&gt;
&lt;span class=&quot;sb&quot;&gt;```&lt;/span&gt;text
Great, now &lt;span class=&quot;nb&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;s install the Ruby library for MySQL.
```bash
sudo gem install mysql -- --with-mysql-dir=/opt/local
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Perfect, now we’ve got all the necessary tools to get you working on Ruby on Rails projects with either PostgreSQL or MySQL.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;Thanks for following along with this guide. I hope it helped some of you setup your Snow Leopard environment. We still believe that &lt;a href=&quot;http://railsboxcar.com&quot;&gt;the ideal hosting environment for your Ruby on Rails applications&lt;/a&gt; is a dedicated Linux server. In fact, we’re just about to release our new Boxcar Conductor tool to help with Rails deployments… stay tuned!&lt;/p&gt;

&lt;p&gt;In the meantime, feel free to &lt;a href=&quot;http://twitter.com/robbyrussell&quot;&gt;follow me on Twitter&lt;/a&gt; or &lt;a href=&quot;http://twitter.com/planetargon&quot;&gt;Planet Argon on Twitter&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Mon, 08 Feb 2010 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2010/02/08/installing-ruby-on-rails-passenger-postgresql-mysql-oh-my-zsh-on-snow-leopard-fourth-edition/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2010/02/08/installing-ruby-on-rails-passenger-postgresql-mysql-oh-my-zsh-on-snow-leopard-fourth-edition/</guid>
        
        <category>rubyonrails</category>
        
        <category>github</category>
        
        <category>ohmyzsh</category>
        
        <category>zsh</category>
        
        <category>postgresql</category>
        
        <category>mysql</category>
        
        <category>rails</category>
        
        <category>ruby</category>
        
        <category>xcode</category>
        
        <category>snowleopard</category>
        
        <category>osx</category>
        
        <category>guide</category>
        
        
      </item>
    
      <item>
        <title>(oh my) Zsh themes Gone Wild! 18+</title>
        <description>&lt;p&gt;Earlier this evening, I accepted a pull-request for theme number 18,
which is now included in &lt;a href=&quot;http://github.com/robbyrussell/oh-my-zsh&quot;&gt;Oh My
Zsh&lt;/a&gt;. To celebrate, I’ve
updated the &lt;a href=&quot;http://wiki.github.com/robbyrussell/oh-my-zsh/themes&quot;&gt;themes wiki
page&lt;/a&gt; with fresh
screenshots.&lt;/p&gt;

&lt;p&gt;Here is a sampling of some of the themes that you can use out of the box
with &lt;a href=&quot;http://github.com/robbyrussell/oh-my-zsh&quot;&gt;Oh My Zsh&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://github.com/robbyrussell/oh-my-zsh&quot;&gt;&lt;img src=&quot;http://robbyonrails.com/files/ohmyzsh-themes.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Oh My Zsh is much more than a collection of themes for your zsh config.
It’s a way of life[^1^](#fn1){#fnref1 .footnote-ref role=”doc-noteref”}.&lt;/p&gt;

&lt;p&gt;Also, be sure to follow &lt;a href=&quot;http://twitter.com/ohmyzsh&quot;&gt;ohmyzsh&lt;/a&gt; on twitter
now!&lt;/p&gt;

&lt;hr /&gt;

&lt;ol&gt;
  &lt;li&gt;::: {#fn1}
well.. at least while you’re in the terminal.
;-)&lt;a href=&quot;#fnref1&quot;&gt;↩︎&lt;/a&gt;{.footnote-back role=”doc-backlink”}
:::&lt;/li&gt;
&lt;/ol&gt;
</description>
        <pubDate>Mon, 28 Dec 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/12/28/zsh-themes-gone-wild-18/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/12/28/zsh-themes-gone-wild-18/</guid>
        
        <category>zsh</category>
        
        <category>ohmyzsh</category>
        
        <category>github</category>
        
        <category>terminal</category>
        
        
      </item>
    
      <item>
        <title>RubyURL goes GOP...</title>
        <description>&lt;p&gt;Will refrain from any political commentary, but was notified by some
friends that the GOP was using the &lt;a href=&quot;:http://github.com/robbyrussell/rubyurl&quot;&gt;source
code&lt;/a&gt; for
&lt;a href=&quot;http://rubyurl.com&quot;&gt;RubyURL&lt;/a&gt; for their new URL shortening site
(&lt;a href=&quot;http://gop.am&quot;&gt;gop.am&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;To celebrate, I decided to daisy chain a few rubyurl-based sites
together and came up with:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://rubyurl.com/hj6l&quot;&gt;http://rubyurl.com/hj6l&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Enjoy!&lt;/p&gt;

&lt;p&gt;As always, you can &lt;a href=&quot;http://github.com/robbyrussell/rubyurl&quot;&gt;fork/clone
here&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Wed, 16 Dec 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/12/16/rubyurl-goes-gop/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/12/16/rubyurl-goes-gop/</guid>
        
        <category>rubyurl</category>
        
        <category>gop</category>
        
        <category>zombieurl</category>
        
        
      </item>
    
      <item>
        <title>Sending email: Controllers versus Models</title>
        <description>&lt;p&gt;While reviewing some code recently, I came across controller code that
resembled the following.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;save&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;CustomerMailer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;deliver_welcome_message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;customer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;flash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Your account has been successfully created. We&apos;ve sent you a welcome letter with…&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;redirect_to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dashboard_path&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
  &lt;span class=&quot;err&quot;&gt;…&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Fairly typical Rails code. Nothing alarming here, but I wanted to
evaluate the call to the mailer in this scenario. When it comes to
sending emails from your application, you can choose to do it from the
controller as in the example above or in your models. Our team prefers
to do this from our model via a callback as we are considering this to
be part of our business logic.&lt;/p&gt;

&lt;p&gt;Each time a customer is created, we want to send them an email. This can
be moved into the model and resembled something like the following..&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;after_create&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:send_welcome_message&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#, other callbacks..&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;send_welcome_message&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;CustomerMailer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;deliver_welcome_message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There are a few benefits to doing it this way.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;We can test that this is being triggered within our model specs
instead of our controller specs. (we prefer to spend more of our
time working within models than controllers)&lt;/li&gt;
  &lt;li&gt;We remove the dependency that all requests must be processed through
our controllers.
    &lt;ul&gt;
      &lt;li&gt;Example: We may one day create rake tasks that data and want
these emails to still be sent out. (We’ve had to do this a few
times)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I definitely don’t think doing this via controllers is a bad idea, I
just lean towards keeping controllers as dumbed down as possible. This
allows us to have less controller code that is focused on passing data
to/from models and letting our models do the heavy lifting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UPDATE:&lt;/strong&gt; DHH was kind enough to &lt;a href=&quot;http://www.loudthinking.com/posts/43-think-of-emails-as-views-delivered-through-smtp&quot;&gt;post a more detailed
response&lt;/a&gt;
on his blog.&lt;/p&gt;
</description>
        <pubDate>Mon, 16 Nov 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/11/16/sending-email-controllers-versus-models/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/11/16/sending-email-controllers-versus-models/</guid>
        
        <category>development</category>
        
        <category>rubyonrails</category>
        
        <category>actionmailer</category>
        
        <category>models</category>
        
        <category>controllers</category>
        
        <category>patterns</category>
        
        
      </item>
    
      <item>
        <title>Using BETWEEN for SQL comparisons</title>
        <description>&lt;p&gt;Recently, &lt;a href=&quot;http://eddorre.com/&quot;&gt;Carlos&lt;/a&gt;, suggested that I should start
sharing some basic SQL tips that help with performance and/or general
usage. I recently came across some code that I didn’t like to read
and/or write. For example, let’s take the following…&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;```
SELECT * FROM brochures WHERE published_at &amp;lt;= now() AND archived_at &amp;gt;= now()
```
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Essentially, this is pulling back some data &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WHERE&lt;/code&gt; the the brochures
are considered published. (&lt;em&gt;We have a project that allows people to
manage their brochure launch dates ahead of time.&lt;/em&gt;) In fact, in this
project, we have no less than 6-8 dates in the database that we’re
comparing data on and it’s easy to get lost in the logic when trying to
understand it.&lt;/p&gt;

&lt;p&gt;Now, there isn’t anything inheriently wrong with how this condition is
constuctued. As a matter of personal taste, I find it annoying to
mentally parse. Also, I find having to write &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;now()&lt;/code&gt; more than once in a
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WHERE&lt;/code&gt; clause to feel like I’m repeating myself.&lt;/p&gt;

&lt;p&gt;Read it outloud…&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“WHERE the brochures published at date is less than and/or equal to
right now AND the archived date is greater than and/or equal to now.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Who talks like that?&lt;/p&gt;

&lt;p&gt;Luckily, there is a better and in my opinion, a more readable way to
express this is with the BETWEEN construct in SQL. (&lt;a href=&quot;http://www.postgresql.org/docs/current/static/functions-comparison.html&quot;&gt;postgresql
docs&lt;/a&gt;,
&lt;a href=&quot;http://dev.mysql.com/doc/refman/5.0/en/comparison-operators.html#operator_between&quot;&gt;mysql
docs&lt;/a&gt;)&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;```
SELECT * FROM brochures WHERE now() BETWEEN published_at AND archived_at
```
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Let’s read this outloud…&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“WHERE the current date is &lt;em&gt;between&lt;/em&gt; the published at and archived at
dates.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This sounds more natural to me.&lt;/p&gt;

&lt;p&gt;Additionally, you can also do the inverse with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NOT&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;```
SELECT ... WHERE now() NOT BETWEEN brochures.published_at AND brochures.archive_at
```
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Remember kids, &lt;em&gt;“code is for humans first and computers second.”&lt;/em&gt; —
Martin Fowler&lt;/p&gt;
</description>
        <pubDate>Sat, 14 Nov 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/11/14/using-between-for-sql-comparisons/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/11/14/using-between-for-sql-comparisons/</guid>
        
        <category>code</category>
        
        <category>sql</category>
        
        <category>development</category>
        
        <category>postgresql</category>
        
        <category>mysql</category>
        
        <category>refactoring</category>
        
        
      </item>
    
      <item>
        <title>Planet Argon Podcast, Episode 3: How We Manage Bugs</title>
        <description>&lt;p&gt;Earlier this week, we published Episode 3 of the &lt;a href=&quot;http://planetargon.com/podcast&quot;&gt;Planet Argon
Podcast&lt;/a&gt;. In this latest episode we
responded to one of the ideas someone in the audience &lt;a href=&quot;http://heybrainstormr.com/t/5DW1&quot;&gt;asked on this
brainstormr&lt;/a&gt;, which was, “How do you
manage bugs?”&lt;/p&gt;

&lt;p&gt;We had a round table discussion about how we classify and prioritize
bugs with our clients, ticketing systems, and other tools that we use to
streamline this process.&lt;/p&gt;

&lt;p&gt;You can listen to this on
&lt;a href=&quot;http://feeds.feedburner.com/PlanetArgonPodcast&quot;&gt;iTunes&lt;/a&gt; or
&lt;a href=&quot;http://planetargon.com/podcast&quot;&gt;online&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Wed, 11 Nov 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/11/11/planet-argon-podcast-episode-3-how-we-manage-bugs/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/11/11/planet-argon-podcast-episode-3-how-we-manage-bugs/</guid>
        
        <category>agile</category>
        
        <category>bugs</category>
        
        <category>lighthouse</category>
        
        <category>planetargon</category>
        
        <category>podcast</category>
        
        <category>collaboration</category>
        
        <category>clients</category>
        
        
      </item>
    
      <item>
        <title>Launching Ruby on Rails projects, the video</title>
        <description>&lt;p&gt;For those of you who didn’t make it to &lt;a href=&quot;http://www.rails-underground.com/&quot;&gt;Rails
Underground&lt;/a&gt; in July to witness my
&lt;em&gt;mind-blowing&lt;/em&gt; talk, &lt;a href=&quot;http://www.slideshare.net/robbyrussell/launching-ruby-on-rails-projects-a-checklist&quot;&gt;Launching Ruby on Rails
projects&lt;/a&gt;
, it appears that Skills Matter has finally &lt;a href=&quot;http://skillsmatter.com/podcast/ajax-ria/robby-russell-launching-ruby-on-rails-projects&quot;&gt;posted a
video&lt;/a&gt;
of it online. :-)&lt;/p&gt;

&lt;p&gt;The sound levels are really low… but hopefully you’ll find it helpful.&lt;/p&gt;

&lt;p&gt;You can also &lt;a href=&quot;http://www.slideshare.net/robbyrussell/launching-ruby-on-rails-projects-a-checklist&quot;&gt;view the
slides&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;related-posts&quot;&gt;Related Posts&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.robbyonrails.com/articles/2008/12/14/launching-ruby-on-rails-projects-a-checklist&quot;&gt;Launching Ruby on Rails projects, a
checklist&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Wed, 11 Nov 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/11/11/launching-ruby-on-rails-projects-the-video/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/11/11/launching-ruby-on-rails-projects-the-video/</guid>
        
        <category>projects</category>
        
        <category>agile</category>
        
        <category>development</category>
        
        <category>clients</category>
        
        <category>presentation</category>
        
        <category>conference</category>
        
        <category>rubyonrails</category>
        
        <category>video</category>
        
        
      </item>
    
      <item>
        <title>Tracking Google Analytics events in development environment with GoogleAnalyticsProxy</title>
        <description>&lt;p&gt;As mentioned in a recent article[^1], I’ve been diving deep into &lt;a href=&quot;http://www.google.com/analytics/&quot;&gt;Google
Analytics&lt;/a&gt; lately while working on a
few client projects. We’re aiming to use much more of the features of
Google Analytics and have been hitting some roadblocks with the
development versus production application environments. Once you begin
to dive into event tracking and AJAX-driven goal conversions, relying on
just the sample code that Google Analytics provides you is going to
result in you looking at a handful of JavaScript errors.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://skitch.com/robbyrussell/nfu1f/pagetracker-is-not-defined&quot;&gt;&lt;img src=&quot;http://img.skitch.com/20091101-c62euttfs5ejejt2eqpfxpnfau.preview.jpg&quot; alt=&quot;pageTracker is not
defined&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;another example from the firebug javascript console…&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://skitch.com/robbyrussell/nfu1g/firebug-pagetracker-is-not-defined&quot;&gt;&lt;img src=&quot;http://img.skitch.com/20091101-p5mb21ufih99n3sa6uafsc4gyf.preview.jpg&quot; alt=&quot;firebug pageTracker is not
defined&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We see JavaScript errors like this because we don’t load the google
analytics code in our development environments. As you can see, we are
only loading this in our production environment.&lt;/p&gt;

&lt;div class=&quot;language-erb highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;%&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;RAILS_ENV&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;production&apos;&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;-%&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;&amp;lt;!--// Google Analytics //--&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text/javascript&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;gaJsHost&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;https:&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;location&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;protocol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;https://ssl.&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;http://www.&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;unescape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;%3Cscript src=&apos;&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;gaJsHost&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;google-analytics.com/ga.js&apos; type=&apos;text/javascript&apos;%3E%3C/script%3E&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;&amp;lt;%&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;-%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To &lt;a href=&quot;http://code.google.com/apis/analytics/docs/gaJS/gaJSApiEventTracking.html#_gat.GA_EventTracker_._trackEvent&quot;&gt;track an event with Google
Analytics&lt;/a&gt;,
you’d need to trigger something like:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;pageTracker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;_trackEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;‘&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Button&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;’&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;‘&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Click&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;’&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;‘&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Get&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;touch&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;’&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As you can see from our code earlier, in development, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pageTracker&lt;/code&gt;
variable isn’t defined and that’s why we’re getting those JS errors. We
also don’t want to add conditionals everywhere in our application to
check if we’re in development or production environment.. as that’d just
make our views uglier than they need to be. So, I decided that I’d
create a proxy class in JavaScript that would allow us to trigger
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_trackEvent()&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_trackPageview()&lt;/code&gt; and handle it appropriately.&lt;/p&gt;

&lt;p&gt;This class works with the following logic:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;if google analytics &lt;strong&gt;is&lt;/strong&gt; loaded, pass the parameters to the real
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pageTracker&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;if google analytics &lt;strong&gt;is NOT&lt;/strong&gt; loaded, output the information to
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;console.log()&lt;/code&gt; for debugging purposes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, on a gallery on our web site… we track when people
navigate next and/or previous through the photos. In our development
environment, I can watch the JavaScript console output the following:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://skitch.com/robbyrussell/nfu19/firebug-gap&quot;&gt;&lt;img src=&quot;http://img.skitch.com/20091101-jwbyprqyaj68hxtjw8bbg6mccm.preview.jpg&quot; alt=&quot;Firebug -
GAP&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And in our production environment, we can see that this was sent to
Google Analytics.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://skitch.com/robbyrussell/nfuut/firebug-trackevent&quot;&gt;&lt;img src=&quot;http://img.skitch.com/20091101-q9dkn8tw3kyndxr55fqed6k8fq.preview.jpg&quot; alt=&quot;Firebug -
trackEvent()&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We’re able to do this by initializing the GoogleAnalyticsProxy class and
calling these functions through it. For example:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;_gap&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GoogleAnalyticsProxy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;_gap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;_trackEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;‘&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Video&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;’&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;‘&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Play&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;’&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;‘&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Homepage&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;video&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;’&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;_gap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;_trackEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;‘&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Video&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;’&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;‘&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Pause&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;’&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;‘&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Homepage&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;video&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;’&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;_gap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;_trackEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;‘&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Button&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;’&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;‘&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Click&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;’&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;‘&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Call&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;action&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;X&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;’&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You’ll see that we’re just calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_gap&lt;/code&gt; versus &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pageTracker&lt;/code&gt;. We then
replace all the instances of pageTracker (except where it is defined in
the google analytics code block they provide you). You’ll find this
located near the bottom of our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;application.html.erb&lt;/code&gt; file.&lt;/p&gt;

&lt;div class=&quot;language-erb highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;%&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;RAILS_ENV&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;production&apos;&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;-%&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;&amp;lt;!--// Google Analytics //--&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text/javascript&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;gaJsHost&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;https:&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;location&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;protocol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;https://ssl.&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;http://www.&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;unescape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;%3Cscript src=&apos;&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;gaJsHost&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;google-analytics.com/ga.js&apos; type=&apos;text/javascript&apos;%3E%3C/script%3E&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;&amp;lt;%&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;-%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We now have &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_gap&lt;/code&gt; available throughout our project and can call
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_trackEvent()&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_trackPageview()&lt;/code&gt; with it. Note: You can use any JS
variable name that you want, _gap is just what I went with.&lt;/p&gt;

&lt;h2 id=&quot;get-googleanalyticsproxy&quot;&gt;Get GoogleAnalyticsProxy&lt;/h2&gt;

&lt;p&gt;I’ve gone ahead and tossed this small JavaScript class (known as
&lt;a href=&quot;http://github.com/robbyrussell/GoogleAnalyticsProxy&quot;&gt;GoogleAnalyticsProxy&lt;/a&gt;)
on Github for your enjoyment. I have some more articles in the works
that will show you some tips for how to make the most of Google
Analytics. If you have any questions and/or ideas for related article
topics, don’t hesitate to let me know.&lt;/p&gt;

&lt;hr /&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.robbyonrails.com/articles/2009/10/21/tracking-ajax-driven-events-in-ruby-on-rails-for-google-analytics-conversion-goals&quot;&gt;Tracking AJAX-driven events in Ruby on Rails for Google Analytics
conversion
goals&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
</description>
        <pubDate>Sun, 01 Nov 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/11/01/googleanalyticsproxy-for-development-environment-tracking-events-in-google-analytics/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/11/01/googleanalyticsproxy-for-development-environment-tracking-events-in-google-analytics/</guid>
        
        <category>javascript</category>
        
        <category>analytics</category>
        
        <category>kpi</category>
        
        <category>googleanalytics</category>
        
        <category>events</category>
        
        <category>proxy</category>
        
        <category>opensource</category>
        
        <category>github</category>
        
        <category>prototype</category>
        
        
      </item>
    
      <item>
        <title>Planet Argon Podcast, Episode 2: The Letter Scotch</title>
        <description>&lt;p&gt;Earlier this week our &lt;a href=&quot;http://planetargon.com/podcast&quot;&gt;new podcast&lt;/a&gt; was
approved and is &lt;a href=&quot;http://planetargon.com/news/2009/10/26/podcast-now-available-in-apple-itunes-store&quot;&gt;now available in the Apple iTunes
Store&lt;/a&gt;.
We’re also &lt;a href=&quot;http://heybrainstormr.com/t/5DW1&quot;&gt;soliciting topic ideas for future
episodes&lt;/a&gt; on
&lt;a href=&quot;http://planetargon.com/projects/brainstormr&quot;&gt;brainstormr&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We posted Episode 2, The Letter Scotch, yesterday for your enjoyment. In
this episode, we covered a handful of web browser tools that we use (and
detest) to debug HTML, CSS, and JavaScript. This included Web Inspector,
Firebug, DebugBar, and a handful of other tools. We all have slightly
different preferences, depending on the tasks that we’re working on and
the team had an open dialogue about the pros/cons of each of these
tools.&lt;/p&gt;

&lt;p&gt;You can learn more about and listen to our podcast at
&lt;a href=&quot;http://planetargon.com/podcast&quot;&gt;http://planetargon.com/podcast&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thanks in advance for listening!&lt;/p&gt;
</description>
        <pubDate>Fri, 30 Oct 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/10/30/planet-argon-podcast-episode-2-the-letter-scotch/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/10/30/planet-argon-podcast-episode-2-the-letter-scotch/</guid>
        
        <category>podcast</category>
        
        <category>planetargon</category>
        
        <category>html</category>
        
        <category>css</category>
        
        <category>firebug</category>
        
        <category>safari</category>
        
        <category>firefox</category>
        
        <category>debugbar</category>
        
        <category>internetexplorer</category>
        
        <category>webbrowsers</category>
        
        <category>javascript</category>
        
        <category>webinspector</category>
        
        <category>itunes</category>
        
        <category>agile</category>
        
        <category>rubyonrails</category>
        
        
      </item>
    
      <item>
        <title>Planet Argon Podcast, Episode 1: Shin Splints</title>
        <description>&lt;p&gt;We’re currently waiting to get our new podcast approved by Apple, but
have uploaded episode 1 to tumblr in the meantime.&lt;/p&gt;

&lt;p&gt;In this short episode, we cover the following topics:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://github.com/binarylogic/authlogic&quot;&gt;Authlogic&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://github.com/notahat/machinist/&quot;&gt;Machinist&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://github.com/robbyrussell/faker&quot;&gt;Faker&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://lesscss.org/&quot;&gt;LESS&lt;/a&gt; (for CSS)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://textorize.org/&quot;&gt;Textorize&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://planetargon.com/what-we-do/development/rails-code-audit&quot;&gt;The 8-Hour Rails Code
Audits&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’re planning to keep this short and focused to a few topics. Once it’s
posted on iTunes, we’ll let you know.&lt;/p&gt;

&lt;p&gt;Please consider &lt;a href=&quot;http://feeds.feedburner.com/PlanetArgonPodcast&quot;&gt;subscribing to the
podcast&lt;/a&gt;. Enjoy!&lt;/p&gt;
</description>
        <pubDate>Thu, 22 Oct 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/10/22/planet-argon-podcast-episode-1-shin-splints/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/10/22/planet-argon-podcast-episode-1-shin-splints/</guid>
        
        <category>podcast</category>
        
        <category>authlogic</category>
        
        <category>gems</category>
        
        <category>machinist</category>
        
        <category>faker</category>
        
        <category>less</category>
        
        <category>css</category>
        
        <category>textorize</category>
        
        
      </item>
    
      <item>
        <title>Tracking AJAX-driven events in Ruby on Rails for Google Analytics conversion goals</title>
        <description>&lt;p&gt;Tracking your
&lt;a href=&quot;http://en.wikipedia.org/wiki/Performance_indicator&quot;&gt;KPI’s&lt;/a&gt; is extremely
important in your online venture. At a minimum, you should be using
something like &lt;a href=&quot;http://www.google.com/analytics/&quot;&gt;Google Analytics&lt;/a&gt; to
track conversions in your application. Setting up goals is actually
quite simple, especially if you’re just tracking that specific pages are
loaded. However, if some of your conversion points occur through AJAX,
you might not be capturing those activities in Google Analytics.&lt;/p&gt;

&lt;p&gt;Lucky for you, it’s actually &lt;a href=&quot;http://www.google.com/support/googleanalytics/bin/answer.py?hl=en&amp;amp;answer=55520&quot;&gt;quite simple to update
this&lt;/a&gt;.
I thought I’d show you a fairly simple example to help you along.&lt;/p&gt;

&lt;p&gt;On our web site, we have a mini contact form at the bottom of many of
our pages. When submitted, if JavaScript is enabled, it’ll perform an
Ajax request to submit the form. If you fill out the main &lt;a href=&quot;http://planetargon.com/get-in-touch&quot;&gt;Get in
Touch&lt;/a&gt; form that gets processed and
we redirect people to a thank you page. The URL for that is unique and
we’re able to track those in Google Analytics quite easily.&lt;/p&gt;

&lt;p&gt;However, with the Ajax-form, the URL in the browser isn’t going to
change so Google Analytics isn’t going to track that conversion. So, we
needed to track that properly.&lt;/p&gt;

&lt;p&gt;To do this, we just need to call a JavaScript function that the Google
Analytics code provides you.&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;pageTracker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;_trackPageview&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/contact_requests/thanks&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Let’s look at some simple code from our controller action. If the
request is from JavaScript, we currently replace the form area with the
content in a partial. (&lt;strong&gt;note&lt;/strong&gt;: if you’re curious about the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_x&lt;/code&gt;, read
&lt;a href=&quot;http://www.robbyonrails.com/articles/2007/08/01/designers-developers-and-the-x_-factor&quot;&gt;Designers, Developers and the x_
factor&lt;/a&gt;)&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;respond_to&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;redirect_to&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:action&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:thanks&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;js&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:update&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;replace&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:x_mini_contact_form_module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:partial&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;mini_contact_form_thanks&apos;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As you can see, the redirect will within the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;format.html&lt;/code&gt; block will
lead people to our conversion point. However, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;format.js&lt;/code&gt; block will
keep the user on the current page and it’ll not trigger Google Analytics
to track the conversion. To make this happen, we’ll just sprinkle in the
following line of code.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;pageTracker._trackPageview(&quot;/contact_requests/thanks&quot;);&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;However, if you need to do something like this in several locations in
your application, you might want to just extend the JavaScriptGenerator
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;page.&lt;/code&gt; GeneratorMethods. (you could toss this in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lib/&lt;/code&gt;, create a
plugin, etc…)&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;ActionView&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Helpers&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;PrototypeHelper&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;JavaScriptGenerator&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#:nodoc:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;GeneratorMethods&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# Calls the Google Analytics pageTracker._trackPageview function with path.&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;#&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# Examples:&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;#&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;#&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# # Triggers: pageTracker._trackPageview(&apos;/contact_requests/thanks&apos;);&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# page.track_page_view &apos;/contact_requests/thanks&apos;&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;#&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;track_page_view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;record&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;pageTracker._trackPageview(&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&apos;);&quot;&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will allow us to do the following:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;track_page_view&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/contact_requests/thanks&quot;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# or using a route/path&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;track_page_view&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;thanks_contact_requests_path&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So, our updated code now looks like:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:update&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;replace&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:x_mini_contact_form_module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:partial&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;mini_contact_form_thanks&apos;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;track_page_view&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;thanks_contact_requests_path&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With this in place, we can sprinkle similar code for our various
conversion points that are Ajax-driven and Google Analytics will pick it
up.&lt;/p&gt;

&lt;p&gt;Happy tracking!&lt;/p&gt;
</description>
        <pubDate>Wed, 21 Oct 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/10/21/tracking-ajax-driven-events-in-ruby-on-rails-for-google-analytics-conversion-goals/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/10/21/tracking-ajax-driven-events-in-ruby-on-rails-for-google-analytics-conversion-goals/</guid>
        
        <category>ajax</category>
        
        <category>rubyonrails</category>
        
        <category>analytics</category>
        
        <category>javascript</category>
        
        <category>prototype</category>
        
        <category>kpi</category>
        
        <category>conversions</category>
        
        
      </item>
    
      <item>
        <title>RailsOnPg released</title>
        <description>&lt;p&gt;Hello fellow &lt;a href=&quot;http://postgresql.org&quot;&gt;PostgreSQL&lt;/a&gt; and &lt;a href=&quot;http://rubyonrails.org/&quot;&gt;Ruby on
Rails&lt;/a&gt; geeks,&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://github.com/alex3t&quot;&gt;Alexander Tretyakov&lt;/a&gt;
(&lt;a href=&quot;http://twitter.com/redvok&quot;&gt;twitter&lt;/a&gt;) recently released a plugin for
Ruby on Rails, which extends migrations and provides you with the
ability to create.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Functions (&lt;a href=&quot;http://developer.postgresql.org/pgdocs/postgres/sql-createtrigger.html&quot;&gt;pg
docs&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Triggers (&lt;a href=&quot;http://developer.postgresql.org/pgdocs/postgres/sql-createfunction.html&quot;&gt;pg
docs&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Views (&lt;a href=&quot;http://developer.postgresql.org/pgdocs/postgres/sql-createview.html&quot;&gt;pg
docs&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While you can already do something like this with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;execute&lt;/code&gt; in your
migrations:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;CREATE VIEW my_tasty_snacks AS SELECT * FROM snacks WHERE food = &apos;Tasty&apos;;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With RailsOnPage, you’re provided a DSL so that you can do the
following:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;create_view&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:my_tasy_snacks&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;*&apos;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;snacks&apos;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;conditions&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;food&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Tasty&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;note: I haven’t tested the above, just a hypothetical example&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Anyhow, if you’re in the habit of using views, functions, or triggers
with your PostgreSQL database and are using Ruby on Rails, you might
&lt;a href=&quot;http://github.com/alex3t/rails_on_pg&quot;&gt;give RailsOnPg a whirl&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Wed, 21 Oct 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/10/21/railsonpg-released/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/10/21/railsonpg-released/</guid>
        
        <category>postgresql</category>
        
        <category>rubyonrails</category>
        
        <category>plugins</category>
        
        <category>databases</category>
        
        
      </item>
    
      <item>
        <title>Email twice. Four months later</title>
        <description>&lt;p&gt;It’s been just over four months since I posted about my experiment,
&lt;a href=&quot;http://www.robbyonrails.com/articles/2009/06/10/email-twice-daily-no-more-no-less&quot;&gt;Email. Twice daily. No more, no
less.&lt;/a&gt;
where I shared my plans to restrict myself to checking email only twice
a day at designated times. In the post, I had hinted at sharing my
lessons months later. So, it’s time to throw my dirty laundry in the
street and expose myself.&lt;/p&gt;

&lt;p&gt;First off.. the brutal truth. It’s really fucking hard to maintain this.
Habits are nearly as hard to &lt;em&gt;make&lt;/em&gt; as they are to break. I suspect that
I honor my rule 2-3 days each week and it’s completely inconsistent the
remainder. Usually, I find myself looking at email at 8:30am and have to
slap myself and yell, “what are you doing?!!?”&lt;/p&gt;

&lt;p&gt;Guilt sinks in and I hit ⌘-q. Problem solved… for a little while.&lt;/p&gt;

&lt;p&gt;So, what has lead to this. Well, one of the biggest hurdles has been
that one of our largest clients is now focused more in the United
Kingdom. Luckily, I’m an early-morning person, but this means that my
10am PDT rule wouldn’t have me checking for their precious emails until
6pm GMT their time. Not exactly acceptable. So, I’ve been more flexible
in the mornings and responding to emails as early as 5-6am PDT. However,
I realize that I’m cheating myself of previous focus time and need to
recalibrate my email windows.&lt;/p&gt;

&lt;p&gt;Given these new constraints, I’m now trying 8:30am and 2:30pm as my
primary email times.&lt;/p&gt;

&lt;p&gt;I’m curious how this has been working out for you…&lt;/p&gt;
</description>
        <pubDate>Wed, 21 Oct 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/10/21/email-twice-four-months-later/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/10/21/email-twice-four-months-later/</guid>
        
        <category>email</category>
        
        <category>productivity</category>
        
        <category>focus</category>
        
        
      </item>
    
      <item>
        <title>The 8-Hour Rails Code Audit</title>
        <description>&lt;p&gt;While our team is typically focused on larger client and internal
projects, we do get an opportunity to assist businesses on a much
smaller scale. Whether this be through retainer-based consulting or
through code audits, we have seen a lot of Ruby on Rails code over what
has nearly been… five years!? We’ve been able to compile a fairly
extensive checklist that we use in our code audit process that we’ve
decided to streamline it into a smaller product.&lt;/p&gt;

&lt;p&gt;Historically, this service has ranged anywhere from $2000-6000,
depending the size and scope of the projects, but we want to help
smaller startups[^1^](#fn1){#fnref1 .footnote-ref role=”doc-noteref”}
and projects outline a roadmap for how they can begin to refactor and
optimize their existing code base so that they can be more efficient at
the start of 2010. So, we’ve scaled things down into an extremely
affordable flat-rate package where we work off of a pre-defined number
of hours.[^2^](#fn2){#fnref2 .footnote-ref role=”doc-noteref”}&lt;/p&gt;

&lt;p&gt;Through the end of 2009, we’re now offering &lt;a href=&quot;http://planetargon.com/what-we-do/development/rails-code-audit&quot;&gt;the 8-Hour Rails Code Audit
package&lt;/a&gt;
for just $1000 USD
(&lt;a href=&quot;http://planetargon.com/what-we-do/development/rails-code-audit&quot;&gt;details&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;We’re currently limiting this service to just two projects per week, &lt;a href=&quot;http://planetargon.com/get-in-touch&quot;&gt;so
reserve your spot now&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;related-posts&quot;&gt;Related Posts&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.robbyonrails.com/articles/2007/06/17/audit-your-rails-development-team&quot;&gt;Audit Your Rails Development
Team&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.robbyonrails.com/articles/2007/06/18/rails-code-audits-and-reviews-continued&quot;&gt;Rails Code Audits and Reviews,
continued&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;::: {#fn1}
Larger projects are welcome to benefit from this service and custom
quotes are available upon request.&lt;a href=&quot;#fnref1&quot;&gt;↩︎&lt;/a&gt;{.footnote-back
role=”doc-backlink”}
:::&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;::: {#fn2}
As always, we’re happy to discuss longer
engagements.&lt;a href=&quot;#fnref2&quot;&gt;↩︎&lt;/a&gt;{.footnote-back role=”doc-backlink”}
:::&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;
</description>
        <pubDate>Tue, 20 Oct 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/10/20/the-8-hour-rails-code-audit/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/10/20/the-8-hour-rails-code-audit/</guid>
        
        <category>code</category>
        
        <category>codeaudit</category>
        
        <category>agile</category>
        
        <category>programming</category>
        
        <category>planetargon</category>
        
        <category>audit</category>
        
        
      </item>
    
      <item>
        <title>Flash Message Conductor now a Gem</title>
        <description>&lt;p&gt;We’ve been doing some early (or late… if you’re a half-full kind of
person) spring cleaning on some of our projects. One of the small
projects,
&lt;a href=&quot;http://github.com/planetargon/flash-message-conductor&quot;&gt;flash_message_conductor&lt;/a&gt;,
which &lt;a href=&quot;http://www.robbyonrails.com/articles/2008/08/29/flash-message-conductor&quot;&gt;we released last year as a
plugin&lt;/a&gt;
is now a gem. We’ve been &lt;a href=&quot;http://www.robbyonrails.com/articles/2009/01/13/question-plugins-or-gems-or-both&quot;&gt;moving away from using plugins in favor of
gems&lt;/a&gt;
as we like locking in specific released versions and being able to
specify them in our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;environment.rb&lt;/code&gt; file is quite convenient.&lt;/p&gt;

&lt;p&gt;To install, just run the following:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo gem install flash-message-conductor --source=http://gemcutter.org
Successfully installed flash-message-conductor-1.0.0
1 gem installed
Installing ri documentation for flash-message-conductor-1.0.0...
Installing RDoc documentation for flash-message-conductor-1.0.0...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You’ll then just need to include the following in your
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config/environment.rb&lt;/code&gt; file.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Initializer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# …&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;flash-message-conductor&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:lib&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;flash_message_conductor&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:source&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;http://gemcutter.org&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can take a peak at the
&lt;a href=&quot;http://github.com/planetargon/flash-message-conductor/blob/master/README&quot;&gt;README&lt;/a&gt;
for usage examples.&lt;/p&gt;

&lt;p&gt;We’ll be packaging up a handful of our various plugins that we reuse on
projects and moving them to gems. Stay tuned… :-)&lt;/p&gt;
</description>
        <pubDate>Tue, 13 Oct 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/10/13/flash-message-conductor-now-a-gem/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/10/13/flash-message-conductor-now-a-gem/</guid>
        
        <category>gem</category>
        
        <category>plugins</category>
        
        <category>github</category>
        
        <category>development</category>
        
        <category>code</category>
        
        <category>rubyonrails</category>
        
        
      </item>
    
      <item>
        <title>Oh My Zsh gets an auto-updater</title>
        <description>&lt;p&gt;I wanted to publically thank everyone for helping me get Oh My Zsh out
there and &lt;a href=&quot;http://github.com/robbyrussell/oh-my-zsh/&quot;&gt;continue to improve
it&lt;/a&gt;. Many of us spend a lot
of time in our terminals throughout the day and I firmly believe that
having a well-working shell is nearly as important as having a
well-working texteditor.&lt;/p&gt;

&lt;p&gt;While Oh My Zsh isn’t a large project, it is my attempt to share what
I’ve learned about using zsh with others… but honestly, my goal is to
learn from you. I don’t have a lot of time to really dive into the
deepend of the zsh-pool so am relying on others to share their tricks,
hacks, functions, themes, etc. So, I thought that if I created a basic
framework with outlined some conventions so that others could
contribute, that perhaps I’d end up with a kickass shell.&lt;/p&gt;

&lt;p&gt;So far… Oh My Zsh has been &lt;a href=&quot;http://github.com/robbyrussell/oh-my-zsh/network&quot;&gt;forked on github 25
times&lt;/a&gt; and is being
&lt;a href=&quot;http://github.com/robbyrussell/oh-my-zsh/watchers&quot;&gt;watched by over 100
people&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Last week, I pushed out an update that introduces an auto-update
feature. I’m quite keen of desktop applications that can auto-update
themselves, so our initial version of this feature will ask you no more
than once a week if you want to check for updates. This means that as we
continue to extend and improve Oh My Zsh, you can keep up-to-date.&lt;/p&gt;

&lt;p&gt;::: thumbnail
&lt;a href=&quot;http://skitch.com/robbyrussell/nbm23/terminal-zsh&quot;&gt;&lt;img src=&quot;http://img.skitch.com/20091001-gsgeh75peiebfs8y6146xuu7r8.preview.jpg&quot; alt=&quot;Terminal 2014
zsh&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
[Uploaded with &lt;a href=&quot;http://plasq.com/&quot;&gt;plasq&lt;/a&gt;’s
&lt;a href=&quot;http://skitch.com&quot;&gt;Skitch&lt;/a&gt;!]{style=”font-family: Lucida Grande, Trebuchet, sans-serif, Helvetica, Arial; font-size: 10px; color: #808080”}
:::&lt;/p&gt;

&lt;p&gt;It’s the beginning of a new month… are you still using Bash? Perhaps
you’re using your own zsh configuration but want to see what else zsh
can offer you? I invite you to install Oh My Zsh today. :-)&lt;/p&gt;

&lt;p&gt;Just run this in your terminal and you’ll get setup. Don’t worry… you
won’t lose your existing configuration. :-)&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wget http://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh -O - | sh&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For more infromation, visit &lt;a href=&quot;http://github.com/robbyrussell/oh-my-zsh/&quot;&gt;http://github.com/robbyrussell/oh-my-zsh/&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Wed, 30 Sep 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/09/30/oh-my-zsh-gets-an-auto-updater/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/09/30/oh-my-zsh-gets-an-auto-updater/</guid>
        
        <category>zsh</category>
        
        <category>bash</category>
        
        <category>terminal</category>
        
        <category>project</category>
        
        
      </item>
    
      <item>
        <title>Planting the seeds</title>
        <description>&lt;p&gt;Yesterday, the Rails team released 2.3.4, which includes standardized
way for loading seed data into your application so that you didn’t have
to clutter your database migrations.&lt;/p&gt;

&lt;p&gt;I noticed a few comments on some blogs where people were asking how to
use this new feature, so here is a quick runthrough a few ways that you
can use it.&lt;/p&gt;

&lt;h2 id=&quot;populating-seed-data-approaches&quot;&gt;Populating Seed Data Approaches&lt;/h2&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;db/seeds.rb&lt;/code&gt; file is your playground. We’ve been evolving our seed
file on a new project and it’s been great at allowing us to populate a
really large data. Here are a few approaches that we’ve taken to
diversify our data so that when we’re working on UI, we can have some
diversified content.&lt;/p&gt;

&lt;h3 id=&quot;basic-example&quot;&gt;Basic example&lt;/h3&gt;

&lt;p&gt;Any code that add to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;db/seeds.rb&lt;/code&gt; is going to executed when you run
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rake db:seed&lt;/code&gt;. You can do something as simple as:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# db/seeds.rb&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Article&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:title&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;My article title&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:body&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Lorem ipsum dolor sit amet, consectetur adipisicing elit&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Just create database records like you would in your Rails application or
in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;script/console&lt;/code&gt;. Simple enough, right? Let’s play with a few other
approaches that we’ve begun to use.&lt;/p&gt;

&lt;h3 id=&quot;use-the-names-of-real-people&quot;&gt;Use the names of real people&lt;/h3&gt;

&lt;p&gt;We’re using the &lt;a href=&quot;http://github.com/fcoury/octopi/&quot;&gt;Octopi gem&lt;/a&gt; to
connect to github, collect all the names of people that follow me there,
and using their names to seed our development database.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;vi&quot;&gt;@robby_on_github&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Octopi&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;robbyrussell&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# add a bunch of semi-real users&lt;/span&gt;
&lt;span class=&quot;vi&quot;&gt;@robby_on_github&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;followers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;follower&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;github_person&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Octopi&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;follower&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;next&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;github_person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;nil?&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# split their name in half... good enough (like the goonies)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;first_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;github_person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos; &apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;last_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;github_person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos; &apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;new_person&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:first_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;first_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:last_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;last_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:email&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Faker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Internet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:password&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;secret&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:password_confirmation&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;secret&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:github_username&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;follower&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:website_url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;github_person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;blog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We do this with a few sources (twitter, github, etc..) to pull in the
names of real people. If you want to be part of my seed data, you might
&lt;a href=&quot;http://github.com/robbyrussell/&quot;&gt;consider following me on Github&lt;/a&gt;. ;-)&lt;/p&gt;

&lt;h3 id=&quot;use-faker-for-fake-data&quot;&gt;Use Faker for Fake data&lt;/h3&gt;

&lt;p&gt;You may have noticed in the previous code sample, that I used Faker in
that code. We are using this a bunch in our seed data file. With Faker,
you can generate a ton of fake data really easy.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;links&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:title&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Faker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Lorem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;words&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos; &apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;capitalize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;:url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;http://&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Faker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Internet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;domain_name&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;:description&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Faker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Lorem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sentences&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos; &apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We might toss something like that into a method so that we can do the
following:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;vi&quot;&gt;@people&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;times&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;generate_link_for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@people&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sort_by&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;…and we’ll get 500 links added randomly across all of the people we
added to our system. You can get fairly creative here.&lt;/p&gt;

&lt;p&gt;For example, we might even wanted random amounts of comments added to
our links.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;generate_link_for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;link&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;links&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:title&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Faker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Lorem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;words&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos; &apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;capitalize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;http://&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Faker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Internet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;domain_name&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:description&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Faker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Lorem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sentences&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos; &apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# let&apos;s randomly add some comments...&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;link&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;valid?&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;times&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;link&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;comments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:person_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@people&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sort_by&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;:body&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Faker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Lorem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;paragraph&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It’s not beautiful, but it gets the job done. It makes navigating around
the application really easy so that we aren’t having to constantly input
new data all the time. As mentioned, it really helps when we’re working
on the UI.&lt;/p&gt;

&lt;h2 id=&quot;your-ideas&quot;&gt;Your ideas?&lt;/h2&gt;

&lt;p&gt;We’re trying a handful of various approaches to seed our database. If
you have some fun ways of populating your development database with
data, we’d love to hear about it.&lt;/p&gt;
</description>
        <pubDate>Sat, 05 Sep 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/09/05/planting-the-seeds/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/09/05/planting-the-seeds/</guid>
        
        <category>seeds</category>
        
        <category>database</category>
        
        <category>development</category>
        
        <category>workflow</category>
        
        <category>faker</category>
        
        <category>github</category>
        
        <category>rubyonrails</category>
        
        <category>code</category>
        
        
      </item>
    
      <item>
        <title>Oh My Zsh gets theme support</title>
        <description>&lt;p&gt;I just pushed a small change to &lt;a href=&quot;http://github.com/robbyrussell/oh-my-zsh/&quot;&gt;Oh My
Zsh&lt;/a&gt;, which gives it
rudimentary support for themes. What I’m hoping to do is collect prompts
from tons of people and make it simple for others to find a PROMPT that
works well for them.&lt;/p&gt;

&lt;p&gt;::: thumbnail
&lt;a href=&quot;http://skitch.com/robbyrussell/b7m4i/robbyrussells-oh-my-zsh-at-2c9f74b5c3f6910e7c66601008e9ddd0444b70c7-github&quot;&gt;&lt;img src=&quot;http://img.skitch.com/20090831-be83mt3js29aaxujgtsab2e7pe.preview.jpg&quot; alt=&quot;robbyrussell&apos;s oh-my-zsh at
2c9f74b5c3f6910e7c66601008e9ddd0444b70c7 -
GitHub&quot; /&gt;&lt;/a&gt;
:::&lt;/p&gt;

&lt;p&gt;As of right now, there are only three for you to choose from. So, please
head over to github, &lt;a href=&quot;http://github.com/robbyrussell/oh-my-zsh/&quot;&gt;fork Oh My
Zsh&lt;/a&gt;, add your theme, and
send a pull request. :-)&lt;/p&gt;

&lt;p&gt;::: thumbnail
&lt;a href=&quot;http://skitch.com/robbyrussell/b7m43/zsh-users-robbyrussell-projects-development-planetargon-brainstorm-zsh&quot;&gt;&lt;img src=&quot;http://img.skitch.com/20090831-j8q4wx315p3x4dj99parmgnn4w.preview.jpg&quot; alt=&quot;zsh /Users/robbyrussell/Projects/development/planetargon/brainstorm
2014
zsh&quot; /&gt;&lt;/a&gt;
:::&lt;/p&gt;

&lt;p&gt;Once I get it merged in, we’ll get a screenshot of it added to the Oh My
Zsh wiki. (&lt;a href=&quot;http://wiki.github.com/robbyrussell/oh-my-zsh/themes&quot;&gt;see
themes&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;I know that many of you have some really sweet prompts configured as I
got a lot of response with my post, &lt;a href=&quot;http://www.robbyonrails.com/articles/2009/02/13/show-me-your-and-ill-show-you-mine-terminal-prompts-with-git-branches&quot;&gt;Show me your and I’ll show you mine
(terminal prompts with git
branches)&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Mon, 31 Aug 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/08/31/oh-my-zsh-gets-theme-support/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/08/31/oh-my-zsh-gets-theme-support/</guid>
        
        <category>shell</category>
        
        <category>zsh</category>
        
        <category>terminal</category>
        
        <category>prompt</category>
        
        <category>git</category>
        
        <category>console</category>
        
        <category>themes</category>
        
        
      </item>
    
      <item>
        <title>..and on the seventh day, Science created zsh</title>
        <description>&lt;p&gt;Inspired by some recent posts from Tom on
&lt;a href=&quot;http://tomafro.net/tags/zsh&quot;&gt;zsh&lt;/a&gt;, I decided that I’d do my part to
help people give it a whirl. I’ve been using zsh for a few years now and
haven’t found myself missing bash.&lt;/p&gt;

&lt;p&gt;If you’re interested in taking a few minutes to give zsh a while, you’re
in luck. I recently reorganized all of my zsh config into a package and
&lt;a href=&quot;http://github.com/robbyrussell/oh-my-zsh/&quot;&gt;tossed it on github&lt;/a&gt; to
share. My goal was to create a reusable tool that would allow people to
get up and running quickly with some of the fun configuration that I’ve
come to rely on on a daily basis.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Auto-complete rake and capistrano tasks&lt;/li&gt;
  &lt;li&gt;Git branch names when you’re in a git project directory structure&lt;/li&gt;
  &lt;li&gt;Tons of color highlighting (grep, git, etc.)&lt;/li&gt;
  &lt;li&gt;Sexy prompts.. (so say me)&lt;/li&gt;
  &lt;li&gt;much much more…&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I invite you to give &lt;a href=&quot;http://github.com/robbyrussell/oh-my-zsh/&quot;&gt;Oh My
Zsh&lt;/a&gt; a whirl, which should
take you less than a minute. Just &lt;a href=&quot;http://github.com/robbyrussell/oh-my-zsh/&quot;&gt;follow the
instructions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Also, Oh My Zsh is Snow Leopard compatible. ;-)&lt;/p&gt;
</description>
        <pubDate>Sun, 30 Aug 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/08/30/and-on-the-seventh-day-science-created-zsh/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/08/30/and-on-the-seventh-day-science-created-zsh/</guid>
        
        <category>terminal</category>
        
        <category>osx</category>
        
        <category>linux</category>
        
        <category>console</category>
        
        <category>bash</category>
        
        <category>zsh</category>
        
        <category>git</category>
        
        <category>github</category>
        
        <category>commandline</category>
        
        
      </item>
    
      <item>
        <title>So long and thanks for all the hoodwinks</title>
        <description>&lt;p&gt;_why,&lt;/p&gt;

&lt;p&gt;If you’re out there and come across this… know that one of my fondest
memories on the internet was with you. Hoodink.d was one of the greatest
things on the internet four years ago and I suspect that a very tiny
fraction of the Ruby community has even heard of it.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/robbyrussell/3838279665/&quot; title=&quot;Thanks hoodwink&apos;d by Robby Russell, on Flickr&quot;&gt;&lt;img src=&quot;http://farm4.static.flickr.com/3450/3838279665_90a7e7632b_o.jpg&quot; alt=&quot;Thanks
hoodwink&apos;d&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fortunately for me, I have a copy of the hoodwink git repository and was
able to get it running tonight in hopes that I might find you lurking in
the mousehole. I’m convinced that you are in a parallel internetverse.
Perhaps you might send me an invite.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/robbyrussell/3838274563/&quot; title=&quot;Hoodwink&apos;d. do you remember? by Robby Russell, on Flickr&quot;&gt;&lt;img src=&quot;http://farm4.static.flickr.com/3437/3838274563_07ee2d960d.jpg&quot; alt=&quot;Hoodwink&apos;d. do you
remember?&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I miss hoodwink… and if you stay missing, I’ll just miss hoodwink
more.&lt;/p&gt;

&lt;p&gt;In the meantime, I wonder how hard it’ll be to get hoodwink to run on
rack.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/robbyrussell/3839209620/&quot; title=&quot;the winker&apos;s satellite office » login by Robby Russell, on Flickr&quot;&gt;&lt;img src=&quot;http://farm4.static.flickr.com/3520/3839209620_a62e917f8d.jpg&quot; alt=&quot;the winker&apos;s satellite office »
login&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Wink you on the other side,&lt;br /&gt;
Robby&lt;/p&gt;

&lt;p&gt;s. you can find me in my own mousehole… should you want to send me an
invite and/or feed me cheese.&lt;/p&gt;

&lt;h2 id=&quot;related-posts&quot;&gt;Related Posts&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.robbyonrails.com/articles/2005/08/25/thank-you-hoodwink-d&quot;&gt;Thank you
hoodwink.d&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Thu, 20 Aug 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/08/20/so-long-and-thanks-for-all-the-hoodwinks/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/08/20/so-long-and-thanks-for-all-the-hoodwinks/</guid>
        
        <category>_why</category>
        
        <category>hoodwinkd</category>
        
        <category>nostalgia</category>
        
        
      </item>
    
      <item>
        <title>Ch-ch-ch-changes at Planet Argon</title>
        <description>&lt;p&gt;Now that the cat is out of the bag, I can share some recent news with
you. Earlier today, we announced that &lt;a href=&quot;http://blueboxgrp.com/&quot;&gt;Blue Box
Group&lt;/a&gt; had acquired &lt;a href=&quot;http://railsboxcar.com&quot;&gt;Rails
Boxcar&lt;/a&gt;, our kickass deployment solution for
Ruby on Rails applications.&lt;/p&gt;

&lt;p&gt;Our team has been offering hosting services for over six years. When I
made the decision to start providing Rails hosting over four years ago,
it was something that I thought the community needed to validate that
Ruby on Rails was a viable solution for building web applications. At
the time, there was only one or two companies offering pre-configured
solutions. The good ole days. :-)&lt;/p&gt;

&lt;p&gt;Over the course of the past 4+ years, we’ve helped deploy and host well
over a thousand web applications built with Ruby on Rails. Perhaps we
even hosted your site at one point or another. We definitely had a lot
of fun and learned a lot from our experience.&lt;/p&gt;

&lt;p&gt;Fast-forward four years, the community now has several great solutions
and options for hosting their Ruby on Rails&lt;br /&gt;
applications. Knowing this, we began to look over the &lt;a href=&quot;http://planetargon.com/what-we-do?src=robbyonrails&quot;&gt;plethora of
services that we
offer&lt;/a&gt; and felt that
we had been spreading ourselves too thinly. We were faced with the big
question of: Should we focus our energy on trying to innovate in this
competitive space or should we find a community-respected vendor to pass
the torch to?&lt;/p&gt;

&lt;p&gt;Rails Boxcar is a product that we are extremely proud of and believe the
acquisition by Blue Box Group will be great for our existing customers.
The acquisition is going to benefit our customers as they’ll be able to
interface with a team with more resources. A team that also aims to
innovate in this space and believes that Rails Boxcar will help them do
that.&lt;/p&gt;

&lt;p&gt;As a byproduct of this deal, our team has an opportunity to focus our
collective energy on designing and developing web applications, which
has also been a central part of what we do for as long as we’ve been in
business. We plan to speed up our efforts on a handful web-based
products that we’ve been internally developing and hope to release in
the near future.&lt;/p&gt;

&lt;p&gt;I had the pleasure of getting to talk thoroughly with the team at Blue
Box Group and really feel like they’ll be able to focus their energy on
maintaining and innovating within the Ruby on Rails hosting world..
definitely more than we could over the coming years. In the end, the
acquisition is going to benefit our customers the most as they’ll be
able to interface with a larger team that is innovating in this space.&lt;/p&gt;

&lt;p&gt;If you’re interested in learning more about the acquisition, please
&lt;a href=&quot;http://www.prlog.org/10308400.html&quot;&gt;read the press release&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;From our perspective, this is a win-win-win situation for everyone
involved. Expect to see some more news from us in the near future… and
if you’re looking for a design and development team, don’t hesitate to
&lt;a href=&quot;http://planetargon.com/get-in-touch?src=robbyonrails&quot;&gt;get in touch with
us&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Wed, 12 Aug 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/08/12/ch-ch-ch-changes-at-planet-argon/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/08/12/ch-ch-ch-changes-at-planet-argon/</guid>
        
        <category>rails</category>
        
        <category>boxcar,</category>
        
        <category>planet</category>
        
        <category>argon,</category>
        
        <category>business,</category>
        
        <category>blue</category>
        
        <category>box</category>
        
        <category>group,</category>
        
        <category>rails</category>
        
        <category>hosting</category>
        
        <category>rubyonrails,</category>
        
        <category>hosting,</category>
        
        <category>announcement</category>
        
        
      </item>
    
      <item>
        <title>Slides from my Rails Underground 2009 talk</title>
        <description>&lt;p&gt;Hello from London!&lt;/p&gt;

&lt;p&gt;Am currently enjoying the talks at Rails Underground 2009 in London and
had the pleasure to be one of the first speakers at the conference. My
talk covered a collection of what our team considers best practices.
Best practices that aid in the successful launch of a web application
and covered a few Rails-specific topics as well.&lt;/p&gt;

&lt;p&gt;I’ll be sharing some posts in the coming week(s) that’ll expand on some
of these topics as promised to the audience.&lt;/p&gt;

&lt;p&gt;::: {#__ss_1770095 style=”width:425px;text-align:left”}
&lt;a href=&quot;http://www.slideshare.net/robbyrussell/launching-ruby-on-rails-projects-a-checklist&quot; title=&quot;Launching Ruby on Rails projects: A checklist&quot;&gt;Launching Ruby on Rails projects: A
checklist&lt;/a&gt;{style=”font:14px Helvetica,Arial,Sans-serif;display:block;margin:12px 0 3px 0;text-decoration:underline;”}&lt;/p&gt;

&lt;p&gt;::: {style=”font-size:11px;font-family:tahoma,arial;height:26px;padding-top:2px;”}
View more
&lt;a href=&quot;http://www.slideshare.net/&quot;&gt;documents&lt;/a&gt;{style=”text-decoration:underline;”}
from &lt;a href=&quot;http://www.slideshare.net/robbyrussell&quot;&gt;Robby
Russell&lt;/a&gt;{style=”text-decoration:underline;”}.
:::
:::&lt;/p&gt;

&lt;p&gt;Since I covered a wide range of topics, I decided to share my slides
online. They won’t provide as much context (as I’m not speaking as
you’ll look at them), but they might hint at some of the topics that I
covered. There was a guy video taping the talks… so I assume that a
video of my talk will be posted online in the near future.&lt;/p&gt;

&lt;p&gt;Until then… &lt;a href=&quot;http://www.slideshare.net/robbyrussell/launching-ruby-on-rails-projects-a-checklist&quot;&gt;here are the
slides&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Fri, 24 Jul 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/07/24/slides-from-my-rails-underground-2009-talk/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/07/24/slides-from-my-rails-underground-2009-talk/</guid>
        
        <category>talk</category>
        
        <category>agile</category>
        
        <category>conference</category>
        
        <category>railsunder</category>
        
        <category>london</category>
        
        <category>presentation</category>
        
        <category>rubyonrails</category>
        
        <category>hosting</category>
        
        <category>deployment</category>
        
        <category>analytics</category>
        
        <category>seo</category>
        
        <category>sem</category>
        
        <category>feedback</category>
        
        
      </item>
    
      <item>
        <title>Using model constants for project sanity</title>
        <description>&lt;p&gt;On one of our larger client projects (approx. 160 models and growing…)
we have a specific model that we refer to quite a bit throughout our
code. This model contains less than 10 records, but each of them sits on
top of an insanely large and complex set of data. Each record refers to
a each of their regions that our client does business in.&lt;/p&gt;

&lt;p&gt;For example… we have, Australia, United Kingdom, Canada, United
States, and so forth. Each of these regional divisions has their own
company code, which are barely distinguishable from the next. They make
sense to our client, but when we’re not interacting with those codes on
a regular basis, we have to look constantly look them up again to make
sure we’re dealing with the right record.&lt;/p&gt;

&lt;p&gt;I wanted to share something that we did to make this easier for our team
to work around these codes, which we should have thought of &lt;em&gt;long&lt;/em&gt; ago.&lt;/p&gt;

&lt;p&gt;Let’s take the following mode, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Division&lt;/code&gt;. We only have about 10 records
in our database, but have conditional code throughout the site that are
dependent upon which divisions specific actions are being triggered
within. Each division has various business logic that we have to
maintain.&lt;/p&gt;

&lt;p&gt;Prior to our change, we’d come across a lot of code like:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;For all divisions except Canada, invoices are sent via email&lt;/li&gt;
  &lt;li&gt;In Canada, invoices are sent via XML to a 3rd-party service&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;process_invoices_for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;division&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;division&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;code&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;XIUHR12&apos;&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# trigger method to send invoices to 3rd party service&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# batch up invoices and send via email&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;sb&quot;&gt;```bash
An alternative that we&apos;d also find ourselves using was.
```&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ruby&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;division&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Canada&apos;&lt;/span&gt;
&lt;span class=&quot;sb&quot;&gt;```ruby
Hell, I think I&apos;ve even seen `&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;division&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;` somewhere in the code
before. To be fair to ourselves, we did inherit this project a few years
ago. ;-)

Throughout the code base, you&apos;ll find business rules like this. Our
developers all agreed that this was far from friendly and/or efficient
and worst of all, it was extremely error-prone. There have been a few
incidents where we read the code wrong and/or got them confused with one
another. We were lacking a convention that we could all begin to rely on
and use.

So, we decided to implement the following change.

### Model Constants

You might already use constants in your Ruby on Rails application. It&apos;s
not uncommon to add a few into `&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;rb&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;` and call it a
day, but you might also consider scoping them within your models. (makes
it much easier for you to maintain them as well)

In our scenario, we decided to add the following constants to our
`&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;division&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;` model.
```&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ruby&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Division&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;AFRICA&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_by_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;XYU238&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;ASIA&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_by_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;XIUHR73&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;AUSTRALIA&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_by_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;XIUHR152&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;CANADA&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_by_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;XIUHR12&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;USA&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_by_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;XIUHR389&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# etc..&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;sb&quot;&gt;```ruby
What this will do is load up ech of these constants with the
corresponding object. It&apos;s basically the equivallent of us doing:
```&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ruby&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;division&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Division&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_by_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;XIUHR389&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;sb&quot;&gt;```text
But, with this approach, we can stop worrying about their codes and use
the division names that we&apos;re talking about with our clients. Our client
usually approaches us with, &quot;In Australia, we need to do X,Y,Z
differently than we do in the other divisions due to new government
regulations.&quot;
```&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ruby&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;division&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Division&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;CANADA&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;division&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Division&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;AFRICA&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Division&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;AUSTRALIA&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We are finding this to be &lt;em&gt;much&lt;/em&gt; easier to read and maintain. When we’re
dealing with a lot of complex business logic in the application, little
changes like this can make a big difference.&lt;/p&gt;

&lt;p&gt;If you have any alternative solutions, we’d love to hear them. Until
then, we’ve been quite pleased with this approach. Perhaps you’ll find
some value in it as well.&lt;/p&gt;
</description>
        <pubDate>Tue, 23 Jun 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/06/23/using-model-constants-for-project-sanity/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/06/23/using-model-constants-for-project-sanity/</guid>
        
        <category>development</category>
        
        <category>ruby</category>
        
        <category>rubyonrails</category>
        
        <category>model</category>
        
        <category>activerecord</category>
        
        <category>businesslogic</category>
        
        <category>code</category>
        
        
      </item>
    
      <item>
        <title>Launching Rails projects, an open call for lessons learned</title>
        <description>&lt;p&gt;I’m working on &lt;a href=&quot;http://www.robbyonrails.com/articles/2009/06/18/speaking-at-rails-underground-2009&quot;&gt;my
presentation&lt;/a&gt;
for &lt;a href=&quot;http://www.rails-underground.com/&quot;&gt;Rails Underground&lt;/a&gt; and was
hoping to solicit a few tips from other people in the industry.&lt;/p&gt;

&lt;p&gt;Have you launched a Ruby on Rails application recently? Are there some
things that you wish you had known beforehand?&lt;/p&gt;

&lt;p&gt;Mind sharing? You can email me with your story at
&amp;lt;robby+launchstory@planetargon.com&amp;gt;. I’ll let you know if your tip gets
used in the presentation and please indicate if you’d be okay with me
posting your tip in a future blog post.&lt;/p&gt;
</description>
        <pubDate>Tue, 23 Jun 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/06/23/launching-rails-projects-an-open-call-for-lessons-learned/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/06/23/launching-rails-projects-an-open-call-for-lessons-learned/</guid>
        
        <category>launching</category>
        
        <category>rubyonrails</category>
        
        <category>tips</category>
        
        <category>conference</category>
        
        <category>rails</category>
        
        <category>lessons</category>
        
        
      </item>
    
      <item>
        <title>Aliasing resources in Ruby on Rails</title>
        <description>&lt;p&gt;Earlier today, a friend working on a project asked me how we approached
routes on our website. If you take a quick peak at &lt;a href=&quot;http://planetargon.com/&quot;&gt;our
website&lt;/a&gt;, you’ll see that we have URLs like so:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://planetargon.com/&quot;&gt;http://planetargon.com/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://planetargon.com/who-we-are&quot;&gt;http://planetargon.com/who-we-are&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://planetargon.com/who-we-are/robby-russell&quot;&gt;http://planetargon.com/who-we-are/robby-russell&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I couldn’t remember where I came across this before and wasn’t quickly
finding it in the &lt;a href=&quot;http://api.rubyonrails.org&quot;&gt;Ruby on Rails API&lt;/a&gt;, so
decided that I’d do a quick write up on it.&lt;/p&gt;

&lt;p&gt;When we launched our new site a few months ago, we were working off an
existing code base. We have a model named, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TeamMember&lt;/code&gt; and a
corresponding controller. When we decided to come up with new
conventions for our URL structure, we opted to ditch the normal Rails
conventions and go our own route. What we weren’t sure about was how to
alias resources in our routes nicely. After some digging around, we came
across the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:as&lt;/code&gt; option.&lt;/p&gt;

&lt;p&gt;So, our route was:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;resources&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:team_members&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Which provided us with:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;/team_members&lt;/li&gt;
  &lt;li&gt;/team_members/robby-russell&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We simply added &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:as =&amp;gt; &apos;who-we-are&apos;&lt;/code&gt; to our route:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;resources&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:team_members&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:as&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;who-we-are&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;…and we got exactly what we were looking for in our URLs.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;/who-we-are&lt;/li&gt;
  &lt;li&gt;/who-we-are/gary-blessington&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you &lt;a href=&quot;http://planetargon.com&quot;&gt;look at our site&lt;/a&gt;, you’ll notice that we
did this in a few areas of our application so that we could define our
own URL structure that was more friendly for visitors and search
engines.&lt;/p&gt;

&lt;p&gt;Anyhow, just a quick tip for those who want to change up their URLs with
Ruby on Rails.&lt;/p&gt;

&lt;p&gt;s., if you know where I can find this documented, let me know so that I
can provide a URL in this post for others. :-)&lt;/p&gt;
</description>
        <pubDate>Tue, 23 Jun 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/06/23/aliasing-resources-in-ruby-on-rails/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/06/23/aliasing-resources-in-ruby-on-rails/</guid>
        
        <category>rubyonrails</category>
        
        <category>rails</category>
        
        <category>routes</category>
        
        <category>seo</category>
        
        <category>development</category>
        
        
      </item>
    
      <item>
        <title>Remember to Flush Your Toilet</title>
        <description>&lt;p&gt;Saw &lt;a href=&quot;http://twitter.com/TeresaBrazen/statuses/2226976754&quot;&gt;this tweet&lt;/a&gt;
the other day…&lt;/p&gt;

&lt;p&gt;::: thumbnail
&lt;a href=&quot;http://skitch.com/robbyrussell/bw5w6/twitter-teresa-brazen-design-principle-flush-t-&quot;&gt;&lt;img src=&quot;http://img.skitch.com/20090620-rdde21aee5smktrwi857mpdytn.preview.jpg&quot; alt=&quot;Twitter / Teresa Brazen: Design Principle: Flush t
...&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
[Uploaded with &lt;a href=&quot;http://plasq.com/&quot;&gt;plasq&lt;/a&gt;’s
&lt;a href=&quot;http://skitch.com&quot;&gt;Skitch&lt;/a&gt;!]{style=”font-family: Lucida Grande, Trebuchet, sans-serif, Helvetica, Arial; font-size: 10px; color: #808080”}
:::&lt;/p&gt;

&lt;p&gt;So, I have to ask. How many toilets (buckets) do you maintain? How many
of them still have projects/tasks in them? Why haven’t you flushed your
toilets yet?&lt;/p&gt;
</description>
        <pubDate>Fri, 19 Jun 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/06/19/remember-to-flush-your-toilet/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/06/19/remember-to-flush-your-toilet/</guid>
        
        <category>productivity</category>
        
        <category>agile</category>
        
        <category>toilets</category>
        
        
      </item>
    
      <item>
        <title>Speaking at Rails Underground 2009</title>
        <description>&lt;p&gt;It’s time to find my passport again…&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/robbyrussell/2241810168/&quot; title=&quot;Waiting at Gatwick Airport by Robby Russell, on Flickr&quot;&gt;&lt;img src=&quot;http://farm3.static.flickr.com/2312/2241810168_2af66d4a2e.jpg&quot; alt=&quot;Waiting at Gatwick
Airport&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’ve been invited to speak at &lt;a href=&quot;http://railsunderground.com/&quot;&gt;Rails
Underground&lt;/a&gt;, which is being held in
London, UK from July 24-25th.&lt;/p&gt;

&lt;p&gt;My talk, which is tentatively titled, &lt;strong&gt;“Launching Ruby on Rails
projects, a checklist”&lt;/strong&gt;, will expand on several ideas that came out a
&lt;a href=&quot;http://www.robbyonrails.com/articles/2008/12/14/launching-ruby-on-rails-projects-a-checklist&quot;&gt;previous article on the
topic&lt;/a&gt;.
Additionally, I plan to share some of the lessons that we’ve learned at
&lt;a href=&quot;http://planetargon.com&quot;&gt;Planet Argon&lt;/a&gt; as we’ve launched projects over
last several years.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.rails-underground.com&quot; title=&quot;I&apos;m speaking at Rails Underground!&quot;&gt;&lt;img src=&quot;http://www.rails-underground.com/images/speaker_badge.png&quot; alt=&quot;I&apos;m speaking at Rails
Underground!&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you’re able to make it, I encourage you to &lt;a href=&quot;http://bw.eventwax.com/rails-underground-09/register&quot;&gt;register for the
event&lt;/a&gt; before it’s
too late. Take a quick peak at the &lt;a href=&quot;http://www.rails-underground.com/09-conference-speakers.html&quot;&gt;list of
speakers&lt;/a&gt;.
I’m grateful to the event organizers for the invite and look forward to
seeing/meeting all of the attendees!&lt;/p&gt;

&lt;p&gt;Also, for those of you in the London area. If you’re seeking a design
and development team that specializes in Ruby on Rails and want to
schedule a meeting with me while I’m visiting, don’t hesitate to &lt;a href=&quot;http://planetargon.com/get-in-touch&quot;&gt;get in
touch with us&lt;/a&gt;. I’m planning on
staying a few days extra around the conference dates to visit some of
our existing clients and would be happy to meet you.&lt;/p&gt;
</description>
        <pubDate>Thu, 18 Jun 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/06/18/speaking-at-rails-underground-2009/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/06/18/speaking-at-rails-underground-2009/</guid>
        
        <category>speaking</category>
        
        <category>conference</category>
        
        <category>london</category>
        
        <category>rubyonrails</category>
        
        <category>planetargon</category>
        
        <category>railsunderground</category>
        
        
      </item>
    
      <item>
        <title>Howdy Rip!</title>
        <description>&lt;p&gt;Chris Wanstrath (&lt;a href=&quot;http://twitter.com/defunkt&quot;&gt;\@defunkt&lt;/a&gt;) just posted
the following on twitter.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“Hello Rip - &lt;a href=&quot;http://hellorip.com/&quot;&gt;http://hellorip.com/&lt;/a&gt;”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;a href=&quot;http://hellorip.com/about.html&quot;&gt;Rip project describes itself&lt;/a&gt; as,
“an attempt to create a next generation packaging system for Ruby.”&lt;/p&gt;

&lt;p&gt;One of the cool features is that it supports multiple environments. For
example, you can have different Rip environments (with different gem
versioning) that are targeted towards specific applications. I have to
dig around more through the project, but this looks fascinating.&lt;/p&gt;

&lt;p&gt;Check it out at &lt;a href=&quot;http://hellorip.com/&quot;&gt;http://hellorip.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’m also curious as to how you think you might be able to start using
this.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;What are some ways that you could use Rip&lt;/strong&gt; —
&lt;a href=&quot;http://heybrainstormr.com/t/pgte&quot;&gt;http://heybrainstormr.com/t/pgte&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Thu, 11 Jun 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/06/11/howdy-rip/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/06/11/howdy-rip/</guid>
        
        <category>ruby</category>
        
        <category>rubygems</category>
        
        <category>gems</category>
        
        <category>git</category>
        
        <category>github</category>
        
        <category>rubyonrails</category>
        
        <category>development</category>
        
        
      </item>
    
      <item>
        <title>Estimating versus Timeboxing, part 1</title>
        <description>&lt;p&gt;As if delivering projects wasn’t hard enough. Delivering projects &lt;em&gt;on
time&lt;/em&gt; is even harder. As practitioners, we’re all responsible for
measuring up the obstacles in front of us and are accountable to those
measurements. At least, we should be.&lt;/p&gt;

&lt;p&gt;One of those measurements is &lt;strong&gt;time&lt;/strong&gt;. Time is a funny thing. People
have a lot of &lt;a href=&quot;http://thinkexist.com/quotations/time&quot;&gt;interesting things to say about
time&lt;/a&gt;. Some say that it’s one of
the most valuable things that we have… but I’ll avoid diving into a
philosophical discussion for now.&lt;/p&gt;

&lt;p&gt;What I wanted to talk about was project estimation. Specifically,
estimates for deliverables. For the past several years, our team has put
a lot of effort into becoming more accurate in our time estimating
skills. Despite analyzing how often we over and/or underestimate the
time each of us believes it’ll take to complete a task, we find
ourselves coming back to the drawing board.&lt;/p&gt;

&lt;p&gt;A few things that we’ve learned.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Tasks that we believe will take a few days/week/more to complete are
often underestimated&lt;/li&gt;
  &lt;li&gt;Tasks that we believe will take less than a few hours are often more
accurate or overestimated&lt;/li&gt;
  &lt;li&gt;Too many tasks were completed with a bigger budget than was
necessary (lower ROI)&lt;/li&gt;
  &lt;li&gt;A lot of time was spent working on requirements refining to get
better estimates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When we began to step back from this and look for patterns, we found
that several of the tasks that we would budget hours for (versus
estimate hours for) were proving to be more accurate. This approach is
most commonly known as
&lt;a href=&quot;http://en.wikipedia.org/wiki/Time_boxing&quot;&gt;timeboxing&lt;/a&gt;. With timeboxing,
we can place a dollar value on a specific task and work within that
constraint. For example, with our clients, both parties can come to the
conclusion that, “we believe that it’s worth up to $800 to implement
this new functionality.” With that, we’re able take that dollar amount
and figure out how many hours to box ourselves within.&lt;/p&gt;

&lt;p&gt;The underlying question to our client with each change/feature request
is, &lt;em&gt;“How valuable is this to your business at this point in time?”&lt;/em&gt;
Whereas, with a typical approach to time estimates, a client comes to
you with a list of changes/features and you provide them with time
estimates. “_We estimate that it’ll take 6 hours at $200/hour for
feature X and we’d do it like this…“_ The client will have to
evaluate your estimate and figure out if it’s worth $1200 and make a
decision. They can respond with, “no, that’s too expensive, can we do it
for less?” The following steps would entail your team trying to find
ways to reduce your estimate.&lt;/p&gt;

&lt;p&gt;While these two paths might seem very similar, it’s been my experience
that the standard approach to estimating takes more time for negotiating
the terms of the agreement.&lt;/p&gt;

&lt;p&gt;However, with timeboxing, you are asking your client to provide you with
an initial budget. This will completely change how you respond to the
feature request. When you have a timebox, from the moment that you begin
to evaluate the request, your brain will add the necessary constraints
to keep things within scope.&lt;/p&gt;

&lt;p&gt;Through this process, we’ve revamped our estimating process so that as
we’re building our iteration costs for clients. For each deliverable, we
break down a series of objectives/tasks and apply timeboxes to each of
those while knowing what the budget is for the deliverable as a whole.
Usually, the deliverable is directly related to the request that came
from our client with a budget. The process is completely transparent and
our team is responsible for working within those constraints.&lt;/p&gt;

&lt;p&gt;..and as we’ve learned from Ruby on Rails, constraints can be extremely
beneficial.&lt;/p&gt;

&lt;p&gt;While I don’t have all the answers yet, my goal is to share some of my
experiences and lessons on the topic. I’d love to hear about how you’re
adopting timeboxing in your project planning and estimating process.&lt;/p&gt;

&lt;p&gt;Anyhow, just some thoughts that I wanted to share. More to come…&lt;/p&gt;

&lt;h3 id=&quot;read-related-articles&quot;&gt;Read Related Articles&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.robbyonrails.com/articles/2006/05/31/the-art-of-delivery-part-1&quot;&gt;The Art of
Delivery&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.robbyonrails.com/articles/2008/05/22/the-art-of-delivery-part-2&quot;&gt;The Art of Delivery, part
2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Wed, 10 Jun 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/06/10/estimating-versus-timeboxing-part-1/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/06/10/estimating-versus-timeboxing-part-1/</guid>
        
        <category>agile</category>
        
        <category>timeboxing</category>
        
        <category>timebox</category>
        
        <category>planning</category>
        
        <category>projects</category>
        
        <category>clients</category>
        
        <category>estimates</category>
        
        <category>process</category>
        
        
      </item>
    
      <item>
        <title>Email. Twice daily. No more, no less.</title>
        <description>&lt;p&gt;On a recent trip to Las Vegas, I picked up &lt;a href=&quot;http://www.amazon.com/4-hour-Work-Week-Escape-Anywhere/dp/0091923530&quot;&gt;The Four Hour
Workweek&lt;/a&gt;
for my Amazon Kindle to read on my flight. When I came back from my
short vacation, I decided that I was going to change how I approach
email on a daily basis. In my position, I receive a lot of
business-related emails on a daily basis, whether that be from
employees, clients, or potential clients. A typical day would consist of
me trying to get a few tasks done while keeping an eye on any new
requests. This resulted in a lot of context-switching and my days were
extremely fragmented. Our team had started an experiment where we’d
track all of our time throughout the day on printout. Our goal was to
log all of our start/stop times for each activity and also capture each
interruption within those time windows. After just a few days of doing
this, I was noticing how much time was being spent on emails each day. I
also noticed that it was rare to have a full hour of uninterrupted work
on a single activity. Aside from distractions that you’d typically find
in an office environment, email was keeping me from attaining the level
of focus that I was seeking on my work.&lt;/p&gt;

&lt;p&gt;So, using some motivation from The Four Hour Workweek[^1^](#fn1){#fnref1
.footnote-ref role=”doc-noteref”}, I opted to come back to the studio
and change my behavior. That morning, I emailed my entire team and my
clients to let them know that I would only be checking my email at 10am
and 4pm each day. I explained that they could call me at the studio if
there was something that needed my urgent attention. Admittedly, I was
nervous as I hit send. What was I getting myself into? What were my
clients going to think? Would they think that I’m just an unorganized
mess?&lt;/p&gt;

&lt;p&gt;Three weeks later…? It was one of the best emails that I’ve sent in
ages.&lt;/p&gt;

&lt;h2 id=&quot;the-results-so-far&quot;&gt;The Results… (so far)&lt;/h2&gt;

&lt;p&gt;Here are a few realizations and conclusions that I’ve been able to
attribute to this change.&lt;/p&gt;

&lt;h3 id=&quot;my-world-didnt-collapse&quot;&gt;My World Didn’t Collapse&lt;/h3&gt;

&lt;p&gt;Before I made this decision, I came up with a lot of excuses for why
this was a bad idea.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;I might not respond fast enough to a new sales lead&lt;/li&gt;
  &lt;li&gt;A client might forget and send me an urgent request via email&lt;/li&gt;
  &lt;li&gt;Insert any other reason related to you just not following up quick
enough…&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In three weeks, none of these things has bitten me in the ass. It hasn’t
been perfect, but I don’t believe that it’s had any significant impact
that outweighed the benefits.&lt;/p&gt;

&lt;h3 id=&quot;less-time-spent-on-emails&quot;&gt;Less Time Spent on Emails&lt;/h3&gt;

&lt;p&gt;I spend less time on email than I did before. Why? I don’t treat email
the same way that I used to. As a result of approaching email
differently, I noticed that I am now more likely to keep my emails short
and sweet… and most importantly, to the point. One of the great things
about Gmail is that it’s made it easy to have &lt;em&gt;conversation&lt;/em&gt;-style
emails with people, but it’s also made it too easy to have conversations
with people. I now realize that so many conversations that I would
participate via email would entail single sentence questions/responses
with similar length follow-ups. Each time you come back to that email,
your attention is on that conversation and those can eat up a lot of
time if you’re not careful.&lt;/p&gt;

&lt;p&gt;So, now that I’m checking email twice a day, I tend to write only what
is necessary to move the conversation forward until the next time I
check my email. As a result, email conversations are slower now, but
they aren’t taking as much of my time. The benefits have outweighed the
negatives.&lt;/p&gt;

&lt;h3 id=&quot;more-focus-time&quot;&gt;More Focus Time&lt;/h3&gt;

&lt;p&gt;Since this change, there has been a handful of days where I have been
able to focus completely on a single activity (task) for over a hour at
a time. My record was nearly three hours one morning early last week.
Unfortunately, I completed the task I had budgeted five hours for was
finished in less than three. ;-)&lt;/p&gt;

&lt;p&gt;I’m able to do this more now because I’ve been able to release my
&lt;em&gt;check-your-email-again-just-to-be-safe&lt;/em&gt; demons. I’ve been able to trust
my system and I’ll share some tips on how I eased myself into this.&lt;/p&gt;

&lt;p&gt;More focus time has allowed me to spend less time working on individual
tasks because they are subjected to nearly as much context-switching.&lt;/p&gt;

&lt;h3 id=&quot;more-creative-time&quot;&gt;More Creative Time&lt;/h3&gt;

&lt;p&gt;Another benefit that I’ve seen since this change is that with this time
that I’ve salvaged, I find myself with more time to be creative. I
haven’t pinpointed what the reason behind this is, but I do feel like
I’ve been more creative the past few weeks than I have been for the
several months prior. Perhaps it’s just a side-effect to altering my
workday… or that I don’t feel like a victim to the INBOX… or that
it’s been extremely sunny in Portland… or that I’m more aware of how
I’m spending my day.&lt;/p&gt;

&lt;p&gt;Whatever it was, it started within days after I implemented this new
approach to managing email. I’m happy to attribute it to this for the
time being. ;-)&lt;/p&gt;

&lt;h2 id=&quot;how-i-did-it&quot;&gt;How I Did It&lt;/h2&gt;

&lt;p&gt;Here are a few things that I did to start this process. Credit is due to
Tim Ferris for suggesting most of these and here are some of my further
thoughts.&lt;/p&gt;

&lt;h3 id=&quot;list-your-excuses&quot;&gt;List Your Excuses&lt;/h3&gt;

&lt;p&gt;Chances are, you don’t have as many as you think you do. I started with
the critical ones and really weighed the pros/cons. It’s safe to use
the, &lt;em&gt;“Will anybody die if I do this?”&lt;/em&gt; question to help you respond to
each of these. You can be a little less cynical and ask yourself, &lt;em&gt;“Will
we go out of business if I do this?”&lt;/em&gt;… or &lt;em&gt;“Will we lose client X if I
do this?”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Then ask yourself, “Is it unreasonable for me to do this?” If the answer
to, “will we lose client X if I do this?” and this don’t match up, you
might want to re-evaluating your client roster. If your clients are
reasonable people, they’ll see that there is value in this that will
benefit both parties. As I mentioned, just remind them that they can
&lt;em&gt;call you&lt;/em&gt; if there an urgent request. If they abuse this, straighten
them out or it’s time to re-evaluate your client roster.&lt;/p&gt;

&lt;p&gt;It’s not unreasonable to protect &lt;em&gt;your&lt;/em&gt; time as much as possible,
despite how much they pay you.&lt;/p&gt;

&lt;h3 id=&quot;set-a-time-use-a-calendar-reminder&quot;&gt;Set a Time (use a calendar reminder)&lt;/h3&gt;

&lt;p&gt;You can’t just say, “I’m only going to check my email twice a day.”
There isn’t any way that I would have been able to honor such a
commitment. “When exactly?,” is the obvious response to that.&lt;/p&gt;

&lt;p&gt;::: thumbnail
&lt;a href=&quot;http://skitch.com/robbyrussell/busrx/planet-argon-calendar&quot;&gt;&lt;img src=&quot;http://img.skitch.com/20090611-psyure4swk4mt833g1iunqqu8b.preview.jpg&quot; alt=&quot;planet argon -
Calendar&quot; /&gt;&lt;/a&gt;
:::&lt;/p&gt;

&lt;p&gt;I set a scheduled event on my calendar that happens everyday at 10am and
4pm. I have a 15 minute notice on that event so that I’m reminded that
it’s time to wrap up what I’m working on. When I have a conflicting
meeting, I will just reschedule my email for another time of the day.
The time is visible to all of my teammates and my clients know when I’ll
be catching up on email.&lt;/p&gt;

&lt;p&gt;Why did I chose 10am and 4pm? Well, I start my day at the studio at 7am.
This allows me to have up to three hours of time to focus on getting
other things done before tackling email. Why 4pm? This is a hour or so
before I leave for the day. Email isn’t the first or the last thing on
my mind at each ends of my workday.&lt;/p&gt;

&lt;h3 id=&quot;communicate-the-change&quot;&gt;Communicate the Change&lt;/h3&gt;

&lt;p&gt;This will &lt;strong&gt;not work&lt;/strong&gt; if you don’t set peoples expectations. If people
are accustomed to you being extremely quick to respond to emails and you
change your behavior all of a sudden, you’re going to freak them out.
Let them know what you’re doing, why you’re doing it, and you might even
encourage them to consider it too. More often than not, everyone you
work with is feeling overwhelmed and wants more control over their day.
Send them a link to this post. ;-)&lt;/p&gt;

&lt;p&gt;It all comes back to managing their expectations.&lt;/p&gt;

&lt;h3 id=&quot;quit-your-email-application&quot;&gt;Quit Your Email application&lt;/h3&gt;

&lt;p&gt;Seriously, quit that application when you’re not using it. In fact, quit
any program that is open when it’s not related to the activity that
you’re focused on. For email, we use Gmail for domains and I run it
through Fluid. This means that at 10am and 4pm, I launch the Fluid app
and start working my way through emails. Once I get through my inbox and
finish what I need to handle right now, I quit it.&lt;/p&gt;

&lt;p&gt;Also… &lt;strong&gt;disable email notifications. They aren’t worth it.&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;inbox-zero&quot;&gt;Inbox Zero&lt;/h3&gt;

&lt;p&gt;I’ve been practicing the habit of keeping my INBOX empty for nearly a
year. Everything gets labelled, organized, and archived properly once I
open up each email. Some stuff gets sent to Highrise to respond to later
while some emails get an immediate response.&lt;/p&gt;

&lt;p&gt;One of my favorite things about maintaining &lt;a href=&quot;http://www.43folders.com/izero&quot;&gt;Inbox
Zero&lt;/a&gt; and checking my email twice daily
is that when I open up my email client, I’m faced with a list of nothing
but unread emails. Since I know they’re all unread, I can start at the
oldest and move my way through them, one by one. When I get to the end
of that list, I’m almost done. I then fire up Highrise to see if there
is anybody to get back to today. If so, I fire off those emails and
close off those tasks. Once I have both lists completed, I’m done.&lt;/p&gt;

&lt;h3 id=&quot;no-cheating&quot;&gt;No Cheating&lt;/h3&gt;

&lt;p&gt;The one thing that I’m working on the hardest right now is not cheating.
I’ve caught myself a few times. I’m waiting in line at the coffee shop
and I pull out my iPhone. Out of habit, I launch the Mail.app and find
myself looking at incoming emails. You might argue that if you’re not in
the middle of something, it’s a good way to feel useful, but I’m sure
that there are other things you can be tackling. Your email will be
there at 10am… I promise.&lt;/p&gt;

&lt;p&gt;The biggest problem with cheating is that if you see that someone
responded to something you sent in your previous email, it’ll force you
to make a decision. a) do you look now? or b) look later? If you choose
&lt;strong&gt;b&lt;/strong&gt;, your brain is going to be wondering what she said. It’s can
really bug you for a few hours. Trust me. :-)&lt;/p&gt;

&lt;h2 id=&quot;in-summary&quot;&gt;In Summary..&lt;/h2&gt;

&lt;p&gt;It’s only been three weeks since I adopted this and I know it’s far from
perfect. However, I assure you… it’s been worth the self-proclaimed
risks. I enjoy my email time more than I used to. As I mentioned
earlier, I like being presented with a healthy list of unread emails to
work my way through. Sometimes it takes only five minutes to get through
them all, sometimes a hour or more if I have a lot of people to follow
up with.&lt;/p&gt;

&lt;p&gt;It’s been a fun ride so far and I’m sure that there are many more
challenges ahead, but I am planning to stay on course. Who knows, maybe
I can move to once daily after a few months?&lt;/p&gt;

&lt;hr /&gt;

&lt;ol&gt;
  &lt;li&gt;::: {#fn1}
&lt;a href=&quot;http://www.fourhourworkweek.com/blog/2007/03/22/how-to-check-e-mail-twice-a-day-or-once-every-10-days/&quot;&gt;How to Check E-mail Twice a Day… or Once Every 10
Days&lt;/a&gt;&lt;a href=&quot;#fnref1&quot;&gt;↩︎&lt;/a&gt;{.footnote-back
role=”doc-backlink”}
:::&lt;/li&gt;
&lt;/ol&gt;
</description>
        <pubDate>Wed, 10 Jun 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/06/10/email-twice-daily-no-more-no-less/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/06/10/email-twice-daily-no-more-no-less/</guid>
        
        <category>productivity</category>
        
        <category>time</category>
        
        <category>email</category>
        
        <category>management</category>
        
        <category>agile</category>
        
        <category>gtd</category>
        
        <category>clients</category>
        
        
      </item>
    
      <item>
        <title>What can we do right now?</title>
        <description>&lt;p&gt;Last month I picked up a new kindle from Amazon and have been reading a
handful of books. One book that I’ve been really impressed with is &lt;a href=&quot;http://chadfowler.com/&quot;&gt;Chad
Fowler’s&lt;/a&gt; new book, &lt;a href=&quot;http://www.pragprog.com/titles/cfcar2/the-passionate-programmer&quot;&gt;The Passionate
Programmer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/robbyrussell/3527248122/&quot; title=&quot;Passionate Programmer by Robby Russell, on Flickr&quot;&gt;&lt;img src=&quot;http://farm4.static.flickr.com/3328/3527248122_cfa19885a1.jpg&quot; alt=&quot;Passionate
Programmer&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I plan to post some more thoughts in upcoming articles, but wanted to
share this gem.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“If you treat your projects like a race, you’ll get to the end a lot
faster than if you treat them like a prison cell. Create movement. Be
the one who pushes. Don’t get comfortable. Always be the one to ask,
“But what can we do right now?””&lt;/p&gt;

  &lt;p&gt;&lt;em&gt;- Chad Fowler, The Passionate Programmer&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sometimes we feel stuck, but that doesn’t stop us from stepping to the
side and assessing the situation. There is always something useful that
&lt;a href=&quot;http://heybrainstormr.com/t/zmBu&quot;&gt;we could be doing right now&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Wed, 03 Jun 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/06/03/what-can-we-do-right-now/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/06/03/what-can-we-do-right-now/</guid>
        
        <category>projects</category>
        
        <category>agile</category>
        
        <category>gtd</category>
        
        <category>team</category>
        
        <category>book</category>
        
        <category>programming</category>
        
        <category>inspiration</category>
        
        <category>motivation</category>
        
        <category>kindle</category>
        
        
      </item>
    
      <item>
        <title>Hello, HeyBrainstormr.com</title>
        <description>&lt;p&gt;If you &lt;a href=&quot;http://twitter.com/robbyrussell&quot;&gt;follow me on twitter&lt;/a&gt;, you
might have heard that we launched a little project that we’ve been
cooking up at &lt;a href=&quot;http://planetargon.com&quot;&gt;Planet Argon&lt;/a&gt;. (&lt;a href=&quot;http://planetargon.com/news/2009/6/2/heybrainstormr-launched&quot;&gt;news
post&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://heybrainstormr.com&quot;&gt;HeyBrainstormr&lt;/a&gt; is a lightweight web
application that we created so that we could start a brainstorm on a
specific topic and solicit ideas from each other. That’s all it does.
Nothing more. Nothing less.&lt;/p&gt;

&lt;p&gt;We know that having an open brainstorming session requires there to be
zero criticism and opted to keep the process anonymous so that even the
quiet people could share their ideas. :-)&lt;/p&gt;

&lt;p&gt;::: thumbnail
&lt;a href=&quot;http://skitch.com/robbyrussell/b12hi/what-can-i-do-right-now-brainstorming-for-the-rest-of-us-heybrainstormr&quot;&gt;&lt;img src=&quot;http://img.skitch.com/20090603-njuf1rttuf7bgn8jna1fjihjeb.preview.jpg&quot; alt=&quot;what can i do right now? : Brainstorming for the rest of us. :
HeyBrainstormr&quot; /&gt;&lt;/a&gt;
:::&lt;/p&gt;

&lt;p&gt;We’ll be posting more details about it on our blog in the near future,
but wanted to invite all of my readers to give it a whirl.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://heybrainstormr.com&quot;&gt;Start a brainstorm now!&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I have a few topics that I started (and
&lt;a href=&quot;http://twitter.com/robbyrussell&quot;&gt;tweeted&lt;/a&gt; about). Feel free to share
your ideas on them. :-)&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://heybrainstormr.com/t/oM4z&quot;&gt;I need some music suggestions.
Help?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://heybrainstormr.com/t/vwC1&quot;&gt;I have writers block. Help me come up with some blog topic
ideas?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://heybrainstormr.com/t/zmBu&quot;&gt;what can i do right now?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We hope that you find it as fun as we have.&lt;/p&gt;
</description>
        <pubDate>Wed, 03 Jun 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/06/03/hello-heybrainstormr-com/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/06/03/hello-heybrainstormr-com/</guid>
        
        <category>heybrainstormr</category>
        
        <category>brainstormr</category>
        
        <category>launch</category>
        
        <category>brainstorming</category>
        
        <category>agile</category>
        
        <category>design</category>
        
        <category>development</category>
        
        <category>planetargon</category>
        
        
      </item>
    
      <item>
        <title>82,520 minutes on Phusion Passenger</title>
        <description>&lt;p&gt;It’s been over 83,520 minutes since I made the switch from using mongrel
as my development environment web server to &lt;a href=&quot;http://www.modrails.com/&quot;&gt;Phusion
Passenger&lt;/a&gt;. I’ve been extremely impressed with
it. &lt;a href=&quot;http://planetargon.com/who-we-are&quot;&gt;Our team&lt;/a&gt; has all switched over
and haven’t really hit any obstacles in the transition.&lt;/p&gt;

&lt;p&gt;Since some people asked me to let them know how this trial period worked
out, I felt it was my duty to encourage you all to try it. You can check
out my previous post, &lt;a href=&quot;http://www.robbyonrails.com/articles/2009/02/11/switch-to-passenger-mod_rails-in-development-on-osx-in-less-than-7-minutes-or-your-money-back&quot;&gt;Switch to Passenger (mod_rails) in development on
OSX in less than 7 minutes or your money
back!&lt;/a&gt;
to get rolling.&lt;/p&gt;

&lt;p&gt;Additionally, if you’re looking for a streamlined Ruby on Rails
deployment environment that includes Passenger, check out &lt;a href=&quot;http://railsboxcar.com&quot;&gt;Rails
Boxcar&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Fri, 10 Apr 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/04/10/82-520-minutes-on-phusion-passenger/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/04/10/82-520-minutes-on-phusion-passenger/</guid>
        
        <category>passenger</category>
        
        <category>mongrel</category>
        
        <category>development</category>
        
        <category>osx</category>
        
        <category>rubyonrails</category>
        
        <category>rails</category>
        
        <category>railsboxcar</category>
        
        <category>hosting</category>
        
        <category>deployment</category>
        
        
      </item>
    
      <item>
        <title>Building a prototype? Bring some rope.</title>
        <description>&lt;p&gt;While scanning through Allison’s copy of &lt;a href=&quot;http://www.amazon.com/gp/product/0470229101?ie=UTF8&amp;amp;tag=robonrai-20&amp;amp;linkCode=as2&amp;amp;camp=1789&amp;amp;creative=390957&amp;amp;creativeASIN=0470229101&quot;&gt;Designing for the Digital Age:
How to Create Human-Centered Products and
Services&lt;/a&gt;&lt;img src=&quot;http://www.assoc-amazon.com/e/ir?t=robonrai-20&amp;amp;l=as2&amp;amp;o=1&amp;amp;a=0470229101&quot; alt=&quot;&quot; /&gt;, I came across
this nugget.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;The problem with software prototypes&lt;/strong&gt;&lt;/p&gt;

  &lt;p&gt;It seems to be widely understood that industrial design and mechanical
engineering prototypes—from paperclips and tape to polished
appearance models—are disposable learning tools. Prototyping is
clearly distinct from manufacturing, so it would be ludicrous to think
that even a late-stage prototype could be reused as part of the final
product. In software, however, the tools used for anything other than
paper prototyping are generally the same tools used for
“manufacturing” (i.e., writing production code). For this reason, many
stakeholders can’t see why a detailed prototype that appears
functional is still many months away from completion.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It immediately reminded me of a
&lt;a href=&quot;http://www.robbyonrails.com/articles/2006/06/07/prototypes-are-your-friends&quot;&gt;few&lt;/a&gt;
&lt;a href=&quot;http://www.robbyonrails.com/articles/2006/03/11/keeping-prototypes-is-a-bad-idea&quot;&gt;posts&lt;/a&gt;
that I had written about three years ago on the topic of developing
prototypes &lt;em&gt;and NOT keeping them&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The author continues with…&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;It’s important to educate stakeholders that prototype code is kind of
like the illusion of automatic doors on Star Trek—it looks like
it’s working, but it’s really a guy standing behind the wall pulling a
rope.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I completely agree that &lt;em&gt;education&lt;/em&gt; is the most important aspect to
managing client expectations. With regard to the amount of work that you
put into a prototype, we need to be careful on how much time and energy
is put into them. If we can get away with a guy (or some quick
Javascript hacks) to demonstrate possible functionality, make sure we
aren’t using much more than rope. &lt;strong&gt;Rope is cheap. Prototypes should be
too.&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;related-posts&quot;&gt;Related Posts&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.robbyonrails.com/articles/2006/06/07/prototypes-are-your-friends&quot;&gt;Prototypes Are Your
Friends&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.robbyonrails.com/articles/2006/03/11/keeping-prototypes-is-a-bad-idea&quot;&gt;Keeping Prototypes is a Bad
Idea&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.robbyonrails.com/articles/2006/09/25/matz-on-considering-interface&quot;&gt;Matz on Considering
Interface&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Thu, 09 Apr 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/04/09/building-a-prototype-bring-some-rope/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/04/09/building-a-prototype-bring-some-rope/</guid>
        
        <category>programming</category>
        
        <category>agile</category>
        
        <category>prototypes</category>
        
        <category>books</category>
        
        <category>quote</category>
        
        <category>development</category>
        
        
      </item>
    
      <item>
        <title>20 articles on Cucumber and a free beverage recipe!</title>
        <description>&lt;p&gt;&lt;a href=&quot;http://cukes.info/&quot;&gt;Cucumber&lt;/a&gt; has been getting quite a bit of attention
in the community and with the new &lt;a href=&quot;http://www.pragprog.com/titles/achbd/the-rspec-book&quot;&gt;RSpec
Book&lt;/a&gt; on nearing
publication, I predict that by this time next year, it’ll become a
household word like
&lt;a href=&quot;http://en.wiktionary.org/wiki/boanthropy&quot;&gt;boanthropy&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;what-is-cucumber&quot;&gt;What is Cucumber?&lt;/h2&gt;

&lt;p&gt;The Cucumber project describes itself as a suite that, “&lt;em&gt;lets software
development teams describe how software should behave in plain text. The
text is written in a business-readable domain-specific language and
serves as documentation, automated tests and development-aid - all
rolled into one format.&lt;/em&gt;”&lt;/p&gt;

&lt;p&gt;One of the great things about Cucumber is that it can be used to test
applications in any language. I haven’t been able to track down a lot of
articles of how people are using it with other languages, so please
comment if you’re aware of some.&lt;/p&gt;

&lt;p&gt;In any event, I’ve been collecting and reading resources from a variety
of Cucumber aficionados and thought I’d share some links with you. To
round it out, I &lt;a href=&quot;http://twitter.com/robbyrussell/status/1487899554&quot;&gt;asked on
twitter&lt;/a&gt; for some
others so that I could hit twenty. :-)&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;http://dannorth.net/whats-in-a-story&quot;&gt;What’s in a Story?&lt;/a&gt;, Dan
North&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://blog.josephwilk.net/ruby/telling-a-good-story-rspec-stories-from-the-trenches.html&quot;&gt;Telling a good story - Rspec stories from the
trenches&lt;/a&gt;,
Joseph Wilk&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://railscasts.com/episodes/155-beginning-with-cucumber&quot;&gt;Beginning with
Cucumber&lt;/a&gt;,
Ryan Bates (Railscasts)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://blog.brightbox.co.uk/posts/using-rspec-cucumber-and-user-stories-to-build-our-internal-systems&quot;&gt;Using RSpec, Cucumber and User stories to build our internal
systems&lt;/a&gt;,
Rahoul Baruah&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.rubyinside.com/cucumber-the-latest-in-ruby-testing-1342.html&quot;&gt;Cucumber: The Latest in Ruby
Testing&lt;/a&gt;,
Ruby Inside&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.pathf.com/blogs/2009/03/using-cucumber-for-acceptance-testing/&quot;&gt;Using Cucumber for Acceptance
Testing&lt;/a&gt;,
Noel Rappin&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.slideshare.net/bkeepers/behavior-driven-development-with-cucumber-presentation&quot;&gt;Behavior Driven Development with
Cucumber&lt;/a&gt;,
Brandon Keepers (presentation/slides)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://pivotallabs.com/users/jdean/blog/articles/763-testing-capistrano-recipes-with-cucumber&quot;&gt;Testing capistrano recipes with
cucumber&lt;/a&gt;,
Jeff Dean&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.benmabey.com/2009/02/17/using-cucumber-to-integrate-distributed-systems-and-test-messaging/&quot;&gt;Using Cucumber to Integrate Distributed Systems and Test
Messaging&lt;/a&gt;,
Ben Mabey&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.pmamediagroup.com/2009/04/tutorial-how-to-install-setup-cucumber/&quot;&gt;Tutorial: How to install/setup
Cucumber&lt;/a&gt;,
Alan Mitchell&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://drnicwilliams.com/2009/03/26/testing-outbound-emails-with-cucumber/&quot;&gt;Testing outbound emails with
Cucumber&lt;/a&gt;,
Dr. Nic Willians&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://japhr.blogspot.com/2009/03/proper-cucumber-sintatra-driving.html&quot;&gt;Proper Cucumber Sintatra
Driving&lt;/a&gt;,
Chris Strom&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://thomaslundstrom.blogspot.com/2009/03/on-getting-started-with-using-cucumber.html&quot;&gt;On getting started using Cucumber for
.NET&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://blog.mattwynne.net/2008/11/14/dry-up-your-cucumber-steps/&quot;&gt;DRY up your Cucumber
Steps&lt;/a&gt;,
Matt Wynne&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://skillsmatter.com/podcast/ajax-ria/cucumber-celerity-firewatir&quot;&gt;Cucumber, Celerity, &amp;amp;
FireWatir&lt;/a&gt;,
Aidy Lewis (presentation/video)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.brynary.com/2009/2/3/cucumber-step-definition-tip-stubbing-time&quot;&gt;Cucumber step definition tip: Stubbing
time&lt;/a&gt;,
Bryan Helmkamp&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.infoq.com/news/2009/03/bdd-with-cucumber&quot;&gt;Story Driven Development Recipes with
Cucumber&lt;/a&gt;,
Sebastien Auvray&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://opensoul.org/2009/3/6/testing-facebook-with-cucumber&quot;&gt;Testing Facebook with
Cucumber&lt;/a&gt;,
Brandon Keepers&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://itsignals.cascadia.com.au/?p=30&quot;&gt;Testing with the help of machinist, forgery, cucumber, webrat and
rspec&lt;/a&gt;, Etienne van Tonder&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://devcaffeine.com/2009/02/16/integration-testing-ssl-with-cucumber/&quot;&gt;Integration testing SSL with
Cucumber&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://devcaffeine.com/2009/02/16/integration-testing-ssl-with-cucumber/&quot;&gt;Continuous Integration Blueprints: How to Build an Army of Killer
Robots With Hudson and
Cucumber&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So.. there you have it. Please post comments with links to any useful
articles not mentioned and I’ll try to keep the list updated.&lt;/p&gt;

&lt;p&gt;Also, be sure to check out the &lt;a href=&quot;http://wiki.github.com/aslakhellesoy/cucumber/tutorials-and-related-blog-posts&quot;&gt;list of tutorials and related blog
posts&lt;/a&gt;
on the &lt;a href=&quot;http://wiki.github.com/aslakhellesoy/cucumber/&quot;&gt;cucumber wiki&lt;/a&gt;
(github).&lt;/p&gt;

&lt;h2 id=&quot;free-recipe-cucumber-water&quot;&gt;FREE RECIPE: Cucumber Water&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;http://robbyonrails.com/files/3225964781_428d359aa5_m.jpg&quot; alt=&quot;&quot; /&gt;{style=”float:right;”}&lt;br /&gt;
And now…for the reason you are all here! If you like cucumbers (eating
them)… I would highly recommend heading to your local farmers market
and purchasing some cucumbers. Aside from being healthy to eat… they
can help make a tasty beverage.&lt;/p&gt;

&lt;p&gt;Then do the following…&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Chop several slices of a cucumber&lt;/li&gt;
  &lt;li&gt;Fill a pitcher with cold water and ice&lt;/li&gt;
  &lt;li&gt;Toss in slices of cucumber&lt;/li&gt;
  &lt;li&gt;Stir and leave in fridge for a while&lt;/li&gt;
  &lt;li&gt;Take out of fridge, pour into cup…&lt;/li&gt;
  &lt;li&gt;Drink… hack… and enjoy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Be sure to check out, &lt;a href=&quot;http://www.wikihow.com/Make-Cucumber-Water&quot;&gt;How to Make Cucumber
Water&lt;/a&gt; on wikihow for
details.&lt;/p&gt;

&lt;p&gt;Happy Hacking!&lt;/p&gt;

&lt;h2 id=&quot;related-posts-by-me&quot;&gt;Related Posts (by me)&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.robbyonrails.com/articles/2008/08/19/rspec-it-should-behave-like&quot;&gt;RSpec: It Should Behave
Like&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.robbyonrails.com/articles/2007/08/02/spec-your-views&quot;&gt;Spec Your
Views&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.robbyonrails.com/articles/2007/06/17/audit-your-rails-development-team&quot;&gt;Audit Your Rails Development
Team&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.robbyonrails.com/articles/2007/02/08/is-bdd-kinkier-than-tdd&quot;&gt;Is BDD kinkier than
TDD?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Thu, 09 Apr 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/04/09/20-articles-on-cucumber-and-a-free-beverage-recipe/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/04/09/20-articles-on-cucumber-and-a-free-beverage-recipe/</guid>
        
        <category>ruby</category>
        
        <category>rspec</category>
        
        <category>cucumber</category>
        
        <category>testing</category>
        
        <category>tdd</category>
        
        <category>bdd</category>
        
        <category>links</category>
        
        <category>recipe</category>
        
        <category>water</category>
        
        
      </item>
    
      <item>
        <title>HTTParty goes foreign</title>
        <description>&lt;p&gt;Just a quick post to get share something I was tinkering with this
evening.&lt;/p&gt;

&lt;p&gt;I came across &lt;a href=&quot;http://rubybook.ca/2009/03/03/google-translation-api-with-ruby/&quot;&gt;this post by Gerald
Bauer&lt;/a&gt;,
which shows you how to use the &lt;a href=&quot;http://code.google.com/apis/ajaxlanguage/documentation/reference.html&quot;&gt;Google Translation
API&lt;/a&gt;
with Ruby via Net::HTTP. I thought I’d play with the service with
&lt;a href=&quot;http://httparty.rubyforge.org/&quot;&gt;HTTParty&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GoogleApi&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;HTTParty&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;base_uri&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;ajax.googleapis.com&apos;&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;translate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;en&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/ajax/services/language/translate&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:query&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:langpair&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:q&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:v&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A few examples from playing with it.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;GoogleApi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;translate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;bonjour&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;en&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;fr&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;{&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;responseData&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;: {&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;translatedText&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;}, &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;responseDetails&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;: null, &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;responseStatus&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;: 200}&quot;&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;GoogleApi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;translate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;Red wine&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;fr&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;{&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;responseData&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;: {&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;translatedText&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Vin rouge&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;detectedSourceLanguage&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;en&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;}, &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;responseDetails&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;: null, &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;responseStatus&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;: 200}&quot;&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;GoogleApi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;translate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;Where is the bathroom?&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;es&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;{&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;responseData&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;: {&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;translatedText&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;\302\277&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;D&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\303\263&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;nde est&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\303\241&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; el ba&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\303\261&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;o?&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;detectedSourceLanguage&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;en&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;}, &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;responseDetails&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;: null, &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;responseStatus&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;: 200}&quot;&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;GoogleApi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;translate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;Good morning&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;it&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;{&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;responseData&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;: {&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;translatedText&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Buon giorno&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;detectedSourceLanguage&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;en&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;}, &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;responseDetails&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;: null, &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;responseStatus&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;: 200}&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What a &lt;em&gt;party&lt;/em&gt;!&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;GoogleApi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;translate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;party&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;it&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;{&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;responseData&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;: {&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;translatedText&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;festa&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;detectedSourceLanguage&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;en&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;}, &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;responseDetails&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;: null, &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;responseStatus&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;: 200}&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;GoogleApi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;translate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;party&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;es&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;{&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;responseData&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;: {&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;translatedText&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;fiesta&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;detectedSourceLanguage&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;en&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;}, &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;responseDetails&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;: null, &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;responseStatus&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;: 200}&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Look how easy that was. :-)&lt;/p&gt;

&lt;p&gt;For a previous post on using this gem, read &lt;a href=&quot;http://www.robbyonrails.com/articles/2008/11/26/the-httparty-has-just-begun&quot;&gt;The HTTParty has just
begun&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Mon, 16 Mar 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/03/16/httparty-goes-foreign/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/03/16/httparty-goes-foreign/</guid>
        
        <category>google</category>
        
        <category>translation</category>
        
        <category>api</category>
        
        <category>httparty</category>
        
        <category>http</category>
        
        <category>rest</category>
        
        <category>language</category>
        
        
      </item>
    
      <item>
        <title>The Rails Hosting Survey 2009 Survey results are in!</title>
        <description>&lt;p&gt;&lt;a href=&quot;http://rails-hosting.com&quot;&gt;&lt;img src=&quot;http://planetargon.com/images/img_rails_hosting_survey.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We recently &lt;a href=&quot;http://www.robbyonrails.com/articles/2009/01/13/take-the-ruby-on-rails-hosting-in-2009-survey&quot;&gt;announced a
survey&lt;/a&gt;
that touched on topics related to the deployment and hosting of Ruby on
Rails applications. We promised to share the results with the community
and have made this information available at &lt;a href=&quot;http://rails-hosting.com&quot;&gt;http://rails-hosting.com&lt;/a&gt;.
You can download the results in CSV, PDF, and view them in HTML
&lt;a href=&quot;http://rails-hosting.com&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thanks again to everyone who helped us execute this survey!&lt;/p&gt;
</description>
        <pubDate>Wed, 11 Mar 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/03/11/the-rails-hosting-survey-2009-survey-results-are-in/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/03/11/the-rails-hosting-survey-2009-survey-results-are-in/</guid>
        
        <category>hosting</category>
        
        <category>rails</category>
        
        <category>rubyonrails</category>
        
        <category>survey</category>
        
        <category>community</category>
        
        
      </item>
    
      <item>
        <title>Git commit-msg for Lighthouse tickets</title>
        <description>&lt;p&gt;A quick follow-up to a post from a few months ago on how our team has a
naming convention for git branches when we’re working on Lighthouse
tickets (&lt;a href=&quot;http://www.robbyonrails.com/articles/2008/12/11/lighthouse-tickets-and-git-branching&quot;&gt;read previous
post&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;I’ve just put together a quick git hook for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;commit-msg&lt;/code&gt;, which will
automatically amend the commit message with the current ticket number
when you’re following the branch naming conventions described
&lt;a href=&quot;http://www.robbyonrails.com/articles/2008/12/11/lighthouse-tickets-and-git-branching&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Just toss this &lt;a href=&quot;http://gist.github.com/65330&quot;&gt;gist&lt;/a&gt; into
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.git/hooks/commit-msg&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;```
  #!/bin/sh

  #
  # Will append the current Lighthouse ticket number to the commit message automatically
  # when you use the LH_* branch naming convention.
  #
  # Drop into .git/hooks/commit-msg
  # chmod +x .git/hooks/commit-msg

  exec &amp;lt; /dev/tty

  commit_message=$1
  ref=$(git symbolic-ref HEAD 2&amp;gt; /dev/null) || return
  branch=${ref#refs/heads/}

  if [[ $branch =~ LH_(.*) ]]
  then
  lighthouse_ticket=${BASH_REMATCH[1]}

    echo &quot;What is the state of ticket #${lighthouse_ticket}? &quot;
    echo &quot;(o)pen &quot;
    echo &quot;(h)old&quot;
    echo &quot;(r)esolved&quot;
    echo &quot;Enter the current state for #${lighthouse_ticket}: (o)&quot;

    state=&quot;open&quot;

    read state_selection

    case $state_selection in
      &quot;o&quot; )
        state=&quot;open&quot;
        ;;
      &quot;h&quot; )
        state=&quot;hold&quot;
        ;;
      &quot;r&quot; )
        state=&quot;resolved&quot;
        ;;
    esac
  echo &amp;gt;&amp;amp;2 &quot;[#${lighthouse_ticket} state:${state}]&quot; &amp;gt;&amp;gt; &quot;$1&quot;
    exit 0
  fi
```
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then a quick example of how this works…&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;```
  ➜  bin git:(LH_9912 ♻ ) git ci -m &quot;another test&quot;
  What is the state of this ticket? 
  (o)pen 
  (h)old
  (r)esolved
  Enter the current state: (o)
  h
  Created commit 1ed2713: another test
   1 files changed, 3 insertions(+), 1 deletions(-)
```
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now to see this in action… (screenshot)&lt;/p&gt;

&lt;p&gt;::: thumbnail
&lt;a href=&quot;http://skitch.com/robbyrussell/brh2m/git-message-hook&quot;&gt;&lt;img src=&quot;http://img.skitch.com/20090216-rerecdmycjpu7brfppgmmbt8tx.preview.jpg&quot; alt=&quot;git message
hook&quot; /&gt;&lt;/a&gt;
:::&lt;/p&gt;

&lt;p&gt;Then we’ll check out the git log really quick.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;```
➜  bin git:(LH_9912) git log
commit 1ed271323c4a054fe56e76bddc9ac81d241a1032
Author: Robby Russell &amp;lt;robby@planetargon.com&amp;gt;
Date:   Mon Feb 16 12:06:33 2009 -0800

    another test
    [#9912 state:hold]
```
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Thanks to &lt;a href=&quot;http://andy.delcambre.com/&quot;&gt;Andy&lt;/a&gt; for helping me figure out
how to read user input during a git hook.&lt;/p&gt;
</description>
        <pubDate>Mon, 16 Feb 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/02/16/git-commit-msg-for-lighthouse-tickets/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/02/16/git-commit-msg-for-lighthouse-tickets/</guid>
        
        <category>git</category>
        
        <category>lighthouse</category>
        
        <category>github</category>
        
        <category>workflow</category>
        
        <category>bash</category>
        
        
      </item>
    
      <item>
        <title>Show me your and I&apos;ll show you mine (terminal prompts with git branches)</title>
        <description>&lt;p&gt;I asked on twitter but only got a small handful of responses. So, I’m
taking it here.&lt;/p&gt;

&lt;p&gt;Show me yours and I’ll show you mine. Your terminal prompt that you’re
using. Inspire me with new ideas for my prompt.&lt;/p&gt;

&lt;p&gt;Here is mine.&lt;/p&gt;

&lt;p&gt;::: thumbnail
&lt;a href=&quot;http://skitch.com/robbyrussell/brub7/zsh-colors&quot;&gt;&lt;img src=&quot;http://img.skitch.com/20090213-rpb4cy7n52bmq2e5gxmf6p875f.preview.jpg&quot; alt=&quot;zsh
colors&quot; /&gt;&lt;/a&gt;
:::&lt;/p&gt;

&lt;p&gt;Now… show me yours.&lt;/p&gt;
</description>
        <pubDate>Fri, 13 Feb 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/02/13/show-me-your-and-ill-show-you-mine-terminal-prompts-with-git-branches/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/02/13/show-me-your-and-ill-show-you-mine-terminal-prompts-with-git-branches/</guid>
        
        <category>terminal</category>
        
        <category>zsh</category>
        
        <category>bash</category>
        
        <category>colors</category>
        
        <category>development</category>
        
        <category>question</category>
        
        
      </item>
    
      <item>
        <title>On shells</title>
        <description>&lt;p&gt;Zsh versus Bash. Fight!&lt;/p&gt;

&lt;p&gt;::: thumbnail
&lt;a href=&quot;http://skitch.com/robbyrussell/brw1q/oh-in-developer-channel&quot;&gt;&lt;img src=&quot;http://img.skitch.com/20090213-ey7a5u8h7p1i12f7qfkwajfjhp.preview.jpg&quot; alt=&quot;OH in developer
channel&quot; /&gt;&lt;/a&gt;
:::&lt;/p&gt;

&lt;p&gt;…because we need another religious war in the developer community. ;-)&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://img518.imageshack.us/img518/6477/powertoolzny0.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;(skitch via Gary)&lt;/p&gt;
</description>
        <pubDate>Fri, 13 Feb 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/02/13/on-shells/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/02/13/on-shells/</guid>
        
        <category>bash</category>
        
        <category>zsh</category>
        
        <category>terminal</category>
        
        <category>development</category>
        
        
      </item>
    
      <item>
        <title>Switch to Passenger (mod_rails) in development on OSX in less than 7 minutes or your money back!</title>
        <description>&lt;p&gt;We recently switched our default builds of &lt;a href=&quot;http://railsboxcar.com&quot;&gt;Rails
Boxcar&lt;/a&gt; to leverage the benefits of using
Passenger (mod_rails) for deployment of your Ruby on Rails applications
and it’s been working out great for our customers. Several of our
customers and colleagues mentioned that they also began using Passenger
in development, which was intriguing.&lt;/p&gt;

&lt;p&gt;But… Mongrel has been working great for us for the past few years. Why
switch?&lt;/p&gt;

&lt;p&gt;It’s true, I’ve been happily using mongrel since it came out as a
replacement to webrick back in early 2006, which makes it &lt;a href=&quot;http://www.dogyears.com&quot;&gt;about 28 in
dog years&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/robbyrussell/191904616/&quot; title=&quot;Nigel and I by Robby Russell, on Flickr&quot;&gt;&lt;img src=&quot;http://farm1.static.flickr.com/45/191904616_188dfe7b69.jpg&quot; alt=&quot;Nigel and
I&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
[Nigel and I.. 2 1/2 years ago back when Mongrel was just a
puppy]{.small}&lt;/p&gt;

&lt;p&gt;But… over the next few weeks, I’m going to evaluate Passenger in my
development workflow. There’s no better way to try something then to
jump head first. So… here goes.&lt;/p&gt;

&lt;p&gt;::: thumbnail
&lt;a href=&quot;http://skitch.com/robbyrussell/brcy4/locke&quot;&gt;&lt;img src=&quot;http://img.skitch.com/20090211-tbjbaearkh6a18n777d1fttbij.preview.jpg&quot; alt=&quot;locke&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
[this guy was a passenger…and I recently started to watch the
show]{.small}
:::&lt;/p&gt;

&lt;p&gt;Our team will be evaluating Passenger in our development work flow with
a forthcoming blog post but if you want to get your feet wet right away,
here are some instructions for setting up Passenger on OSX with
PrefPane, which were &lt;a href=&quot;http://www.fngtps.com/2008/04/using-passenger-on-osx-for-rails-development&quot;&gt;inspired by Manfred’s
posts&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;installing-passenger-via-rubygems&quot;&gt;Installing Passenger via RubyGems&lt;/h2&gt;

&lt;p&gt;To install Passenger on your OSX machine, just run the following with
root credentials.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo gem install passenger&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This will install the passenger gem on your machine. Now we need to go
ahead and run a script that is provided with this gem (also with root
credentials).&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo passenger-install-apache2-module&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You’ll want to follow the instructions that appear. When you see
something similar to the following output from the command, you’ll want
to copy/paste that into an apache configuration file. I just created a
file at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/apache2/other/passenger.conf&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Edit this file with your editor of choice&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mate /etc/apache2/other/passenger.conf&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Mine looks like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;```
  #/etc/apache2/other/passenger.conf

  # Passenger modules and configuration
  LoadModule passenger_module /opt/local/lib/ruby/gems/1.8/gems/passenger-2.0.6/ext/apache2/mod_passenger.so
  PassengerRoot /opt/local/lib/ruby/gems/1.8/gems/passenger-2.0.6
  PassengerRuby /opt/local/bin/ruby

  # Set the default environment to development
  RailsEnv development

  # Which directory do you want Apache to be able to look into for projects?
  &amp;lt;Directory &quot;/Users/robbyrussell/Projects/development&quot;&amp;gt;
      Order allow,deny
      Allow from all
  &amp;lt;/Directory&amp;gt;
```
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Once you finish running through &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo passenger-install-apache2-module&lt;/code&gt;,
you’ll need to restart Apache on your workstation. This can be done by
simply turning off/on Web Sharing in your Sharing Preference Pane.&lt;/p&gt;

&lt;p&gt;::: thumbnail
&lt;a href=&quot;http://skitch.com/robbyrussell/brm7c/sharing&quot;&gt;&lt;img src=&quot;http://img.skitch.com/20090211-q8aceehkngyc2gx8m9e7818cjx.preview.jpg&quot; alt=&quot;Sharing&quot; /&gt;&lt;/a&gt;
:::&lt;/p&gt;

&lt;p&gt;Alright, we got through the hard part. Now, in order for you to begin
using Passenger, we need to setup Apache to point to your individual
Ruby on Rails application(s). You can hack on Apache configuration files
more, but there is an easier way thanks to the &lt;a href=&quot;http://www.fngtps.com/passenger-preference-pane&quot;&gt;Passenger Preference
Pane&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This will manage your VHost files for you!&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;setting-up-preference-pane&quot;&gt;Setting up Preference Pane&lt;/h3&gt;

&lt;p&gt;If you followed my post on &lt;a href=&quot;http://www.robbyonrails.com/articles/2008/01/22/installing-ruby-on-rails-and-postgresql-on-os-x-third-edition&quot;&gt;installing Ruby on Rails via
MacPorts&lt;/a&gt;,
you’re going to need to install Ruby Cocoa, which can be done with the
following. If you’re using the Ruby provided from Apple, you can skip
this step.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo port install rb-cocoa&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Once that is done, go ahead and move on and &lt;a href=&quot;http://www.robbyonrails.com/articles/2008/01/22/installing-ruby-on-rails-and-postgresql-on-os-x-third-edition&quot;&gt;download Passenger
Preference
Pane&lt;/a&gt;.
Once downloaded, you can install the preference pane, by double-clicking
on the following file.&lt;/p&gt;

&lt;p&gt;::: thumbnail
&lt;a href=&quot;http://skitch.com/robbyrussell/brm6d/passengerpane-1.2&quot;&gt;&lt;img src=&quot;http://img.skitch.com/20090211-ekkryqi9ymt7i8efgurs5hri6i.preview.jpg&quot; alt=&quot;PassengerPane-1.2&quot; /&gt;&lt;/a&gt;
:::&lt;/p&gt;

&lt;p&gt;The next part is really simple as well. Just begin to add your various
Ruby on Rails projects into the Preference Pane… and when you’re done,
you should be able to run your applications over port 80 without any
problems.&lt;/p&gt;

&lt;p&gt;As you can see, I’ve already setup a handful of projects and we don’t
have to start/stop mongrels for each one or worry about port numbers
when running multiple projects. (time savings!)&lt;/p&gt;

&lt;p&gt;::: thumbnail
&lt;a href=&quot;http://skitch.com/robbyrussell/brm61/passenger&quot;&gt;&lt;img src=&quot;http://img.skitch.com/20090211-bdsycieq5wsuyf4bbq89mit1aq.preview.jpg&quot; alt=&quot;Passenger&quot; /&gt;&lt;/a&gt;
:::&lt;/p&gt;

&lt;p&gt;Voila. Simple enough. You might need to stop/start Apache, couldn’t
remember if I needed to or not.&lt;/p&gt;

&lt;p&gt;For each host that you add into this panel, it’ll automatically be added
so that you can immediately browse to http://yourhost.local and it
&lt;em&gt;should&lt;/em&gt; just work. :-)&lt;/p&gt;

&lt;h4 id=&quot;things-to-still-figure-out&quot;&gt;Things to still figure out…&lt;/h4&gt;

&lt;p&gt;Debugging. If you’re used to doing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--debugger&lt;/code&gt;, it appears that you can
do something similar with the
&lt;a href=&quot;http://github.com/ddollar/socket-debugger/tree/master&quot;&gt;socket-debugger&lt;/a&gt;
plugin. Not tried it myself, but worth looking into.&lt;/p&gt;

&lt;p&gt;Browser testing via VMWare/Parallels/VirtualBox. Does anybody have any
tips on how to best appraoch this? Our designers are curious…&lt;/p&gt;

&lt;p&gt;As I mentioned, this is day one of trying it out and managed to motivate
our entire design and development team to try it with me so that we can
all learn about issues together and find solutions quicker. If you’ve
been using this approach for a while, I’d be interested in hearing your
story and if there are any issues that we should be aware of.&lt;/p&gt;
</description>
        <pubDate>Wed, 11 Feb 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/02/11/switch-to-passenger-mod_rails-in-development-on-osx-in-less-than-7-minutes-or-your-money-back/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/02/11/switch-to-passenger-mod_rails-in-development-on-osx-in-less-than-7-minutes-or-your-money-back/</guid>
        
        <category>development</category>
        
        <category>planetargon</category>
        
        <category>boxcar</category>
        
        <category>rubyonrails</category>
        
        <category>rails</category>
        
        <category>passenger</category>
        
        <category>osx</category>
        
        <category>apache</category>
        
        
      </item>
    
      <item>
        <title>Planet Argon - new site</title>
        <description>&lt;p&gt;We’ll be posting some more updates on the &lt;a href=&quot;http://blog.planetargon.com&quot;&gt;Planet Argon
blog&lt;/a&gt; in near future, but I wanted to
invite you all to check out our new site at &lt;a href=&quot;http://planetargon.com/&quot;&gt;http://planetargon.com/&lt;/a&gt;.
The last site was &lt;a href=&quot;http://www.robbyonrails.com/articles/2005/10/25/planet-argon-2-0-release-candidate.&quot;&gt;launched over three years
ago&lt;/a&gt;.
we thought it was time for a refresh. :-)&lt;/p&gt;
</description>
        <pubDate>Mon, 02 Feb 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/02/02/planet-argon-new-site/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/02/02/planet-argon-new-site/</guid>
        
        <category>planetargon</category>
        
        
      </item>
    
      <item>
        <title>Rolling out new updates for Rails Boxcar</title>
        <description>&lt;p&gt;Alex, Director of Deployment Services, has been hard at work helping us
get our new suite of hosting plans out for &lt;a href=&quot;http://railsboxcar.com/&quot;&gt;Rails
Boxcar&lt;/a&gt;, a deployment environment that we’ve
designed to help you get your Ruby on Rails applications running as
painless and quickly as possible. With this new announcement, we’ve
rebuilt the Boxcar image based on the feedback of &lt;a href=&quot;http://railsboxcar.com/testimonials&quot;&gt;our existing
customers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Additionally, we’ve been looking over some of early results from the
&lt;a href=&quot;http://blog.planetargon.com/2009/1/13/take-the-ruby-on-rails-hosting-in-2009-survey&quot;&gt;Ruby on Rails Hosting in 2009
Survey&lt;/a&gt;
that we’ve been running the past few weeks, which has further boosted
our confidence that we’re on the right track with this big change.&lt;/p&gt;

&lt;p&gt;What are some of the changes?&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Moving to &lt;a href=&quot;http://www.rubyenterpriseedition.com/&quot;&gt;Ruby Enterprise
Edition&lt;/a&gt; (REE)&lt;/li&gt;
  &lt;li&gt;Moving to &lt;a href=&quot;http://www.modrails.com/&quot;&gt;Passenger&lt;/a&gt; (mod_rails) by
default&lt;/li&gt;
  &lt;li&gt;All-new pricing structure! (starting at $35/month)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means that with a Rails Boxcar, you can now get a pre-configured
deployment environment using some of the most efficient platforms for
hosting your Ruby on Rails applications. (REE has &lt;a href=&quot;http://www.rubyenterpriseedition.com/comparisons.html#overall_conclusion&quot;&gt;shown to increase
performance by 33% in some
cases&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;We’re really excited about this new setup and would like to invite you
all to check out our new plans and &lt;a href=&quot;http://railsboxcar.com/contact&quot;&gt;send us any
questions&lt;/a&gt; that you might have.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://railsboxcar.com/&quot;&gt;Visit RailsBoxcar.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Wed, 28 Jan 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/01/28/rolling-out-new-updates-for-rails-boxcar/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/01/28/rolling-out-new-updates-for-rails-boxcar/</guid>
        
        <category>rails</category>
        
        <category>rubyonrails</category>
        
        <category>railsboxcar</category>
        
        <category>boxcar</category>
        
        <category>deployment</category>
        
        <category>hosting</category>
        
        <category>rails-hosting</category>
        
        <category>rails-deployment</category>
        
        <category>phusion</category>
        
        <category>ruby</category>
        
        <category>enterprise</category>
        
        
      </item>
    
      <item>
        <title>Rails Hosting Survey - 5 days left...</title>
        <description>&lt;p&gt;Wow. Thanks to all of you who &lt;a href=&quot;http://search.twitter.com/search?q=rails+hosting+survey&quot;&gt;have helped get the
word&lt;/a&gt; out about
the Ruby on Rails Hosting 2009 Survey. We just &lt;strong&gt;&lt;em&gt;passed 900 people&lt;/em&gt;&lt;/strong&gt;
and we have about five more days left to hit the 1500 milestone that I
set for myself.&lt;/p&gt;

&lt;p&gt;If you can spare five minutes to help us reach this goal, we’d really
appreciate it.&lt;/p&gt;

&lt;p&gt;Here is a quick sample of the questions that we’re asking the community.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Where is your source code hosted?&lt;/li&gt;
  &lt;li&gt;Which database do you typically use in production?&lt;/li&gt;
  &lt;li&gt;which performance monitoring tool do you use?&lt;/li&gt;
  &lt;li&gt;How much of your monthly budget is allocated for deployment and
hosting expenses?&lt;/li&gt;
  &lt;li&gt;So, can Rails scale? ;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don’t hesitate… we only have a few days left!&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.surveymonkey.com/s.aspx?sm=bRkqJnEzDbVmwI9PqVkSIg_3d_3d&quot;&gt;&lt;img src=&quot;http://robbyonrails.com/files/take-the-survey.png&quot; alt=&quot;&quot; /&gt;{style=”padding-top: 20px; padding-bottom: 40px;”}&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For more information, read the original post, &lt;a href=&quot;http://www.robbyonrails.com/articles/2009/01/13/take-the-ruby-on-rails-hosting-in-2009-survey&quot;&gt;Take the Ruby on Rails
Hosting in 2009
Survey&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Tue, 27 Jan 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/01/27/rails-hosting-survey-5-days-left/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/01/27/rails-hosting-survey-5-days-left/</guid>
        
        <category>hosting</category>
        
        <category>rubyonrails</category>
        
        <category>rails</category>
        
        <category>survey</category>
        
        <category>community</category>
        
        
      </item>
    
      <item>
        <title>Lessons through failure. Episode 1</title>
        <description>&lt;p&gt;I fucked up this last week.&lt;/p&gt;

&lt;p&gt;On Monday, our primary contact for a large client sent over some last
minute requirements and deadlines that were needed by end-of-day
Wednesday. I didn’t have a lot of time to collect requirements and
execute it without having to rearrange my priorities. But, I accepted
the challenge.&lt;/p&gt;

&lt;p&gt;The big change involved was that we were going to be supplied with a ton
of data to be imported in to the database and approximately 20% of the
data provided was &lt;em&gt;new&lt;/em&gt; records, while the rest were duplicates.
However, the other 80% wasn’t to be discarded as there were a few
attributes that needed to be updated from the data file (which was
supplied from the client’s parent company). In my haste to get the task
done on time (didn’t get proper export file to be imported in our system
until Wednesday morning)… I ended up running a few tests locally and
pushed it out to production.&lt;/p&gt;

&lt;p&gt;I managed to get the import file to run in production before leaving on
Wednesday afternoon. The following morning, I came into the office to
find out that my import process didn’t match up records properly and
resulted in nearly all of the 80% side of that to be duplicated in the
system. This resulted in lost productivity for our client, their
vendors, and our team over a 12 hour period as people were confused
about why reports were running weird, online transactions didn’t account
for the duplicated, etc.&lt;/p&gt;

&lt;p&gt;It took me most of Thursday and Friday to clean up the data that got
skewed due to that oversight. Hi ho.&lt;/p&gt;

&lt;p&gt;So, the take away from this? Sure, I could have blamed it on a lack of
sufficient time to properly test things, but that’s bullshit. I should
have had at least one other developer from our team review the problem
and evaluate my proposed solution prior to me attempting to push into
production.&lt;/p&gt;

&lt;p&gt;Luckily, the client was happy that we were able to finish the last
minute tasks, despite the unexpected headaches that cropped up.&lt;/p&gt;

&lt;p&gt;If anything, I was just disappointed in myself, but
&lt;a href=&quot;http://www.the-love-shack.net/&quot;&gt;Alex&lt;/a&gt; reminded me how important it was
to &lt;a href=&quot;http://www.codinghorror.com/blog/archives/000576.html&quot;&gt;fail early, fail
often&lt;/a&gt;. It didn’t
kill me (or anybody else for that matter), cost us the project, nor was
it irreparable.&lt;/p&gt;

&lt;p&gt;In the real world, deadlines and requirements change on a moments notice
and it’s experiences like this that will make ourselves more confident
that we can quickly respond to and execute.&lt;/p&gt;

&lt;p&gt;What was your latest failure?&lt;/p&gt;
</description>
        <pubDate>Sat, 17 Jan 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/01/17/lessons-through-failure-episode-1/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/01/17/lessons-through-failure-episode-1/</guid>
        
        <category>coding</category>
        
        <category>failure</category>
        
        <category>fail</category>
        
        <category>productivity</category>
        
        <category>team</category>
        
        <category>programming</category>
        
        <category>agile</category>
        
        
      </item>
    
      <item>
        <title>Rails Hosting Survey (continued)</title>
        <description>&lt;p&gt;Thanks to everyone has taken the survey and mentioned it on twitter. We
just passed 400 people. We have a ways to go before we hit our goal of
at least 1500 people surveyed. (if we can get even more than that…
great!)&lt;/p&gt;

&lt;p&gt;The survey is taking most people less than 5 minutes to complete, so if
you haven’t filled it out yet and have experience deploying Ruby on
Rails applications, here’s a link. :-)&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.surveymonkey.com/s.aspx?sm=bRkqJnEzDbVmwI9PqVkSIg_3d_3d&quot;&gt;&lt;img src=&quot;http://robbyonrails.com/files/take-the-survey.png&quot; alt=&quot;&quot; /&gt;{style=”padding-top: 20px; padding-bottom: 40px;”}&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Any help that you can provide in getting the word out would be greatly
appreciated.&lt;/p&gt;

&lt;p&gt;Thanks!&lt;/p&gt;
</description>
        <pubDate>Thu, 15 Jan 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/01/15/rails-hosting-survey-continued/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/01/15/rails-hosting-survey-continued/</guid>
        
        <category>hosting</category>
        
        <category>survey</category>
        
        <category>rubyonrails</category>
        
        <category>rails</category>
        
        
      </item>
    
      <item>
        <title>Take the Ruby on Rails Hosting in 2009 Survey</title>
        <description>&lt;p&gt;&lt;strong&gt;Calling all Ruby on Rails developers and system administrators.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The team at &lt;a href=&quot;http://planetargon.com&quot;&gt;Planet Argon&lt;/a&gt; is hoping to collect
some information about how everyone is currently managing the deployment
and hosting of their Ruby on Rails applications. We are inviting you all
to participate in the &lt;a href=&quot;http://rubyurl.com/rk5Y&quot;&gt;Rails Hosting in 2009
survey&lt;/a&gt;, which consists of nearly forty
questions about you and your Rails hosting experiences. &lt;strong&gt;Most people
say it is taking [less than five (5) minutes]{.underline} to complete
it.&lt;/strong&gt; =)&lt;/p&gt;

&lt;p&gt;We will collect responses for the survey until the end of January and
will then publish the results (with anonymous raw data) for everyone in
the community to share and use.&lt;/p&gt;

&lt;p&gt;Our goal is to use this information ourselves to continue to evolve our
&lt;a href=&quot;http://railsboxcar.com&quot;&gt;hosting-related products&lt;/a&gt; and &lt;a href=&quot;http://planetargon.com/deployment-consulting&quot;&gt;deployment
services&lt;/a&gt; for you. We also
want all of our fellow hosting providers and development teams to have
access to this information so that they can continue to improve their
services. Rails deployment and hosting is getting easier for us all, but
we know that there is always room from improvement.&lt;/p&gt;

&lt;p&gt;We make an effort to keep our ear close to the ground in the community
to listen for trends and problems, but sometimes it’s better to just ask
directly.&lt;/p&gt;

&lt;p&gt;So, if you have a few minutes to spare, please take the survey!&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.surveymonkey.com/s.aspx?sm=bRkqJnEzDbVmwI9PqVkSIg_3d_3d&quot;&gt;&lt;img src=&quot;http://robbyonrails.com/files/take-the-survey.png&quot; alt=&quot;&quot; /&gt;{style=”padding-top: 20px; padding-bottom: 40px;”}&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;update:&lt;/strong&gt; some people mentioned that we should have made some options
multi-select. it’s too late to change it without losing submissions. so,
for questions like: Monit, God, or Other (and you’re using God &lt;strong&gt;and&lt;/strong&gt;
Monit, put that in Other and we’ll track them accordingly)&lt;/p&gt;

&lt;p&gt;P.S. Please spread the word about the survey!&lt;/p&gt;
</description>
        <pubDate>Tue, 13 Jan 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/01/13/take-the-ruby-on-rails-hosting-in-2009-survey/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/01/13/take-the-ruby-on-rails-hosting-in-2009-survey/</guid>
        
        <category>hosting</category>
        
        <category>survey</category>
        
        <category>rubyonrails</category>
        
        <category>ruby</category>
        
        
      </item>
    
      <item>
        <title>Question: Plugins or Gems? (or both?)</title>
        <description>&lt;p&gt;Our development team likes to extract reusable pieces of code for our
projects and have historically used plugins. However, we are finding
more and more people releasing these sorts of
modules/components/patterns as gems.&lt;/p&gt;

&lt;p&gt;Which do you prefer and why?&lt;/p&gt;

&lt;p&gt;If you use both, how do you decide to use plugins or gems?&lt;/p&gt;
</description>
        <pubDate>Tue, 13 Jan 2009 00:00:00 +0000</pubDate>
        <link>https://robbyonrails.com/articles/2009/01/13/question-plugins-or-gems-or-both/</link>
        <guid isPermaLink="true">https://robbyonrails.com/articles/2009/01/13/question-plugins-or-gems-or-both/</guid>
        
        <category>question</category>
        
        <category>rubyonrails</category>
        
        <category>rails</category>
        
        <category>gems</category>
        
        <category>plugins</category>
        
        
      </item>
    
  </channel>
</rss>