<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Tim Riley / timriley.info</title>
  <link href="https://timriley.info"/>
  <updated>2024-11-27T04:41:10+00:00</updated>
  <author>
    <name>Tim Riley</name>
  </author>
  <id>https://timriley.info</id>
  <entry>
    <title>Tim in open source, September 2024</title>
    <link rel="alternate" href="https://timriley.info/writing/2024/10/05/tim-in-open-source-september-2024"/>
    <id>https://timriley.info/writing/2024/10/05/tim-in-open-source-september-2024</id>
    <published>2024-10-05T02:22:00+00:00</published>
    <updated>2024-10-05T02:22:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Hello there, friends! It’s been a couple of months since &lt;a href="/writing/2024/07/24/tim-in-open-source-july-2024"&gt;my last open source update&lt;/a&gt;, so what have I been up to? A bunch of things, all culminating in a new Hanami 2.2 beta!&lt;/p&gt;
&lt;h2&gt;RedDotRubyConf returns!&lt;/h2&gt;
&lt;p&gt;First came &lt;a href="https://reddotrubyconf.com"&gt;RedDotRubyConf 2024&lt;/a&gt; in Singapore, back again after seven years! It was great: excellent mix of talks, a small but engaged crowd, and as always, amazing hospitality. Thank you to Ted and all the organisers for bringing it back! I hope it can continue into the future!&lt;/p&gt;
&lt;p&gt;RedDotRubyConf is a special event for me: it was my first opportunity to speak at a Ruby conference, as well as my first chance to give a workshop. Before ths one, I’d attended three different editions over the years, dating all the way back to &lt;a href="https://andycroll.com"&gt;Andy’s&lt;/a&gt; last event &lt;a href="(https://web.archive.org/web/20120602230212/http://reddotrubyconf.com/)"&gt;in 2012&lt;/a&gt;. My talk this year was a nice bookend to how I started. All the way back in 2013, I presented my stack of &lt;a href="https://www.youtube.com/watch?v=6ecNAjVWqaI"&gt;dry-rb, ROM and Roda&lt;/a&gt; as a vision for a new generation of Ruby apps. This year I presented that vision brought to its complete and streamlined conclusion in Hanami. For those of us cultivating Ruby on the side, some fruit takes time to bear.&lt;/p&gt;
&lt;p&gt;Speaking of time: after I returned from Singapore, things were slow for a while. I had made a big push to &lt;a href="https://hanamirb.org/blog/2024/07/16/hanami-220beta1/"&gt;release 2.2.0.beta1&lt;/a&gt; before the conference, and that pace was not something I could maintain. This, alongside attending a Buildkite off-site, meant things were pretty quiet for a few weeks.&lt;/p&gt;
&lt;h2&gt;Hanami actions, meet validation contracts&lt;/h2&gt;
&lt;p&gt;It wasn’t long before I found a productive groove again. One of the things that helped here was some work that &lt;a href="https://github.com/krzykamil"&gt;Krzysztof Piotrowski&lt;/a&gt; did to explore using full &lt;a href="https://dry-rb.org/gems/dry-validation"&gt;dry-validation&lt;/a&gt; contracts as the means of params validation in Hanami actions.&lt;/p&gt;
&lt;p&gt;Contracts in actions has been a much-demanded feature! In fact, some work began on it all the way back at the RubyConf hack day in November 2023 (thanks to &lt;a href="https://github.com/danhealy"&gt;Dan Healy&lt;/a&gt;). While that effort petered out, Krzysztof progressed things far enough to arrive at a &lt;a href="https://github.com/hanami/controller/pull/451"&gt;fully functional implementation&lt;/a&gt;, ready for review. He’d also been making some great contributions to Hanami lately, so I didn’t want to keep him waiting.&lt;/p&gt;
&lt;p&gt;After reviewing Krzysztof’s work, I managed to put together &lt;a href="https://github.com/hanami/controller/pull/453"&gt;another iteration&lt;/a&gt; of the feature that I was happy to see go in. And thanks to &lt;a href="https://github.com/hanami/controller/pull/453#discussion_r1736717602"&gt;some helpful feedback&lt;/a&gt; from Adam Lassek, I came back and did &lt;a href="https://github.com/hanami/controller/pull/454"&gt;one more thing&lt;/a&gt;, making it so that straight-up &lt;code&gt;Dry::Validation::Contract&lt;/code&gt; classes could be used for the validation, rather than the &lt;code&gt;Hanami::Params&lt;/code&gt; subclasses that were required in the older, 1.x-era behaviour that we had inherited for this feature.&lt;/p&gt;
&lt;p&gt;The result is a useful spectrum of options. You can start with the simplest approach, embedding a contract directly in your action. Now instead of the &lt;code&gt;params&lt;/code&gt; blocks you could use before (which exposed the &lt;a href="https://dry-rb.org/gems/dry-schema"&gt;dry-schema&lt;/a&gt; features only), you can use &lt;code&gt;contract&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;class Create &amp;lt; MyApp::Action
  contract do
    required(:title).filled(:string)
    required(:slug).filled(:string)
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you have a contract that you want to share across actions, you can also reference its class directly:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;class Create &amp;lt; MyApp::Action
  contract Posts::Contract
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One of the internal adjustments I made was to defer the initialization of contracts until the time the action is itself initialized. This allows you to take advantage of one of dry-validation’s most powerful features: contracts that can interact with the rest of your app via &lt;a href="https://dry-rb.org/gems/dry-validation/1.10/external-dependencies/"&gt;external dependencies&lt;/a&gt;. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;module MyApp
  module Posts
    class Contract &amp;lt; Dry::Validation::Contract
      include Deps[&amp;quot;repos.post_repo&amp;quot;]

      params do
        required(:title).filled(:string)
        required(:slug).filled(:string)
      end

      rule(:slug) do
        unless post_repo.unique_slug?(values[:slug])
          key.failure(&amp;quot;must be unique&amp;quot;)
        end
      end
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With external dependencies, your validation contracts can leverage business logic anywhere in your app, while still allowing for that logic to reside in, well, a &lt;em&gt;logical&lt;/em&gt; place. And when you use validation contracts in a Hanami app, our &lt;code&gt;Deps&lt;/code&gt; mixin makes this as easy as can be.&lt;/p&gt;
&lt;p&gt;(Why exactly is deferring initialization of the contract required for this? It’s because the default dependencies you specify with &lt;code&gt;Deps&lt;/code&gt; are resolved at the time of calling &lt;code&gt;.new&lt;/code&gt; on the contract. We can’t call that too early, like in the class body of an action, because otherwise we’d run into all sorts of load ordering troubles.)&lt;/p&gt;
&lt;p&gt;In fact, there’s one last little dependency-related treat for you in this feature. You saw above how contracts could take their dependencies via &lt;code&gt;Deps&lt;/code&gt;? Well now you can do exactly the same with actions, with the contract itself as a dep!&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;class Create &amp;lt; MyApp::Action
  include Deps[&amp;quot;posts.contract&amp;quot;]
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is especially nice if you’re sharing your contracts between actions as well as other kinds of classes in your app, because it means you can use &lt;code&gt;Deps&lt;/code&gt; as a consistent approach for using them across all places:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;class CreatePost &amp;lt; MyApp::Operation
  # Another class, same Dep!
  include Deps[&amp;quot;posts.contract&amp;quot;]
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I started this post with a little Ruby reminiscence, so why not do a little more. Back in its early days, dry-validation was one of the most important gems in spurring dry-rb adoption. Input validation is something that every app needs, and there are few solutions out there as complete and portable as dry-validation (try it in your Rails app, really!). Today, dry-validation is as relevant as ever, and with it now fully integrated into Hanami, it’s also easier to use than ever.&lt;/p&gt;
&lt;p&gt;In fact, I think the code snippets above serve as a great example of the kind of vision we’re building towards with Hanami: actions as standalone classes, input validation as an first-class concern itself provided by standalone classes, and a simple and universal dependencies mixin to bring them together where required. Small, focused components, each with its place, and a clear strategy for connecting them. These are not the Ruby apps you’re used to. We’re bringing something new. The little integration you see above is in many ways the the culmination of 10 years of multiple streams of volunteer OSS work.&lt;/p&gt;
&lt;p&gt;I think that enabling development approaches like this is vital part of fostering a vibrant and diverse Ruby ecosystem. If this resonates with you, we’d &lt;a href="https://hanamirb.org/donate/"&gt;love your support&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;What gives me heart is that after all these years, we’re still finding new champions and contributors. If it wasn’t for Krzysztof taking on the challenge to bring contracts to actions, it would not be shipping in 2.2. Thank you, Krzysztof!&lt;/p&gt;
&lt;h2&gt;Hello again, MySQL&lt;/h2&gt;
&lt;p&gt;I really didn’t intend this post to become a treatise on input validation and its meaning for the greater Ruby community, so let’s keep things moving!&lt;/p&gt;
&lt;p&gt;Here’s something much more straighforward: when we introduced our new database layer &lt;a href=""&gt;in beta1&lt;/a&gt;, we included SQLite and Postgres support. There was one major database missing: MySQL. &lt;a href="https://github.com/hanami/cli/pull/226"&gt;Now it’s here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The experience is as you’d expect: &lt;code&gt;hanami new my_app --database=mysql&lt;/code&gt; will give you everything you need to get started with Hanami and MySQL, and after that, all the &lt;code&gt;hanami db&lt;/code&gt; commands will work with your MySQL database as required. This was one of the last big outstanding items on our to-do list for 2.2, and now it’s done!&lt;/p&gt;
&lt;p&gt;What’s more, we also had another new contributor come in and help make our database layer just that little bit nicer. Thanks to Kyle Plump, now if for any reason your &lt;code&gt;Gemfile&lt;/code&gt; doesn’t contain the right gem(s) for your configured database(s), we’ll &lt;a href="https://github.com/hanami/hanami/pull/1453"&gt;give you a helpful warning&lt;/a&gt;. Thank you Kyle! It’s been tremendous to work with you these last couple months.&lt;/p&gt;
&lt;h2&gt;New ways to go multi-database&lt;/h2&gt;
&lt;p&gt;The astute among you will have noticed my use of “gem(s)” for “database(s)” in the last paragraph. There’s reason for this, even putting aside my predilection for syntactical whimsy: in this last month, I introduced a whole new way to work with multiple databases in Hanami!&lt;/p&gt;
&lt;p&gt;Since beta1, we’ve supported multiple databases along one axis: while Hanami slices may all share a single database, each may also have its own. This is as easy to configure as prefixing your database URL env var with a slice name: &lt;code&gt;MY_SLICE__DATABASE_URL&lt;/code&gt;. Hanami takes care of the rest. (Of course, you can also choose to configure slice databases explicitly where you need greater control.)&lt;/p&gt;
&lt;p&gt;This arrangement was how I was intending to leave things. Shipping a new fully-featured database layer for Hanami already felt ambitious enough. But friend-of-the-framework &lt;a href="https://github.com/parndt"&gt;Phil Arndt&lt;/a&gt; needed something more: to work with multiple databases within a &lt;em&gt;single&lt;/em&gt; slice.&lt;/p&gt;
&lt;p&gt;Speifically, Phil needed to set up multiple ROM &lt;a href="https://rom-rb.org/learn/introduction/core-concepts/#gateways"&gt;gateways&lt;/a&gt;. Gateways are ROM’s abstraction for a connection to a specific data source (remember: ROM works with more than just &lt;a href="https://github.com/rom-rb/rom-sql"&gt;SQL&lt;/a&gt;, it also has adapters for things like &lt;a href="https://github.com/rom-rb/rom-http"&gt;HTTP&lt;/a&gt; and &lt;a href="https://github.com/rom-rb/rom-csv"&gt;CSV&lt;/a&gt; and &lt;a href="https://github.com/rom-rb/rom-yaml"&gt;YAML&lt;/a&gt; and more!).&lt;/p&gt;
&lt;p&gt;I hate to disappoint Phil, so I rolled up my sleeves, and now Hanami has native support for &lt;a href="https://github.com/hanami/hanami/pull/1452"&gt;multiple gateways within each slice&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;What I love about this is that I was able to maintain the same zero-config approach we’ve had for our database layer so far. So now your ENV vars can take you even further than before. You can start with a single database (let’s use MySQL here, to stick with the theme of this &lt;s&gt;dissertation&lt;/s&gt; blog post):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DATABASE_URL=mysql2://localhost/my_app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then from there, you can go multi-database within an app by appending a suffix for each gateway:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DATABASE_URL=mysql2://localhost/my_app
DATABASE_URL__ARTIFACTS=mysql2://localhost/my_app_artifacts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There’s no limit. You can have as many gateways as you like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DATABASE_URL=mysql2://localhost/my_app
DATABASE_URL__ARTIFACTS=mysql2://localhost/my_app_artifacts
DATABASE_URL__WEBHOOKS=mysql2://localhost/my_app_webhooks
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Slices can come to the party too. Combine &lt;em&gt;slice prefixes&lt;/em&gt; with &lt;em&gt;gateway suffixes&lt;/em&gt; for the ultimate in code/database granularity:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Configure multiple databases for an `Artifacts` slice
ARTIFACTS__DATABASE_URL=postgres://localhost/my_app_artifacts
ARTIFACTS__DATABASE_URL__LEGACY=postgres://localhost/my_app_artifacts_legacy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Zero-config is the start but not the end. Like many of the features in Hanami 2, &lt;em&gt;progressive disclosure&lt;/em&gt; is at the core of our design for gateways. If you need more than the basics, there’s another layer waiting for you. So aside from the environment variables, you can also configure gateways directly in your &lt;code&gt;:db&lt;/code&gt; provider:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;Hanami.app.configure_provider :db do
  config.gateway :extra do |gw|
    # If not given, this will still be filled from `ENV[&amp;quot;DATABASE_URL__EXTRA&amp;quot;]`
    gw.database_url = &amp;quot;...&amp;quot;

    # Specify an adapter to use by name (more on this later)
    gw.adapter :yaml

    # Or configure an adapter explicitly
    gw.adapter :yaml do |a|
      # You can call `a.plugin` here
      # Or also `a.extension` if this is an `:sql` adapter
    end
  end

  # Multiple gateways can be configured
  config.gateway :another do |gw|
    # ...
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you have a provider with a complex multi-gateway setup, then you can also configure adapters separately, and they’ll be used across all relevant gateways:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;Hanami.app.configure_provider :db do
  # This adapter config will apply to all sql gateways
  config.adapter :sql do |a|
    a.extension :is_distinct_from
  end

  config.gateway :extra do |gw|
    # ...
  end

  # More gateways here...
end
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Did someone say dry-cli?&lt;/h2&gt;
&lt;p&gt;Yes! Someone did say say dry-cli, and it was &lt;a href="https://github.com/benoittgt"&gt;Benoit Tigeot&lt;/a&gt;, who a while back proposed a &lt;a href="https://discourse.dry-rb.org/t/dry-cli-option-to-hide-command/1823"&gt;couple of&lt;/a&gt; &lt;a href="https://discourse.dry-rb.org/t/usage-of-did-you-mean-for-command-with-typos-in-dry-cli/1824"&gt;very nice&lt;/a&gt; enhancements, then went &lt;a href="https://github.com/dry-rb/dry-cli/pull/137"&gt;and implemented&lt;/a&gt; &lt;a href="https://github.com/dry-rb/dry-cli/pull/138"&gt;them both&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Thanks to Benoit’s work, you can now hide certain commands from the CLI’s standard usage output:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;register &amp;quot;completion&amp;quot;, Commands::Completion, hidden: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As well as receive useful suggestions if you invoke the CLI with a typo:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ./my-cli comma
I don't know how to 'comma'. Did you mean: 'command' ?

Commands:
  my-cli command         # This is a command
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I have to make an apology here. Benoit shipped these PRs a couple of months ago. But since that time I’ve been pretty much single-mindedly working through all the things above. It took a &lt;a href="https://ruby.social/@benoit/113168994252724226"&gt;friendly nudge from Benoit&lt;/a&gt; on Mastodon (I love Mastodon — &lt;a href="https://ruby.social/@timriley"&gt;let’s be friends&lt;/a&gt;) for these to get back on my radar again. I was frankly a little embarrassed at how I’d let down a smart new contributor by this long delay.&lt;/p&gt;
&lt;p&gt;It’s not easy dong Hanami maintenance on nights and weekends. I wrote about needing to find balance &lt;a href="/writing/2024/07/24/tim-in-open-source-july-2024"&gt;in my last update&lt;/a&gt;. Once Hanami 2.2 is out, I hope will become easier to stay on top of contributions like this, because I won’t have the spectre of large, uncompleted features (like a whole database layer) weighing me down.&lt;/p&gt;
&lt;p&gt;Anyway, I’ll be making sure we cut a new dry-cli release soon, with both of these included, as well as &lt;a href="https://github.com/dry-rb/dry-cli/pull/135"&gt;documentable command namespaces&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Just before: a release!&lt;/h2&gt;
&lt;p&gt;Speaking of releases, the one big goal I had after after all of the above was getting them packaged up and out into people’s hands for testing. I did this last week, &lt;a href="https://hanamirb.org/blog/2024/09/25/hanami-220beta2/"&gt;releasing Hanami 2.2.0.beta2&lt;/a&gt;. Step by step, we’re getting there.&lt;/p&gt;
&lt;p&gt;I’d really love it if you could &lt;a href="https://hanamirb.org/blog/2024/09/25/hanami-220beta2/"&gt;check out the announcement&lt;/a&gt;, then run a &lt;code&gt;gem install hanami --pre&lt;/code&gt; and kick the tyres on all the new features. This is how we get things in truly tip top shape.&lt;/p&gt;
&lt;h2&gt;Right now: a break!&lt;/h2&gt;
&lt;p&gt;The release came when it did, because as of this last week, I’m away on a three week holiday with my family.&lt;/p&gt;
&lt;p&gt;After all the work on Hanami things (not to mention Buildkite things), I’m hoping to truly disconnect and recharge. If you’re waiting on issue/PR feedback from me, please forgive the brief interruption. I’ll be back again in late October.&lt;/p&gt;
&lt;h2&gt;Up next: 2.2 by RubyConf!&lt;/h2&gt;
&lt;p&gt;I’m looking forward to attending my second &lt;a href="https://rubyconf.org"&gt;RubyConf&lt;/a&gt; in Chicago later this year! &lt;a href="https://web.archive.org/web/20231106123831/https://rubyconf.org/"&gt;Last year&lt;/a&gt; in sunny San Diego I got to reintroduce Hanami to America through &lt;a href="https://www.youtube.com/watch?v=L35MPfmtJZM"&gt;my talk&lt;/a&gt; and at the Hack Day.&lt;/p&gt;
&lt;p&gt;In the leadup to RubyConf 2023, we worked very hard to get 2.1 out. Alas, it &lt;a href="/writing/2024/01/09/2023-in-review"&gt;didn’t quite work out that way&lt;/a&gt;, but that’s no reason not to try again! So this year, we’re working very hard to get 2.2 out.&lt;/p&gt;
&lt;p&gt;We have a couple of very good motivators for this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/cllns"&gt;Sean Collins&lt;/a&gt; is on the programme, giving a &lt;a href="https://rubyconf.org/schedule/#sz-tab-45610"&gt;Hanami workshop&lt;/a&gt;!&lt;/li&gt;
&lt;li&gt;I’ll be back again represeting Hanami at the Hack Day, ready to help anyone wanting to contribute.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I &lt;em&gt;really&lt;/em&gt; want everyone coming into contact with Hanami over RubyConf to be able to &lt;code&gt;gem install hanami&lt;/code&gt; and receive in return our full vision for maintainable, layered, database-backed Ruby apps.&lt;/p&gt;
&lt;p&gt;We’re &lt;em&gt;so close&lt;/em&gt; to getting this done. We’ve been executing against &lt;a href="link-to-forum-post"&gt;a well-understood plan&lt;/a&gt; and integrating &lt;a href="https://rom-rb.org/"&gt;mature, production-tested systems&lt;/a&gt;, so I expect no surprises this time around. As I write this, our &lt;a href="https://github.com/orgs/hanami/projects/6/views/1"&gt;project board&lt;/a&gt; only has dozen small issues left.&lt;/p&gt;
&lt;p&gt;If we succeed, I’ll look forward to showing and talking Hanami 2.2 to many of you in Chicago! Let’s do this!&lt;/p&gt;
&lt;h2&gt;Thank you (again!) to Hanami’s contributors&lt;/h2&gt;
&lt;p&gt;There’s a certain lovely vibe to this post: I’m not working alone. So many of the improvements above were spurred on by Hanami contributors, new and old. So let me thank all of you one more time:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Thank you &lt;a href="https://github.com/krzykamil"&gt;Krzysztof Piotrowski&lt;/a&gt;, for spurring on the work on contracts in actions&lt;/li&gt;
&lt;li&gt;Thank you &lt;a href="https://github.com/kyleplump"&gt;Kyle Plump&lt;/a&gt;, for taking on all number of small issues and helping us with polish (it didn’t get a mention above, but I love &lt;a href=""&gt;where we landed with the .keep files&lt;/a&gt;!)&lt;/li&gt;
&lt;li&gt;Thank you &lt;a href="https://github.com/parndt"&gt;Phil Arndt&lt;/a&gt; and &lt;a href="https://github.com/alassek"&gt;Adam Lassek&lt;/a&gt;, for your feedback on this last month or so of work&lt;/li&gt;
&lt;li&gt;Thank you &lt;a href="https://github.com/benoittgt"&gt;Benoit Tigeot&lt;/a&gt;, for dry-cli fixes. I really hope we can work together more in the future!&lt;/li&gt;
&lt;li&gt;Thank you &lt;a href="https://github.com/gustavothecoder"&gt;Gustavo Ribeiro&lt;/a&gt;, for your PR to dry-cli.&lt;/li&gt;
&lt;li&gt;Thank you &lt;a href="https://github.com/cllns"&gt;Sean Collins&lt;/a&gt;, for everything you’re doing to prepare for the RubyConf workshop!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And I know this isn’t everyone. For example, a few weeks ago &lt;a href="https://github.com/dcr8898"&gt;Damian Rossney&lt;/a&gt; and Kyle Plump got together to pair on &lt;a href="https://github.com/hanami/router/pull/273"&gt;a fix&lt;/a&gt; to this &lt;a href="https://github.com/hanami/router/issues/255"&gt;gnarly hanami-router issue&lt;/a&gt;. I love to see this! I already replied in the issue, but reviewing this is my number one thing to do when I get back from my break.&lt;/p&gt;
&lt;h2&gt;A cheeky question&lt;/h2&gt;
&lt;p&gt;Whew! What an update! If you made it this far, thank you for reading!&lt;/p&gt;
&lt;p&gt;Since you’re here, you dedicated few, let me ask something of you. I’ve been writing these open source updates &lt;a href="/writing/2020/03/27/open-source-status-update-march-2020"&gt;since March 2020&lt;/a&gt;. This is my 31st edition! I’m not the fastest writer, so each one takes some hours, typically a whole night of writing, provided I start early enough. So tell me: should I keep writing these updates? What do you take from these? Is there anything you think I could do differently? Or on the flip side, if any of you are regular long-form journallers: how do you keep it up?&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Tim in open source, July 2024</title>
    <link rel="alternate" href="https://timriley.info/writing/2024/07/24/tim-in-open-source-july-2024"/>
    <id>https://timriley.info/writing/2024/07/24/tim-in-open-source-july-2024</id>
    <published>2024-07-24T02:38:00+00:00</published>
    <updated>2024-07-24T02:38:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;It’s been a hot minute since &lt;a href="/writing/2023/10/20/open-source-status-update-september-2023"&gt;my last open source status update&lt;/a&gt;. The fact is, I’ve been too busy working on open source to write about my work on open source.&lt;/p&gt;
&lt;p&gt;One thing I realise, however, is that work not &lt;em&gt;proclaimed&lt;/em&gt; is work not noticed, so let me tell you what I’ve been doing.&lt;/p&gt;
&lt;p&gt;In the nine months since September, I:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Worked on Hanami 2.1&lt;/li&gt;
&lt;li&gt;Presented on Hanami at RubyConf (San Diego) and RubyConf Taiwan&lt;/li&gt;
&lt;li&gt;Took a blessed short summer break&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hanamirb.org/blog/2024/02/27/hanami-210/"&gt;Released Hanami 2.1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hanamirb.org/blog/2024/04/04/new-leadership-for-hanami/"&gt;Accepted the leadership&lt;/a&gt; of the Hanami project&lt;/li&gt;
&lt;li&gt;Organised &lt;a href="https://rubyincommon.org"&gt;a Ruby unconference&lt;/a&gt; in Sydney, a companion event to RubyConf AU&lt;/li&gt;
&lt;li&gt;Meticulously planned &lt;a href="https://discourse.hanamirb.org/t/integrating-rom-into-hanami-2-2/971"&gt;Hanami 2.2’s database layer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Built Hanami 2.2’s database layer&lt;/li&gt;
&lt;li&gt;Released &lt;a href="https://hanamirb.org/blog/2024/07/16/hanami-220beta1/"&gt;a beta of Hanami 2.2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Prepared to present at RedDotRubyConf in Singapore&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That’s a lot! And herein was a pattern, repeated twice:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Plan to announce a major new release at a conference&lt;/li&gt;
&lt;li&gt;Push hard to get it ready (oh, and prepare the talk too)&lt;/li&gt;
&lt;li&gt;End up not quite making it&lt;/li&gt;
&lt;li&gt;Go do the conference anyway (always fun, at least!)&lt;/li&gt;
&lt;li&gt;Then come home to yet another big push to finally finish everything off&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the first instance, it was Hanami 2.1 and RubyConf. We were &lt;em&gt;so close&lt;/em&gt; to release, but discovered a deal-breaking limitation our assets handling. After frantic hours attempting workarounds from California/Rome/Christchurch, we pulled the plug. There was no easy fix. So after a short break, I overhauled that part of assets system (the right decision: it’s now more flexible and better fits Hanami architecture!) and finally &lt;a href="https://hanamirb.org/blog/2024/02/27/hanami-210/"&gt;shipped Hanami 2.1&lt;/a&gt; in February.&lt;/p&gt;
&lt;p&gt;In the second case, it’s been Hanami 2.2 and RedDotRubyConf (I’m flying to Singapore as I write this!). This time around, we didn’t get so close to a full release, but far enough to &lt;a href="https://hanamirb.org/blog/2024/07/16/hanami-220beta1/"&gt;put out a beta&lt;/a&gt;. Once I’m back, I expect another month or two of concerted work to get everything finished.&lt;/p&gt;
&lt;p&gt;So, a pattern of near misses, but real progress nonetheless! I’m also encouraged by some promising signs in this latter instance.&lt;/p&gt;
&lt;p&gt;This time around, we have some new active contributors, without whom we couldn’t have shipped the beta. Thank you &lt;a href="https://github.com/alassek"&gt;Adam&lt;/a&gt;, &lt;a href="https://github.com/cllns"&gt;Sean&lt;/a&gt;, and &lt;a href="https://github.com/waiting-for-dev"&gt;Marc&lt;/a&gt;! This time around, we went from &lt;a href="https://discourse.hanamirb.org/t/plans-for-hanami-2-2/972"&gt;release plan&lt;/a&gt; to a &lt;a href="https://hanamirb.org/blog/2024/07/16/hanami-220beta1/"&gt;quite-complete beta&lt;/a&gt; in less than 60 days, instead of the 15 months between the previous major releases. This time around, we’re mere steps away from finishing the full stack vision for Hanami 2, from providing the streamlined experience we envisioned more than five years ago.&lt;/p&gt;
&lt;p&gt;Here’s the rub: everything I’ve done with Hanami has been a fully “nights and weekends” deal for me. For the case of Hanami 2.2, it’s meant practically &lt;em&gt;every&lt;/em&gt; night and weekend for two straight months. It’s not sustainable. This is why I couldn’t write you those monthly updates. Speaking of patterns: I’ve done this now for too many years, missed too much family time already, and I need to break this cycle.&lt;/p&gt;
&lt;p&gt;I’m optimistic, though. Change is coming. The above was the old era. We’re about the enter the new: Hanami 2.2 is on the way! This is something to be excited for! It’s exciting for you, because with just a few commands you’ll have yourself a whole new way of building modular, maintainable, database-backed apps of all shapes in Ruby. It’s also exciting for me, because it means I can at last look up to the horizon and start planning all the great ways we can promote and build upon this new foundation.&lt;/p&gt;
&lt;p&gt;It also means I plan to figure out ways to make this whole endeavour sustainable for me. This is the only way we can serve the Hanami and Ruby communities long into the future. Hanami turns 10 this year, and I want it to live for decades more. If you’re experienced with funding OSS, I’d love to chat with you about this.&lt;/p&gt;
&lt;p&gt;So there we are, you’re all caught up on a productive few months for Hanami! I’m looking forward to sharing my next update with you, where I hope we can celebrate the release of 2.2 and the beginning of our new era!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>2023 in review</title>
    <link rel="alternate" href="https://timriley.info/writing/2024/01/09/2023-in-review"/>
    <id>https://timriley.info/writing/2024/01/09/2023-in-review</id>
    <published>2024-01-08T19:55:00+00:00</published>
    <updated>2024-01-08T19:55:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;2023: back in action, in more ways than one.&lt;/p&gt;
&lt;h2&gt;Five conferences&lt;/h2&gt;
&lt;p&gt;After a three year break, I spoke at five conferences in the last twelve months:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://rubyconfth.com/past/2022/"&gt;RubyConf Thailand&lt;/a&gt; — a wonderful second edition of this event, the chance to share Hanami 2 on the stage for the first time, and first-time in person hangs with another Buildkiter!&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rubyconf.org.au/2023"&gt;RubyConf AU&lt;/a&gt; — a joyous reunion for my home community. Good to be back, and positive signs for the future. Met many Buildkiters in person here!&lt;/li&gt;
&lt;li&gt;&lt;a href="https://brightonruby.com/2023/"&gt;Brighton Ruby&lt;/a&gt; — I’ve admired Andy and this event for the longest time. To attend and contribute a talk was a dream come true.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.archive.org/web/20231216135657/https://rubyconf.org/"&gt;RubyConf&lt;/a&gt; — my first time at the US-based RubyConf, and a brilliant time all around: made new friends, had many great conversations, and got to work with some first-time Hanami contributors during the hack day!&lt;/li&gt;
&lt;li&gt;&lt;a href="https://2023.rubyconf.tw"&gt;RubyConf Taiwan&lt;/a&gt; — What a revelation! A whole thriving Ruby community I had no idea about, and a conference was chock-a-block with interesting talks. Being able to keynote opposite Matz was a real honour.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I’d commend any of these conferences to you, and I hope to get back to each of them in the future.&lt;/p&gt;
&lt;p&gt;Being able to talk at so many events was definitely not my plan at the outset of the year! But one thing led to another, and I just rolled with it. It was a pleasure to share Hanami with people in so many places.&lt;/p&gt;
&lt;p&gt;One thing I appreciated over this period was the chance to refine my method of introducing Hanami, and moreover, how I deliver conference talks in general. I’m very happy with how this ended up. I ended the year giving two very different presentations, each in their own way imbuing the audience (I hope!) with a sense of both whimsy and possibility: one involved song and dance, and the other, a surprise costume reveal!&lt;/p&gt;
&lt;p&gt;&lt;img src="/assets/rubyconf-2023-42DF8860.jpg" alt="Delivering “Livin’ la Vida Hanami” at RubyConf 2023" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/assets/rubyconf-tw-2023-CAC50B15.jpg" alt="After “Quest of the Rubyist” at RubyConf Taiwan 2023" /&gt;&lt;/p&gt;
&lt;h2&gt;A near-release of Hanami&lt;/h2&gt;
&lt;p&gt;Back at the end of 2021, we made a big push to get Hanami 2.0 released before I made it to RubyConf Thailand, &lt;a href="https://hanamirb.org/blog/2022/11/22/announcing-hanami-200/"&gt;and we succeeded&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;I then spent all of 2022 working towards Hanami 2.1, which would introduce our view layer and a completely new approach to handling assets. As RubyConf approached in November, we attempted to do the same thing, to use the conference as motivation and make a big push to get the release out. I spent 2+ months working every night and weekend towards this, and we got &lt;em&gt;so close&lt;/em&gt;, but didn’t quite make it.&lt;/p&gt;
&lt;p&gt;Just two days before my talk, while I was already at the conference, we discovered what turned out to a release-blocking issue with our front end assets compilation. After a scramble at various stopgap fixes, we decided nothing would quite cut the mustard, and deferred the release. This was disappointing, but was the best choice for the project. After a break over Christmas, I’m now ready to take a final pass at this and make the right choices for the future.&lt;/p&gt;
&lt;p&gt;Given this, I hope for 2024 to be a big &lt;em&gt;two-release&lt;/em&gt; year for Hanami, with 2.1 happening next month, followed by 2.2 whenever it’s ready.&lt;/p&gt;
&lt;h2&gt;Family trips!&lt;/h2&gt;
&lt;p&gt;Once the Brighton Ruby opportunity came up, we decided to make the most of it and turn it into a family trip to Europe, our first overseas trip in the post-2020 era. It was an excellent time. We got to spend a bonus summer across Brighton, London, Paris, Amsterdam, Rotterdam and Brussels. The kids travelled very well, and we all can’t wait to do it again.&lt;/p&gt;
&lt;p&gt;We also took a few nice road trips over the year, visiting Orange, Wagga Wagga, and ending the year with a relaxing week over Christmas in a house at Bawley Point.&lt;/p&gt;
&lt;h2&gt;Work at Buildkite&lt;/h2&gt;
&lt;p&gt;I had a good year at Buildkite. I spent the year in the same team I joined in 2022. Early in the year we hired some folks and brought the team to its full complement. Everyone is lovely, and we put in some great work towards &lt;a href="https://buildkite.com/releases/2023-03"&gt;four&lt;/a&gt; &lt;a href="https://buildkite.com/releases/2023-06"&gt;different&lt;/a&gt; &lt;a href="https://buildkite.com/releases/2023-09"&gt;quarterly&lt;/a&gt; &lt;a href="https://buildkite.com/releases/2023-12"&gt;releases&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I reached my first “bikkiversary” milestone in July, I joined an active on-call roster for the first time, I helped establish some clear direction for our future in front end development, and ended the year acting as an engineering manager, just to help keep things steady while my existing manager took some leave. Every one of these has been a pleasure. I’m continually humbled by the talent that surrounds me at Buildkite, and I’m looking forward to another year of learning.&lt;/p&gt;
&lt;p&gt;Best of all, we gathered for our BIPOP (Buildkite In-Person On-site Party; yes, we love acronyms), our whole-company event in Cairns. This was a blast.&lt;/p&gt;
&lt;h2&gt;Reading&lt;/h2&gt;
&lt;p&gt;I read 22 books. In order:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Distress, Greg Egan&lt;/li&gt;
&lt;li&gt;Sea of Tranquility, Emily St. John Mandel&lt;/li&gt;
&lt;li&gt;The Water Knife, Paolo Bacigalupi&lt;/li&gt;
&lt;li&gt;Shards of Earth, Adrian Tchaikovsky&lt;/li&gt;
&lt;li&gt;Eyes of the Void, Adrian Tchaikovsky&lt;/li&gt;
&lt;li&gt;The Thousand Earths, Stephen Baxter&lt;/li&gt;
&lt;li&gt;Eversion, Alastair Reynolds&lt;/li&gt;
&lt;li&gt;Lords of Uncreation, Adrian Tchaikovsky&lt;/li&gt;
&lt;li&gt;Fractal Noise, Christopher Paolini&lt;/li&gt;
&lt;li&gt;Me, Ricky Martin&lt;/li&gt;
&lt;li&gt;Ancillary Justice, Ann Leckie&lt;/li&gt;
&lt;li&gt;Ancillary Sword, Ann Leckie&lt;/li&gt;
&lt;li&gt;Ancillary Mercy, Ann Leckie&lt;/li&gt;
&lt;li&gt;Provenance, Ann Leckie&lt;/li&gt;
&lt;li&gt;Translation State, Ann Leckie&lt;/li&gt;
&lt;li&gt;Creation Node, Stephen Baxter&lt;/li&gt;
&lt;li&gt;The Spare Man, Mary Robinette Kowal&lt;/li&gt;
&lt;li&gt;Some Desperate Glory, Emily Tesh&lt;/li&gt;
&lt;li&gt;Fugitive Telemetry, Martha Wells&lt;/li&gt;
&lt;li&gt;System Collapse, Martha Wells&lt;/li&gt;
&lt;li&gt;Fourth Wing, Rebecca Yarros&lt;/li&gt;
&lt;li&gt;Iron Flame, Rebecca Yarros&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I enjoyed them all! Adrian Tchaikovsky’s &lt;em&gt;Final Architecture&lt;/em&gt; trilogy was thrilling, and discovering Ann Leckie’s Imperial Radch world was a true wonder: I’m so glad I got to binge all five stories together. It was a joy to revisit Murderbot with the most recent two books, and I don’t think I’ve read anything faster than I did &lt;em&gt;Fourth Wing&lt;/em&gt; while hanging around a coast house at Christmas.&lt;/p&gt;
&lt;p&gt;This year I also figured out my ideal e-reading situation. It’s the iPad mini, and Apple’s Books app in particular. Nothing beats its responsiveness and ease of use. And the iPad mini helps with the occasional tech book I read too. And thanks to Calibre and the Obok plugin, I can also buy books from Kobo and get them into Apple Books in short order.&lt;/p&gt;
&lt;h2&gt;Assorted things&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;We ended the year by giving the kids (9 &amp;amp; 7) each their own bedrooms. My desk/office is once again located next to my bed. Given I did this for three years already in our previous apartment, I expect this to work out well enough.&lt;/li&gt;
&lt;li&gt;I started using the &lt;a href="https://retro.app"&gt;Retro app&lt;/a&gt; to share photos with a small group of friends. It’s brilliant! It has me sharing photos much more regularly, and helped me end the year with a nice collection of memories.&lt;/li&gt;
&lt;li&gt;Thanks to several long plane rides, I did &lt;a href="https://letterboxd.com/timriley/films/diary/for/2023/"&gt;a decent amount of movie watching&lt;/a&gt;. Notables: No Time to Die (sob), Shotgun Wedding (fun), Jules (surprising), and Indy 5 (a triumph). Best soundtracks? Tetris, The Thomas Crown Affair.&lt;/li&gt;
&lt;li&gt;I continue to pay attention to the little computer on my wrist telling me to exercise, though this unfortunately wavered towards the end of the year during my crunch on Hanami and conference talks.&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Open source status update, September 2023</title>
    <link rel="alternate" href="https://timriley.info/writing/2023/10/20/open-source-status-update-september-2023"/>
    <id>https://timriley.info/writing/2023/10/20/open-source-status-update-september-2023</id>
    <published>2023-10-20T13:01:00+00:00</published>
    <updated>2023-10-20T13:01:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;With the two big PRs introducing our next generation of asset support merged (&lt;a href="https://github.com/hanami/assets/pull/120"&gt;here&lt;/a&gt; and &lt;a href="https://github.com/hanami/hanami/pull/1319"&gt;here&lt;/a&gt;), September was a month for rapid iteration and working towards getting assets out in a 2.1 beta release.&lt;/p&gt;
&lt;p&gt;The pace was lively! Towards the end of the month, Luca and I were trading PRs and code reviews on almost a daily basis. Thanks our opposing timezones, Hanami was being written nearly 24h a day!&lt;/p&gt;
&lt;h2&gt;Assorted small things&lt;/h2&gt;
&lt;p&gt;Most of the work was fairly minor: an &lt;a href="https://github.com/hanami/hanami/pull/1337"&gt;error logging fix&lt;/a&gt;, some &lt;a href="https://github.com/hanami/hanami/pull/1334"&gt;test updates for the new assets&lt;/a&gt;, &lt;a href="https://github.com/hanami/assets/pull/127"&gt;error handling around asset manifests&lt;/a&gt;, and a bit of &lt;a href="https://github.com/hanami/assets/pull/129"&gt;zeitwerkin’&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Making our better errors better&lt;/h2&gt;
&lt;p&gt;There was one interesting piece though. Earlier in this release cycle (back in June!), I overhauled our user-facing error handling. I added a middleware to &lt;a href="https://github.com/hanami/hanami/pull/1309"&gt;catch errors and render static error pages intended display in production&lt;/a&gt;. As part of this change, I adjusted our router to raise exceptions for not found routes: doing this would allow the error to be caught and a proper 404 page displayed. So that was production sorted. For development, we &lt;a href="https://github.com/hanami/hanami/pull/1311"&gt;integrated the venerable better_errors&lt;/a&gt;, wrapped by our own hanami-webconsole gem.&lt;/p&gt;
&lt;p&gt;It was only some months later that we realised 404s in development were being returned as 500s. This turned out to be because better_errors defaults to a 500 response code at all times. &lt;a href="https://github.com/BetterErrors/better_errors/blob/fde3b7025db17b5cda13fcf8d08dfb3f76e189f6/lib/better_errors/middleware.rb#L109-L129"&gt;In its middleware&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;status_code = 500
# ...
response = Rack::Response.new(content, status_code, headers)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Well, maybe not &lt;em&gt;quite&lt;/em&gt; at all times. The lines right beneath &lt;code&gt;status_code = 500&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;status_code = 500
if defined?(ActionDispatch::ExceptionWrapper) &amp;amp;&amp;amp; exception
  status_code = ActionDispatch::ExceptionWrapper.new(env, exception).status_code
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Looks like Ruby on Rails gets its own little exception carved out here, via some hard-coded constant checks that reach deep inside Rails internals. This will allow better_errors to return a 404 for a not found error in Rails, but not in any other Ruby framework.&lt;/p&gt;
&lt;p&gt;This is not a new change. It arrived &lt;a href="https://github.com/BetterErrors/better_errors/pull/176"&gt;over ten years ago&lt;/a&gt;, and I can hardly blame the authors for wanting a way to make this work nicely with the predominant Ruby application framework of the day.&lt;/p&gt;
&lt;p&gt;Today, however, is a different day! We’re here to &lt;em&gt;change&lt;/em&gt; the Ruby framework balance. 😎 So we needed a way to make this work for Hanami. What didn’t feel feasible at this point was a significant change to better_errors: our time was limited and at best we had the appetite only for a minor tactical fix.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/hanami/webconsole/pull/7"&gt;Our resulting fix in webconsole&lt;/a&gt; (&lt;a href="https://github.com/hanami/hanami/pull/1330"&gt;along with this counterpart in hanami&lt;/a&gt;) does monkey patch better_errors, but I was very pleased with how gently we could do it. The patch is tiny:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;module BetterErrorsExtension
  # The BetterErrors middleware always returns a 500 status when rescuing an exception
  # (outside of Rails). This is not not always appropriate, such as for a
  # `Hanami::Router::NotFoundError`, which should be a 404.
  #
  # To account for this, gently patch `BetterErrors::Middleware#show_error_page` (which is
  # called only when an exception has been rescued) to pass that rescued exception to a proc
  # we inject into the rack env here in our own middleware. This allows our middleware to know
  # the about exception class and provide the correct status code after BetterErrors is done
  # with its job.
  #
  # @see Webconsole::Middleware#call
  def show_error_page(env, exception = nil)
    if (capture_proc = env[CAPTURE_EXCEPTION_PROC_KEY])
      capture_proc.call(exception)
    end

    super
  end
end
BetterErrors::Middleware.prepend(BetterErrorsExtension)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In order to know which response code to use for the page, we need access to the exception that better_error is catching. Right now it provides no hooks to expose that. So instead we prepend some behaviour in front of their &lt;code&gt;#show_error_page&lt;/code&gt;, which is only called by the time an error is to be rendered. We look for a proc on the rack env, and if one is there, we pass the exception to it, and then let better_errors get on with the rest of its normal work.&lt;/p&gt;
&lt;p&gt;Then, in our own webconsole middleware, we set that proc to capture the exception, using Ruby closure semantics to assign that exception directly to a local variable:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;def call(env)
  rescued_exception = nil
  env[CAPTURE_EXCEPTION_PROC_KEY] = -&amp;gt; ex { rescued_exception = ex }

  # ...
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After that, we call the better_errors middleware, letting it do its own thing:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;def call(env)
  rescued_exception = nil
  env[CAPTURE_EXCEPTION_PROC_KEY] = -&amp;gt; ex { rescued_exception = ex }

  status, headers, body = @better_errors.call(env)
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then once that is done, we can use the exception (if we have one) to fetch an appropriate response code from the Hanami app config, and then override better_errors’ response code with our own:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;def call(env)
  rescued_exception = nil
  env[CAPTURE_EXCEPTION_PROC_KEY] = -&amp;gt; ex { rescued_exception = ex }

  status, headers, body = @better_errors.call(env)

  # Replace the BetterErrors status with a properly configured one for the Hanami app
  if rescued_exception
    status = Rack::Utils.status_code(
      @config.render_error_responses[rescued_exception.class.name]
    )
  end

  [status, headers, body]
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s it! Given how light touch this is, and how stable better_errors is, I’m confident this will serve our purposes quite well for now.&lt;/p&gt;
&lt;p&gt;We don’t want to live with this forever, however. In our future I see a fit for purpose developer errors reporter that is fully integrated with Hanami’s developer experience. Given current timelines, this will probably won’t come for at least 12 months, so if this is something you’re interested in helping with, please reach out!&lt;/p&gt;
&lt;h2&gt;Kickstarting dry-operation!&lt;/h2&gt;
&lt;p&gt;While the work on Hanami continued, I also helped kickstart work on a new dry-rb gem: &lt;a href="https://github.com/dry-rb/dry-operation"&gt;dry-operation&lt;/a&gt;! Serving as the successor to &lt;a href="http://dry-rb.org/gems/dry-transaction"&gt;dry-transaction&lt;/a&gt;, with dry-operation we’ll introduce significant new flexibility to modelling composable business operations, while still keeping a high-level API that presents their key flows in an easy to follow way.&lt;/p&gt;
&lt;p&gt;Much of the month was spent ideating on various approaches with &lt;a href="http://waiting-for-dev.github.io"&gt;Marc Busqué&lt;/a&gt; and &lt;a href="https://alchemists.io/"&gt;Brooke Kuhlmann&lt;/a&gt;, and then by the end of the month, Marc was already underway with the development work. &lt;a href="http://waiting-for-dev.github.io/blog/2023/10/10/open_source_status_september_2023"&gt;Go check out Marc’s September update&lt;/a&gt; for a little more of the background on this.&lt;/p&gt;
&lt;p&gt;I’m excited we’re finally providing a bridge to the future for dry-transaction, and at the same time building one of the final pieces of the puzzle for full stack Hanami apps. This is an interesting one for me personally, too, since I’m acting more as a “product manager” for this effort, with Marc doing most of the direct development work. Marc’s been in the dry-rb/Hanami orbit for a while now, and I’m excited for this opportunity for him to step up his contributions. More on this in the future!&lt;/p&gt;
&lt;h2&gt;Releasing Hanami 2.1.0.beta2!&lt;/h2&gt;
&lt;p&gt;After all of this, we capped the month off with &lt;a href="https://hanamirb.org/blog/2023/10/04/hanami-210beta2/"&gt;the release of Hanami 2.1.0.beta2&lt;/a&gt;! This was a big step: our first beta to include &lt;em&gt;both&lt;/em&gt; views and assets together. In the time since this release we’ve already learnt a ton and found way to take things to another level… but more on that next month. 😉 See you then!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Open source status update, August 2023</title>
    <link rel="alternate" href="https://timriley.info/writing/2023/09/10/open-source-status-update-august-2023"/>
    <id>https://timriley.info/writing/2023/09/10/open-source-status-update-august-2023</id>
    <published>2023-09-10T13:30:00+00:00</published>
    <updated>2023-09-10T13:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;After &lt;a href="/writing/2023/07/23/open-source-status-update-october-2022-july-2023"&gt;last month’s omnibus update&lt;/a&gt;, I’m back again, so soon!&lt;/p&gt;
&lt;p&gt;August turned out to bring a lot of forward motion for our work on Hanami’s front end assets support. While Luca was taking his summer break, I carried on his work &lt;a href="https://github.com/hanami/assets/pull/120"&gt;preparing hanami-assets 2.1&lt;/a&gt; and &lt;a href="https://github.com/hanami/hanami/pull/1319"&gt;its integration into the Hanami framework&lt;/a&gt;. Last week we caught up for a quick chat about these, and now both are merged!&lt;/p&gt;
&lt;p&gt;Personally, I think this was an exciting evolution of how Luca and I work together. While previously we each took care of fairly distinct lines of work (there was enough to do, after all!), here we literally worked in tandem on one specific area, and it came out great!&lt;/p&gt;
&lt;p&gt;Luca and I also hopped on another video call during August, this time with &lt;a href="http://github.com/swilgosz"&gt;Seb Wilgosz&lt;/a&gt; of &lt;a href="https://hanamimastery.com/"&gt;Hanami Mastery&lt;/a&gt; to record a special core team interview for the site’s 50th episode! I really enjoyed the chance to answer community questions about Hanami, and personally, it was a moment of reassurance that we’re still on the right track and are delivering useful things to people.&lt;/p&gt;
&lt;p&gt;The episode isn’t published yet, but one thing that &lt;em&gt;did&lt;/em&gt; arise from the episode is a new &lt;a href="https://github.com/orgs/hanami/projects/2/views/1"&gt;Hanami 2.1 GitHub project&lt;/a&gt; that I put together for tracking our remaining work for the release. Previously, this was in Trello, and with the move to GitHub I hope it will make not our remaining work move visible, but also create clearer opportunities for potential contributors.&lt;/p&gt;
&lt;p&gt;Now, with those big two PRs merged and our remaining work more clearly listed, the pace is picking up! We’re now at the point where we can focus on the direct user experience of working with assets within a full Hanami app. I expect a lot will shake out from this in quick order. But more on that next month!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Open source status update, October 2022–July 2023</title>
    <link rel="alternate" href="https://timriley.info/writing/2023/07/23/open-source-status-update-october-2022-july-2023"/>
    <id>https://timriley.info/writing/2023/07/23/open-source-status-update-october-2022-july-2023</id>
    <published>2023-07-23T07:45:00+00:00</published>
    <updated>2023-07-23T07:45:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;It’s been a hot minute since my &lt;a href="/writing/2022/10/20/open-source-status-update-september-2022/"&gt;last&lt;/a&gt; open source status update! Let’s get caught up, and hopefully we can resume the monthly cadence from here.&lt;/p&gt;
&lt;h2&gt;Released Hanami 2.0&lt;/h2&gt;
&lt;p&gt;In Novemver we &lt;a href="https://hanamirb.org/blog/2022/11/22/announcing-hanami-200/"&gt;released Hanami 2.0.0&lt;/a&gt;! This was a huge milestone! Both for the Hanami project and the Ruby communuity, but also for us as a development team: we’d spent a &lt;em&gt;long&lt;/em&gt; time in the wilderness.&lt;/p&gt;
&lt;p&gt;All of this took some doing. It was a mad scramble to get here. The team and I worked non-stop over the preceding couple of months to get this release ready (including me during the mornings of a family trip to Perth).&lt;/p&gt;
&lt;p&gt;Anyway, if you’ve followed me here for a while, most of the Hanami 2 features should hopefully feel familiar to you, but if you’d like a refresher, check out the &lt;a href="https://discourse.hanamirb.org/t/highlights-of-hanami-2-0/728"&gt;Highlights of Hanami 2.0&lt;/a&gt; that I wrote to accompany the release announcement.&lt;/p&gt;
&lt;h2&gt;Spoke at RubyConf Thailand&lt;/h2&gt;
&lt;p&gt;Just two weeks after the 2.0 release, I spoke at &lt;a href="https://rubyconfth.com/past/2022/"&gt;RubyConf Thailand 2022&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Given I was 100% focused on Hanami dev work until the release, this is probably the least amount of time I’ve had for conference talk preparation, but I was happy with the result. I found a good hook (“new framework, new you”, given the new year approaching) and put together a streamlined introduction to Hanami that fit within the ~20 minutes alotted to the talks (in this case, it was a boon that we hadn’t yet released our view or persistence layers 😆).&lt;/p&gt;
&lt;p&gt;Check it out here:&lt;/p&gt;
&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/jxJ4-iadvIk?si=rwmn8RO8lzZSVP7x" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen&gt;&lt;/iframe&gt;&lt;br&gt;
&lt;h2&gt;Overhauled hanami-view internals and improved performance&lt;/h2&gt;
&lt;p&gt;With the 2.0 release done, we decided to release our view and persistence layers progressively, as 2.1 and 2.2 respectively. This would allow us to keep our focus on one thing at a time and improve the timeliness of the upcoming releases.&lt;/p&gt;
&lt;p&gt;So over the Christmas break (including several nights on a family trip to the coast), I started work on the first big blocker for our view layer: hanami-view performance. We were slower than Rails, and that just doesn’t cut the mustard for a framework that advertises itself as fast and light.&lt;/p&gt;
&lt;p&gt;Finding the right approach here took several goes, and it was &lt;a href="https://github.com/hanami/view/pull/223"&gt;finally ready for this pull request at the end of February&lt;/a&gt;. I managed to find a &amp;gt;2x performance boost while simplifying our internals, improving the ergonomics of &lt;code&gt;Hanami::View::Context&lt;/code&gt; and our part and scope builders, and still retaining all existing features.&lt;/p&gt;
&lt;h2&gt;Spoke at RubyConf Australia&lt;/h2&gt;
&lt;p&gt;Also in February, I spoke at &lt;a href="https://rubyconf.org.au/2023"&gt;RubyConf Australia 2023&lt;/a&gt;! After a 3 year hiatus, this was a wonderful reunion for the Ruby community across Australia and New Zealand. It looked like we lost no appetite for these events, so I’m encouraged for next year and beyond.&lt;/p&gt;
&lt;p&gt;To fit the homecoming theme, I brought a strong tinge of &lt;em&gt;Australiana&lt;/em&gt; to my talk, and expanded it to include a preview of the upcoming view and persistence layers. Check it out:&lt;/p&gt;
&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/-B9AbFsQOKo?si=IdY561NsKLMDsvMe" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen&gt;&lt;/iframe&gt;&lt;br&gt;
&lt;h2&gt;Created Hanami::View::ERB, a new ERB engine&lt;/h2&gt;
&lt;p&gt;After performance, the next big issue for hanami-view was having our particular needs met by our template rendering engines, as well as making auto-escaping the default for our “first party supported” engines (ERB, Haml, Slim) that output HTML.&lt;/p&gt;
&lt;p&gt;ERB support was an interesting combination of all these issues. For hanami-view, we don’t expect any rendering engine to require explicit capturing of block content. This is what allows methods on parts and scopes simply to &lt;code&gt;yield&lt;/code&gt; and have the returned value match content provided to the block from within the template.&lt;/p&gt;
&lt;p&gt;To support this with ERB, we previously had to require our users install and use the &lt;a href="https://github.com/apotonick/erbse"&gt;erbse&lt;/a&gt; gem, a little-used and incomplete ERB implementation that provided this implicit block capturing behaviour by default (but did not support auto-escaping of HTML-unsafe values). For a long while we also had to require users use &lt;a href="https://github.com/hamlit/hamlit-block"&gt;hamlit-block&lt;/a&gt; for the same reasons, and as such we had to build a compatibility check between ourselves and &lt;a href="https://github.com/jeremyevans/tilt"&gt;Tilt&lt;/a&gt; to ensure the right engines were available. This arrangement was awkward and untenable for the kind of developer experience we want for Hanami 2.&lt;/p&gt;
&lt;p&gt;So to fix all of this, I &lt;a href="https://github.com/hanami/view/pull/226"&gt;wrote our own ERB engine&lt;/a&gt;! This provides &lt;em&gt;everything&lt;/em&gt; we need from ERB (implicit block capture as well as auto-escaping) and also allows for hanami-view to be used out of the box without requiring manual installation of other gems.&lt;/p&gt;
&lt;p&gt;Meanwhile, in the years since my formative work on hanami-view (aka dry-view), Haml and Slim evolved to both use &lt;a href="https://github.com/judofyr/temple"&gt;Temple&lt;/a&gt; and provide configuration hooks for all the behaviour we require, so this allowed me to drop our template engine compatibility checks and instead just automatically configure Haml or Slim to match our needs if they’re installed.&lt;/p&gt;
&lt;p&gt;To support our auto-escaping of HTML-unsafe values, we’ve adopted the &lt;code&gt;Object&lt;/code&gt; and &lt;code&gt;String&lt;/code&gt; &lt;code&gt;#html_safe?&lt;/code&gt; patches that are prevalent across relevant libraries in the Ruby ecosystem. This gives us the broadest possible compatibility, as well as a streamlined and unsurprising user experience. While you might see folks decry monkey patches in general, this is one example where it makes sense for Hanami to take a pragmatic approach, and I’m very pleased with the outcome.&lt;/p&gt;
&lt;h2&gt;Implemented helpers for hanami-view&lt;/h2&gt;
&lt;p&gt;After performance and rendering/HTML safety, the last remaining pre-release item for hanami-view was support for helpers. This needed a bit of thinking to sort out, since the new hanami-view provides a significantly different set of view abstractions compared to the 1.x edition.&lt;/p&gt;
&lt;p&gt;Here’s how I managed to sort it out:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In hanami-view, &lt;a href="https://github.com/hanami/view/pull/227"&gt;add configurable part_class and scope_class settings&lt;/a&gt; to hanami-view&lt;/li&gt;
&lt;li&gt;In hanami, &lt;a href="https://github.com/hanami/hanami/pull/1303"&gt;configure slice-specific part and scope classes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;In hanami, &lt;a href="https://github.com/hanami/hanami/pull/1304"&gt;include first-party helper modules into these classes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;By this point, all the wiring is in place for first-party helpers&lt;/li&gt;
&lt;li&gt;So in hanami-view, &lt;a href="https://github.com/hanami/view/pull/229"&gt;port all relevant helpers from the 1.x hanami-helpers gem&lt;/a&gt;, including a new, simpler &lt;code&gt;TagHelper&lt;/code&gt; for generating HTML tags.&lt;/li&gt;
&lt;li&gt;And over in hanami, &lt;a href="https://github.com/hanami/hanami/pull/1305"&gt;introduce a FormHelper&lt;/a&gt; that integrates with the full app, including pulling params from the request&lt;/li&gt;
&lt;li&gt;Then &lt;a href="https://github.com/hanami/hanami/pull/1307"&gt;seamlessly provide all first-party as well as user-defined helpers&lt;/a&gt; to view scopes and classes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After this, all helpers should appear whereer you need them in your views, whether in templates, part classes or scope classes. Each slice will also generate a &lt;code&gt;Views::Helpers&lt;/code&gt; module to serve as the starting point for your own collection of helpers, too.&lt;/p&gt;
&lt;p&gt;With hanami-view providing parts and scopes, the idea is that you can and should use available-everywhere helpers less than before, but they can still be valuable from time to time, and with their introduction, now you have every possible option available for building your views.&lt;/p&gt;
&lt;h2&gt;Added friendly error pages&lt;/h2&gt;
&lt;p&gt;While focused on views, I also took the chance to make our error views friendly too. Now we:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/hanami/hanami/pull/1309"&gt;Render nice error pages in production mode&lt;/a&gt; (this is also configurable so you can add custom handling of specific error types)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/hanami/hanami/pull/1311"&gt;Render detailed error pages in development mode&lt;/a&gt;, including full stack trace and interactive console, courtesy of &lt;a href="https://github.com/BetterErrors/better_errors"&gt;better_errors&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Worked on integrating hanami-assets&lt;/h2&gt;
&lt;p&gt;Alongside all of this, Luca has been working hard on our support for front end assets via an &lt;a href="https://github.com/hanami/assets-js"&gt;esbuild plugin&lt;/a&gt; and its &lt;a href="https://github.com/hanami/assets/pull/120"&gt;integration with the framework&lt;/a&gt;. This has been nothing short of heroic: he’s been beset by numerous roadblocks but overcome each one, and now we’re getting really close.&lt;/p&gt;
&lt;p&gt;Back in June, Luca and I had our first ever pairing session on this work! We got a long way in just a couple of hours. I’m looking forward to pitching in with this as my next focus.&lt;/p&gt;
&lt;h2&gt;Prepared the Hanami 2.1.0.beta1 release&lt;/h2&gt;
&lt;p&gt;With all the views work largely squared away, I figured it was time to make a beta release and get this stuff out there for people to test, so we &lt;a href="https://hanamirb.org/blog/2023/06/29/hanami-210beta1/"&gt;released it as 2.1.0.beta1&lt;/a&gt; at the end of June.&lt;/p&gt;
&lt;h2&gt;Spoke at Brighton Ruby!&lt;/h2&gt;
&lt;p&gt;Also at the end of June I spoke at &lt;a href="https://brightonruby.com"&gt;Brighton Ruby&lt;/a&gt;! I’ve wanted to attend this event for the longest time, and it did not disappoint. I had a wonderful day at the conference and enjoyed meeting a bunch of new Ruby friends.&lt;/p&gt;
&lt;p&gt;For my talk I further evolved the content from the previous iterations, and this time included a look at how we might grow a Hanami app into a more real thing, as well as reflections on what Hanami 2’s release might mean for the Ruby community. I also experimented with a fun new theme and narrative device, which you shall be able to see once the video is out 😜&lt;/p&gt;
&lt;p&gt;Thank you so much to &lt;a href="https://andycroll.com"&gt;Andy&lt;/a&gt; for the invitation and the support. ❤️&lt;/p&gt;
&lt;h2&gt;Took a holiday&lt;/h2&gt;
&lt;p&gt;After all of that, I took a break! You might’ve noticed my mentions of all the Hanami work I was doing while ostensibly on family trips. Well, after Brighton Ruby, I was all the way in &lt;em&gt;Europe&lt;/em&gt; with the family, and made sure to have a good proper 4 weeks of (bonus summer) holiday. It was fanastic, and I didn’t look at Ruby code one bit.&lt;/p&gt;
&lt;h2&gt;What’s next&lt;/h2&gt;
&lt;p&gt;Now that I’m back, I’ll focus on doing whatever is necessary to complete our front end assets integration and get that out as a 2.1 beta2 release. Our new assets stuff is the completely new, so some time for testing and bug fixing will be useful.&lt;/p&gt;
&lt;p&gt;Over the rest of the beta period I hope to complete a few smaller general framework improvements and fixes, and from there we can head towards 2.1.0 final.&lt;/p&gt;
&lt;p&gt;I suspect it will take at least one more OSS status updates before that all happens, so I can check in with you about it all then!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Open source status update, September 2022</title>
    <link rel="alternate" href="https://timriley.info/writing/2022/10/20/open-source-status-update-september-2022"/>
    <id>https://timriley.info/writing/2022/10/20/open-source-status-update-september-2022</id>
    <published>2022-10-10T13:10:00+00:00</published>
    <updated>2022-10-10T13:10:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Hello there, friends! This is going to be a short update from me because I’m deep in the throes of Hanami 2.0 release preparation right now. Even still, I didn’t want to let September pass without an update, so let’s take a look.&lt;/p&gt;
&lt;h2&gt;A story about Hanami::Action memory usage&lt;/h2&gt;
&lt;p&gt;September started and ended with me looking at the &lt;a href="https://github.com/jeremyevans/r10k"&gt;r10k&lt;/a&gt; memory usage charts for hanami-controller versus Rails. The results were surprising!&lt;/p&gt;
&lt;p&gt;&lt;img src="https://user-images.githubusercontent.com/3134/197044951-8e5742ae-8437-43b2-aba7-11352b5b306d.png" alt="Initial memory usage for Hanami::Action vs Rails" /&gt;&lt;/p&gt;
&lt;p&gt;We’d been running some of these checks as part of our 2.0 release prep, the idea being that it’d help us shake out any obvious performance improvements we’d need to make. And it certainly did in this case! Hanami (just like its dry-rb underpinnings) is meant to be the smaller and lighter framework; why were we being outperformced by Rails?&lt;/p&gt;
&lt;p&gt;To address this I wrote a simple memory profile script for &lt;code&gt;Hanami::Action&lt;/code&gt; inheritance (now &lt;a href="https://github.com/hanami/controller/blob/e47fe2484e3d07811e5e817abff17c9a0b027595/benchmarks/memory_profile_action.rb"&gt;checked in here&lt;/a&gt;) and started digging.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://gist.github.com/timriley/6c512c100c179070c673afc578618386"&gt;Here were there initial results&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Total allocated: 184912288 bytes (1360036 objects)
Total retained:  104910880 bytes (780031 objects)

allocated memory by gem
-----------------------------------
  56242240  concurrent-ruby-1.1.10
  53282480  dry-configurable-0.15.0
  34120000  utils-8585be837309
  30547488  other
  10720080  controller/lib
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s 185MB allocated for 10k subclasses, with concurrent-ruby, dry-configurable and hanami-utils being the top three gems allocating memory.&lt;/p&gt;
&lt;p&gt;This led me straight to dry-configurable, and after a couple of weeks of work, I &lt;a href="https://github.com/dry-rb/dry-configurable/pull/138"&gt;arrived at this PR&lt;/a&gt;, separating our storage of setting definitions from their configured values, among other things. This change allows us to copy less data at the moment of class inheritance, and in the case of a dry-configurable-focused memory profile, cut the allocated memory by more than half.&lt;/p&gt;
&lt;p&gt;From there, I moved back into hanami-controller and updated it to &lt;a href="https://github.com/hanami/controller/pull/392"&gt;use dry-configurable for all of its inheritable attributes&lt;/a&gt; (some were handled separately), also taking advantage the support for &lt;a href="https://github.com/dry-rb/dry-configurable/pull/136"&gt;custom config classes&lt;/a&gt; that Piotr added so we could preserve Hanami::Action’s existing configuration API.&lt;/p&gt;
&lt;p&gt;This &lt;a href="https://gist.github.com/timriley/417595e404e58efb92a5290a4942ef1a"&gt;considerably improved our benchmark&lt;/a&gt;! Behold:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Total allocated: 32766232 bytes (90004 objects)
Total retained:  32766232 bytes (90004 objects)

allocated memory by gem
-----------------------------------
  21486072  other
  10880120  dry-configurable-0.16.1
    400040  3.1.2/lib
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yes, we brought 185MB allocated memory down to 33MB! This also brought us on par with Rails in the extreme end of the r10k memory usage benchmark:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://user-images.githubusercontent.com/3134/196795166-d60b6db8-a75a-44ed-a201-44cd7a054f27.png" alt="Updated memory usage for Hanami::Action vs Rails" /&gt;&lt;/p&gt;
&lt;p&gt;Here’s a thing though: the way r10k generates actions for its Rails benchmark is to create a &lt;em&gt;single controller class&lt;/em&gt; with a method per action. So for the point on the far right of that chart, that’s a single class with 10k methods. Hardly realistic.&lt;/p&gt;
&lt;p&gt;So I made a quick tweak to see how things would look if the r10k Rails benchmark generated a class per endpoint like we do with Hanami::Action:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://user-images.githubusercontent.com/3134/196795365-4e7111b6-62a6-4dff-adc2-29201375f3d9.png" alt="Hanami::Action vs Rails with a separate controller class per action" /&gt;&lt;/p&gt;
&lt;p&gt;That’s more like it. This is another extreme, however: more realistically, we’d see Rails apps with somewhere between 5-10 actions per controller class, which would lower its dot a little in that graph. In my opinion this would be a useful thing to upstream into r10k. It’s already a contrived benchmark, yes, but it’d be more useful if it at least mimicked realistic application structures.&lt;/p&gt;
&lt;p&gt;Either way, we finished the month much more confident that we’ll be delivering on our promise of Hanami as the lighter, faster framework alternative. A good outcome!&lt;/p&gt;
&lt;p&gt;Along the way, however, things did feel bleak at times. I wasn’t confident that I’d be able to make things right, and it didn’t feel great to think we might’ve spent years putting somethign together that wasn’t going to be able to deliver on some of those core promises. Luckily, I found all the wins we needed, and learnt a few things along the way.&lt;/p&gt;
&lt;h2&gt;Hanami 2.0, here we come&lt;/h2&gt;
&lt;p&gt;What else happened in September? Possibly the biggst thing is that we organised ourselves for the runway towards the final Hanami 2.0.0 release.&lt;/p&gt;
&lt;p&gt;We want to do everything possible to make sure the release happens this year, so I spent some time organising the remaining tasks on &lt;a href="https://trello.com/b/lFifnBti/hanami-20"&gt;our Trello board&lt;/a&gt; into date-based lists, aiming for a release towards the end of November. It looked achievable! The three of us in the core team re-committed ourselves to doing everything we could to complete these tasks in our estimated timeframes.&lt;/p&gt;
&lt;p&gt;So far, things have gone very well!&lt;/p&gt;
&lt;p&gt;&lt;img src="https://user-images.githubusercontent.com/3134/196941690-6a871cbe-3b57-470e-8b32-6887ccab2e22.png" alt="Hanami 2.0.0 release progress on Trello" /&gt;&lt;/p&gt;
&lt;p&gt;We’ve all been working tremendously hard, and so far, this has let us keep everything to the schedule. I’ll have a lot to share about our work across October, but that’s all for next month’s update. So in the meantime, I have to put my head back down and get back to shipping a framework. See you all again soon!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Open source status update, August 2022</title>
    <link rel="alternate" href="https://timriley.info/writing/2022/09/18/open-source-status-update-august-2022"/>
    <id>https://timriley.info/writing/2022/09/18/open-source-status-update-august-2022</id>
    <published>2022-09-17T21:30:00+00:00</published>
    <updated>2022-09-17T21:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;August’s OSS work landed one of the last big Hanami features, saw another Hanami release out the door, began some thinking about memory usage, and kicked off a fun little personal initiative. Let’s dive in!&lt;/p&gt;
&lt;h2&gt;Conditional slice loading in Hanami&lt;/h2&gt;
&lt;p&gt;At the beginning of the month I &lt;a href="https://github.com/hanami/hanami/pull/1189"&gt;merged support for conditional slice loading in Hanami&lt;/a&gt;. I’d wanted this feature for a long time, and in fact I’d hacked in workarounds to achieve the same more than 2 years ago, so I was very pleased to finally get this done, and for the implementation work to be as smooth as it was.&lt;/p&gt;
&lt;p&gt;The feature provides a new &lt;code&gt;config.slices&lt;/code&gt; setting on your app class, which you can configure like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;module MyApp
  class App &amp;lt; Hanami::App
    config.slices = %w[admin]
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For an app consisting of both &lt;code&gt;Admin&lt;/code&gt; and &lt;code&gt;Main&lt;/code&gt; slices and for the config above, when the app is booted, only the &lt;code&gt;Admin&lt;/code&gt; slice will be loaded:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;require &amp;quot;hanami/prepare&amp;quot;

Hanami.app.slices.keys # =&amp;gt; [:admin]

Admin::Slice # exists, as expected
Main         # raises NameError, since it was never loaded
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As we see from &lt;code&gt;Main&lt;/code&gt; above, slices absent from this list will not have their namespace defined, nor their slice class loaded, nor any of their Ruby source files. Within that Ruby process, they effectively do not exist.&lt;/p&gt;
&lt;p&gt;Specifying slices to load can be very helpful to improve boot time and minimize memory usage for specific deployed workloads of your app.&lt;/p&gt;
&lt;p&gt;Imagine you have a subset of background jobs that run via a dedicated job runner, but whose logic is otherwise unneeded for the rest of your app to function. In this case, you could organize those jobs into their own slice, and then load only that slice for the job runner’s process. This arrangement would see the job runner boot as quickly as possible (no extraneous code to load) as well as save all the memory otherwise needed by all those classes. You could also do the invserse for your main deployed process: specify all slices &lt;em&gt;except&lt;/em&gt; this jobs slice, and you gain savings there too.&lt;/p&gt;
&lt;p&gt;Organising code into slices to promote operational efficiency like this also gives you the benefit of greater clarity in the separation of responsibilities between those slices: when a single slice of code is loaded and the rest of your app is made to disappear, that will quickly surface any insidious dependencies from that slice to the rest of your code (they’ll be raised as exceptions!). Cleaning these up will help ensure your slices remain useful as abstractions for reasoning about and maintaining your app.&lt;/p&gt;
&lt;p&gt;To make it easy to tune the list of slices to load, I also introduced a new &lt;code&gt;HANAMI_SLICES&lt;/code&gt; env var that sets this config without you having to write code inside your app class. In this way, you could use them in your &lt;code&gt;Procfile&lt;/code&gt; or other similar deployment code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;web: HANAMI_SLICES=main,admin bundle exec puma -C config/puma.rb
feed_worker: HANAMI_SLICES=feed bundle exec rake jobs:work
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This effort was also another example of why I’m so happy to be working alongside the Hanami core team. After initially proposing a more complex arrangement including separate lists for including or excluding slices, &lt;a href="https://github.com/hanami/hanami/pull/1189#pullrequestreview-1063372221"&gt;Luca jumped in&lt;/a&gt; and help me dial this back to the much simpler arrangement of the single list only. For an Hanami release in which we’re going to be introducing so many new ideas, the more we can keep simple around them, the better, and I’m glad to have people who can remind me of this.&lt;/p&gt;
&lt;h2&gt;Fixed how slice config is applied to component classes&lt;/h2&gt;
&lt;p&gt;Our action and view integration code relies on their classes detecting when they’re defined inside a slice’s namespace, then applying relevant config from the slice to their own class-level config object. It turned out our code for doing this broke a little when we adjusted our default class hierarchies. Thanks to some of our wonderful early adopters, we picked this up quickly and &lt;a href="https://github.com/hanami/hanami/pull/1193"&gt;I fixed it&lt;/a&gt;. Now things just work like you expect however you choose to configure your action classes, whether through the app-level &lt;code&gt;config.actions&lt;/code&gt; object, or by directly updating &lt;code&gt;config&lt;/code&gt; in a base action class.&lt;/p&gt;
&lt;p&gt;In doing this work, I became convinced we need an API on dry-configurable to determine whether any config value has been assigned or mutated by the user, since it would help so much in reliably detecting whether or not we should ignore config values at particular levels. For now, we could work around it, but I hope to bring this to dry-configurable at some point in the future.&lt;/p&gt;
&lt;h2&gt;Released Hanami 2.0.0.beta2&lt;/h2&gt;
&lt;p&gt;Another month passed, so it was time for another release! With my European colleagues mostly enjoying some breaks over their summer, I hunkered down in chilly Canberra and &lt;a href="https://hanamirb.org/blog/2022/08/16/announcing-hanami-200beta2/"&gt;took care of the 2.0.0.beta2 release&lt;/a&gt;. Along with the improvements above, this release also included slice and action generators (&lt;code&gt;hanami generate slice&lt;/code&gt; and &lt;code&gt;hanami generate action&lt;/code&gt;, thank you Luca!), plus a very handle CLI middlewares inspector (&lt;a href="https://github.com/hanami/cli/pull/30"&gt;thank you Marc!&lt;/a&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;$ hanami middlewares

/    Dry::Monitor::Rack::Middleware (instance)
/    Rack::Session::Cookie
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The list of things to do over the beta phase is getting smaller. I don’t expect we’ll need too many more of these releases!&lt;/p&gt;
&lt;h2&gt;Created memory usage benchmarks for dry-configurable&lt;/h2&gt;
&lt;p&gt;As the final 2.0 release gets closer, we’ve been doing various performance tests just to make sure the house is in order. One thing we discovered is that &lt;code&gt;Hanami::Action&lt;/code&gt; is not as memory efficient as we’d like it to be. One of the biggest opportunities to improve this looked to be in dry-configurable, since that’s what is used to manage the per-class action configuration.&lt;/p&gt;
&lt;p&gt;I suspected any effort here would turn out to be involved (and no surprise, it turned out to be involved 😆), so I thought it would be useful as a first step to &lt;a href="https://github.com/dry-rb/dry-configurable/pull/137"&gt;establish a memory benchmark&lt;/a&gt; to revisit over the course of any work. This was also a great way to get my head in this space, which turned out to take over most of my September (but more on that next month).&lt;/p&gt;
&lt;h2&gt;Quietly relaunched Decaf Sucks&lt;/h2&gt;
&lt;p&gt;Decaf Sucks was once a thriving little independent online café review community, with its own web site (starting from humble beginnings as a &lt;a href="/writing/2009/09/02/decaf-sucks-and-a-rails-rumble-redux/"&gt;Rails Rumble entry in 2009&lt;/a&gt;) and even native iOS app (&lt;a href="https://www.icelab.com.au/notes/decaf-sucks-launch-countdown-development-complete"&gt;two iterations&lt;/a&gt;, &lt;a href="https://www.icelab.com.au/notes/announcing-decaf-sucks-20"&gt;in fact&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;I was immensitely proud of what Decaf Sucks became, and for the collaboration with &lt;a href="https://www.makenosound.com"&gt;Max Wheeler&lt;/a&gt; in building it.&lt;/p&gt;
&lt;p&gt;Unfortunately, as various internet APIs changed, the site atrophied, eventually became disfunctional, and we had to take it down. I still have the database, however, and I want to bring it back!&lt;/p&gt;
&lt;p&gt;This time around, my plan is to do it as a fully open source Hanami 2 example application. Max is even on board to bring back all the UI goodness. For now, you can &lt;a href="https://github.com/timriley/decafsucks"&gt;follow along with the early steps on GitHub&lt;/a&gt;. Right now the app is little more than the basic Hanami skeleton with added database integration and a CI setup (Hello &lt;a href="https://buildkite.com"&gt;Buildkite!&lt;/a&gt;), but I plan to grow it bit by bit. Perhaps I’ll try to have something small that I can share with each of these monthly OSS updates.&lt;/p&gt;
&lt;p&gt;After Hanami 2 ships, hopefully this will serve as a useful resource for people wanting to see how it plays out in a real working app. And beyond that, I look forward to it serving once again as a place for me to commemorate my coffee travels!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Open source status update, May–July 2022</title>
    <link rel="alternate" href="https://timriley.info/writing/2022/08/08/open-source-status-update-may-july-2022"/>
    <id>https://timriley.info/writing/2022/08/08/open-source-status-update-may-july-2022</id>
    <published>2022-08-08T05:20:00+00:00</published>
    <updated>2022-08-08T05:20:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Hi there friends, it’s certainly been a while, and a lot has happened across May, June and July: &lt;a href="/writing/2022/06/14/joining-buildkite-and-sticking-with-ruby/"&gt;I left my job, took some time off, and started a new job&lt;/a&gt;. I also managed to get a good deal of open source work done, so let’s take a look at that!&lt;/p&gt;
&lt;h2&gt;Released Hanami 2.0.0.alpha8&lt;/h2&gt;
&lt;p&gt;Since we’d skipped a month in our releases, I helped &lt;a href="https://hanamirb.org/blog/2022/05/19/announcing-hanami-200alpha8/"&gt;get Hanami 2.0.0.alpha8 out the door&lt;/a&gt; in May. The biggest change here was that we’d finished relocating the action and view integration code into the hanami gem itself, wrapped up in distinct “application” classes, like &lt;code&gt;Hanami::Application::Action&lt;/code&gt;. In the end, this particular naming scheme turned out to be somewhat short lived! Read on for more :)&lt;/p&gt;
&lt;h2&gt;Resurrected work using dry-effects within hanami-view&lt;/h2&gt;
&lt;p&gt;As part of an effort to make it easy to use our conventional view “helpers” in all parts of our view layer, I &lt;a href="/writing/2020/10/06/open-source-status-update-september-2020/"&gt;resurrected my work from September 2020(!)&lt;/a&gt; on using dry-effects within hanami-view. The idea here was to achieve two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;To ensure we keep only a single context object for the entire view rendering, allowing its state to be preserved and accessed by all view components (i.e. allowing both templates, partials and parts all to access the very same context object)&lt;/li&gt;
&lt;li&gt;To enable access to the &lt;em&gt;current&lt;/em&gt; template/partial’s &lt;code&gt;#locals&lt;/code&gt; from within the context, which might help make our helpers feel a little more streamlined through implicit access to those locals&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I got both of those working (&lt;a href="https://github.com/hanami/view/pull/179"&gt;here’s my work in progress&lt;/a&gt;), but I discovered the performance had worsened due to the cost of using an effect to access the locals. I took a few extra passes at this, reducing the number of effects to one, and memoziing it, leaving us with improved performance over the &lt;code&gt;main&lt;/code&gt; branch, but with a slightly different stance: the single effect is for accessing the context object only, so any helpers, instead of expecting access to locals, will instead only have access to that context. The job from here will be to make sure that the context object we build for Hanami’s views has everything we need for an ergonomic experience working with our helpers. I’m feeling positive about the direction here, but it’ll be a little while before I get back to it. Read on for more on this (again!).&lt;/p&gt;
&lt;h2&gt;Unified application and slice&lt;/h2&gt;
&lt;p&gt;The biggest thing I did over this period was to unify Hanami’s &lt;code&gt;Application&lt;/code&gt; and &lt;code&gt;Slice&lt;/code&gt;. This one took some doing, and I was glad that I had a solid stretch of time to work on it between jobs.&lt;/p&gt;
&lt;p&gt;I already wrote about this &lt;a href="/writing/2022/05/15/open-source-status-update-april-2022/"&gt;back in April’s update&lt;/a&gt;, noting that I’d settled on the approach of having a composed slice inside the &lt;code&gt;Hanami::Application&lt;/code&gt; class to providing slice-like functionality at the application level. This was the approach I continued with, and as I went, I was able to move more and more functionality out of &lt;code&gt;Hanami::Application&lt;/code&gt; and into &lt;code&gt;Hanami::Slice&lt;/code&gt;, with that composed “application slice” being the thing that preserved the existing application behaviour. At some point, a pattern emerged: the application &lt;em&gt;is&lt;/em&gt; a slice, and we could achieve everything we wanted (and more) by turning &lt;code&gt;class Hanami::Application&lt;/code&gt; into &lt;code&gt;class Hanami::Application &amp;lt; Hanami::Slice&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Turning the application into a slice sublcass is indeed &lt;a href="https://github.com/hanami/hanami/pull/1162"&gt;how I finished the work&lt;/a&gt;, and I’m extremely pleased with how it turned out. It’s made slices so much more powerful. Now, each slice can have its own config, its own dedicated settings and routes, can be run on its own as a Rack application, and can even have its own set of child slices.&lt;/p&gt;
&lt;p&gt;As a user of Hanami you won’t be required to use all of this per-slice power features, but they’ll be there if or when you want them. This is a great example of &lt;em&gt;progressive disclosure&lt;/em&gt;, a principle I follow as much as possible when designing Hanami’s features: a user should be able to work with Hanami in a simple, straightforward way, and then as their needs grow, they can then find additional capabilities waiting to serve them.&lt;/p&gt;
&lt;p&gt;Let’s explore this with a concrete example. If you’re building a simple Hanami app, you can start with a single top-level &lt;code&gt;config/settings.rb&lt;/code&gt; that defines all of the app’s own settings. This settings object is made available as a &lt;code&gt;&amp;quot;settings&amp;quot;&lt;/code&gt; component registration in both the app as well as all its slices. As the app grows and you add a slice or two, you start to add more slice-specific settings to this component. At this point you start to feel a little uncomfortable that settings specific to &lt;code&gt;SliceA&lt;/code&gt; are also available inside &lt;code&gt;SliceB&lt;/code&gt; and elsewhere. So you wonder, could you go into &lt;code&gt;slices/slice_a/&lt;/code&gt; and drop a dedicated &lt;code&gt;config/settings.rb&lt;/code&gt; there? The answer to that is now yes! Create a &lt;code&gt;config/settings.rb&lt;/code&gt; inside &lt;em&gt;any&lt;/em&gt; slice directory and it will now become a dedicated settings component for that slice alone. This isn’t a detail you had to burden yourself with in order to get started, but it was ready for you when you needed it.&lt;/p&gt;
&lt;p&gt;Another big benefit of this code reorganisation is that the particular responsibilities of &lt;code&gt;Hanami::Application&lt;/code&gt; are &lt;em&gt;much&lt;/em&gt; clearer: its job is to provide the single entrypoint to the app and coordinate the overall boot process; everything else comes as part of it also being a slice. This distinction is made clear through the number of public methods that exist across the two classes: &lt;code&gt;Application&lt;/code&gt; now has only 2 distinct public methods, whereas &lt;code&gt;Slice&lt;/code&gt; currently brings 27.&lt;/p&gt;
&lt;p&gt;There’s &lt;a href="https://github.com/hanami/hanami/pull/1162"&gt;plenty more detail over in the pull request&lt;/a&gt;: go check it out!&lt;/p&gt;
&lt;p&gt;The work here also led to changes across the ecosystem:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Because we’re now importing components from the app into other slices in the root key namespace (rather than prefixed by &lt;code&gt;&amp;quot;application.&amp;quot;&lt;/code&gt; as before), we needed to &lt;a href="https://github.com/dry-rb/dry-system/pull/236"&gt;make that possible in dry-system&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;But with it being possible for each slice to have its own dedicated version of certain components (like &lt;code&gt;&amp;quot;settings&amp;quot;&lt;/code&gt; in the example above), we also needed to make dry-system &lt;a href="https://github.com/dry-rb/dry-system/pull/241"&gt;prefer local components when importing&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;This itself needed a tweak to dry-container such that we could &lt;a href="https://github.com/dry-rb/dry-container/pull/83"&gt;reverse the order of its &lt;code&gt;Container#merge&lt;/code&gt; via a block parameter&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;To make certain components conditional within the slice containers, we’re using dry-system’s class-based provider sources, which &lt;a href="https://github.com/dry-rb/dry-system/pull/240"&gt;needing tweaking to support deeper class hierarchies&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is one the reasons I’m excited about Hanami’s use of the dry-rb gems: it’s pushing them in directions no one has had to take them before. The result is not only the streamlined experience we want for Hanami, but also vastly more powerful underpinnings.&lt;/p&gt;
&lt;h2&gt;Devised a slimmed down core app structure&lt;/h2&gt;
&lt;p&gt;While I had my head down working on internal changes like the above, Luca had been thinking about Hanami 2 adoption and the first run user experience. As we had opted for a slices-only approach for the duration of our alpha releases, it meant a fairly &lt;em&gt;bulky&lt;/em&gt; overall app structure: every slice came with multiple deeply nested files. This might be overwhelming to new users, as well as feeling like overkill for apps that are intended to start small and stay small.&lt;/p&gt;
&lt;p&gt;To this end, we agreed upon a stripped back starter structure. Here’s how it looks at its core (ignoring tests and other general Ruby files):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;├── app/
│   ├── action.rb
│   └── actions/
├── config/
│   ├── app.rb
│   ├── routes.rb
│   └── settings.rb
├── config.ru
└── lib/
    ├── my_app/
    │   └── types.rb
    └── tasks/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s it! Much more lightweight. This approach takes advantage of the Hanami app itself becoming a fully-featured slice, with &lt;code&gt;app/&lt;/code&gt; now as its source directory.&lt;/p&gt;
&lt;p&gt;In fact, I took this opportunity to &lt;a href="https://github.com/hanami/hanami/pull/1174"&gt;unify the code loading rules for both the app and slices&lt;/a&gt;, which makes for a much more intuitive experience. You can now drop any ruby source file into &lt;code&gt;app/&lt;/code&gt; or a &lt;code&gt;slices/[slice_name]/&lt;/code&gt; slice dir and it will be loaded in the same way: starting at the root of each directory, classes defined therein are expected to inhabit the namespace that the app or slice represents, so &lt;code&gt;app/some_class.rb&lt;/code&gt; would be &lt;code&gt;MyApp::SomeClass&lt;/code&gt; and &lt;code&gt;slices/my_slice/some_class&lt;/code&gt; would be &lt;code&gt;MySlice::SomeClass&lt;/code&gt;. Hat tip to &lt;a href="/writing/2021/10/11/open-source-status-update-september-2021/"&gt;me of September 2021&lt;/a&gt; for implementing the dry-system namespaces feature that enabled this! 😜&lt;/p&gt;
&lt;p&gt;(Yet another little dry-system tweak came out of preparing this too, with &lt;a href="https://github.com/dry-rb/dry-system/pull/237"&gt;&lt;code&gt;Component#file_name&lt;/code&gt; now exposed&lt;/a&gt; for auto-registration rules).&lt;/p&gt;
&lt;p&gt;This new initial structure for starter Hanami 2.0 apps is another example of progressive disclosure in our design. You can start with a simple all-in-one approach, everything inside an &lt;code&gt;app/&lt;/code&gt; directory, and then as various distinct concerns present themselves, you can extract them into dedicated slices as required.&lt;/p&gt;
&lt;p&gt;Along with this, some of our names have become shorter! Yes, &lt;a href="https://github.com/hanami/hanami/pull/1180"&gt;“application” has become “app”&lt;/a&gt; (and &lt;code&gt;Hanami::Application&lt;/code&gt; has become &lt;code&gt;Hanami::App&lt;/code&gt;, and so on). These shorter names are easier to type, as well as more reflective of the words we tend to use when verbally describing these structures.&lt;/p&gt;
&lt;p&gt;We also tweaked our actions and views integration code so that it is automatically available when you inherit directly from &lt;code&gt;Hanami::Action&lt;/code&gt;, so it will no longer be necessary to have the verbose &lt;code&gt;Hanami::Application::Action&lt;/code&gt; as the superclass for the app’s actions. We also &lt;a href="https://github.com/hanami/hanami/pull/1172"&gt;ditched that namespace&lt;/a&gt; for both routes and settings too, so now you can just inherit from &lt;code&gt;Hanami::Settings&lt;/code&gt; and the like.&lt;/p&gt;
&lt;h2&gt;Devised a slimmed down release strategy&lt;/h2&gt;
&lt;p&gt;Any of you following my updates would know by now that the Hanami 2.0 release has been a long time coming. We have ambitious goals, we’re doing our best, and everything &lt;em&gt;is&lt;/em&gt; slowly coming together. But as hard as it might’ve been for folks who’re waiting, it’s been doubly so for us, feeling the weight of both the work along with everyone’s expectations.&lt;/p&gt;
&lt;p&gt;So to make sure we can focus our efforts and get something out the door sooner rather than later, we decided to stagger our 2.0 release. We’ll start off with an initial 2.0 release centred around hanami, hanami-cli, hanami-controller, and hanami-router (enough to write some very useful API applications, for example), then follow up with a “full stack” 2.1 release including database persistence, views, helpers, assets and everything else.&lt;/p&gt;
&lt;p&gt;I’m already feeling empowered by this strategy: 2.0 feels actually achievable now! And all of the other release-related work like updated docs and a migration guide will become correspondingly easier too.&lt;/p&gt;
&lt;h2&gt;Released Hanami 2.0.0.beta1!&lt;/h2&gt;
&lt;p&gt;With greater release clarity as well as all the above improvements under our belt, it was time to usher in a new phase of Hanami 2.0 development, so &lt;a href="https://hanamirb.org/blog/2022/07/20/announcing-hanami-200beta1/"&gt;we released 2.0.0.beta1&lt;/a&gt; in July! This new version suffix represents just how close we feel we are to our final vision for 2.0. This is an exciting moment!&lt;/p&gt;
&lt;h2&gt;And a bunch more&lt;/h2&gt;
&lt;p&gt;This update is getting rather long, so let me list a bunch of other Hanami improvements I managed to get done:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You can now &lt;a href="https://github.com/hanami/hanami/pull/1186"&gt;autoload your app’s constants from within your settings file&lt;/a&gt;, which is useful if you’re referring to a types module to type check your settings. This turned out to be a larger change than expected, but it’s further bolstered our slice capabilities, because it led to each slice getting its own Zeitwerk autoloader instance, meaning each slice fully owns its code loading lifecycle.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.env&lt;/code&gt; file loading is now &lt;a href="https://github.com/hanami/hanami/pull/1190"&gt;a much clearer step upon subclassing &lt;code&gt;Hanami::App&lt;/code&gt;&lt;/a&gt;, rather than it being a side effect of loading the settings. This is useful in case you want to directly access the &lt;code&gt;ENV&lt;/code&gt; for any other purpose.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;.prepare_container&lt;/code&gt; escape hatch allowing for last-minute manual adjustments to a slice’s internal dry-system container is now &lt;a href="https://github.com/hanami/hanami/pull/1185"&gt;run after all possible framework setup has been applied&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;In the few places we rescue from &lt;code&gt;LoadError&lt;/code&gt;, we now ensure &lt;a href="https://github.com/hanami/hanami/pull/1166"&gt;we don’t swallow any unintended errors&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Hanami now &lt;a href="https://github.com/hanami/hanami/pull/1168"&gt;respects RACK_ENV if HANAMI_ENV is not set&lt;/a&gt;, which is useful for Rack-first apps that happen to be migrating to Hanami 2.0 (these do exist!)&lt;/li&gt;
&lt;li&gt;I &lt;a href="https://github.com/hanami/hanami/pull/1175"&gt;removed the settings allowing for customisation of the routes and settings file paths&lt;/a&gt;. The further I go with batteries included framework development, the better sense I think I’m developing for what &lt;em&gt;should’t&lt;/em&gt; be configurable. This is one such case: we can’t provide reasonable documentation or support if these files can live in arbitrary locations.&lt;/li&gt;
&lt;li&gt;Last but not least, I built &lt;a href="https://github.com/hanami/hanami/pull/1189"&gt;conditional slice loading&lt;/a&gt;! But since this isn’t merged yet, I’ll go into details on this in next month’s update ;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Outside my Hanami development, a new job and a new computer meant I also took the change to &lt;a href="http://github.com/timriley/dotfiles"&gt;reboot my dotfiles&lt;/a&gt;, which are now powered by &lt;a href="https://www.chezmoi.io"&gt;chezmoi&lt;/a&gt;. I can’t speak highly enough of chezmoi, it’s an extremely powerful tool and I’m loving the flexibility it affords!&lt;/p&gt;
&lt;p&gt;That’s it from me for now. I’ll come back to you all in another month!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Joining Buildkite, and sticking with Ruby</title>
    <link rel="alternate" href="https://timriley.info/writing/2022/06/14/joining-buildkite-and-sticking-with-ruby"/>
    <id>https://timriley.info/writing/2022/06/14/joining-buildkite-and-sticking-with-ruby</id>
    <published>2022-06-14T06:50:00+00:00</published>
    <updated>2022-06-14T06:50:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Last week I finished up at Culture Amp, and I’m excited to announce that I’ll be joining &lt;a href="https://buildkite.com/home"&gt;Buildkite&lt;/a&gt; as an engineer!&lt;/p&gt;
&lt;p&gt;My time at Culture Amp was special. It was my first role after a decade of running &lt;a href="https://www.icelab.com.au/"&gt;Icelab&lt;/a&gt; with Max and Michael. Culture Amp hired everyone at Icelab after we decided to close the business, providing both a smooth transition and new opportunities to a singular group. I built a great working relationship with my manager, I was trusted to do big things, and I relished the chance to work with and learn from a large group of engineers. I’m deeply thankful for all of this.&lt;/p&gt;
&lt;p&gt;Towards the end, I was serving as Culture Amp’s Director of Back End Engineering, and moving into engineering management. However, as any astute reader of this blog might attest, I am deeply motivated by &lt;em&gt;hands on&lt;/em&gt; programming work, and all the learning and collaboration opportunities that go with it. I realised it was not the time to draw that chapter to a close (it might never!), and through that consideration I connected with Buildkite.&lt;/p&gt;
&lt;p&gt;I’m excited to join Buildkite for many reasons! It’s a great Australian company with heart and personality. It &lt;a href="https://buildkite.com/about"&gt;brims with people&lt;/a&gt; I’ve long dreamt of working with. Developer tooling is an area close to my heart. And they’re growing a (majestic) Ruby app at the core of their tech. I can’t wait to dig in.&lt;/p&gt;
&lt;p&gt;For me, this is also an intentional decision to stick with Ruby. The work I’m doing in Ruby OSS right now might be one of the biggest “dents in the universe” I get to make. I want to see this effort through, to complete our vision for &lt;a href="https://hanamirb.org/"&gt;Hanami&lt;/a&gt; 2.0, then learn from how it’s adopted by our community.&lt;/p&gt;
&lt;p&gt;I have some time off between jobs, which I’ll use to give our Hanami work a real boost: I’ll be commiting nearly 6 weeks of full-time work to Hanami! Based on previous experience, this should see me get through what otherwise might have taken 6 &lt;em&gt;months&lt;/em&gt; of part-time effort. I’m hoping this will get us significantly closer to 2.0. I’ll likely start another tweet thread of my efforts, so &lt;a href="https://twitter.com/timriley"&gt;find me on Twitter&lt;/a&gt; if you’d like to follow along!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Open source status update, April 2022</title>
    <link rel="alternate" href="https://timriley.info/writing/2022/05/15/open-source-status-update-april-2022"/>
    <id>https://timriley.info/writing/2022/05/15/open-source-status-update-april-2022</id>
    <published>2022-05-15T12:20:00+00:00</published>
    <updated>2022-05-15T12:20:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;April was a pretty decent month for my OSS work! Got some things wrapped up, kept a few things moving, and opened up a promising thing for investigation. What are these things, you say? Let’s take a look!&lt;/p&gt;
&lt;h2&gt;Finished centralisation of Hanami action and view integrations&lt;/h2&gt;
&lt;p&gt;I &lt;a href="/writing/2022/04/10/open-source-status-update-march-2022/"&gt;wrote about the need&lt;/a&gt; to centralise these integrations last month, and in April, I &lt;a href="https://github.com/hanami/hanami/pull/1156"&gt;finally got the work done!&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This was a relief to get out. As a task, while necessary, it felt like drudge work – I’d been on it since early March, after all! I was also conscious that this was also blocking Luca’s work on helpers all the while.&lt;/p&gt;
&lt;p&gt;My prolonged work on this (in part, among other things like Easter holidays and other such Real Life matters) contributed to us missing April’s Hanami release. The good thing is that it’s done now, and I’m hopeful we can have this released via another Hanami alpha sometime very soon.&lt;/p&gt;
&lt;p&gt;In terms of the change to Hanami apps, the biggest change from this is that your apps should use a new superclass for actions and views:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;require &amp;quot;hanami/application/action&amp;quot;

module Main
  module Action
    # Used to inherit from Hanami::Action
    class Base &amp;lt; Hanami::Application::Action
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Aside from the benefit to us as maintainers of having this integration code kept together, this distinct superclass should also help make it clearer where to look when learning about how actions and views work within full Hanami apps.&lt;/p&gt;
&lt;h2&gt;Enabled proper access to full &lt;code&gt;locals&lt;/code&gt; in view templates&lt;/h2&gt;
&lt;p&gt;I wound up doing a little more work in actions and views this month. The first was a quickie to unblock some more of Luca’s helpers work: making access to the &lt;code&gt;locals&lt;/code&gt; hash within templates &lt;a href="https://github.com/hanami/view/pull/208"&gt;work like we always expected it would&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This turned out to be a fun one. For a bit of background, the context for every template rendering in hanami-view (i.e. what &lt;code&gt;self&lt;/code&gt; is for any given template) is an &lt;code&gt;Hanami::View::Scope&lt;/code&gt; instance. This instance contains the template’s locals, makes the full locals hash available as &lt;code&gt;#locals&lt;/code&gt; (and &lt;code&gt;#_locals&lt;/code&gt;, for various reasons), and uses &lt;code&gt;#method_missing&lt;/code&gt; to make also make each local directly available via its own name.&lt;/p&gt;
&lt;p&gt;Luca found, however, that calling &lt;code&gt;locals&lt;/code&gt; within the template didn’t work at all! After I took a look, it seemed that while &lt;code&gt;locals&lt;/code&gt; didn’t work, &lt;code&gt;self.locals&lt;/code&gt; or just plain &lt;code&gt;_locals&lt;/code&gt; &lt;em&gt;would&lt;/em&gt; work. Strange!&lt;/p&gt;
&lt;p&gt;Turns out, this all came down to implementation details in &lt;a href="https://github.com/rtomayko/tilt"&gt;Tilt&lt;/a&gt;, which we use as our low-level template renderer. The way Tilt works is that it will compile a template down into &lt;a href="https://github.com/rtomayko/tilt/blob/9b02c6f27e720abb0ec3e95856c6c14df24c9b15/lib/tilt/template.rb#L272-L276"&gt;a single Ruby method&lt;/a&gt; that receives a &lt;code&gt;locals&lt;/code&gt; param:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;def compile_template_method(local_keys, scope_class=nil)
  source, offset = precompiled(local_keys)
  local_code = local_extraction(local_keys)

  # &amp;lt;...snip...&amp;gt;

  method_source &amp;lt;&amp;lt; &amp;lt;&amp;lt;-RUBY
    TOPOBJECT.class_eval do
      def #{method_name}(locals)
        #{local_code}
  RUBY
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because of this, &lt;code&gt;locals&lt;/code&gt; is actually a &lt;em&gt;local variable&lt;/em&gt; in the context of that method execution, which will override any other methods also available on the scope object that Tilt turns into &lt;code&gt;self&lt;/code&gt; for the rendering.&lt;/p&gt;
&lt;p&gt;Here is how we were originally rendering with Tilt:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;tilt(path).render(scope, &amp;amp;block)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;My first instinct was simply to pass our locals hash as the (optional) second argument to Tilt’s &lt;code&gt;#render&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;tilt(path).render(scope, scope._locals)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But even that didn’t work! Because in generating that &lt;code&gt;local_code&lt;/code&gt; above, Tilt will actually take the &lt;code&gt;locals&lt;/code&gt; and &lt;a href="https://github.com/rtomayko/tilt/blob/9b02c6f27e720abb0ec3e95856c6c14df24c9b15/lib/tilt/template.rb#L251-L259"&gt;explode it out into individual variable assignments&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;def local_extraction(local_keys)
  local_keys.map do |k|
    if k.to_s =~ /\A[a-z_][a-zA-Z_0-9]*\z/
      &amp;quot;#{k} = locals[#{k.inspect}]&amp;quot;
    else
      raise &amp;quot;invalid locals key: #{k.inspect} (keys must be variable names)&amp;quot;
    end
  end.join(&amp;quot;\n&amp;quot;)
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But we don’t need this at all, since hanami-view’s scope object is already making those locals available individually, and we want to ensure access to those locals continues to run through the scope object.&lt;/p&gt;
&lt;p&gt;So the ultimate fix is to make locals of our locals. Yo dawg:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;tilt(path).render(scope, {locals: scope._locals}, &amp;amp;block)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This gives us our desired access to the &lt;code&gt;locals&lt;/code&gt; hash in templates (because that &lt;code&gt;locals&lt;/code&gt; key is itself turned into a solitary local variable), while preserving the rest of our existing scope-based functionality.&lt;/p&gt;
&lt;p&gt;It also shows me that I probably should’ve written an integration test back when I &lt;a href="https://github.com/dry-rb/dry-view/commit/c1bf77e14cb4dac3cc10a5b7e2abd276334024ea"&gt;introduced access to a scope’s locals back in January 2019&lt;/a&gt;. 😬&lt;/p&gt;
&lt;p&gt;Either way, I’m excited this came up and I could fix it, because it’s an encouraging sign of just how much of this view system we’ll be able to put to use in creating a streamlined and powerful view layer for our future Hanami users!&lt;/p&gt;
&lt;h2&gt;Merged a fix to stop unwanted view rendering of halted requests&lt;/h2&gt;
&lt;p&gt;Thanks to our extensive use of Hanami at Culture Amp, my friend and colleague Andrew &lt;a href="https://github.com/hanami/controller/pull/372"&gt;discovered and fixed a bug&lt;/a&gt; with our automatic rendering of views within actions, which I was happy to merge in.&lt;/p&gt;
&lt;h2&gt;Shipped some long awaited dry-configurable features&lt;/h2&gt;
&lt;p&gt;After keeping poor &lt;a href="https://github.com/ojab"&gt;ojab&lt;/a&gt; waiting way too long, I also merged a couple of nice enhancements he made to dry-configurable:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/dry-rb/dry-configurable/pull/105"&gt;Make &lt;code&gt;Config#finalize!&lt;/code&gt; finalize itself recurively and (optionally) allow freezing of setting values&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dry-rb/dry-configurable/pull/131"&gt;Accept hashes as possible setting values when using &lt;code&gt;#update&lt;/code&gt;&lt;/a&gt;, which &lt;a href="https://github.com/dry-rb/dry-configurable/pull/133"&gt;I followed up&lt;/a&gt; with support for anything implicitly convertible to a hash.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I then released these as &lt;a href="https://github.com/dry-rb/dry-configurable/releases/tag/v0.15.0"&gt;dry-configurable 0.15.0&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Started work on unifying Hanami slices and application&lt;/h2&gt;
&lt;p&gt;Last but definitely not least, I started work on one of the last big efforts we need in place before 2.0: making Hanami slices act as much as possible like complete, miniature Hanami applications. I’m going to talk about this a lot more in future posts, but for now, I can point you to a few PRs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/hanami/hanami/pull/1159"&gt;Introducing &lt;code&gt;Hanami::SliceName&lt;/code&gt;&lt;/a&gt; (a preliminary, minor refactoring to fix some slice and application name determination responsibilities that had somehow found their way into our configuration class).&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/hanami/hanami/pull/1160"&gt;A first, abandoned attempt&lt;/a&gt; at combining slices and applications, using a mixin for shared behaviour.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/hanami/hanami/pull/1162"&gt;A much more promising attempt&lt;/a&gt; using a composed slice object within the application class, which is currently the base of my further work in this area.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Apart from opening up some really interesting possibilities around making slices fully a portable, mountable abstraction (imagine bringing in slices from gems!), even for our shorter-term needs, this work looks valuable, since I think it should provide a pathway for having application-wide settings kept on the application class, while still allowing per-slice customisation of those settings in whichever slices require them.&lt;/p&gt;
&lt;p&gt;The overall slice structure is also something that’s barely changed since I put it in place way back in late 2019. Now it’s going to get the spit and polish it deserves. Hopefully I’ll be able to share more progress on this next month :) See you then!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Two years of open source status updates</title>
    <link rel="alternate" href="https://timriley.info/writing/2022/05/03/two-years-of-open-source-status-updates"/>
    <id>https://timriley.info/writing/2022/05/03/two-years-of-open-source-status-updates</id>
    <published>2022-05-02T14:15:00+00:00</published>
    <updated>2022-05-02T14:15:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Back in March of 2020, I decided to take up the habit of writing monthly status updates for my open source software development. 22 updates and 25k words later, I’m happy to be celebrating two years of status updates!&lt;/p&gt;
&lt;p&gt;As each month ticks around, I can find it hard to break away from cutting code and write my updates, but every time I get to publishing, I’m really happy to have captured my progress and thinking.&lt;/p&gt;
&lt;p&gt;After all, these posts now help remind me I managed to do all of the following over the last two years (and these were just the highlights!):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Renamed dry-view to hanami-view and kicked off view/application integration (&lt;a href="/writing/2020/03/27/open-source-status-update-march-2020"&gt;Mar 2020&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Received my first GitHub sponsor (&lt;a href="/writing/2020/04/30/open-source-status-update-april-2020"&gt;Apr 2020&lt;/a&gt;), thank you Benny Klotz (who still sponsors me today!)&lt;/li&gt;
&lt;li&gt;Shared my Hanami 2 application template (&lt;a href="/writing/2020/05/07/sharing-my-hanami-2-application-template"&gt;May 2020&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Achieved seamless view/action/application integration (&lt;a href="/writing/2020/06/01/open-source-status-update-may-2020"&gt;May 2020&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Brought class-level configuration to Hanami::Action (&lt;a href="/writing/2020/06/28/open-source-status-update-june-2020"&gt;Jun 2020&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Introduced application-level configuration for actions and views (&lt;a href="/writing/2020/08/03/open-source-status-update-july-2020"&gt;Jul 2020&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Added automatic inference for an action’s paired view, along with automatic rendering (&lt;a href="/writing/2020/08/03/open-source-status-update-july-2020"&gt;Jul 2020&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Introduced application integration for view context classes (&lt;a href="/writing/2020/08/03/open-source-status-update-july-2020"&gt;Jul 2020&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Supported multiple boot file dirs in dry-system, allowing user-replacement of standard bootable components in Hanami (&lt;a href="/writing/2020/08/31/open-source-status-update-august-2020"&gt;Aug 2020&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Rebuilt the Hanami Flash class (&lt;a href="/writing/2020/08/31/open-source-status-update-august-2020"&gt;Aug 2020&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Resumed restoring hanami-controller features through automatic enabling of CSRF protection (&lt;a href="/writing/2020/10/06/open-source-status-update-september-2020"&gt;Sep 2020&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Added automatic configuration to views (inflector, template, part namespace) (&lt;a href="/writing/2020/11/03/open-source-status-update-october-2020"&gt;Oct 2020&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Released a non-web Hanami application template (&lt;a href="/writing/2020/11/03/open-source-status-update-october-2020"&gt;Oct 2020&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Started the long road to Hanami/Zeitwerk integration with an autoloading loader for dry-system (&lt;a href="/writing/2020/12/07/open-source-status-update-november-2020"&gt;Nov 2020&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Introduced dedicated “component dir” abstraction to dry-system, along with major cleanups and consistency wins (&lt;a href="/writing/2021/01/06/open-source-status-update-december-2020"&gt;Dec 2020&lt;/a&gt;/&lt;a href="/writing/2021/02/01/open-source-status-update-january-2021"&gt;Jan 2021&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Added support for dry-system component dirs with mixed namespaces (&lt;a href="/writing/2021/03/09/open-source-status-update-february-2021"&gt;Feb&lt;/a&gt;/&lt;a href="/writing/2021/05/10/open-source-status-update-march-april-2021"&gt;Mar/Apr 2021&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Released dry-system with all these changes, along with Hanami with working Zeitwerk integration (&lt;a href="/writing/2021/05/10/open-source-status-update-march-april-2021"&gt;Mar/Apr 2021&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Ported Hanami’s app configuration to dry-configurable (&lt;a href="/writing/2021/06/08/open-source-status-update-may-2021"&gt;May 2021&lt;/a&gt;),&lt;/li&gt;
&lt;li&gt;Laid the way for dry-configurable 1.0 with some API changes (&lt;a href="/writing/2021/06/08/open-source-status-update-may-2021"&gt;May&lt;/a&gt;/&lt;a href="/writing/2021/09/06/open-source-status-update-july-august-2021"&gt;Jul 2021&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Returned to dry-system and added configurable constant namespaces (&lt;a href="/writing/2021/07/11/open-source-status-update-june-2021"&gt;Jun&lt;/a&gt;/&lt;a href="/writing/2021/09/06/open-source-status-update-july-august-2021"&gt;Jul/Aug&lt;/a&gt;/&lt;a href="/writing/2021/10/11/open-source-status-update-september-2021"&gt;Sep&lt;/a&gt;/&lt;a href="/writing/2021/11/15/open-source-status-update-october-2021"&gt;Oct 2021&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Introduced compact slice source dirs to Hanami, using dry-systems constant namespaces (&lt;a href="/writing/2021/10/11/open-source-status-update-september-2021"&gt;Sep&lt;/a&gt;/&lt;a href="/writing/2021/11/15/open-source-status-update-october-2021"&gt;Oct 2021&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Added fully configurable source dirs to Hanami (&lt;a href="/writing/2021/12/13/open-source-status-update-november-2021"&gt;Nov&lt;/a&gt;/&lt;a href="/writing/2022/02/14/open-source-status-update-december-2021-january-2022"&gt;Dec 2021&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Shipped a huge amount of dry-system improvements over two weeks of dedicated OSS time in &lt;a href="/writing/2022/02/14/open-source-status-update-december-2021-january-2022"&gt;Jan 2022&lt;/a&gt;, including the overhaul of bootable components as part of their rename to providers, as well as partial container imports and exports, plus much more&lt;/li&gt;
&lt;li&gt;Introduced concrete slice classes and other slice registration improvements to Hanami (&lt;a href="/writing/2022/03/19/open-source-status-update-february-2022"&gt;Feb 2022&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Refactored and relocated action and view integration into the hanami gem itself, and introduced &lt;code&gt;Hanami::SliceConfigurable&lt;/code&gt; to make it possible for similar components to integrate (&lt;a href="/writing/2022/04/10/open-source-status-update-march-2022"&gt;Mar 2022&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is a lot! To add some extra colour here, a big difference betwen now and pre-2020 is that I’ve been working on OSS exclusively in my personal time (nights and weekends), and I’ve also been slugging away at a single large goal (Hanami 2.0, if you hadn’t heard!), and the combination of this can make the whole thing feel a little thankless. These monthly updates are timely punctuation and a valuable reminder that I am moving forward.&lt;/p&gt;
&lt;p&gt;They also capture a lot of in-the-moment thinking that’d otherwise be lost to the sands of time. What I’ve grown to realise with my OSS work is that it’s as much about the &lt;em&gt;process&lt;/em&gt; as anything else. For community-driven projects like dry-rb and Hanami, the work will be done when it’s done, and there’s not particularly much we can do to hurry it. However, what we should never forget is to make that work-in-progress readily accessible to our community, to bring people along for the ride, and to share whatever lessons we discover along the way. The passing of each month is a wonderful opportunity for me to do this 😀&lt;/p&gt;
&lt;p&gt;Finally, a huge thank you from me to anyone who reads these updates. Hearing from folks and knowing there are people out there following along is a huge encouragement to me.&lt;/p&gt;
&lt;p&gt;So, let’s keep this going. I’m looking forward to another year of updates, and—&lt;em&gt;checks calendar&lt;/em&gt;–writing April’s post in the next week or so!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Salubrious Ruby: Don’t mutate what you don’t own</title>
    <link rel="alternate" href="https://timriley.info/writing/2022/04/28/salubrious-ruby-dont-mutate-what-you-dont-own"/>
    <id>https://timriley.info/writing/2022/04/28/salubrious-ruby-dont-mutate-what-you-dont-own</id>
    <published>2022-04-28T13:00:00+00:00</published>
    <updated>2022-04-28T13:00:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;When we’re writing a method in Ruby and receiving objects as arguments, a helpful principle to follow is “don’t mutate what you don’t own.”&lt;/p&gt;
&lt;p&gt;Why is this? Those arguments come from places that we as the method authors can’t know, and a well-behaved method shouldn’t alter the external environment unexpectedly.&lt;/p&gt;
&lt;p&gt;Consider the following method, which takes an array of numbers and appends a new, incremented number:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;def append_number(arr)
  last_number = arr.last || 0
  arr &amp;lt;&amp;lt; last_num + 1
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we pass in an array, we’ll get a new number appended in the returned array:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;my_arr = [1, 2]
my_new_arr = append_number(my_arr) # =&amp;gt; [1, 2, 3]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But we’ll also quickly discover that this has been achieved by mutating our original array:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;my_arr = [1, 2]
my_new_arr = append_number(arr) # =&amp;gt; [1, 2, 3]
my_arr # =&amp;gt; [1, 2, 3]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can confirm by an object equality check that this is still the one same array:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;my_new_arr.eql?(my_arr) # =&amp;gt; true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This behavior is courtesy of Ruby’s &lt;code&gt;Array#&amp;lt;&amp;lt;&lt;/code&gt; method (aka &lt;code&gt;#append&lt;/code&gt; or &lt;code&gt;#push&lt;/code&gt;), which appends the given object to the receiver (that is, &lt;code&gt;self&lt;/code&gt;), before then returning that same self. This kind of self-mutating behaviour is common across both the &lt;code&gt;Array&lt;/code&gt; and &lt;code&gt;Hash&lt;/code&gt; classes, and while it can provide some conveniences in local use (such as a chain of &lt;code&gt;#&amp;lt;&amp;lt;&lt;/code&gt; calls to append multiple items to the same array), it can lead to surprising results when that array or hash comes from anywhere &lt;em&gt;non&lt;/em&gt;-local.&lt;/p&gt;
&lt;p&gt;Imagine our example above is part of a much larger application. In this case, &lt;code&gt;my_arr&lt;/code&gt; will most likely come from somewhere far removed and well outside the purview of &lt;code&gt;append_number&lt;/code&gt; or whatever class contains it. As the authors of &lt;code&gt;append_number&lt;/code&gt;, we have no idea how that original array might otherwise be used! For this reason, the courteous approach to take is not to mutate the array, but instead create and return a new copy:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;def append_number(arr)
  last_number = arr.last || 0

  # There are many ways we can achieve the copy; here's just one
  arr + [last_number]
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way, the caller of our method can trust their original values to go unchanged, which is what they would likely expect, especially if our method doesn’t give any hint that it will mutate.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;my_arr = [1, 2]
my_new_arr = append_number(arr) # =&amp;gt; [1, 2, 3]
my_arr # =&amp;gt; [1, 2]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a very simple example, but the same princple applies for all kinds of mutable objects passed to your methods.&lt;/p&gt;
&lt;p&gt;A more telling story here comes from earlier days of Ruby, around how we handled options hashes passed to methods. We used to do things like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;def my_method(options = {})
  some_opt = options.delete(:some_opt)
  # Do something with some_opt...
end

my_method(some_opt: &amp;quot;some value&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using trailing options hashes like this was how we provided “keyword arguments” before Ruby had them as a language feature. Now the trouble with the method above is that we’re mutating that &lt;code&gt;options&lt;/code&gt; hash by deleting the &lt;code&gt;:some_opt&lt;/code&gt; key. So if the user of our method had code like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;common_options = {some_opt: &amp;quot;some value&amp;quot;}

first_result = my_method(common_options)
second_result = my_method(common_options)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We’d find ourselves in trouble by the time we call &lt;code&gt;my_method&lt;/code&gt; the second time, because at that point the &lt;code&gt;common_options&lt;/code&gt; hash will no longer have &lt;code&gt;some_opt:&lt;/code&gt;, since the first invocation of &lt;code&gt;my_method&lt;/code&gt; deleted it — oops!&lt;/p&gt;
&lt;p&gt;This is a great illustration of why modern Ruby’s keyword arguments work the way they do. When we accept a splatted keyword hash argument like &lt;code&gt;**options&lt;/code&gt;, Ruby ensures it comes into the method as a new hash, which means that operations like &lt;code&gt;options.delete(:some_opt)&lt;/code&gt; do in fact become local in scope, and therefore safe to use.&lt;/p&gt;
&lt;p&gt;So now that we’ve covered both arrays and hashes now as Ruby’s most common “container” objects, what about the other kinds of application-specific structures that we might encounter in real world codebases? Objects representing domain models of various kinds, an instance of an &lt;code&gt;ActiveRecord::Base&lt;/code&gt; subclass, even? Even in those cases, this principle still holds true. Our code is easier to understand and test when we can reduce the number of dimenstions to its behaviour, and mutating passed-in objects is a big factor in this, especially if you think about methods calling other methods and so on. There are ways we can design our applications to make this a natural approach to take, even for rich domain objects, but that is a topic for another day!&lt;/p&gt;
&lt;p&gt;Until then, hopefully this walkthrough here can serve as a reminder to keep our methods courteous, to respect mutable values provided from outside, and wherever possible, leave them undisturbed and unmutated. &lt;a href="/writing/2022/03/24/salubrious-ruby/"&gt;Salubrious!&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bonus content!&lt;/strong&gt; In preparing this post, I thought about whether it might be helpful to note that Ruby is a “pass by reference” language, since that’s the key underlying behavior that can result in these accidental mutations. However, my intuition here was actually back to front! Thanks to this &lt;a href="https://stackoverflow.com/a/22827949"&gt;wonderful stackoverflow answer&lt;/a&gt;, I was reminded that Ruby is in fact a “pass by value” language, but that all values are references.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Open source status update, March 2022</title>
    <link rel="alternate" href="https://timriley.info/writing/2022/04/10/open-source-status-update-march-2022"/>
    <id>https://timriley.info/writing/2022/04/10/open-source-status-update-march-2022</id>
    <published>2022-04-10T12:30:00+00:00</published>
    <updated>2022-04-10T12:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;My OSS work in March was a bit of a grind, but I made progress nonetheless. I worked mostly on relocating and refactoring the Hanami action and view integration code.&lt;/p&gt;
&lt;p&gt;For some context, it was &lt;a href="/writing/2020/06/01/open-source-status-update-may-2020/"&gt;back in May 2020&lt;/a&gt; that I first write the action/view integration code for Hanami 2.0. Back then, there were a couple of key motivators:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reduce boilerplate to an absolute minimum, to the extent that simply inheriting from &lt;code&gt;Hanami::View&lt;/code&gt; within a slice would give you a view class fully integrated with the Hanami application.&lt;/li&gt;
&lt;li&gt;Locate the integration code in the non-core gems themselves (i.e. in the hanami-controller and hanami-view gems, rather than hanami), to help set an example for how alternative implementations may also integrate with the framework.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since then, we’ve learnt a few things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;As we’ve gone about refining the core framework, we’ve wound up having to synchronize changes from time to time across the hanami, hanami-controller, and hanami-view gems all at once.&lt;/li&gt;
&lt;li&gt;Other Hanami contributors have noted that the original integration approach was a little too “magical,” and didn’t allow users any path to opt out of the integration code.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once I finished my work on the concrete slice classes &lt;a href="/writing/2022/03/19/open-source-status-update-february-2022/"&gt;last month&lt;/a&gt;, I decided that now was the time to address these concerns, to bring the action and view class integrations back into the hanami gem, and to take a different approach to activating the integration code.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://github.com/hanami/hanami/pull/1156"&gt;work in progress is over in this PR&lt;/a&gt;, and thankfully, it’s nearly done!&lt;/p&gt;
&lt;p&gt;The impact within Hanami 2 applications will be fairly minimal: the biggest change is that your base action and view classes will now inherit from &lt;em&gt;application&lt;/em&gt; variants:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;# slices/main/lib/action/base.rb

require &amp;quot;hanami/application/action&amp;quot;

module Main
  module Action
    class Base &amp;lt; Hanami::Application::Action
      # Previously, this inherited from Hanami::Action
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By using this explicit application superclass for actions and views, we hopefully make it easier for our users to understand and distinguish between the integrated and standalone variants of these classes. This distinct superclass should also provide us a clear place to hang extra API documentation relating to the integrated behavior of actions and views.&lt;/p&gt;
&lt;p&gt;More importantly for the overall experience, &lt;code&gt;Hanami::Application::Action&lt;/code&gt; and &lt;code&gt;Hanami::Application::View&lt;/code&gt; are both now kept within the core hanami gem. While the framework heads into this final stretch of work before 2.0 final, this will allow us to keep together the aspects of the integration that tend to change together, giving us our best chance at providing a tested, reliable, streamlined actions and views experience.&lt;/p&gt;
&lt;p&gt;This is a pragmatic move above all else — we’re a team with little time, so the more we can do to give ourselves confidence in this integrated experience working properly, like having all the code and tests together in one place, the quicker we should be able to get to the 2.0 release. Longer term, we’ll want to provide a first-class integration story for third party components, and I believe we can lead the way in how we deliver that via our actions and views, but that’s now firmly a post-2.0 concern in my mind.&lt;/p&gt;
&lt;p&gt;In the meantime, I did take this opportunity to rethink and provide some better hooks for classes like &lt;code&gt;Hanami::Application::View&lt;/code&gt; to integrate with the rest of the framework, chiefly via a new &lt;code&gt;Hanami::SliceConfigurable&lt;/code&gt; module. You can see how it works by checking out the code for &lt;code&gt;Hanami::Application::View&lt;/code&gt; itself:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;# frozen_string_literal: true

require &amp;quot;hanami/view&amp;quot;
require_relative &amp;quot;../slice_configurable&amp;quot;
require_relative &amp;quot;view/slice_configured_view&amp;quot;

module Hanami
  class Application
    class View &amp;lt; Hanami::View
      extend Hanami::SliceConfigurable

      def self.configure_for_slice(slice)
        extend SliceConfiguredView.new(slice)
      end
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Any class that extends &lt;code&gt;Hanami::SliceConfigurable&lt;/code&gt; will have its own &lt;code&gt;.configure_for_slice(slice)&lt;/code&gt; method called whenever it is sublcassed within a module namespace that happens to match the namespace managed by an Hanami slice. Using the &lt;code&gt;slice&lt;/code&gt; object passed to that hook, that class can then read any slice- or application-level config to set itself up to integrate with the application.&lt;/p&gt;
&lt;p&gt;In the example above, we extend a slice-specific instance of &lt;code&gt;SliceConfiguredView&lt;/code&gt;, which will copy across application level view configs, as well configure the view’s part namespaces to match the slice’s namespace. The reason we build a  module instance here (this module builder pattern is &lt;a href="https://dejimata.com/2017/5/20/the-ruby-module-builder-pattern"&gt;a whole technique&lt;/a&gt; that I’ll gladly go into one day, but it’s a little out of scope for these monthly updates) is so that we don’t have to keep any trace of the &lt;code&gt;slice&lt;/code&gt; as state on the class after we’re done using it for configuration, making it so the resulting class is as standalone as possible, and not offering any way for its users to inadvertently couple themselves to the whole slice instance.&lt;/p&gt;
&lt;p&gt;Overall, this change is feeling quite settled now. All the code has been moved in and refactored, and all that’s left  is a final polishing pass before merge, which I hope I can get done this week! A huge thank you to &lt;a href="https://github.com/cllns"&gt;Sean Collins&lt;/a&gt; for his &lt;a href="https://github.com/hanami/controller/pull/365"&gt;original work&lt;/a&gt; in proposing an adjustment to our action integration code. It was Sean’s feedback and exploratory work here that got me off the fence here, and made it so easy to get started with these changes!&lt;/p&gt;
&lt;p&gt;That’s it for me for now. See you all again next month, hopefully with some more continued core framework polishing.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Let the shape of the code reflect its flow</title>
    <link rel="alternate" href="https://timriley.info/writing/2022/03/24/let-the-shape-of-the-code-reflect-its-flow"/>
    <id>https://timriley.info/writing/2022/03/24/let-the-shape-of-the-code-reflect-its-flow</id>
    <published>2022-03-24T11:50:00+00:00</published>
    <updated>2022-03-24T11:50:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Say you’re building a system for handling messages off some kind of queue. For each message, you need to run a series of steps: first to decode the message, next to wrap it in some common structure, and finally, to process the message based some logic provided by the users of your system.&lt;/p&gt;
&lt;p&gt;Let’s imagine the queue subscription as provided: we’ll have a &lt;code&gt;subscriber&lt;/code&gt; object that yields a &lt;code&gt;message&lt;/code&gt; to us via a &lt;code&gt;#handle&lt;/code&gt; method:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;subscriber.handle do |message|
  # Here's where we need to hook our logic
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For each of processing steps, let’s also imagine we have corresponding private methods in our class:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;#decode(message)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;#build_event(decoded_message)&lt;/code&gt; — with an “event” being that common structure I mentioned above&lt;/li&gt;
&lt;li&gt;&lt;code&gt;#process(event)&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;With these set up, we could wire them all together in our handler block like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;subscriber.handle do |message|
  process(build_event(decode(message)))
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is hard to grok, however. There’s a lot going on in that one line, and most critically, you have to read it inside out in order to understand its flow: start with &lt;code&gt;decode&lt;/code&gt;, then work backwards to &lt;code&gt;build_event&lt;/code&gt; and then &lt;code&gt;process&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Instead, we should strive to &lt;strong&gt;let the shape of our code reflect its flow.&lt;/strong&gt; We want to make it easy for the reader of the code to quickly understand the flow of logic even with just a glance.&lt;/p&gt;
&lt;p&gt;One step in this direction could be to use intermediate variables to hold the results of each step:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;subscriber.handle do |message|
  decoded_message = decode(message)
  event = build_event(decoded_message)
  process(event)
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This isn’t bad, but the variable names at the beginning of the line add extra noise, and they push back the most meaningful part of each step — the private method names — into a less prominent location.&lt;/p&gt;
&lt;p&gt;What I would recommend here is that we take advantave of Ruby’s &lt;code&gt;Object#then&lt;/code&gt; to turn this into something that actually &lt;em&gt;looks&lt;/em&gt; like a pipeline, since that’s the flow that we’re actually creating via these methods: the steps run in sequence, and the output of one step feeds into the next.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;subscriber.handle do |message|
  message
    .then { |message| decode(message) }
    .then { |decoded| build_event(decoded) }
    .then { |event| process(event) }
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This makes it much clearer that this is a pipeline of three distinct steps, with &lt;code&gt;message&lt;/code&gt; as its starting point. Through the shape of those blocks, and the pipe separators distinguishing the block argument from the block body, it also brings greater prominence to the name of the method that we’re calling for each step.&lt;/p&gt;
&lt;p&gt;Most importantly, we’ve made this code much more &lt;em&gt;scannable&lt;/em&gt;. We’re giving the eye of the reader hooks to latch onto, via the repeated “thens” stacked on top of each other, in addition to their corresponding blocks. The shape of the code embodies its flow, and in doing so, we’ve created a table-of-contents-like structure that both summarises the behaviour, and can serve as a jumping off point for further exploration if required.&lt;/p&gt;
&lt;p&gt;To further reduce noise here, we could try Ruby’s new numbered implicit block arguments:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;subscriber.handle do |message|
  message
    .then { decode(_1) }
    .then { build_event(_1) }
    .then { process(_1) }
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, I’d consider this a step too far, since it takes away what is otherwise a helpful signal, with the block argument name previously serving as a hint to the type of value that we’re dealing with at each point in the pipeline.&lt;/p&gt;
&lt;p&gt;By taking the time to consider the flow of our logic, and finding a way for the shape of code to embody that flow, we’ve made our code easier to understand, easier to maintain, and — why not say it? — &lt;em&gt;truer to itself.&lt;/em&gt; This is a method I’d walk away from feeling very satisfied having written. Salubrious!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Salubrious Ruby</title>
    <link rel="alternate" href="https://timriley.info/writing/2022/03/24/salubrious-ruby"/>
    <id>https://timriley.info/writing/2022/03/24/salubrious-ruby</id>
    <published>2022-03-24T09:40:00+00:00</published>
    <updated>2022-03-24T09:40:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;I’ve been writing Ruby for over 21 years now — over half my life! — and in this time, I’ve learnt so much about both the language as well as software development in general. You might have noticed some of the products of that learning in my open source contributions, chiefly the &lt;a href="https://dry-rb.org"&gt;dry-rb&lt;/a&gt; and &lt;a href="https://hanamirb.org"&gt;Hanami&lt;/a&gt; projects.&lt;/p&gt;
&lt;p&gt;I honestly feel blessed to have found a language that could accompany me on a learning journey over so many years.&lt;/p&gt;
&lt;p&gt;For much of my time with Ruby, I worked with a tight-knit group of people: the developers at &lt;a href="https://www.icelab.com.au"&gt;Icelab&lt;/a&gt;, and my OSS collaborators. Over the last few years, however, I’ve had the good fortune of working with a much larger group of Rubyists at &lt;a href="https://www.cultureamp.com"&gt;Culture Amp&lt;/a&gt;, and have had many opportunities to share what goes into making a good Ruby application.&lt;/p&gt;
&lt;p&gt;There’s little I enjoy more than talking quality code, but lately I’ve been so focused on &lt;em&gt;shipping&lt;/em&gt; said code, that I haven’t taken much time to step back and acknowledge what I’ve learnt along the way.&lt;/p&gt;
&lt;p&gt;With this announcement, I’m hoping to change this, and create a little accountability for myself along the way! So with no further ado, let me announce a new series here on this little blog, which I’m calling &lt;em&gt;Salubrious Ruby.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I hope you’ll join me while I share the things both big and small that go into making a healthy, wholesome Ruby application!&lt;/p&gt;
&lt;p&gt;As I go, I’ll keep pointers to all the articles below:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/writing/2022/03/24/let-the-shape-of-the-code-reflect-its-flow"&gt;Let the shape of the code reflect its flow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/writing/2022/04/28/salubrious-ruby-dont-mutate-what-you-dont-own"&gt;Don’t mutate what you don’t own&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Open source status update, 🇺🇦 February 2022</title>
    <link rel="alternate" href="https://timriley.info/writing/2022/03/19/open-source-status-update-february-2022"/>
    <id>https://timriley.info/writing/2022/03/19/open-source-status-update-february-2022</id>
    <published>2022-03-19T12:55:00+00:00</published>
    <updated>2022-03-19T12:55:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;After the &lt;a href="/writing/2022/02/14/open-source-status-update-december-2021-january-2022/"&gt;huge month of January&lt;/a&gt;, February was naturally a little quieter, but I did help get a couple of nice things in place.&lt;/p&gt;
&lt;h2&gt;Stand with Ukraine 💙💛&lt;/h2&gt;
&lt;p&gt;This is my first monthly OSS update since Russia began its brutal, senseless war on Ukraine. Though I was able to ship some work this month, there are millions of people whose lives and homeland have been torn to pieces. For a perspective from one of our Ruby friends in Ukraine, read &lt;a href="https://zverok.space/blog/2022-03-03-WAR.html"&gt;this piece&lt;/a&gt; and &lt;a href="https://zverok.space/blog/2022-03-15-STILL-WAR.html"&gt;this update&lt;/a&gt; from Victor Shepelev, aka &lt;a href="http://twitter.com/zverok/"&gt;zverok&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let’s all continue to &lt;a href="https://ua-aid-centers.com/funds-and-foundations"&gt;support Ukraine&lt;/a&gt;, and help the international community continue doing the same.&lt;/p&gt;
&lt;h2&gt;Concrete slice classes&lt;/h2&gt;
&lt;p&gt;For this month I focused mostly on getting &lt;a href="https://github.com/hanami/hanami/pull/1150"&gt;concrete slice classes in place&lt;/a&gt; for Hanami applications. As I described in the &lt;a href="https://hanamirb.org/blog/2022/03/08/announcing-hanami-200alpha7/"&gt;alpha7 release announcement&lt;/a&gt;, concrete slice classes give you a nice place for any slice-specific configuration.&lt;/p&gt;
&lt;p&gt;They live in &lt;code&gt;config/slices/&lt;/code&gt;, and look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# config/slices/main.rb:

module Main
  class Slice &amp;lt; Hanami::Slice
    # Slice config goes here...
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As of this moment, you can use the slice classes to configure your slice imports:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;# config/slices/main.rb:

module Main
  class Slice &amp;lt; Hanami::Slice
    # Import all exported components from &amp;quot;search&amp;quot; slice
    import from: :search
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As well as particular components to export:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;# config/slices/search.rb:

module Search
  class Slice &amp;lt; Hanami::Slice
    # Export the &amp;quot;index_entity&amp;quot; component only
    export [&amp;quot;index_entity&amp;quot;]
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Later on, I’ll look to expand this config and find a way for a subset of application-level settings to be configured on individual slices, too. I imagine that configuring &lt;code&gt;source_dirs&lt;/code&gt; on a per-slice basis may be useful, for example, if you want particular source dirs to be used on one slice, but not others.&lt;/p&gt;
&lt;p&gt;The other thing you can currently do on these slice classes is configure their container instance:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;# config/slices/search.rb:

module Search
  class Slice &amp;lt; Hanami::Slice
    prepare_container do |container|
      # `container` (a Dry::System::Container subclass) is available here with
      # slice-specific configuration already applied
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is an advanced feature and not something we expect typical Hanami users to need. However, I wanted this in place so I could continue providing “escape valves” across the framework, to allow Hanami users to reach below the framework layer and manually tweak the lower-level parts without having to eject entirely from the framework and all the other niceties it provides.&lt;/p&gt;
&lt;h2&gt;Slice registration refactors&lt;/h2&gt;
&lt;p&gt;As part of implementing the concrete slice classes, I was able to make some &lt;a href="https://github.com/hanami/hanami/pull/1150"&gt;quite nice refactors&lt;/a&gt; around the way we handle slices within the Hanami application:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;All responsibility for slice loading and registration has now away from &lt;code&gt;Application&lt;/code&gt; (which is already doing a lot of other work!) into a new &lt;a href="https://github.com/hanami/hanami/blob/32c5bd4bafe938e9c14ba2611f10670f7fefa98b/lib/hanami/application/slice_registrar.rb"&gt;&lt;code&gt;SliceRegistrar&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;.prepare&lt;/code&gt; methods inside both &lt;a href="https://github.com/hanami/hanami/blob/32c5bd4bafe938e9c14ba2611f10670f7fefa98b/lib/hanami/application.rb#L57-L68"&gt;&lt;code&gt;Application&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/hanami/hanami/blob/32c5bd4bafe938e9c14ba2611f10670f7fefa98b/lib/hanami/slice.rb#L47-L59"&gt;&lt;code&gt;Slice&lt;/code&gt;&lt;/a&gt; are now roughly identical in structure, with their many constituent setup steps extracted into their own well-named methods (&lt;a href="https://github.com/hanami/hanami/blob/32c5bd4bafe938e9c14ba2611f10670f7fefa98b/lib/hanami/slice.rb#L155-L164"&gt;for example&lt;/a&gt;). This will make this phase of the boot process much easier to understand and maintain, and I also think it hints at a future in which we have an &lt;em&gt;extensible&lt;/em&gt; boot process, wherein other gems may register their own steps as part of the overall sequence that is run when you &lt;code&gt;.prepare&lt;/code&gt; and application or slice.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The single-file-app dream lives on&lt;/h2&gt;
&lt;p&gt;One nice outcome of the concrete slice work is the fact that these classes are &lt;em&gt;not actually required&lt;/em&gt; in your Hanami application for it to boot and do its job. It will still look for directories under &lt;code&gt;slices/&lt;/code&gt; and dynamically create those classes if they don’t already exist in &lt;code&gt;config/slices/&lt;/code&gt;. What’s even better, however, is that I made this behaviour more easily user-invokable via a public &lt;code&gt;Application.register_slice&lt;/code&gt; method. This means you can choose to explicitly register a slice, in cases where the framework may not otherwise detect it:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;module MyApp
  class Application &amp;lt; Hanami::Application
    # That's all! This will define a `Main::Slice` class for you.
    register_slice :main
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But that’s not all! Since these slice classes will now be the place for slice-specific configuration, we may need to provide this when explicitly registering a slice too. For this, you can provide a block that is then evaluated within the context of the generated slice class:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;module MyApp
  # Defines `Main::Slice` class and instance_evals the given block
  class Application &amp;lt; Hanami::Application
    register_slice(:main) do
      import from: :search
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And lastly, you can also provide your own concrete slice class at this point, too:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;module MyApp
  class Application &amp;lt; Hanami::Application
  end
end

module Main
  class Slice &amp;lt; Hanami::Slice
  end
end

MyApp::Application.register_slice :main, Main::Slice
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One of the guiding forces behind this level of flexibility (apart from it just feeling like the Right Thing To Do) is that I want to keep open the option for single-file Hanami applications. While the framework will always be designed primarily for fully-fledged applications, with their components spread across many source files, sometimes a single file app is still the right tool for the job, and I want Hanami to work here too. As I put the final polish on the core application and slice structures over the coming couple of months, I’ll be keeping this firmly in mind, and will look to share a nice example of this in a future blog post :)&lt;/p&gt;
&lt;h2&gt;Removed some unusued (and unlikely to be used) flexibility&lt;/h2&gt;
&lt;p&gt;While I like to try and keep the Hanami framework flexible — and we’ve already looked at several approaches to this just above — I’m also conscious of the cost of this flexibility, and how in certain cases, those costs are just not worth it. One example of this was the &lt;a href="https://github.com/dry-rb/dry-system/pull/206"&gt;removal of the configurable key separator in dry-system&lt;/a&gt; earlier this year. In this case, keeping the key separator configurable meant not only significant internal complexity, but also the fact that we could never write documentation that we could be fully confident would work for all people. To boot, we hadn’t heard of a single user wanting to change that separating over the whole of dry-system’s existence.&lt;/p&gt;
&lt;p&gt;As part of &lt;a href="https://github.com/hanami/hanami/pull/1150"&gt;my work this month&lt;/a&gt;, I removed a couple of similar settings from Hanami:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I removed the &lt;code&gt;config.slices_namespace&lt;/code&gt; setting, which existed in theory to allow slices to also live inside the application’s own module namespace if a user desired (e.g. &lt;code&gt;MyApp::Main&lt;/code&gt; instead of just &lt;code&gt;::Main&lt;/code&gt;). In reality, I think that extra level of nesting will be too invoncenient for users to want. More importantly, I think that having our slices always mapping to single top-level modules will be important for our documentation (and generators, and many other things I’m sure) to be clearer.&lt;/li&gt;
&lt;li&gt;I also remove the &lt;code&gt;config.slices_dir&lt;/code&gt; setting, for much the same reasons. Hanami will be far easier to document and support if slices are always loaded from &lt;code&gt;slices/&lt;/code&gt; and nowhere else.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Made &lt;code&gt;Application.shutdown&lt;/code&gt; complete&lt;/h2&gt;
&lt;p&gt;Did you know that you can both boot &lt;em&gt;and&lt;/em&gt; shut down an Hanami application? The latter will call &lt;code&gt;stop&lt;/code&gt; on any registered providers, which can be useful if you need to actively disconnect from any external resources, such as database connections.&lt;/p&gt;
&lt;p&gt;You can shutdown an Hanami application via &lt;code&gt;Application.shutdown&lt;/code&gt;, but the implementation was only partially complete. As of &lt;a href="https://github.com/hanami/hanami/pull/1154"&gt;this PR&lt;/a&gt;, shutdown now works for both slices (and their providers) and when shutting down an application, it will shutdown all the slices in turn.&lt;/p&gt;
&lt;h2&gt;Simplified configuration by permitting &lt;code&gt;env&lt;/code&gt; to be provided just once&lt;/h2&gt;
&lt;p&gt;Another little one: the application configuration depends on knowing the current Hanami env (i.e. &lt;code&gt;Hanami.env&lt;/code&gt;) in several ways, such as knowing when to set env-specific defaults, or apply user-provided env-specific config. Until now, it’s been theoretically possible to re-set the env even after the configuration has loaded, which makes the env-specific behaviour much harder to reason about. With &lt;a href="https://github.com/hanami/hanami/pull/1153"&gt;this change&lt;/a&gt;, the env is now set just once (based on the &lt;code&gt;HANAMI_ENV&lt;/code&gt; env var) when the configuration is initialized, allowing us to much more confidently address the env across all facets of the configuration behavior.&lt;/p&gt;
&lt;p&gt;(This and the &lt;code&gt;shutdown&lt;/code&gt; work together in a single evening session. For many reasons, I was feeling down, and this was a nice little bit of therapy for me. So much of what I’ve been doing here lately spans multiple days and weeks, and having a task I could complete in an hour was a refreshing change.)&lt;/p&gt;
&lt;h2&gt;Worked to define a consistent action and view class structure&lt;/h2&gt;
&lt;p&gt;This effort was mostly driven by Luca, but we worked together to arrive at a consistent structure for the action and view classes to be generated in Hanami applications.&lt;/p&gt;
&lt;p&gt;For actions, for example, the following classes will be generated:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A single application-level base class, e.g. &lt;code&gt;MyApp::Action::Base&lt;/code&gt; in &lt;code&gt;lib/my_app/action/base.rb&lt;/code&gt;. This is where you would put any logic or configuration that should apply to every action across all slices within the application.&lt;/li&gt;
&lt;li&gt;A base class for each slice, e.g. &lt;code&gt;Main::Action::Base&lt;/code&gt; in &lt;code&gt;slices/main/lib/action/base.rb&lt;/code&gt;, inheriting from the application-level base class. This is where you would put anything that should apply to all the actions only in the particular slice.&lt;/li&gt;
&lt;li&gt;Every individual action class would then go into the &lt;code&gt;actions/&lt;/code&gt; directory within the slice, e.g. &lt;code&gt;Main::Actions::Articles::Index&lt;/code&gt; in &lt;code&gt;slices/main/actions/articles/index.rb&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For views, the structure is much the same, with &lt;code&gt;MyApp::View::Base&lt;/code&gt; and &lt;code&gt;Main::View::Base&lt;/code&gt; classes located within an identical structure.&lt;/p&gt;
&lt;p&gt;The rationale for this structure is that it provides a clear place for any code to live that serves as supporting “infrastructure” for your application’s actions and views: it can go right alongside those &lt;code&gt;Base&lt;/code&gt; classes, in their own directories, clearly separated from the rest of your concrete actions and views.&lt;/p&gt;
&lt;p&gt;This isn’t an imagined requirement: in a standard Hanami 2 application, we’ll already be generating additional classes for the view layer, such as a view context class (e.g. &lt;code&gt;Main::View::Context&lt;/code&gt;) and a base view part class (e.g. &lt;code&gt;Main::View::Part&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;This structure is intended to serve as a hint that your own application-level action and view behavior can and should be composed of their own single-responsibility classes as much as possible. This is one of the many ways in which Hanami as a framework can help our users make better design choices, and build this up as a muscle that they can apply to all facets of their application.&lt;/p&gt;
&lt;h2&gt;Released alpha7&lt;/h2&gt;
&lt;p&gt;Last but not least, I cut the release of Hanami 2.0.0.alpha7 and &lt;a href="https://hanamirb.org/blog/2022/03/08/announcing-hanami-200alpha7/"&gt;shared it with the world&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;What’s next?&lt;/h2&gt;
&lt;p&gt;My next focus has been on a mostly internal refactor to move a bunch of framework integration code from hanami-controller and hanami-view back into the hanami gem itself, since a lot of that is interdependent and important to maintain in sync in order to provide a cohesive, integrated experience for people building full stack Hanami applications. This should hopefully be ready by the next alpha, and will then free me up to move back onto application/slice polish.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Open source status update, December 2021 and January 2022</title>
    <link rel="alternate" href="https://timriley.info/writing/2022/02/14/open-source-status-update-december-2021-january-2022"/>
    <id>https://timriley.info/writing/2022/02/14/open-source-status-update-december-2021-january-2022</id>
    <published>2022-02-13T13:20:00+00:00</published>
    <updated>2022-02-13T13:20:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;I’ve been on an absolute tear with my OSS work lately. I’ve been feeling such good momentum that every time I got a moment at the computer, I just had to keep pushing forward with code rather than writing one of these updates. Now that there’s been a couple of Hanami releases and a big dry-system one since the my last update, it’s time for a roundup.&lt;/p&gt;
&lt;p&gt;The best thing about the last two months was that I took the first two week in January off from work, and dedicated myself 9-5 to OSS while everyone was still on holidays (the reality was probably more like 10am-4pm then 9pm-12am, but I was having too much fun to stop). This allowed me to get through some really chunky efforts that would’ve been really hard to string along across post-work nighttime efforts alone.&lt;/p&gt;
&lt;p&gt;All of this has meant I’ve got a lot to get through here, so I’m going to revert to dot list form with at most a comment or two, otherwise I’ll never get this post done (and you probably won’t want to read it all anyway). Here goes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Back in December I &lt;a href="https://github.com/hanami/hanami/pull/1133"&gt;started the new &lt;code&gt;source_dirs&lt;/code&gt; configuration for Hanami&lt;/a&gt;, along with some corresponding &lt;a href="https://github.com/dry-rb/dry-system/pull/195"&gt;broadening of the dry-system component dir config API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;I also &lt;a href="https://github.com/hanami/hanami/pull/1135"&gt;updated the Hanami router to initialize itself lazily&lt;/a&gt;, which will allow for for the Hanami applications to scale down better to Lambda/FaaS-size deployments, since you can now keep the application non-booted and still access the router for e.g. a subset of your routes. It’s also an important step towards faster application reloading in development.&lt;/li&gt;
&lt;li&gt;In January (happy new year!) I took care of the &lt;a href="https://hanamirb.org/blog/2022/01/12/announcing-hanami-200alpha5/"&gt;Hanami 2.0.0.alpha5 release&lt;/a&gt; in January (featuring the &lt;code&gt;source_dirs&lt;/code&gt; config, the lazy router, and more).&lt;/li&gt;
&lt;li&gt;I then moved onto dry-system for the rest of the month, kicking things off by finishing a &lt;a href="https://github.com/dry-rb/dry-system/pull/198"&gt;comprehensive glossary for dry-system&lt;/a&gt;, intended to go into our docs as a quick reference to alo important terms, but I also used it as a trial run for some naming changes that I’ve been wanting to make for a while.&lt;/li&gt;
&lt;li&gt;That was received well so I went ahead and &lt;a href="https://github.com/dry-rb/dry-system/pull/200"&gt;renamed “bootable components” to “providers”&lt;/a&gt; (and the “init” provider lifecycle step to “prepare”), as well as the renaming the “component provider” concept to “external provider sources.” This change has made so many parts of dry-system easier to explain more precisely, since we avoid a range of confusing overlaps. It feels really good.&lt;/li&gt;
&lt;li&gt;With the naming change done, I &lt;a href="https://github.com/dry-rb/dry-system/pull/202"&gt;overhauled the provider implementation&lt;/a&gt; as well, making it so that providers were backed by distinct classes and corresponding instances, and also clarifying just how providers can access their target container. This one took my four different tries to get right (and ate at my mind over a whole weekend), but I think it turned out really well. The new class-backed providers open up a lot of possibilities for better handling complex provider code, while still preserving ease of use for simpler providers.&lt;/li&gt;
&lt;li&gt;In more provider tidy up, I &lt;a href="https://github.com/dry-rb/dry-system/pull/211"&gt;removed the &lt;code&gt;use&lt;/code&gt; method from the provider API&lt;/a&gt;, since the target container can now be consistently accessed, and can be used to start other provider’s via its own standard &lt;code&gt;.prepare&lt;/code&gt; and &lt;code&gt;.start&lt;/code&gt; methods.&lt;/li&gt;
&lt;li&gt;I &lt;a href="https://github.com/dry-rb/dry-system/pull/204"&gt;updated our built-in “settings” provider source to use Dotenv&lt;/a&gt; rather than maintain our own &lt;code&gt;.env*&lt;/code&gt; file loading and parsing code. Goodbye extra code, I will not miss you.&lt;/li&gt;
&lt;li&gt;I scratched one of my biggest itches and &lt;a href="https://github.com/dry-rb/dry-system/pull/201"&gt;removed an overload of dry-configurable’s setting method inside dry-system&lt;/a&gt;. This one proved to be a bit of a challenge to sort out, since it required &lt;a href="https://github.com/dry-rb/dry-configurable/pull/130"&gt;adjustments to class-level &lt;code&gt;config&lt;/code&gt; access inside dry-configurable&lt;/a&gt;, and dry-configurable’s test suite is so exacting it often doesn’t leave much wriggle room. In this case I was able to thread the needle and even bring some clarity to previous unspecified behaviours.&lt;/li&gt;
&lt;li&gt;Another little thing, I &lt;a href="https://github.com/dry-rb/dry-system/pull/206"&gt;removed support for a configurable key namespace separator in dry-system&lt;/a&gt;, which really never made sense given how much dry-system needs to manipulate these keys, and given how important it is for our docs to make sense with &lt;code&gt;&amp;quot;.&amp;quot;&lt;/code&gt; used as the key separator everywhere.&lt;/li&gt;
&lt;li&gt;I &lt;a href="https://github.com/dry-rb/dry-system/pull/207"&gt;expended dry-system’s API for its container configuration&lt;/a&gt;, formalising container configuration as a distinct phase that can be completed with its own &lt;code&gt;configured!&lt;/code&gt; method. This allows dry-system’s &lt;code&gt;config&lt;/code&gt; object to be configured directly rather than forcing everyone to go through the &lt;code&gt;configure do |config|&lt;/code&gt; block-based API, which was a quirk specific to dry-system, and not in any other usage of dry-configurable throughout our ecosystem. This not only makes things easier for our direct users, but will make Hanami’s integration of dry-system more straightforward, since it’s (fairly lengthy) config and setup code no longer needs to descend into that extra block.&lt;/li&gt;
&lt;li&gt;In another glossary-driven change, I &lt;a href="https://github.com/dry-rb/dry-system/pull/208"&gt;renamed the “manual registrar” to “manifest registrar”&lt;/a&gt;, since it loads registrations from manifest files, also freeing the term “manual registration“ for any individual act of calling &lt;code&gt;.register&lt;/code&gt; on the container.&lt;/li&gt;
&lt;li&gt;This one is big! I added support for &lt;a href="https://github.com/dry-rb/dry-system/pull/209"&gt;partial container exports and imports&lt;/a&gt;, which is only something I’ve been thinking about for three years or more. And it turned out to be really straightforward! This is going to be a huge part of dry-system usage going forward, and really reinforces how it can help you better organise your code both in the small and in the large. With dry-system containers able to hide most of their implementation away and just export a few “public” components, they’ll begin to work much better as representations of key high-level concerns withing your applications.&lt;/li&gt;
&lt;li&gt;I &lt;a href="https://github.com/dry-rb/dry-system/pull/210"&gt;tidied dry-system’s dependency graph plugin&lt;/a&gt;, and ensured it &lt;a href="https://github.com/dry-rb/dry-system/pull/214"&gt;supported all injector strategies&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;I fixed a 2-year-old bug and made it so that &lt;a href="https://github.com/dry-rb/dry-system/pull/212"&gt;registrations in providers preserve all their options&lt;/a&gt; when moving into the target container.&lt;/li&gt;
&lt;li&gt;I fixed a 2.5-year-old bug and ensured that &lt;a href="https://github.com/dry-rb/dry-system/pull/213"&gt;providers can’t inadvertently re-start themselves&lt;/a&gt; while already in the process of starting and cause an infinite loop&lt;/li&gt;
&lt;li&gt;I restored &lt;a href="https://github.com/dry-rb/dry-system/pull/215"&gt;support for a configurable &lt;code&gt;instance&lt;/code&gt; proc on component dir config&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;I &lt;a href="https://github.com/dry-rb/dry-system/pull/218"&gt;added an option to providers to support conditional loading&lt;/a&gt;, making it so that you can have a provider enabled only in particular circumstances, and have it not be loaded (saving time and memory, and improving clarity) when it’s not needed.&lt;/li&gt;
&lt;li&gt;I added &lt;a href="https://github.com/dry-rb/dry-system/pull/219"&gt;a couple more useful predicates to &lt;code&gt;Dry::System::Identifier&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;And finally, I got &lt;a href="https://github.com/ianks"&gt;Ian Ker-Seymer’s&lt;/a&gt; work on a &lt;a href="https://github.com/dry-rb/dry-system/pull/197"&gt;dry-system Zeitwerk plugin across the line&lt;/a&gt;, making it so that dry-system users can use an integrated Zeitwerk setup with just a single line.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Last week &lt;a href="https://github.com/dry-rb/dry-system/releases/tag/v0.23.0"&gt;I released all of that dry-system work in version 0.23.0&lt;/a&gt;, officially our biggest release ever (go read the release notes!). We’re now looking really close to a 1.0 release for dry-system, with just a few things left to go.&lt;/p&gt;
&lt;p&gt;I then updated Hanami to &lt;a href="https://github.com/hanami/hanami/pull/1148"&gt;use this latest dry-system&lt;/a&gt; (including all the updated terminology) and &lt;a href="https://github.com/hanami/hanami/pull/1149"&gt;support partial slice imports and exports&lt;/a&gt;, which we then &lt;a href="https://hanamirb.org/blog/2022/02/10/announcing-hanami-200alpha6/"&gt;released as Hanami 2.0.0.alpha6&lt;/a&gt; just a few days ago.&lt;/p&gt;
&lt;p&gt;Getting to this point was a lot of work, and it represents a big milestone in our Hanami 2.0 journey. I’m extremely grateful that I could make this my sole focus for a little while.&lt;/p&gt;
&lt;h2&gt;Thank you to my sponsors ❤️&lt;/h2&gt;
&lt;p&gt;My work in Ruby OSS is kindly supported by my &lt;a href="https://github.com/sponsors/timriley"&gt;GitHub sponsors&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thank you in particular to &lt;a href="https://github.com/jasoncharnes"&gt;Jason Charnes&lt;/a&gt; and now also Seb Wilgosz (of &lt;a href="https://hanamimastery.com"&gt;HanamiMastery&lt;/a&gt;) for your support as my upper tier sponsors!&lt;/p&gt;
&lt;p&gt;The 22 dot points in this post show that Hanami 2 is truly getting closer, but there are many dots left to go. &lt;a href="https://github.com/sponsors/timriley"&gt;I’d love for your support too&lt;/a&gt; in helping make this happen.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Open source status update, November 2021</title>
    <link rel="alternate" href="https://timriley.info/writing/2021/12/13/open-source-status-update-november-2021"/>
    <id>https://timriley.info/writing/2021/12/13/open-source-status-update-november-2021</id>
    <published>2021-12-13T12:35:00+00:00</published>
    <updated>2021-12-13T12:35:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;One of the things I find hardest about writing these open source status updates is my opening sentence. So I’m just going to leave this one in here and get on with journalling another month of steady progress!&lt;/p&gt;
&lt;h2&gt;We shipped the first of our monthly Hanami alphas!&lt;/h2&gt;
&lt;p&gt;After our &lt;a href="https://hanamirb.org/blog/2021/11/09/announcing-hanami-200alpha3/"&gt;last alpha release&lt;/a&gt; and our promise to ship monthly, we delivered the next alpha right on time, with &lt;a href="https://hanamirb.org/blog/2021/12/07/announcing-hanami-200alpha4/"&gt;Hanami 2.0.0.alpha4 going out the door&lt;/a&gt; on the 7th of December.&lt;/p&gt;
&lt;p&gt;The release was a very orderly process this time around, especially compared to my late night marathons of the last two. Luca was our release manager (we’re alternating duties), and a few days before the release, we already had the gems lined up and release announcement prepared. I’ve a feeling this will make for a really helpful cadence for us. Anyway, go check out the &lt;a href="https://hanamirb.org/blog/2021/12/07/announcing-hanami-200alpha4/"&gt;announcement post&lt;/a&gt; for the changes, a few of which I’ll cover in detail below.&lt;/p&gt;
&lt;h2&gt;Access to routes helpers inside all actions&lt;/h2&gt;
&lt;p&gt;Way back in July, Marc Busqué &lt;a href="https://github.com/hanami/hanami/pull/1119"&gt;prepared a class&lt;/a&gt; to provide access to helper methods returning URLs for all of the named routes in an application. This languished for a while, but we got it merged in preparation for the alpha3 release. This made the routes helpers accessible via a component registered with the &lt;code&gt;&amp;quot;routes&amp;quot;&lt;/code&gt; container key. This is all fine if you want to inject this object explicitly, but we want to make it easier still in the common usage contexts, like action and view classes.&lt;/p&gt;
&lt;p&gt;For alpha4 we’ve made it automatically available in both those places!&lt;/p&gt;
&lt;p&gt;Making it available in the actions was &lt;a href="https://github.com/hanami/controller/pull/358"&gt;a fairly straightforward extension&lt;/a&gt; of the existing set of dependencies we automatically provide. There was a little wrinkle here, however: the “resolver” that we have the Hanami application provide to the router (for resolving action objects from string container keys) was resolving those objects eagerly, i.e. directly fetching them from the container for every declared route. This led to an infinite loop, since the first action would auto-inject the router helper, which in turn required the router, which then tried to load each again again, and so on. After &lt;a href="https://github.com/hanami/hanami/pull/1132"&gt;a small change to make the router resolver lazy&lt;/a&gt;, we were back in business and the routes helper was available. The lazy router resolver is something we would have wanted to build before 2.0.0 final anyway (a big principle behind 2.0.0 is to make it easy to load only subsets of your application), so I’m glad we had this as a prompt to do it now.&lt;/p&gt;
&lt;p&gt;The end result is that you can now have routes like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;module TestApp
  class Routes &amp;lt; Hanami::Application::Routes
    define do
      slice :main, at: &amp;quot;/&amp;quot; do
        get &amp;quot;test&amp;quot;, to: &amp;quot;test&amp;quot;
        get &amp;quot;examples&amp;quot;, to: &amp;quot;examples.index&amp;quot;, as: :examples
      end
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then access your named routes inside an action with no extra work:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;module Main
  module Actions
    class Test &amp;lt; Hanami::Action
      def handle(req, res)
        res.redirect routes.path(:examples)
      end
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thanks to Marc’s ongoing work in this space, we’ll have the same arrangement set up for views by this time next month, too.&lt;/p&gt;
&lt;h2&gt;Improvements to the Hanami console&lt;/h2&gt;
&lt;p&gt;I’ve been preparing another Hanami app at work lately, and noticed we’d experienced some regressions in the console experience compared to the arrangement we had in place back for &lt;a href="/writing/2020/05/07/sharing-my-hanami-2-application-template/"&gt;our original Hanami 2 application template&lt;/a&gt;. Notably, slice helpers weren’t available, nor was there a shortcut to access the application class. I &lt;a href="https://github.com/hanami/cli/pull/5"&gt;fixed these here&lt;/a&gt;, and now this wonderful experience is available again:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ./bin/hanami console

test_app[development]&amp;gt; app
=&amp;gt; RecognitionService::Application

test_app[development]&amp;gt; main
=&amp;gt; #&amp;lt;Hanami::Slice:0x00007fdc97ab47a0
 @application=RecognitionService::Application,
 @container=Main::Container,
 @name=:main,
 @namespace=Main,
 @namespace_path=&amp;quot;main&amp;quot;,
 @root=#&amp;lt;Pathname:/Users/tim/Source/cultureamp/recognition-service/slices/main&amp;gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Having such quick and easy access to the application and its slices is a big part of what makes the Hanami console experience so pleasurable! And this is in addition to its constant start time, no matter how large your app (remember my note above about having the framework support loading subsets of the application?), because it only loads the most minimal subset of the app, and then brings in the rest of your components lazily, only as you require them.&lt;/p&gt;
&lt;p&gt;While I was in CLI land, I also decided to finally figure out why our IRB-based console wasn’t offering a custom prompt, and &lt;a href="https://github.com/hanami/cli/pull/6"&gt;managed to get that one fixed too&lt;/a&gt;. IRB shows its age in this regard: Pry offers a much nicer setup and customisation experience.&lt;/p&gt;
&lt;h2&gt;Preparing for flexible source dir configuration&lt;/h2&gt;
&lt;p&gt;In the remainder of my OSS time this month, I was slowly moving towards the next evolution of the configurable Hanami component dirs I shipped &lt;a href="/writing/2021/11/15/open-source-status-update-october-2021"&gt;last month&lt;/a&gt;. The plan here is to make it support “source dir” configuration in general, not just for component dirs. This should allow a user to configure component dirs when they want source dirs to be auto-registered (along with full access to the dry-system component dir configuration), as well as ordinary source dirs, when they want a directory to be autoloaded by Zeitwerk, but not populate the container.&lt;/p&gt;
&lt;p&gt;The beginning of my work on the Hanami side &lt;a href="https://github.com/hanami/hanami/pull/1133"&gt;is available here&lt;/a&gt;, and the approach I’m taking is to leverage dry-system’s own &lt;code&gt;Config::ComponentDirs&lt;/code&gt; class for most of the component dir heavy lifting. This means we’ll need to add a range of new behaviours to that class, which is &lt;a href="https://github.com/dry-rb/dry-system/pull/195"&gt;in progress here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I’m hoping to have this one ready by the end of the year. More on this in next month’s update.&lt;/p&gt;
&lt;!-- ## Thank you to my sponsors ❤️

The log4j incident this week has [hopefully](https://twitter.com/FiloSottile/status/1469441477642178561) [served](https://twitter.com/FiloSottile/status/1469749412998041610) as a [timely](https://twitter.com/carmatrocity/status/1469829256146468865) [reminder](https://twitter.com/solnic29a/status/1470306767594733568) that our open source model [needs work](https://christine.website/blog/open-source-broken-2021-12-11).

I’m working on building the next generation of Ruby web frameworks – bringing vitality and diversity to a space sorely needs it – almost exclusively in my spare time. If you’d like to support this effort, consider [sponsoring my work on GitHub](https://github.com/sponsors/timriley). Triply so if you’re a company working with any of Hanami, dry-rb, or rom-rb.

Thanks as ever to [Jason Charnes](https://github.com/jasoncharnes) for your support as my sole level 3 sponsor. --&gt;
</content>
  </entry>
  <entry>
    <title>Open source status update, October 2021</title>
    <link rel="alternate" href="https://timriley.info/writing/2021/11/15/open-source-status-update-october-2021"/>
    <id>https://timriley.info/writing/2021/11/15/open-source-status-update-october-2021</id>
    <published>2021-11-15T00:30:00+00:00</published>
    <updated>2021-11-15T00:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;It’s one of my favourite kinds of monthly updates, folks, because I have another Hanami alpha release to share! Let’s dive straight into it.&lt;/p&gt;
&lt;h2&gt;We shipped dry-system 0.21.0!&lt;/h2&gt;
&lt;p&gt;Remember &lt;a href="https://timriley.info/writing/2021/10/11/open-source-status-update-september-2021/"&gt;all that good stuff&lt;/a&gt; I described about dry-system’s component dir namespaces last month? It’s now properly released! You can upgrade to dry-system 0.21.0 to give it a try.&lt;/p&gt;
&lt;p&gt;You can also check out the new &lt;a href="https://dry-rb.org/gems/dry-system/0.21/component-dirs/"&gt;component dirs page&lt;/a&gt; of our user documentation that I created for this release, which outlines every configurable aspect of dry-system’s component dirs. There’s a lot more we can do to improve dry-system’s documentation, but this is at least a start.&lt;/p&gt;
&lt;h2&gt;We shipped Hanami 2.0.0.alpha3!&lt;/h2&gt;
&lt;p&gt;And here’s the big one! After 6 months, we shipped another Hanami 2.0 alpha release! Go &lt;a href="https://hanamirb.org/blog/2021/11/09/announcing-hanami-200alpha3/"&gt;read the announcement post&lt;/a&gt; to start with – I’ll wait! – then we can look a bit more behind the scenes.&lt;/p&gt;
&lt;p&gt;So what was this actually like for me? Shipping this alpha turned out almost exactly like the last one: a few nights straight trying to get everything ready, followed by another in which I decide we’re shipping come hell or high water. And exactly like last time, it ended with me in a daze at 1:30am having just finished the blog post, waiting for the Hanami website static generation to to complete so I could finally hit the sack 😅&lt;/p&gt;
&lt;p&gt;A notable milestone for me was that this was the first time I pushed out the releases of all the individual Hanami gems. I’m looking forward to this becoming a regular thing in the future.&lt;/p&gt;
&lt;h2&gt;Streamlined slice directories&lt;/h2&gt;
&lt;p&gt;Given the months I spent getting dry-system ready for week, of course this is favourite aspect of this release. We now support this as a standard source directory structure for each of your application’s slices:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;└── slices
    └── main
        ├── actions/
        ├── lib/
        ├── repositories/
        └── views/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;lib/&lt;/code&gt; is intended to hold your slice’s core business logic. Inside &lt;code&gt;lib/&lt;/code&gt;, every file is expected to define a class inside your slice’s Ruby namespace, in this case &lt;code&gt;Main&lt;/code&gt;. So &lt;code&gt;lib/my_class.rb&lt;/code&gt; would define &lt;code&gt;Main::MyClass&lt;/code&gt;, and would be registered in the slice as &lt;code&gt;&amp;quot;my_class&amp;quot;.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Previously, this had to be &lt;code&gt;lib/main/my_class.rb&lt;/code&gt;, and removing that one redundant &lt;code&gt;main/&lt;/code&gt; from the path was the driver behind our months of work in this space. I’m thankful now there’s one less possible papercut for our users, but I’m also thankful we did the work &lt;em&gt;right&lt;/em&gt;, because it opened up the possibility for those other source directories in the slice!&lt;/p&gt;
&lt;p&gt;&lt;code&gt;actions/&lt;/code&gt;, &lt;code&gt;views/&lt;/code&gt;, and &lt;code&gt;repositories/&lt;/code&gt; are these new entrants here. The intention for these directories is to hold special categories of classes for your slice. In this case &lt;code&gt;actions/&lt;/code&gt; and &lt;code&gt;views/&lt;/code&gt; are key &lt;em&gt;entrypoints&lt;/em&gt;, in that their job is to provide an external interface, then and mostly coordinate with other objects from the slice to invoke the necessary business logic. &lt;code&gt;repositories/&lt;/code&gt; are intended to be the key interface between your slice and its persistence layer, and in many cases may provide cross-cutting behaviour within the slice.&lt;/p&gt;
&lt;p&gt;For each of these non-&lt;code&gt;lib/&lt;/code&gt; directories, there are some different rules: they’re expected to hold files defining classes in a matching namespace. So in this case, &lt;code&gt;actions/posts/show.rb&lt;/code&gt; would define &lt;code&gt;Main::Actions::Posts::Show&lt;/code&gt;, and would be registered under a matching namespace, at &lt;code&gt;&amp;quot;actions.posts.show&amp;quot;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You can add your own additional source directories too, too. To start with, we’ve made them configurable via &lt;code&gt;config.component_dir_paths&lt;/code&gt; inside your application class, which defaults to &lt;code&gt;[&amp;quot;actions&amp;quot;, &amp;quot;repositories&amp;quot;, &amp;quot;views&amp;quot;]&lt;/code&gt;. I plan to make this a much richer configuration object in the coming months, allowing you to add directories for autoloading only (i.e. not for component auto-registration), as well as manually configure the namespace rules for those dirs that are auto-registering components.&lt;/p&gt;
&lt;h2&gt;New loading rules for the top-level lib/&lt;/h2&gt;
&lt;p&gt;Along with the changes to the source directories in the slices, we made a big change to how we handle the top-level &lt;code&gt;lib/&lt;/code&gt;. Previously, we would auto-register components from &lt;code&gt;lib/[application_namespace]/&lt;/code&gt;. These would end up in the application container, and then in turn be imported into every slice container.&lt;/p&gt;
&lt;p&gt;For example, given a &lt;code&gt;lib/my_app/my_shared_class.rb&lt;/code&gt;, the application container would have a &lt;code&gt;&amp;quot;my_shared_class&amp;quot;&lt;/code&gt; component, and every slice would then have an imported &lt;code&gt;&amp;quot;application.my_shared_class&amp;quot;&lt;/code&gt; component. This general approach is one I’ve worked with for a while now, including in the in-house Icelab framework that preceded Hanami 2 development, and it’s worked reasonably well. The application container has been a good place to put components that are common to other slices. For a moment in the lead up to this release, I even toyed with further enshrining this approach, and loading such components from a special &lt;code&gt;app/&lt;/code&gt; directory. However, this led to some great feedback and a follow-up discussion with Luca, in which he noted that directories like these are ripe for unintended misuse, in that they create undesirable coupling between the application and all of its slices (not to mention the possible confusion with the Rails-like &lt;code&gt;app/&lt;/code&gt; directory naming).&lt;/p&gt;
&lt;p&gt;So to address this concern and encourage a healthier separation of concerns within Hanami 2 apps, we’ve changed the code loading rules for the top-level &lt;code&gt;lib/&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;lib/[application_namespace]/&lt;/code&gt; will still be autoloaded, so you won’t need to manage any manual &lt;code&gt;require&lt;/code&gt; statements to bring in your own classes&lt;/li&gt;
&lt;li&gt;However, &lt;code&gt;lib/[application_namespace]&lt;/code&gt; will no longer be a component directory, so files here will no longer auto-register in the application container&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With these changes, you can still use &lt;code&gt;lib/[application_namespace]&lt;/code&gt; to keep your own shared base classes and other manually invokable code, but if you would like to share &lt;em&gt;components&lt;/em&gt; with your slices, we’re encouraging you to create your own dedicated slices to house them. This act of slice creation and naming will hopefully encourage you to take the opportunity to draw more appropriate boundaries between these extra slices, giving them better cohesion and allowing other slices to import only the parts they need.&lt;/p&gt;
&lt;p&gt;For example, if you previously had clusters of components in your app container related to (a) password encryption as well as (b) exchange rate calcuation, both as common concerns, then the approach to take here would be to extract them into well-bounded slices of their own, at &lt;code&gt;slices/password_encryption&lt;/code&gt; and &lt;code&gt;slices/exchange_rates&lt;/code&gt;, and then import those from only the other slices that need them, e.g.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;module MyApp
  class Application &amp;lt; Hanami::Application
    config.slice :admin do
      # Importing a common &amp;quot;password_encryption&amp;quot; slice into the admin slice; all components from
      # the imported slice will be accessible with keys prefixed by &amp;quot;password_encryption.&amp;quot;
      import :password_encryption
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a much more intentional arrangement of concerns, and should result in a better factored and more maintainable modular application.&lt;/p&gt;
&lt;p&gt;Of course, if you really do need to register components in the application container, you can still do this via a file in &lt;code&gt;config/boot/&lt;/code&gt;, e.g.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;Hanami.application.register_bootable :my_component do |container|
  start do
    require &amp;quot;some_global_component&amp;quot;

    register &amp;quot;my_component&amp;quot;, SomeGlobalComponent.new
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hopefully this provides just the right amount of friction (compared to the ease of creating a new slice with its own auto-registered classes) that this path is only taken for the very small number of components that truly need to be global.&lt;/p&gt;
&lt;p&gt;Lastly, after all of these changes, one thing we haven’t messed with is the fact that the top-level &lt;code&gt;lib/&lt;/code&gt; directory is still added to Ruby’s standard &lt;code&gt;$LOAD_PATH&lt;/code&gt;, which means you have a place to put any files &lt;em&gt;outside&lt;/em&gt; of the application namespace (e.g. if you’re incubating future-gems inside your application) and still &lt;code&gt;require&lt;/code&gt; them explicitly.&lt;/p&gt;
&lt;h2&gt;All in on Zeitwerk&lt;/h2&gt;
&lt;p&gt;One thing is common to all the changes above: they all depend on the &lt;a href="https://github.com/fxn/zeitwerk"&gt;Zeitwerk&lt;/a&gt; autoloader. Our experience with Zeitwerk has been fantastic: it’s well documented, has been configurable wherever we need it, and then utterly predictable once we’ve set it up.&lt;/p&gt;
&lt;p&gt;These changes have further embedded the importance of Zeitwerk for Hanami 2: it’s become well and truly a “load bearing” part of the framework. So we’ve decided to go &lt;em&gt;all in&lt;/em&gt; on Zeitwerk for Hanami 2.&lt;/p&gt;
&lt;p&gt;Initially, the autoloader was configurable, with it being possible to opt out and fall back to managing your own standard &lt;code&gt;require&lt;/code&gt; statements everywhere. As of alpha3, however, the autoloader is now always on, an expected part of the framework. As a former “require what you require” kind of Rubyist, now that I’ve had some experience with Zeitwerk, there’s no way I’m going back. I expect every Hanami 2 user to feel the same, and with Zeitwerk in place, we give ourselves the best chances for further streamlining our users’ experience in the future.&lt;/p&gt;
&lt;p&gt;Thank you &lt;a href="https://github.com/fxn"&gt;Xavier Noria&lt;/a&gt; for your sterling work in this space 🙏🏼&lt;/p&gt;
&lt;h2&gt;Coming up: monthly alphas!&lt;/h2&gt;
&lt;p&gt;Getting to the point of releasing alpha3 was a lot of work, and hopefully the last of our big foundational overhauls. Now we’re on the other side of this, we want to really build our momentum towards a 2.0.0 final release. So from here we’ll be releasing monthly alphas, collecting up any of the work that’s happened over the previous month. We’ve already set a date for the next one: look out for something on or around December 7th!&lt;/p&gt;
&lt;p&gt;I’m &lt;em&gt;really&lt;/em&gt; excited about this next phase. If you’ve followed along with my previous OSS updates, you would’ve picked up how much of a drag it has been for working on months on end with no reward or punctuation of any kind. I hope the monthly cadence will help us keep our changes short and sharp, and things overall moving at a much faster clip.&lt;/p&gt;
&lt;h2&gt;Thank you to my sponsors ❤️&lt;/h2&gt;
&lt;p&gt;My work in Ruby OSS is kindly supported by my &lt;a href="https://github.com/sponsors/timriley"&gt;GitHub sponsors&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thank you in particular to &lt;a href="https://github.com/jasoncharnes"&gt;Jason Charnes&lt;/a&gt; for your continued support as my sole level 3 sponsor. Jason, you’re an absolute mensch.&lt;/p&gt;
&lt;p&gt;If you want to help make Hanami 2 a reality, I’d love it if you could &lt;a href="https://github.com/sponsors/timriley"&gt;join my sponsors too&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Open source status update, September 2021</title>
    <link rel="alternate" href="https://timriley.info/writing/2021/10/11/open-source-status-update-september-2021"/>
    <id>https://timriley.info/writing/2021/10/11/open-source-status-update-september-2021</id>
    <published>2021-10-11T00:45:00+00:00</published>
    <updated>2021-10-11T00:45:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;After the last few months of seemingly slow progress (and some corresponding malaise), September was a real watershed for my OSS work! It featured seven gem releases and one giant PR being made ready for merge. Let’s take a look.&lt;/p&gt;
&lt;h2&gt;dry-configurable 0.13.0 is out, at last!&lt;/h2&gt;
&lt;p&gt;I started the month resolved to finish the work on dry-configurable 0.13.0 that I &lt;a href="/writing/2021/09/06/open-source-status-update-july-august-2021/"&gt;detailed in the last episode&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I started my updating and merging any of the pending changes across dry-configurable’s dependent dry-rb gems: &lt;a href="https://github.com/dry-rb/dry-container/pull/80"&gt;dry-container&lt;/a&gt;, &lt;a href="https://github.com/dry-rb/dry-monitor/pull/45"&gt;dry-monitor&lt;/a&gt;, &lt;a href="https://github.com/dry-rb/dry-schema/pull/373"&gt;dry-schema&lt;/a&gt;, and &lt;a href="https://github.com/dry-rb/dry-validation/pull/691"&gt;dry-validation&lt;/a&gt;. Fortunately, thanks to the &lt;a href="https://github.com/dry-rb/dry-configurable/pull/121"&gt;extra compatibility work&lt;/a&gt; I’d done in dry-configurable, all of these changes were straightforward.&lt;/p&gt;
&lt;p&gt;By this time, there was nothing left to do but release! So on the evening of the 12th of September, I decided I get this &lt;em&gt;done.&lt;/em&gt; At 9:55pm, I &lt;a href="https://github.com/dry-rb/dry-configurable/releases/tag/v0.13.0"&gt;shipped dry-configurable 0.13.0&lt;/a&gt;, at long last!&lt;/p&gt;
&lt;p&gt;And then I shipped the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/dry-rb/dry-container/releases/tag/v0.9.0"&gt;dry-container 0.9.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dry-rb/dry-system/releases/tag/v0.20.0"&gt;dry-system 0.20.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dry-rb/dry-monitor/releases/tag/v0.5.0"&gt;dry-monitor 0.50&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dry-rb/dry-schema/releases/tag/v1.8.0"&gt;dry-schema 1.8.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dry-rb/dry-validation/releases/tag/v1.7.0"&gt;dry-validation 1.7.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;And for good measure, even though it turned out not to need dry-configurable compatibility changes, &lt;a href="https://github.com/dry-rb/dry-effects/releases/tag/v0.2.0"&gt;dry-effects 0.2.0&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By 11:30pm, this was all done and I happily &lt;a href="https://twitter.com/dry_rb/status/1437045303962595331"&gt;sent out the announcement tweet&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the time since, we haven’t seen or heard of any issues with the changes, so I think I can consider this change a success!&lt;/p&gt;
&lt;p&gt;Despite it taking as long as it did, I’m glad we made this change to move dry-configurable to a clearer API. A lesson I’m taking away from this is to think again before mixing optional positional parameters with keyword args splats in Ruby methods; though this is largely a non-issue for Ruby 3.0, moving away from this while retaining backwards compatibility did cause some grief for Ruby 2.7, and on top of that, I value the extra clarity that keyword arguments bring for anything but an immediately-obvious and required singular positional argument.&lt;/p&gt;
&lt;h2&gt;Testing compact slice lib paths in Hanami&lt;/h2&gt;
&lt;p&gt;With the dry-configurable taken care of, it was time to do the same for the saga of &lt;a href="https://github.com/dry-rb/dry-system/pull/181"&gt;dry-system namespaces&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Before I committed to polishing off the implementation in dry-system, I wanted to double check that it’d do the job we needed in Hanami: to elide the redundant lib directory in e.g. &lt;code&gt;slices/main/lib/main/&lt;/code&gt;, turning it into &lt;code&gt;slices/main/lib/&lt;/code&gt; only, with all the classes defined therein still retaining their &lt;code&gt;Main&lt;/code&gt; Ruby constant namespace. As hoped, &lt;a href="https://github.com/hanami/hanami/pull/1123"&gt;it did exactly that!&lt;/a&gt; As I shared with the other Hanami contributors:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Like all of my favourite PRs, it was 3 months of dry-system work, followed by a 3-line change in Hanami 😄&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A fun quip, but I think this is an important aspect of the work we’re doing in Hanami 2. We’re not putting in this amount of effort just to arrive at a toughly coupled framework that can deliver only for a small subset of users. Rather, we’re trying to establish powerful, flexible, well-factored building blocks that deliver not only the default Hanami experience, but also serve as useful tools unto themselves. The idea with this approach is that it should allow an Hanami user to “eject” themselves from any particular aspect of the framework’s defaults whenever their needs require it: they can dive deeper and use/configure constituent parts directly, while still using the rest of the framework for the value it provides.&lt;/p&gt;
&lt;h2&gt;dry-system component dir namespaces are done, at last!&lt;/h2&gt;
&lt;p&gt;Confident about the use case in Hanami, I used the rest of the month (and a little bit of October, &lt;em&gt;shh!&lt;/em&gt;) to finish off the dry-system namespaces. Here’s how they look:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;class AdminContainer &amp;lt; Dry::System::Container
  configure do |config|
    config.root = __dir__

    config.component_dirs.add &amp;quot;lib&amp;quot; do |dir|
      dir.namespaces.add &amp;quot;admin&amp;quot;, key: nil
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This example configures a single namespace for &lt;code&gt;lib/admin/&lt;/code&gt; that ensures the components have top-level identifiers (e.g. &lt;code&gt;&amp;quot;foo.bar&amp;quot;&lt;/code&gt; rather than &lt;code&gt;&amp;quot;admin.foo.bar&amp;quot;&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Namespaces take care of more than just container keys. If you wanted to mimic what we’re doing in Hanami and expect all classes in &lt;code&gt;lib/&lt;/code&gt; to use the &lt;code&gt;Admin&lt;/code&gt; const namespace, you could do the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;config.component_dirs.add &amp;quot;lib&amp;quot; do |dir|
  dir.namespaces.root const: &amp;quot;admin&amp;quot;
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You’re not limited to just a single namespace, either:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;config.component_dirs.add &amp;quot;lib&amp;quot; do |dir|
  dir.namespaces.add &amp;quot;admin/system_adapters&amp;quot;, key: nil, const: nil
  dir.namespaces.add &amp;quot;admin&amp;quot;, key: nil
  dir.namespaces.add &amp;quot;elsewhere&amp;quot;, key: &amp;quot;stuff.and.things&amp;quot;
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you want to learn more, &lt;a href="https://github.com/dry-rb/dry-system/pull/181"&gt;go read the overview in the PR&lt;/a&gt;: there’s around 3,000 words explaining the history, rationale, and full details of feature, as well as a whole bunch of implementation notes.&lt;/p&gt;
&lt;p&gt;Getting this ready to merge took around three weeks of really concerted work (almost every night!), but I’m super glad to finally have it done. Component dir namespaces represent another huge leap for dry-system. &lt;strong&gt;With namespaces, dry-system can load and manage code in almost any conceivable structure.&lt;/strong&gt; With this change giving us support for “friendlier” source directory structures like the one we’ll use in Hanami, I hope it means that dry-system will also become as &lt;em&gt;approachable&lt;/em&gt; as it already is powerful.&lt;/p&gt;
&lt;h2&gt;Figured out a strategy for upcoming Hanami alpha releases&lt;/h2&gt;
&lt;p&gt;A final highlight of the month was getting together for another in-person chat with Luca! (Can you believe we’ve only done this four or five times at most?) Among other things, we figured out a strategy for the next and subsequent Hanami 2.0.0.alpha releases.&lt;/p&gt;
&lt;p&gt;Here’s the plan:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;As soon as the dry-system namespaces are released and the new slice lib paths configured in Hanami, we’ll ship alpha3. A code loading change is a big change and we want to get it into people’s hands for testing ASAP.&lt;/li&gt;
&lt;li&gt;Our focus will continue to be on stripping as much boilerplate as possible away from the generated application code, and now that I’m done with all the big efforts from the last month, my contributions here should be a lot more iterative&lt;/li&gt;
&lt;li&gt;Going forward, we’ll ship a new alpha release every month, collecting up all the changes from that month&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So this means you should here about a new Hanami release by the time I’m writing my next set of notes here!&lt;/p&gt;
&lt;h2&gt;Thank you to my sponsors ❤️&lt;/h2&gt;
&lt;p&gt;My work in Ruby OSS is kindly supported by my &lt;a href="https://github.com/sponsors/timriley"&gt;GitHub sponsors&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thank you in particular to &lt;a href="https://github.com/jasoncharnes"&gt;Jason Charnes&lt;/a&gt; for your unwavering support as my sole level 3 sponsor (it was great to chat to you this month as well!). Thanks also to &lt;a href="https://github.com/swilgosz"&gt;Sebastian Wilgosz&lt;/a&gt; of &lt;a href="https://hanamimastery.com"&gt;HanamiMastery&lt;/a&gt; for upgrading his sponsorship!&lt;/p&gt;
&lt;p&gt;If you’d like to support my work in bringing Hanami 2 to life, &lt;a href="https://github.com/sponsors/timriley"&gt;I’d love for you to join my sponsors too&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;See you all next month!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Open source status update, July and August 2021</title>
    <link rel="alternate" href="https://timriley.info/writing/2021/09/06/open-source-status-update-july-august-2021"/>
    <id>https://timriley.info/writing/2021/09/06/open-source-status-update-july-august-2021</id>
    <published>2021-09-06T13:00:00+00:00</published>
    <updated>2021-09-06T13:00:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Hello again, open source fans! After two months, we’re overdue for an update! July and August were rather quiet months for me on the OSS front, but I’ve been steadily beavering away at two fairly large things.&lt;/p&gt;
&lt;h2&gt;Preparing for the dry-configurable 0.13.0 release&lt;/h2&gt;
&lt;p&gt;Back in May (&lt;em&gt;May!&lt;/em&gt;) &lt;a href="/writing/2021/06/08/open-source-status-update-may-2021/"&gt;I shared details&lt;/a&gt; of a couple of major changes we wanted to make to dry-configurable’s API before 1.0:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The default value for the setting must be supplied as &lt;code&gt;default:&lt;/code&gt; rather than a second positional argument&lt;/li&gt;
&lt;li&gt;A setting’s constructor (or ”processor”) can no longer be supplied as a block, instead it should be a proc object passed to &lt;code&gt;constructor:&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those changes have long since merged, but the release didn’t immediately follow, for a couple of reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Since dry-configurable is so widely used across the dry-rb and related ecosystems, we needed to double check that every dependent gem would continue to work undisturbed&lt;/li&gt;
&lt;li&gt;In some early checks for this, we found some small incompatibilities that would need to be addressed&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Do you ever have a chore on your to-do list that you just feel… &lt;em&gt;ugh&lt;/em&gt; about? Well this was one such thing for me, which is why it didn’t go anywhere for a little while.&lt;/p&gt;
&lt;p&gt;But an unglamorous task like this (along with an even stronger aversion to too much open WIP) turned out to be just what I needed to ease myself back into programming in August, after a few weeks of struggling to get anything done.&lt;/p&gt;
&lt;p&gt;So, I started by &lt;a href="https://github.com/dry-rb/dry-configurable/issues/120"&gt;drawing up an enormous matrix&lt;/a&gt; (seriously, check it, it’s enormous!) of dependent gems to test against both present and future of dry-configurable, and then slowly got to work on it.&lt;/p&gt;
&lt;p&gt;To start with, I &lt;a href="https://github.com/dry-rb/dry-configurable/pull/121"&gt;cracked the nut&lt;/a&gt; that was likely to give us as-good-as-possible backwards API compatibility. This was one of those fun Ruby 2.7/3.0 keyword argument incompatibilities. Check out the diff from that PR for probably one of the longer comment blocks I’ve written in a while.&lt;/p&gt;
&lt;p&gt;Then, I discovered and fixed &lt;a href="https://github.com/dry-rb/dry-system/pull/186"&gt;a minor issue&lt;/a&gt; with dry-system’s delegation of keyword arguments to a dry-configurable method. Released that as dry-system 0.18.2 and 0.19.2. That in turns fixed some issues with a few other gems relying on both dry-configurable and dry-system together.&lt;/p&gt;
&lt;p&gt;Next, I made &lt;a href="https://github.com/dry-rb/dry-schema/pull/371"&gt;a tiny change&lt;/a&gt; to dry-schema to ensure compatibility with another aspect of the upcoming dry-configurable. Released that as dry-schema 1.7.1.&lt;/p&gt;
&lt;p&gt;Then lastly, I &lt;a href="https://github.com/dry-rb/dry-configurable/pull/124"&gt;added some flags&lt;/a&gt; to allow users to manually disable the deprecation notices if they’re not yet able to upgrade their app or their dependent gems to use the latest API.&lt;/p&gt;
&lt;p&gt;And after all of that, we’re truly just around the corner from shipping this new dry-configurable! Here’s what’s left:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I need to write some good release notes for the changes&lt;/li&gt;
&lt;li&gt;Then merge each of the still-open API compatibility PRs for each of the dependent gems (there’s about 4 to go)&lt;/li&gt;
&lt;li&gt;Then release dry-configurable 0.13.0&lt;/li&gt;
&lt;li&gt;Then release minor version bumps for all of the dependent gems (6 in total)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I’m hoping to get this done in the next ~week or so. It’ll be a relief to have this done!&lt;/p&gt;
&lt;h2&gt;Continued work on first-class dry-system namespace support&lt;/h2&gt;
&lt;p&gt;The other large thing I’ve continued to work on is the expanded support for namespaces in dry-system that I &lt;a href="/writing/2021/07/11/open-source-status-update-june-2021/"&gt;detailed in my last update&lt;/a&gt;. I turned to this one when I wanted something a bit more “fun” while I was avoiding the dry-configurable work back in July. It’s now at the point where I’m really quite happy with the new abstractions I was able to identify along the way.&lt;/p&gt;
&lt;p&gt;Once the dry-configurable work is done, I plan to come back to this one, and before I spend any more time tidying it for release, I want to actually exercise the new feature from Hanami’s side to make sure it can achieve the elided source directory structure that inspired this work in the first place. All things going well, I should be able to show you it in action in next month’s post!&lt;/p&gt;
&lt;h2&gt;Thank you to my sponsors (new and old!) ❤️&lt;/h2&gt;
&lt;p&gt;Thank you to &lt;a href="https://github.com/htcarr3"&gt;Thomas Carr&lt;/a&gt; for joining my GitHub sponsors in July!&lt;/p&gt;
&lt;p&gt;And of course, a continued thanks to &lt;a href="https://github.com/jasoncharnes"&gt;Jason Charnes&lt;/a&gt; and the rest of my sponsors for your ongoing support!&lt;/p&gt;
&lt;p&gt;If you too would like to support my work, you can &lt;a href="https://github.com/sponsors/timriley"&gt;sponsor me on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;See you all next month! With these two things close to done, we might just be cresting a hill.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Open source status update, June 2021</title>
    <link rel="alternate" href="https://timriley.info/writing/2021/07/11/open-source-status-update-june-2021"/>
    <id>https://timriley.info/writing/2021/07/11/open-source-status-update-june-2021</id>
    <published>2021-07-11T13:00:00+00:00</published>
    <updated>2021-07-11T13:00:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;June felt like a little bit of a down month for me in OSS. For various reasons, I struggled to find decent blocks of time to make headway into all the loops I opened &lt;a href="/writing/2021/06/08/open-source-status-update-may-2021/"&gt;back in May&lt;/a&gt;. But in the end, I found a way to nudge some other important things forward!&lt;/p&gt;
&lt;h2&gt;Beginning work on first-class namespace support for dry-system&lt;/h2&gt;
&lt;p&gt;Rather than push against the grain at a low ebb, I used what time I had in June to do some more nourishing ”fun” work, to get back into dry-system again and prepare the way for Hanami to have a flatter Ruby source file directory structure.&lt;/p&gt;
&lt;p&gt;What we want is instead of &lt;code&gt;Main::HelloWorld&lt;/code&gt; being defined in this file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;slices/main/lib/main/hello_world.rb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We want too remove that second redundant &lt;code&gt;main/&lt;/code&gt; and have the file like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;slices/main/lib/hello_world.rb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The idea here is to make the deeper hierarchies imposed by each namespaced Hanami slice a little less ”in your face,” and in doing so, make this whole framework just that much more approachable to a wider range of users.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/fxn/zeitwerk"&gt;Zeitwerk&lt;/a&gt; already supports part of what we’d like to do here, through its support for &lt;a href="https://github.com/fxn/zeitwerk#custom-root-namespaces"&gt;custom root namespaces&lt;/a&gt;. Using this, we’d set up that elided source directory from above like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;loader = Zeitwerk::Loader.new
loader.push_dir &amp;quot;slices/main/lib&amp;quot;, namespace: Main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, for Hanami to use both Zeitwerk and dry-system together, more work is required. This is also made more challenging by the fact that dry-system needs to map names in two directions, from identifier to source file/constant (when lazy loading a single registration) and from source file to constant/identifier (when finalizing a container and crawling its managed directories for all source files), whereas Zeitwerk only needs to go in one direction, from constant to source file.&lt;/p&gt;
&lt;p&gt;Until now, the only support dry-system has had for namespacing was its &lt;code&gt;default_namespace&lt;/code&gt; setting on its component dirs. This worked very simplistically. Given a setting like the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;config.component_dirs.add &amp;quot;lib&amp;quot; do |dir|
  dir.default_namespace = &amp;quot;main&amp;quot;
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If dry-system encountered a file like &lt;code&gt;main/hello_world.rb&lt;/code&gt; inside that component dir, then it would strip off the leading &lt;code&gt;&amp;quot;main&amp;quot;&lt;/code&gt; when forming its identifier, leaving it as &lt;code&gt;&amp;quot;hello_world&amp;quot;&lt;/code&gt;. And when the container is lazy loading, it would first try prepending the &lt;code&gt;default_namespace&lt;/code&gt; to find a file for a given identifier, before falling back to searching without the namespace.&lt;/p&gt;
&lt;p&gt;This has worked fine for most use cases, but it won’t cut it for our Hanami plans.&lt;/p&gt;
&lt;p&gt;So to address this, &lt;strong&gt;I’m adding namespaces as a first-class concept to dry-system component dirs&lt;/strong&gt;. A replacement for the &lt;code&gt;default_namespace&lt;/code&gt; config above would look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;config.component_dirs.add &amp;quot;lib&amp;quot; do |dir|
  dir.namespaces.add &amp;quot;main&amp;quot;
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To add a namespace, we specify the path within the component dir, along with an &lt;code&gt;identifier&lt;/code&gt; and/or &lt;code&gt;const&lt;/code&gt; to use within that path.&lt;/p&gt;
&lt;p&gt;The example above is actually shorthand for the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;config.component_dirs.add &amp;quot;lib&amp;quot; do |dir|
  dir.namespaces.add &amp;quot;main&amp;quot;, identifier: nil, const: &amp;quot;main&amp;quot;
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Playing this out, this means: ”within the &lt;code&gt;main/&lt;/code&gt; subdirectory of this component dir, don’t add anything to the beginning of the component’s identifiers, but expect them all to be within the &lt;code&gt;Main&lt;/code&gt; object namespace.”&lt;/p&gt;
&lt;p&gt;Following on from this, we should be able to configure some wildly divergent component loading rules. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;dir.namespaces.add &amp;quot;main&amp;quot;, identifier: &amp;quot;bits_and_bobs&amp;quot;, const: &amp;quot;stuff_and_things&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this config, we’d expect files in &lt;code&gt;main/&lt;/code&gt; to define classes inside the &lt;code&gt;StuffAndThings&lt;/code&gt; namespace, and have their identifiers all prefixed by &lt;code&gt;&amp;quot;bits_and_bobs&amp;quot;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;As you might’ve noticed, the &lt;code&gt;namespaces.add&lt;/code&gt; method means we can configure multiple namespaces:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;dir.namespaces.add &amp;quot;main&amp;quot;
dir.namespaces.root # special method for component dir root
dir.namespaces.add &amp;quot;admin&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Namespaces will be referenced in order when resolving components or loading files into the container, too, which improves what was otherwise a poorly defined part of dry-system’s behaviour. So with the configuration above, if you had a &lt;code&gt;main/component.rb&lt;/code&gt; &lt;em&gt;and&lt;/em&gt; an &lt;code&gt;admin/component.rb&lt;/code&gt;, the file inside &lt;code&gt;main/&lt;/code&gt; would be favoured in all cases and would always appear in the container as the &lt;code&gt;&amp;quot;component&amp;quot;&lt;/code&gt; registration, because its namespace was added first.&lt;/p&gt;
&lt;p&gt;So, after all of this, we now have the necessary tools to support our elided directory structure from the top, which would look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;config.component_dirs.add &amp;quot;lib&amp;quot; do |dir|
  dir.namespaces.root const: &amp;quot;main&amp;quot;
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I have a &lt;a href="https://github.com/dry-rb/dry-system/pull/181"&gt;messy work in progress PR&lt;/a&gt; underway to support all of this, so feel free to check it out if you’d like to see the code equivalent of the under construction gif. This will end up being &lt;em&gt;yet another&lt;/em&gt; fairly big overhaul of dry-system internals, so I think it’ll probably take me the better part of July to get it finished. Iterating over the namespaces as part of component loading will also have some performance implications, so I’ll want to do some benchmarking of that process too.&lt;/p&gt;
&lt;p&gt;That said, I’m really excited to find another opportunity to make dry-system more powerful and better structured than before! And of course, to then employ that to create a nicer experience for future Hanami users :)&lt;/p&gt;
&lt;h2&gt;Thank you to my sponsors (new and old!) ❤️&lt;/h2&gt;
&lt;p&gt;A huge thank you to &lt;a href="https://github.com/g3d"&gt;Bohdan V.&lt;/a&gt; for joining my GitHub sponsors during June!&lt;/p&gt;
&lt;p&gt;Thanks also to &lt;a href="https://github.com/jasoncharnes"&gt;Jason Charnes&lt;/a&gt; and the rest of my sponsors for your continued support! If you’d like to join them in supporting my work, you too can &lt;a href="https://github.com/sponsors/timriley"&gt;sponsor me on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;See you all next month, folks!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Open source status update, May 2021</title>
    <link rel="alternate" href="https://timriley.info/writing/2021/06/08/open-source-status-update-may-2021"/>
    <id>https://timriley.info/writing/2021/06/08/open-source-status-update-may-2021</id>
    <published>2021-06-08T13:55:00+00:00</published>
    <updated>2021-06-08T13:55:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Well didn’t May go by quickly! Here’s what I got up to in OSS over the month.&lt;/p&gt;
&lt;p&gt;For starters, do you remember the big &lt;a href="/writing/2021/05/10/open-source-status-update-march-april-2021/"&gt;Hanami 2.0.0.alpha2 I mentioned last month&lt;/a&gt;? Yep, that happened. And it was a little cheeky of me to sneak it into my March/April update, because it happened into the first week of May!&lt;/p&gt;
&lt;p&gt;So after a short while recuperating from that big push, there wasn’t a whole lotta time left in the month! And apart from this, it was kind of a funny month, because it brought a different kind of work to the sort I’d been doing for a while.&lt;/p&gt;
&lt;h2&gt;Welcoming Marc Busqué to the team!&lt;/h2&gt;
&lt;p&gt;The real highlight of the month was &lt;a href="https://github.com/waiting-for-dev"&gt;Marc Busqué&lt;/a&gt; getting on board with Hanami development! Marc’s brought a huge amount of fresh energy to the team and got right into productive development work. It’s great be to working with you, Marc!&lt;/p&gt;
&lt;h2&gt;Preparing dry-configurable’s API for 1.0&lt;/h2&gt;
&lt;p&gt;One of the first things Marc did was make some adjustments to dry-configurable’s &lt;code&gt;setting&lt;/code&gt; API, to make it more consistent as one of the final steps before we can release 1.0 of that gem.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;setting&lt;/code&gt; will now take only a single positional argument, for the name of the setting. Everything else must be provided via keyword arguments for improved consistency and clarity, plus easier wrapping by other gems. This means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The default value for the setting must be supplied as &lt;code&gt;default:&lt;/code&gt; rather than a second positional argument&lt;/li&gt;
&lt;li&gt;A setting’s constructor (or ”processor”) can no longer be supplied as a block, instead it should be a proc object passed to &lt;code&gt;constructor:&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We merged these in &lt;a href="https://github.com/dry-rb/dry-configurable/pull/111"&gt;these&lt;/a&gt; &lt;a href="https://github.com/dry-rb/dry-configurable/pull/112"&gt;PRs&lt;/a&gt;, which include a deprecation pathway, so the previous usage will (largely) continue to work. While we were in dry-configurable, I also &lt;a href="https://github.com/dry-rb/dry-configurable/pull/113"&gt;made a fix&lt;/a&gt; for it to work with preexisting &lt;code&gt;#initialize&lt;/code&gt; methods accepting keyword args when its module is included, as well as &lt;a href="https://github.com/dry-rb/dry-configurable/pull/114"&gt;removing implicit hash conversion&lt;/a&gt; which can result in unexpected destructuring when passing a configurable object to a method accepting a keyword args splat.&lt;/p&gt;
&lt;p&gt;These dry-configurable changes haven’t yet been released, but hopefully we can make it happen sometime in June. The reason is that they were in service of a couple of larger efforts, both of which are still in flight (read on below for more detail!).&lt;/p&gt;
&lt;p&gt;In the meantime, after these API changes, I did a sweep of the dry-rb ecosystem to bring things up to date, which led to PRs in &lt;a href="https://github.com/dry-rb/dry-system/pull/179"&gt;dry-system&lt;/a&gt;, &lt;a href="https://github.com/dry-rb/dry-container/pull/77"&gt;dry-container&lt;/a&gt;, &lt;a href="https://github.com/dry-rb/dry-effects/pull/83"&gt;dry-effects&lt;/a&gt;, &lt;a href="https://github.com/dry-rb/dry-monitor/pull/43"&gt;dry-monitor&lt;/a&gt;, &lt;a href="https://github.com/dry-rb/dry-rails/pull/44"&gt;dry-rails&lt;/a&gt;, &lt;a href="https://github.com/dry-rb/dry-schema/pull/356"&gt;dry-schema&lt;/a&gt;, &lt;a href="https://github.com/dry-rb/dry-validation/pull/686"&gt;dry-validation&lt;/a&gt;, and &lt;a href="https://github.com/hanami/view/pull/190"&gt;hanami-view&lt;/a&gt;. Phew! It just goes to show how load-bearing this little gem is for our overall ecosystem (and how wide-ranging the impact of API changes can be). I haven’t merged these yet either, but will hope to do so in the next week or so, once we’ve ensured we have compatibility with both current and future dry-configurable APIs for the range of dry-rb gems that are past their respective 1.0 releases.&lt;/p&gt;
&lt;h2&gt;Porting Hanami::Configuration to dry-configurable&lt;/h2&gt;
&lt;p&gt;One thing that led to a couple of those dry-configurable fixes was my work in &lt;a href="https://github.com/hanami/hanami/pull/1107"&gt;updating &lt;code&gt;Hanami::Configuration&lt;/code&gt; to use dry-configurable&lt;/a&gt;. This class had gotten pretty sprawling with its manual handling of reading/writing a wide range setting values, which is squarely in dry-configurable’s wheelhouse, and the result is much tidier (and now consistent with how we’re handling configuration in both hanami-controller and hanami-view). This one again isn’t quite ready to merge (are you sensing a theme?), but it’s probably just an hour away from being done. I’ll look forward to having this one ticked off!&lt;/p&gt;
&lt;h2&gt;Updating Hanami’s application settings to use dry-configurable (and more)&lt;/h2&gt;
&lt;p&gt;Hanami’s application settings (the ones you define for yourself in &lt;code&gt;config/settings.rb&lt;/code&gt;) have been very dry-configurable-&lt;em&gt;like&lt;/em&gt; since their inception, but backed by custom code instead. It’s been on my to-do list &lt;a href="https://trello.com/c/cJEcVuU0/62-build-application-settings-on-top-of-dry-configurable"&gt;for a long time&lt;/a&gt; to switch this over to dry-configurable, but with Marc joining the team, we’ve finally got some traction here! You can &lt;a href="https://github.com/hanami/hanami/pull/1105"&gt;the original PR&lt;/a&gt; and an &lt;a href="https://github.com/hanami/hanami/pull/1110"&gt;current, in-progress PR&lt;/a&gt; as well.&lt;/p&gt;
&lt;p&gt;This one was a lot of collaborative fun. Marc made the broad initial steps, I jumped in to poke around and explore the design possibilities, and then he took my direction and ran with it, adding some other nice improvements along the way, like introducing a ”settings store” abstraction, which in our default implementation will continue to rely on dotenv.&lt;/p&gt;
&lt;p&gt;This one is &lt;em&gt;also&lt;/em&gt; close to being done. Watch this space (and all the other spaces I’ve mentioned so far, if you’re keen).&lt;/p&gt;
&lt;h2&gt;Providing a default types module to application settings (or not), and probably turning the whole thing into a regular class&lt;/h2&gt;
&lt;p&gt;Marc pivoted quickly from the above work to another long-standing to-do of ours: &lt;a href="https://github.com/hanami/hanami/pull/1111"&gt;making a types module automatically available to the application settings&lt;/a&gt;. Having type-safe settings is one of the nicest features of the way we’re handling them, and I’d like this to be as smooth as possible for our users!&lt;/p&gt;
&lt;p&gt;This turned out to be a bit of a rabbit hole, as evidenced by this &lt;a href="https://github.com/hanami/hanami/pull/1111#discussion_r639472752"&gt;sprawling PR discussion&lt;/a&gt;, but I think it’s led us to a good place.&lt;/p&gt;
&lt;p&gt;Currently, the application settings must be defined in a block provided to the &lt;code&gt;Hanami::Application.settings&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;Hanami.application.settings do
  setting :sentry_dsn
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Due to the combination of Ruby’s use of the lexical scope for constant lookups within blocks and dry-types’ standard reliance upon types collections as modules, with custom types defined as constants, it was nigh on impossible to auto-generate and provide an ergonomic, idiomatic types module for use within a block like that (see the linked PR discussion for details).&lt;/p&gt;
&lt;p&gt;So this led us to the decision to move the application settings definition to a good ol’ ordinary Ruby class:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;module MyApp
  class Settings &amp;lt; Hanami::Application::Settings
    setting :sentry_dsn
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will still be looked up and loaded by the framework automatically, but because we’re using a regular class, we can rely on all the regular Ruby techniques for referring to a types module. This means we could choose to access a types module that the user has already created for themselves, e.g. &lt;code&gt;MyApp::Types&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;require &amp;quot;my_app/types&amp;quot;

module MyApp
  class Settings &amp;lt; Hanami::Application::Settings
    setting :sentry_dsn, MyApp::Types::String
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or even create our own localised types module right within the class:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;require &amp;quot;dry/types&amp;quot;

module MyApp
  Types = Dry.Types

  class Settings &amp;lt; Hanami::Application::Settings
    setting :sentry_dsn, Types::String
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is much simpler and less likely to confuse! Better still, because we have a regular class at our disposal, users can now add their own custom behavior to their settings:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;require &amp;quot;dry/types&amp;quot;

module MyApp
  Types = Dry.Types

  class Settings &amp;lt; Hanami::Application::Settings
    setting :sentry_dsn, Types::String.optional

    def sentry_enabled?
      !sentry_dsn.nil?
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So I think this is a positive direction to be heading in. Plus, it reinforces the Hanami philosophy of ”a place for everything and everything in it’s place,” with this settings class being a great exemplar of a single-responsibility class, even for something that’s a special part of the framework boot process.&lt;/p&gt;
&lt;h2&gt;Plans for June&lt;/h2&gt;
&lt;p&gt;Well, I think that about brings you all up to speed for now. My plan for the rest of June is to make sure I can help merge all of those PRs! And then I’ll be getting back into Zeitwerk-land hand looking for ways to simplify the Ruby source file structures that we have inside our application and slice directories.&lt;/p&gt;
&lt;h2&gt;Thank you to my sponsors (including NEW SPONSORS!!) ❤️&lt;/h2&gt;
&lt;p&gt;May turned out to be hugely encouraging month for my GitHub sponsorships!&lt;/p&gt;
&lt;p&gt;Thank you to &lt;a href="https://github.com/jasoncharnes"&gt;Jason Charnes&lt;/a&gt; for upgrading your sponsorship! And thank you to &lt;a href="https://github.com/janko"&gt;Janko Marohnić&lt;/a&gt; and &lt;a href="https://github.com/graudeejs"&gt;Aldis Berjoza&lt;/a&gt; for beginning new sponsorships! 🥰 Thanks also to &lt;a href="https://github.com/swilgosz"&gt;Sebastian Wilgosz&lt;/a&gt; who began a periodic sponsorship based on a portion of his &lt;a href="https://hanamimastery.com"&gt;Hanami Mastery&lt;/a&gt; project sponsorships.&lt;/p&gt;
&lt;p&gt;Little things like this this really do mean a lot, so folks, thanks again! 🙏🏼&lt;/p&gt;
&lt;p&gt;If you’d like to support my ongoing OSS work, I’d love it if you could &lt;a href="https://github.com/sponsors/timriley"&gt;join my cadre of intelligent and very good looking sponsors on GitHub&lt;/a&gt;. And as ever, thank you to my existing sponsors for your ongoing support!&lt;/p&gt;
&lt;p&gt;See you next month!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Open source status update, March and April 2021</title>
    <link rel="alternate" href="https://timriley.info/writing/2021/05/10/open-source-status-update-march-april-2021"/>
    <id>https://timriley.info/writing/2021/05/10/open-source-status-update-march-april-2021</id>
    <published>2021-05-10T12:45:00+00:00</published>
    <updated>2021-05-10T12:45:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Hello again, open source friends. It’s been a little while, so today you get a double update for March and April, and what a couple of months they were!&lt;/p&gt;
&lt;p&gt;For most of March I had a break from OSS, because we moved apartments! The move itself was a culmination of a multi-year purchase and saving process, so I definitely enjoyed the opportunity to savour the moment. We’re well settled now, and in matters relating to this here blog, I now have a dedicated room with a nice big desk (and new display!) from which to do all my computering, so I suppose you can all expect some more &lt;em&gt;expansive&lt;/em&gt; thoughts in the future.&lt;/p&gt;
&lt;p&gt;As for April, it was momentous for several other reasons. Let’s run through them…&lt;/p&gt;
&lt;h2&gt;Added system-wide defaults for dry-system component_dirs&lt;/h2&gt;
&lt;p&gt;Wrapping up &lt;a href="https://github.com/dry-rb/dry-system/pull/162"&gt;this pull request&lt;/a&gt; was the first thing I did to get back into swing of things. I had taken care of most of it before the house move, but I needed to polish it a little before it was ready to merge.&lt;/p&gt;
&lt;p&gt;With this now in place, it’s possible to configure settings in a dry-system container that apply to all of its subsequently added component dirs:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;class MyContainer &amp;lt; Dry::System::Container
  configure do |config|
    # ...

    # These can be configured within each component_dir, but you can now
    # provide defaults for all dirs here
    config.component_dirs.loader = MyCustomLoader
    config.component_dirs.default_namespace = &amp;quot;my_app&amp;quot;

    # These added component_dirs will have the above settings as defaults
    config.component_dirs.add &amp;quot;lib&amp;quot;

    config.component_dirs.add &amp;quot;another_dir&amp;quot; do |dir|
      # You can still override the defaults within any given dir
      dir.loader = AnotherLoader
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Apart from wanting this just to ensure the new dry-system component_dirs feature felt fully rounded, I also felt it was important for dry-system’s autoloader support to be ergonomic, allowing it to become just a one or two-liner.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;# Configure autoloader support in one place, instead of once for every
# added component_dir
config.component_dirs.loader = Dry::System::Loader::Autoloading
config.component_dirs.add_to_load_path = false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The review for this PR raised an interesting nugget. The change resulted in two places with the exact same collection of settings defined:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The top-level &lt;code&gt;config.component_dirs&lt;/code&gt; (&lt;code&gt;Dry::System::Config::ComponentDirs&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The individual component dir configuration object yielded to each call of &lt;code&gt;config.component_dirs.add&lt;/code&gt; (&lt;code&gt;Dry::System::Config::ComponentDir&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To keep these in sync, I wanted to have the settings defined in one place, and copied over into the other. My initial implementation used some deep knowledge of dry-configurable internals:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;module Dry
  module System
    module Config
      class ComponentDirs
        # ...

        # Settings from ComponentDir can be configured here to apply all added dirs as
        # defaults
        @_settings = ComponentDir._settings.dup
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the review, &lt;a href="https://github.com/dry-rb/dry-system/pull/162#discussion_r597007361"&gt;Piotr aked why I was using this ivar&lt;/a&gt;, which led to a helpful discussion. I had used the ivar because it offered a one-liner to import a copy of the settings all at once, but in talking it through, I realised I’d been relying on private aspects of dry-configurable that I wouldn’t recommend to others. Despite both of these gems inhabiting the same dry-rb ecosystem, we should really be using public APIs like any other gem user. So this is really an opportunity for a public API to emerge and provide this same outcome, something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;_settings.replace(ComponentDir._settings.dup)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Until then (see &lt;a href="https://github.com/dry-rb/dry-configurable/issues/109"&gt;this issue&lt;/a&gt; if you want to help!), I’ve avoided the direct access of the ivar through this slightly wordier but just as effective approach:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;# Settings from ComponentDir are configured here as defaults for all added dirs
ComponentDir._settings.each do |setting|
  _settings &amp;lt;&amp;lt; setting.dup
end
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Released dry-system 0.19.0&lt;/h2&gt;
&lt;p&gt;With that last PR merged, and the changelog prepared (&lt;a href="https://github.com/dry-rb/dry-system/pull/161"&gt;so big it needed its own PR!&lt;/a&gt;), we were ready to release dry-system 0.19.0! This release was big enough that it merited &lt;a href="https://dry-rb.org/news/2021/04/22/dry-system-0-19-released-with-zeitwerk-support-and-more-leading-the-way-for-hanami-2-0/"&gt;its own announcement post&lt;/a&gt;. Check it out for the headline features that this release brings. For all the juicy details, you always &lt;a href="https://timriley.info/writing/2020/12/07/open-source-status-update-november-2020"&gt;the&lt;/a&gt; &lt;a href="https://timriley.info/writing/2021/01/06/open-source-status-update-december-2020"&gt;previous&lt;/a&gt; &lt;a href="https://timriley.info/writing/2021/02/01/open-source-status-update-january-2021"&gt;four&lt;/a&gt; &lt;a href="https://timriley.info/writing/2021/03/09/open-source-status-update-february-2021/"&gt;months&lt;/a&gt; of my open source status updates.&lt;/p&gt;
&lt;p&gt;As part of preparing for the release, I also teed up a &lt;a href="https://github.com/dry-rb/dry-system/milestone/1"&gt;dry-system 1.0 issues milestone&lt;/a&gt; on GitHub. Please check them out if you’d like to pitch in! Most of the issues are much more targeted than the sweeping refactors I did over the last while, so they should be approachable.&lt;/p&gt;
&lt;p&gt;(Sidenote: I also prepared a &lt;a href="https://github.com/dry-rb/dry-configurable/milestone/1"&gt;similar 1.0 issues milestone for dry-configurable&lt;/a&gt;, the other key gem we’d like to bring to 1.0 sometime this year)&lt;/p&gt;
&lt;h2&gt;Finished Hanami Zeitwerk integration&lt;/h2&gt;
&lt;p&gt;With the dry-system release done, I turned my attention back to Hanami, and quite promptly &lt;a href="https://github.com/hanami/hanami/pull/1100"&gt;finished the Zeitwerk integration&lt;/a&gt;. This turned out to be a breeze after all the foundational work on dry-system. Zeitwerk author and all around mensch Xavier ”fxn” Noria also took a look, and &lt;a href="https://github.com/hanami/hanami/pull/1100#issuecomment-819607632"&gt;this comment of his&lt;/a&gt; left me chuffed:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;That is all? Man, I do not know the details but this patch tells me the work behind this integration is really good 💯.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Thank you for the encouragement, fxn 😊&lt;/p&gt;
&lt;p&gt;As for how the integration works, there’s nothing really particular to dive into (unusual for me, I know!). It all… just works. There’s a new &lt;code&gt;config.autloader&lt;/code&gt; setting on the Hanami application class that you can use to configure the Zeitwerk instance directly, or set to false or nil if you want to disable autloading. In most cases, though, you shouldn’t have to worry, everything will just work.&lt;/p&gt;
&lt;p&gt;As part of testing this out, I ported one of my work’s Hanami 2.0 applications to the new Zeitwerk support, and in the process was able to remove more than 150 individual &lt;code&gt;require&lt;/code&gt; lines from the app. This is going to make building Hanami apps a much nicer experience!&lt;/p&gt;
&lt;h2&gt;Released Hanami 2.0.0.alpha2!&lt;/h2&gt;
&lt;p&gt;Finally, all of the ducks were in a row to cut the long-awaited next alpha release of Hanami 2.0.0. The team banded together to get all the final pieces sorted over a furious few days of activity, leading to the release and my &lt;a href="https://hanamirb.org/blog/2021/05/04/announcing-hanami-200alpha2/"&gt;official announcement&lt;/a&gt; on 4th May (pushed at about 1am here in Canberra!).&lt;/p&gt;
&lt;p&gt;Please go check out the announcement, I think it’s a great summary of everything we’ve been working towards for these last two years, and it will hopefully get you excited for what Hanami can offer the next generation of Ruby apps (apps of all kinds, too, because &lt;a href="https://github.com/hanami/hanami/pull/1102"&gt;as of this last-moment PR&lt;/a&gt;, we’re explicitly no longer a web-only framework).&lt;/p&gt;
&lt;p&gt;As for my feelings about all of this, I think my tweet at the time says it all:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hanami 2.0.0.alpha2!&lt;/p&gt;
&lt;p&gt;I’m incredibly proud of what we’ve assembled so far, and can’t wait to take this vision to full fruition.&lt;/p&gt;
&lt;p&gt;In the meantime, what a milestone! I’m so happy 😭&lt;/p&gt;
&lt;p&gt;Thanks to @jodosha, @&lt;em&gt;solnic&lt;/em&gt; and the whole Hanami team for such a meaningful couple years of work.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you’d like to kick the tyres on this new alpha release, go check out &lt;a href="https://github.com/hanami/hanami-2-application-template"&gt;the application template&lt;/a&gt; that we’ve brought up to date and moved into the Hanami GitHub org.&lt;/p&gt;
&lt;h2&gt;What’s next?&lt;/h2&gt;
&lt;p&gt;Our goal for the next alpha is to reduce the boilerplate from the app template as much as possible. Hop onto &lt;a href="https://discourse.hanamirb.org/t/hanami-v2-0-0-alpha2-app-template-feedback/606/24"&gt;this discussion thread&lt;/a&gt; if you’d like to share your thoughts!&lt;/p&gt;
&lt;p&gt;There’s a lot of further work to the framework internals I’d like to do (i.e. much of what’s on &lt;a href="https://trello.com/b/lFifnBti/hanami-20"&gt;the Trello board&lt;/a&gt;), but having a spick and span template is a great crystalising goal for the next release, getting us as quickly as possible to our desired generated application, leaving time to work on the internals afterwards.&lt;/p&gt;
&lt;h2&gt;Thank you to my sponsors ❤️&lt;/h2&gt;
&lt;p&gt;It’s been a huge effort to get to where we are today, and I certainly enjoyed reaching the milestone, but there’s still plenty left to do. I’d love it if you could &lt;a href="https://github.com/sponsors/timriley"&gt;sponsor me on GitHub&lt;/a&gt; to help sustain my efforts over all the Hanami releases to come. And as ever, thank you to my existing sponsors for your ongoing support!&lt;/p&gt;
&lt;p&gt;See you next month!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Open source status update, February 2021</title>
    <link rel="alternate" href="https://timriley.info/writing/2021/03/09/open-source-status-update-february-2021"/>
    <id>https://timriley.info/writing/2021/03/09/open-source-status-update-february-2021</id>
    <published>2021-03-09T11:20:00+00:00</published>
    <updated>2021-03-09T11:20:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Well hey there, Ruby open source fans! February for me was all about consolidating the &lt;a href="/writing/2021/02/01/open-source-status-update-january-2021/"&gt;dry-system breakthroughs&lt;/a&gt; I made last month.&lt;/p&gt;
&lt;p&gt;I started off by testing the work on a real app I made, and happily, all was fine! Things all looking good, I wrote myself a list and shared it with my Hanami colleagues:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[timriley] So what’s left for me to do here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Release dry-configurable with the new cloneable values support&lt;/li&gt;
&lt;li&gt;Apply @Nikita Shilnikov’s excellent feedback to https://github.com/dry-rb/dry-system/pull/157&lt;/li&gt;
&lt;li&gt;Merge https://github.com/dry-rb/dry-system/pull/155&lt;/li&gt;
&lt;li&gt;Merge https://github.com/dry-rb/dry-system/pull/155&lt;/li&gt;
&lt;li&gt;Merge https://github.com/hanami/controller/pull/341&lt;/li&gt;
&lt;li&gt;Merge https://github.com/hanami/hanami/pull/1093&lt;/li&gt;
&lt;li&gt;In a new PR, configure Zeitwerk for Hanami, and enable the autoloading loader for Hanami’s container component_dirs&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;Everything started well: by the 15th of Feb I &lt;a href="https://github.com/dry-rb/dry-configurable/releases/tag/v0.12.1"&gt;released dry-configurable 0.12.1&lt;/a&gt; with the new &lt;code&gt;cloneable&lt;/code&gt; option for custom setting values. And a mere hour later, I merged the two dry-system PRs! Woo, we’re on the home straight!&lt;/p&gt;
&lt;p&gt;At that point, I gave &lt;a href="https://github.com/flash-gordon"&gt;Nikita&lt;/a&gt; the go-ahead to test all the dry-system changes on some of the apps that he manages: he’s absolutely brazen about running our bleeding edge code, and I love it. In this case, it was very helpful, because it revealed a little wrinkle in my heretofore &lt;em&gt;best laid plans&lt;/em&gt;: if you configure a dry-system container with a component dir and a default namespace, and some of the component files sit &lt;em&gt;outside&lt;/em&gt; that namespace, then they would fail to load. This was a valid use case missing from our test suite, and something I must’ve broke in my major changes last month.&lt;/p&gt;
&lt;p&gt;This turned out to be relatively simple for me to hack in a fix, but at the same time I noticed an opportunity for yet another improvement: spread across multiple parts of dry-system was a bunch of (often repeated) string manipulation code working on container identifiers, doing things like removing a leading namespace, or converting a delimited identifier to a file path. It felt like there was a &lt;code&gt;Dry::System::Identifier&lt;/code&gt; abstraction just waiting to be let out.&lt;/p&gt;
&lt;p&gt;And so I did it! &lt;a href="https://github.com/dry-rb/dry-system/pull/158"&gt;In this omnibus PR&lt;/a&gt;, I introduced &lt;code&gt;Dry::System::Identifier&lt;/code&gt;, refactored component building once more, and, not to be forgotten, fixed the bug Nikita found.&lt;/p&gt;
&lt;p&gt;I’m really happy with both of the refactorings. Let’s start with &lt;code&gt;Identifier&lt;/code&gt;: now we have just a single place for all the logic dealing with identifier string manipulations, but we also get to provide a new, rich aspect of our component API for users of dry-system. For example, as of my work last month, it’s now possible to configure per-component behaviour around auto-registration, and we can now use the component’s &lt;code&gt;identifier&lt;/code&gt; like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;config.component_dirs.add &amp;quot;lib&amp;quot; do |dir|
  dir.default_namespace = &amp;quot;my_app&amp;quot;

  dir.auto_register = lambda do |component|
    !component.identifier.start_with?(&amp;quot;entities&amp;quot;)
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Isn’t that neat? The &lt;code&gt;Identifier&lt;/code&gt; began life as an internal-only improvement, but here we get to make our user’s life easier too, with namespace-aware methods like &lt;code&gt;#start_with?&lt;/code&gt; (which will return true only if the &lt;code&gt;&amp;quot;entities&amp;quot;&lt;/code&gt; is complete leading namespace, like &lt;code&gt;&amp;quot;entities.user&amp;quot;&lt;/code&gt;, and not &lt;code&gt;&amp;quot;entities_abc.user&amp;quot;&lt;/code&gt;). I’d like to add a range of similar conveniences to &lt;code&gt;Identifier&lt;/code&gt; before we release 1.0. Please let me know what you’d like to see!&lt;/p&gt;
&lt;p&gt;The other benefit of &lt;code&gt;Identifier&lt;/code&gt; is that it’s vastly simplified how we load components. Check out how we use it in &lt;code&gt;ComponentDir#component_for_path&lt;/code&gt;, which is used when the container is finalizing, and the auto-registrar crawls a directory to register a corresponding component for each file (comments added below for the sake of explanation):&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;def component_for_path(path)
  separator = container.config.namespace_separator

  # 1. Convert a path, like &amp;quot;my_app/articles/operations/create.rb&amp;quot;
  #    to an identifier key, &amp;quot;my_app.articles.operations.create&amp;quot;
  key = Pathname(path).relative_path_from(full_path).to_s
    .sub(RB_EXT, EMPTY_STRING)
    .scan(WORD_REGEX)
    .join(separator)

  # 2. Create the Identifier using the key, but without any namespace attached
  identifier = Identifier.new(key, separator: separator)

  # 3. If the identifier is part of the component dir's configured default
  #    namespace, then strip the namespace from the front of the key and
  #    rebuild the identifier with the namespace attached
  if identifier.start_with?(default_namespace)
    identifier = identifier.dequalified(default_namespace, namespace: default_namespace)
  end

  # 4. By this point, the identifier will be appropriate for both default
  #    namespaced components as well as non-namespaced components, so we can go
  #    ahead and use it to build our component!
  build_component(identifier, path)
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Even without the comments, this method is concise and easy to follow thanks to the high-level &lt;code&gt;Identifier&lt;/code&gt; API. What you’re also witnessing above is the very fix for the bug Nikita found! Getting to this point was a perfect example of &lt;a href="https://twitter.com/KentBeck/status/250733358307500032"&gt;Kent Beck’s&lt;/a&gt; “for each desired change, make the change easy (warning: this may be hard), then make the easy change” process. And you bet, it felt good!&lt;/p&gt;
&lt;p&gt;In this change I actually introduced a pair of methods on &lt;code&gt;ComponentDir&lt;/code&gt;: &lt;code&gt;#component_for_path&lt;/code&gt; (as we saw above), which is used when finalizing a container, as well as &lt;code&gt;#component_for_identifier&lt;/code&gt;, which is used when lazy-loading components on a non-finalized container. Previously, these two methods were both class-level ”factory” methods on &lt;code&gt;Component&lt;/code&gt; itself. By moving them to &lt;code&gt;ComponentDir&lt;/code&gt;, not only are they much closer to the details that are important for building the component, they provide a nice symmatry which will help ensure we don’t miss either case when making changes to component loading in future. &lt;code&gt;Component&lt;/code&gt; winds up being a much simpler class, too, which is nice.&lt;/p&gt;
&lt;p&gt;After all of this, Nikita gave me a happy thumbs up and we were good to merge this PR and resume preparation for a major dry-system release!&lt;/p&gt;
&lt;p&gt;But not so fast, I also:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Spent a full evening adding API docs to the code I introduced/changed in this PR&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dry-rb/dry-system/pull/158"&gt;Got the thing merged&lt;/a&gt;, then &lt;a href="https://github.com/dry-rb/dry-system/pull/160"&gt;swept through the user docs&lt;/a&gt; to make sure they reflected the recent changes&lt;/li&gt;
&lt;li&gt;And &lt;a href="https://github.com/dry-rb/dry-system/pull/161"&gt;started working on a mammoth CHANGELOG entry&lt;/a&gt; upcoming release&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It was in writing the CHANGELOG entry that I realised I needed to make &lt;em&gt;one last change&lt;/em&gt; before I can really consider this release done: I want to create a way to configure default values for all component dirs. This will be helpful for when we eventually add a one-liner &lt;code&gt;use :zeitwerk&lt;/code&gt; plugin to dry-system, which will need to ensure that &lt;code&gt;dir.loader = Dry::Systen::Loader::Autoloading&lt;/code&gt; and &lt;code&gt;dir.add_to_load_path = false&lt;/code&gt; are set for all subsequent user-configured component dirs. Given the amount of breaking changes we’ll be making with this release, I’d hate to see yet any unnecessary extra churn arise from this work. So that’s my first task for the month of March.&lt;/p&gt;
&lt;p&gt;In the meantime, &lt;a href="https://github.com/hanami/hanami/pull/1093"&gt;I have Hanami ready and waiting&lt;/a&gt; for this new dry-system release! As soon as our ducks are finally in a row, I’ll be able to merge this and begin the long-anticipated work on configuring Zeitwerk within Hanami.&lt;/p&gt;
&lt;p&gt;Looking back on this month, I spent most of it feeling frustrated that I was &lt;em&gt;still&lt;/em&gt; working on dry-system after all this time, when I just wanted to get back and make that very last change in Hanami before we could ship the next alpha release. This was exacerbated by a series of late nights that I pulled trying to get that bugfix and related &lt;code&gt;Identifier&lt;/code&gt; changes working in a way I was happy with. I finished the month feeling pretty drained and the slightest bit cranky.&lt;/p&gt;
&lt;p&gt;I’m indeed happy with the outcome and glad I put in the work. And yes, I got to the point where I could laugh about it. From the PR description:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;As part of this work, I also continued my dry-system refactoring journey, because this is more or less my life now 😆.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What I’m taking away from this experience is a reminder that I need to be patient. When working on nights-and-weekends open source, things will only happen when they can happen, and the best we can do is the best we can do. I’ll keep on pushing hard, but February has reminded me to make sure I take care of my (physical and mental) health too.&lt;/p&gt;
&lt;p&gt;We’re already a good week into March by the time I’m writing this, and later this month I expect to be moving apartments (yay!), so March may be a slightly less full month from me on the OSS front. If I can squeeze in that last dry-system fix and get myself in a position to begin some early experiments of Zeitwerk in Hanami, I’ll be happy. That’ll put us in a good place to ship the next Hanami alpha early in April.&lt;/p&gt;
&lt;h2&gt;Thank you to my sponsors ❤️&lt;/h2&gt;
&lt;p&gt;If you want to give me a boost in all of these efforts, I’d love for you to &lt;a href="https://github.com/sponsors/timriley"&gt;sponsor me on GitHub&lt;/a&gt;. Thank you to my sponsors for your ongoing support!&lt;/p&gt;
&lt;p&gt;See you next month!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Open source status update, January 2021</title>
    <link rel="alternate" href="https://timriley.info/writing/2021/02/01/open-source-status-update-january-2021"/>
    <id>https://timriley.info/writing/2021/02/01/open-source-status-update-january-2021</id>
    <published>2021-02-01T11:50:00+00:00</published>
    <updated>2021-02-01T11:50:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;I had a very satisfying January in Ruby OSS work! This month was all about overhauling the dry-system internals. That I’ve written about this in both &lt;a href="https://timriley.info/writing/2020/12/07/open-source-status-update-november-2020/"&gt;November&lt;/a&gt; and &lt;a href="https://timriley.info/writing/2021/01/06/open-source-status-update-december-2020/"&gt;December&lt;/a&gt; just goes to show (a) how long things actually take when you’re doing this on the side (and I’m not lazing about, I spend at least 3 nights a week working on OSS), and (b) just how much there was going on inside of dry-system.&lt;/p&gt;
&lt;p&gt;So to set the scene, here’s the circuitous path I took in adding &lt;a href="https://github.com/dry-rb/dry-system/pull/155"&gt;rich component directory configuration&lt;/a&gt; to dry-system. This is the commit history before I tidied it:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-text"&gt;2020-11-24 Add some early WIP
2020-11-25 Accept pre-configured component dirs
2020-11-25 Configure with block as part of initialize
2020-11-25 Provide path when initializing ComponentDir
2020-12-01 Add Rubocop rule
2020-12-01 Allow ComponentDirs to be cloned
2020-12-01 Clarify names
2020-12-01 Fix wording of spec
2020-12-01 Fixup naming
2020-12-01 Start getting component_dirs in place
2020-12-01 Update auto-registrar to use component_dirs
2020-12-01 Update specs for component_dirs
2020-12-03 Total messy WIP
2020-12-23 Get some WIP laid down on Booter#find_component
2020-12-23 Remove some WIP comments
2020-12-23 Tidy
2020-12-23 Update file_exists? behavior
2021-01-04 Add error
2021-01-04 Get things closer
2021-01-04 Provide custom dry-configurable cloneable value
2021-01-04 Remove unused settings
2021-01-04 Use a Concurrent::Map
2021-01-05 Add FIXME about avoiding config.default_namespace
2021-01-05 Introduce ComponentDir with behaviour separate to config
2021-01-05 Remove note, now that Loader#call is doing a require!, we’re fine
2021-01-05 Remove top-level default_namespace config
2021-01-05 Tidy Component
2021-01-05 Update FIXME
2021-01-11 Add docs
2021-01-11 Add docs for file exists
2021-01-11 Document Booter#boot_files method as public
2021-01-11 Don’t preserve file_path when namespacing component
2021-01-11 Expand docs
2021-01-11 Fix
2021-01-11 Flesh out ComponentDir
2021-01-11 Remove TODO
2021-01-11 Remove unused method
2021-01-11 Rip out the Component cache
2021-01-11 Stop setting file attribute on Component
2021-01-11 Tidy up Component file_path attr
2021-01-11 Tweak names
2021-01-11 Use a faster way of building namespaces path
2021-01-12 Use cloneable option for component_dirs setting
2021-01-14 Do not load components with auto_register: false
2021-01-14 Initialize components directly from AutoRegistrar
2021-01-14 Remove stale comment
2021-01-14 Remove unneeded requiring of component
2021-01-14 Scan file for magic comment options when locating
2021-01-14 Use dry-configurable master
2021-01-15 Add spec (and adjust approach) for skipping lazy loading of auto-register-disabled components
2021-01-15 Add unit tests for ComponentDir
2021-01-15 Tidy AutoRegistrar
2021-01-16 Add extra attributes to component equalizer
2021-01-16 Add unit tests for Component.locate
2021-01-16 Make load_component easier to understand
2021-01-18 Use base Dry::Container missing component error
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yep. 56 commits and just under two calendar months of work, with the break in early December being my sting doing Advent of Code. Luckily, that “Total messy WIP” left me with a passing test suite after some heavy refactoring, but it did take a day or two to figure out just what I was doing again! Note to self: leave more notes to self.&lt;/p&gt;
&lt;h2&gt;Rich, independent component directory configuration for dry-system&lt;/h2&gt;
&lt;p&gt;The (tidied) &lt;a href="https://github.com/dry-rb/dry-system/pull/155"&gt;pull request for this change&lt;/a&gt; has a lengthy description, focused on implementation. If you’re interested in the details, please have a read!&lt;/p&gt;
&lt;p&gt;Here’s the long and the short of it, though: previously, dry-system would let you configure a top-level &lt;code&gt;auto_register&lt;/code&gt; setting, which would contain an array of string paths within the container root, which the system would use to populate the container. This would often be used alongside another top-level setting, &lt;code&gt;default_namespace&lt;/code&gt;, which would strip away a common namespace prefix from the container identifiers, and a call to &lt;code&gt;.load_paths!&lt;/code&gt; for each directory being auto-registered, to ensure the sources files within those directories could be properly required:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;class MyApp::Container &amp;lt; Dry::System::Container
  configure do |config|
    config.root = __dir__
    config.auto_register = [&amp;quot;lib&amp;quot;]
    config.default_namespace = &amp;quot;my_app&amp;quot;
  end

  load_paths! &amp;quot;lib&amp;quot;
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Those are three different things you would need to know how to use &lt;em&gt;just right&lt;/em&gt; in order to set up a properly working dry-system container. Luckily, most users could copy a working example and then tweak from there. Also, users would typically only set up a single directory for auto-registration, so those three elements would only need to apply to that one directory only. If you ever tried to do more (for example, now that we have an autoloading loader, configure one directory to use the autoloder and another not to), things would fall apart.&lt;/p&gt;
&lt;p&gt;Things brings us to the rich component directory configuration, and indeed the introduction of a ”Component Directory” as a first-class concept within dry-system. Here’s how a container setup would look now:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;class MyApp::Container &amp;lt; Dry::System::Container
  configure do |config|
    config.root = __dir__

    config.component_dirs.add &amp;quot;lib&amp;quot; do |dir|
      dir.auto_register = proc do |component|
        !component.path.match?(%r{/entities/})
      end
      dir.add_to_load_path = false
      dir.loader = Dry::System::Loader::Autoloading
      dir.default_namespace = &amp;quot;my_app&amp;quot;

      # Also available, `dir.memoize`, accepting a boolean or proc
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the behavior for handling a given component directory can be configured on that directory and that directory alone. In the above example, another component diretory could be added with diametrically opposed settings to the first, and everything will still be dandy!&lt;/p&gt;
&lt;p&gt;As you can also see, the degree of configurability has also increased greatly over the released versions of dry-system. Now you can opt into or out of auto-registration for &lt;em&gt;specific&lt;/em&gt; components by passing a proc to the &lt;code&gt;auto_register&lt;/code&gt; setting. Memoization of registered components can also be enabled, disabled, or configured specifically with the &lt;code&gt;memoize&lt;/code&gt; setting.&lt;/p&gt;
&lt;p&gt;(While you’re here, also check out the dry-configurable change I made to allow &lt;a href="https://github.com/dry-rb/dry-configurable/pull/102"&gt;cloneable setting values&lt;/a&gt;, without which we couldn’t have provided this rich nested API for configuring particular directories)&lt;/p&gt;
&lt;h2&gt;Consistent component loading behavior, including magic comments!&lt;/h2&gt;
&lt;p&gt;With the changes above in place, I could remove the &lt;code&gt;.auto_register!&lt;/code&gt; container class method (&lt;a href="https://github.com/dry-rb/dry-system/pull/157"&gt;done in this pull request&lt;/a&gt;, also with its own lengthy description), which leaves the &lt;code&gt;component_dirs&lt;/code&gt; setting as the &lt;em&gt;only&lt;/em&gt; way to tell dry-system how to load components from source files.&lt;/p&gt;
&lt;p&gt;Not only does this make for an easier to configure container, it also supports a more consistent component loading experience! Now, every configurable aspect of component loading is respected in the container’s two methods of auto-registering components: either via finalizing the container (which loads everything up front and freezes the container) or via lazy-loading (which loads components just in time, and is useful for keeping container load time down when running unit tests or using an interactive console, among other things).&lt;/p&gt;
&lt;p&gt;It also means that magic comments within source files are respected in all cases, where previously, only a subset of comments were considered, and only when finalizing a container, not during lazy-loading.&lt;/p&gt;
&lt;p&gt;This means you can now have a source file like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;# auto_register: false

class MyEntity
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And &lt;code&gt;MyEntity&lt;/code&gt; will &lt;em&gt;never&lt;/em&gt; find its way into your container.&lt;/p&gt;
&lt;p&gt;Or you can have a source file like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;# memoize: true

class MySpecialComponent
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And when the component is registered, it will be memoized automatically.&lt;/p&gt;
&lt;p&gt;Magic comments for dry-system are great, I use them all the time, and now they’re even more powerful!&lt;/p&gt;
&lt;h2&gt;More consistent, easier to understand dry-system internals&lt;/h2&gt;
&lt;p&gt;I’ve worked in the dry-system codebase quite regularly over the last few years, and certain parts have always felt a little too complicated, often leaving me confused, or at least afraid to change them. This is no discredit everyone who worked on dry-system previously! Its an amazing innovation, and its features just grew organically over the years to make it the capable, powerful system it is today!&lt;/p&gt;
&lt;p&gt;However, given I was going to be deep in the code again to implement the changes I wanted, I took the chance to refactor as much as I could. And I’m just delighted in the outcome! For example, check out how &lt;a href="https://github.com/dry-rb/dry-system/blob/4c9c28c07f4369061dd6fc3d7f3263a67d7a8ae4/lib/dry/system/container.rb#L623-L643"&gt;&lt;code&gt;.load_component&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/dry-rb/dry-system/blob/4c9c28c07f4369061dd6fc3d7f3263a67d7a8ae4/lib/dry/system/container.rb#L673-L689"&gt;&lt;code&gt;.load_local_component&lt;/code&gt;&lt;/a&gt; (which are used for lazy-loading components) used to look:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;def load_component(key, &amp;amp;block)
  return self if registered?(key)

  component(key).tap do |component|
    if component.bootable?
      booter.start(component)
    else
      root_key = component.root_key

      if (root_bootable = component(root_key)).bootable?
        booter.start(root_bootable)
      elsif importer.key?(root_key)
        load_imported_component(component.namespaced(root_key))
      end

      load_local_component(component, &amp;amp;block) unless registered?(key)
    end
  end

  self
end

def load_local_component(component, default_namespace_fallback = false, &amp;amp;block)
  if booter.bootable?(component) || component.file_exists?(component_paths)
    booter.boot_dependency(component) unless finalized?

    require_component(component) do
      register(component.identifier) { component.instance }
    end
  elsif !default_namespace_fallback
    load_local_component(component.prepend(config.default_namespace), true, &amp;amp;block)
  elsif manual_registrar.file_exists?(component)
    manual_registrar.(component)
  elsif block_given?
    yield
  else
    raise ComponentLoadError, component
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here’s &lt;a href="https://github.com/dry-rb/dry-system/pull/155/commits/c72545ab37c1915aaa98764b7c90a7c27530b69a"&gt;how they look now&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;def load_component(key)
  return self if registered?(key)

  component = component(key)

  if component.bootable?
    booter.start(component)
    return self
  end

  booter.boot_dependency(component)
  return self if registered?(key)

  if component.file_exists?
    load_local_component(component)
  elsif manual_registrar.file_exists?(component)
    manual_registrar.(component)
  elsif importer.key?(component.root_key)
    load_imported_component(component.namespaced(component.root_key))
  end

  self
end

def load_local_component(component)
  if component.auto_register?
    register(component.identifier, memoize: component.memoize?) { component.instance }
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Just look at that improvement! We went from a pair of methods that &lt;em&gt;always&lt;/em&gt; confused me (with their mixed responsibilities, multiple conditionals and levels of nesting) to a simple top-to-bottom flow in &lt;code&gt;.load_component&lt;/code&gt;, and &lt;code&gt;.load_local_component&lt;/code&gt; reduced to a simple 3-liner with just a single job.&lt;/p&gt;
&lt;p&gt;Weeks later, I’m still marvelling at this. I think it’s one of the best refactorings I’ve ever done.&lt;/p&gt;
&lt;p&gt;These improvements didn’t come on their own. As you might notice there, &lt;code&gt;component&lt;/code&gt; is carrying a lot more of its own weight. This includes a new set of methods for finding and loading components from within component directories (namely &lt;code&gt;Dry::System::Component.locate&lt;/code&gt; and &lt;code&gt;.new_from_component_dir&lt;/code&gt;), and indeed the new &lt;code&gt;Dry::System::ComponentDir&lt;/code&gt; abstraction itself, which together provide the consistent component loading behavior I described above.&lt;/p&gt;
&lt;h2&gt;Dry::System::Loader converted to a class interface&lt;/h2&gt;
&lt;p&gt;One thing I noticed during the work on component loading is that a new &lt;code&gt;Dry::System::Loader&lt;/code&gt; would be instantiated for every component, even through it carried no other state apart from the component itself, so I &lt;a href="https://github.com/dry-rb/dry-system/pull/157/commits/85a2d9e39bc32b60ba4d1f6c931578b972c4f02f"&gt;turned it into a stateless, class-level interface&lt;/a&gt;, and hey presto, we save an object allocation for every component we load.&lt;/p&gt;
&lt;p&gt;This is a breaking change, but hey, so is everything else I’ve described so far! I figure this is the right time to sort these things out before we look to a dry-system 1.0 release sometime in the next few months (which is seeming much more attractive after this round of work!).&lt;/p&gt;
&lt;h2&gt;I appreciated being appreciated 🥺&lt;/h2&gt;
&lt;p&gt;Given how significant my plans were for all these changes, I made sure to keep &lt;a href="https://solnic.codes"&gt;Piotr&lt;/a&gt; and the other maintainers in the loop over those couple of months of work.&lt;/p&gt;
&lt;p&gt;Then, when Piotr reviewed my first finished pull request for this work, he left me &lt;a href="https://github.com/dry-rb/dry-system/pull/155#pullrequestreview-573005098"&gt;the most amazing comment&lt;/a&gt;. I want to repeat it here in full (that is, to take it &lt;a href="https://youtu.be/J5KbMolsUPA?t=34"&gt;straight in the pool room&lt;/a&gt;):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;@timriley thanks for this very detailed description, it made much more sense for me to carefully read it and understand the changes rather than to examine the diff. Since what you did here, conceptually, makes perfect sense to me, AND it resulted in simplified implementation which at the same time makes the library much more powerful, I have nothing to complain about 😆 FWIW - I’ve read the diff and nothing stands out as potentially problematic. I reckon seeing it work in real-world apps will be a much better verification process, it’s a huge change after all.&lt;/p&gt;
&lt;p&gt;This is clearly a huge milestone and I honestly didn’t expect that the lib will be so greatly improved prior 1.0.0, so thank you for this huge effort, really ❤️&lt;/p&gt;
&lt;p&gt;One thing I’ll probably experiment with would be a backward-compatibility shim so that previous methods (that you removed) could still work. This should make it simpler to upgrade, but please tell me if this is a stupid idea.&lt;/p&gt;
&lt;p&gt;I will also upgrade dry-rails!&lt;/p&gt;
&lt;p&gt;Tim, seriously, again, this is an epic refactoring, I’m so happy. You’re taking dry-system to the next level. Thank you! 🚀 🎉 🙇🏻&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;After a full year of labouring away at this stuff, often to uncertain or, frankly, even unknowable ends, a comment like this has just given me the fuel to go another year more. Thank you, Piotr ♥️&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If there’s someone out there in OSS land whose work you appreciate, please take the time to tell them! It might mean more than you think.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Next steps with dry-system, Hanami, and Zeitwerk, and the alpha2 release&lt;/h2&gt;
&lt;p&gt;Now that the bulk of the dry-system work is done, here’s what I’m looking to get done next:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Run some final tests using the dry-system branches within a real application&lt;/li&gt;
&lt;li&gt;Work with Piotr to coordinate related changes to dry-rails (which configures dry-system auto-registration)&lt;/li&gt;
&lt;li&gt;Updating Hanami to configure &lt;code&gt;component_dirs&lt;/code&gt; within its own dry-system containers&lt;/li&gt;
&lt;li&gt;Then work out how to enable Zeitwork within Hanami and use the autoloading loader for its component directories by default, while still providing a clean way for application authors to opt out if they’d rather use traditional &lt;code&gt;require&lt;/code&gt;-based code loading&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I expect this will take most of February. Once this is done, we’ll finally be in the clear for a 2.0.0.alpha2 release of Hanami. Focusing on Zeitwerk and dry-system has pushed it back a couple of months, but I hope everyone will agree it was worth the wait!&lt;/p&gt;
&lt;h2&gt;Thank you to my sponsors! 🙌🏼&lt;/h2&gt;
&lt;p&gt;Thanks to my GitHub sponsorts for your continuing support! If you’re reading this and would like to support my work in making Hanami 2.0 a reality, &lt;a href="https://github.com/sponsors/timriley"&gt;I’d really appreciate your support&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;See you all next month!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Open source status update, December 2020</title>
    <link rel="alternate" href="https://timriley.info/writing/2021/01/06/open-source-status-update-december-2020"/>
    <id>https://timriley.info/writing/2021/01/06/open-source-status-update-december-2020</id>
    <published>2021-01-06T11:50:00+00:00</published>
    <updated>2021-01-06T11:50:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Happy new year! Before we get too far through January, here’s the recap of my December in OSS.&lt;/p&gt;
&lt;h2&gt;Advent of Code 2020 (in Go!)&lt;/h2&gt;
&lt;p&gt;This month started off a little differently to usual. After spending some time book-learning about Go, I decided to &lt;a href="https://github.com/timriley/aoc2020"&gt;try the Advent of Code for the first time&lt;/a&gt; as a way to build some muscle memory for a new language. And gosh, it was a lot of fun! Turns out I like programming and problem-solving, go figure. After ~11 days straight, however, I decided to put the effort on hold. I could tell the pace wasn’t going to be sustainable for me (it was a lot of late nights), and I’d already begun to feel pretty comfortable with various aspects of Go, so that’s where I left it for now.&lt;/p&gt;
&lt;h2&gt;Rich dry-system component_dir configuration (and cleanups!)&lt;/h2&gt;
&lt;p&gt;Returning to my regular Ruby business, December was a good month for dry-system. After &lt;a href="/writing/2020/12/07/open-source-status-update-november-2020"&gt;the work in November&lt;/a&gt; to prepare the way for Zeitwerk, I moved onto introducing a new &lt;code&gt;component_dirs&lt;/code&gt; setting, which permits the addition of any number of component directories (i.e. where dry-system should look for your Ruby class files), each with their own specific configurations:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;class MyApp::Container &amp;lt; Dry::System::Container
  configure do |config|
    config.root = __dir__

    config.component_dirs.add &amp;quot;lib&amp;quot; do |dir|
      dir.auto_register = true    # defaults to true
      dir.add_to_load_path = true # defaults to true
      dir.default_namespace = &amp;quot;my_app&amp;quot;
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Along with this, I’m removing the following from &lt;code&gt;Dry::System::Container&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The top-level &lt;code&gt;default_namespace&lt;/code&gt; and &lt;code&gt;auto_register&lt;/code&gt; settings&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;.add_to_load_path!&lt;/code&gt; and &lt;code&gt;.auto_register!&lt;/code&gt; methods&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Together, this means there’ll be only a single place to configure the behaviour related to the loading of components from directories: the singular &lt;code&gt;component_dirs&lt;/code&gt; setting.&lt;/p&gt;
&lt;p&gt;This has been a rationalization I’ve been wanting to make for a long time, and happily, it’s proving to be a positive one: as I’ve been working through the changes, it’s allowed me to simplify some of the gnarlier parts of the gem.&lt;/p&gt;
&lt;p&gt;What all of this provides is the right set of hooks for Hanami to specify the component directories for your app, as well as configure each one to work nicely with Zeitwerk. That’s the end goal, and I suspect we’ll arrive there in late January or February, but in the meantime, I’ve enjoyed the chance to tidy up the internals of this critical part of the Hanami 2.0 underpinnings.&lt;/p&gt;
&lt;p&gt;You can &lt;a href="https://github.com/dry-rb/dry-system/pull/155"&gt;follow my work in progress over in this PR&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Helpers for hanami-view 2.0&lt;/h2&gt;
&lt;p&gt;Towards the end of the month I had a call with Luca (the second in as many months, what a treat!), in which we discussed how we might bringing about full support for view helpers into hanami-view 2.0.&lt;/p&gt;
&lt;p&gt;Of course, these won’t be ”helpers” in quite the same shape you’d expect from Rails or any of the Ruby static site generators, because if you’ve ever heard me talk about dry-view or hanami-view 2.0 (&lt;a href="/writing/2020/07/14/philly-rb-talk-on-hanami-view-2-0"&gt;here’s a refresher&lt;/a&gt;), one of its main goals is to help move you from a gross, global soup of unrelated helpers towards view behaviour modelled as focused, testable, well-factored object oriented code.&lt;/p&gt;
&lt;p&gt;In this case, we finished the discussion with a plan, and Luca turned it around within a matter of days, with a quickfire set of PRs!&lt;/p&gt;
&lt;p&gt;First he introduced the concept of &lt;a href="https://github.com/hanami/view/pull/183"&gt;custom anonymous scopes for any view&lt;/a&gt;. A &lt;a href="https://dry-rb.org/gems/dry-view/0.7/scopes/"&gt;scope&lt;/a&gt; in dry-view/hanami-view parlance is the object that provides the total set of methods available to use within the template. For a while we’ve supported &lt;a href="https://dry-rb.org/gems/dry-view/0.7/scopes/#defining-a-scope-class"&gt;defining custom scope classes&lt;/a&gt; to add behavior for a view that doesn’t belong on any one of its particular exposures, but this requires a fair bit of boilerplate, especially if it’s just for a method or two:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;class ArticleViewScope &amp;lt; Hanami::View::Scope
  def custom_method
    # Custom behavior here, can access all scope facilities, e.g. `locals` or `context`
  end
end

class ArticleView &amp;lt; Hanami::View
  config.scope = ArticleViewScope

  expose :article do |slug:|
    # logic to load article here
  end
end

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So to make this eaiser, we now we have this new class-level block:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;class ArticleView &amp;lt; Hanami::View
  expose :article do |slug:|
    # logic to load article here
  end

  # New scope block!
  scope do
    def custom_method
      # Custom behavior here, can access all scope facilities, e.g. `locals` or `context`
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So nice! Also nice? That it was a literal 3-line change to the hanami-view code 😎 Also, also nice? You can still “upgrade” to a fully fledged class if the code ever requires it.&lt;/p&gt;
&lt;p&gt;Along with this, Luca also began &lt;a href="https://github.com/hanami/helpers/pull/166"&gt;adapting the existing range of global helpers&lt;/a&gt; for use in hanami-view 2.0. I may dislike the idea of helpers in general, but truly stateless things like html builders, etc. I’m generally happy to see around, and with the improvements to template rendering we have over hanami-view 1.x, we’ll be able to make these a lot more expressive for Hanami view developers. This PR is just the first step, but I expect we’ll be able to make some quick strides once this is in place.&lt;/p&gt;
&lt;h2&gt;Thank you to my sponsors! 🙌🏼&lt;/h2&gt;
&lt;p&gt;Thank you to my six GitHub sponsorts for your continuing support! If you’re reading this and would like to chip in and help push forward the Ruby web application ecosystem for 2021, &lt;a href="https://github.com/sponsors/timriley"&gt;I’d really appreciate your support&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;See you all next month!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>2020 in review</title>
    <link rel="alternate" href="https://timriley.info/writing/2021/01/04/2020-in-review"/>
    <id>https://timriley.info/writing/2021/01/04/2020-in-review</id>
    <published>2021-01-04T11:55:00+00:00</published>
    <updated>2021-01-04T11:55:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;2020, hey? What a time to finally get back on my ”year in review” horse. It was a calamitous year for many of us, but there’s a lot I’m thankful for from 2020.&lt;/p&gt;
&lt;h2&gt;Work&lt;/h2&gt;
&lt;p&gt;In January I started a new job with Culture Amp, as part of the Icelab closing and the whole team moving across. I couldn’t have found a better place to work: the people are inspiring, I’ve wound up with a great mentor/manager, and the projects are nourishing. There’s so much I can contribute to here, and I know I’m still only scratching the surface.&lt;/p&gt;
&lt;p&gt;A new workplace with it’s own tech ecosystem meant I did a lot of learning for work this year. Among other things, I started working with event sourcing, distributed systems, AWS CDK, and even a little Go as the year came to an end.&lt;/p&gt;
&lt;p&gt;I’m full-time remote with Culture Amp. Under ordinary circumstances (which went out the window before long), this would mean semi-regular visits to the Melbourne office, and I was lucky enough to do that a couple of times in January and February before travel became unworkable. Aside from that, I’ve enjoyed many hours over Zoom working with all my new colleagues.&lt;/p&gt;
&lt;p&gt;And just to tie a bow in the a big year of work-related things, Michael, Max, and I worked through to December putting the finishing touches on the very last Icelab project, &lt;a href="http://navigatesenatecommittees.senate.gov.au"&gt;Navigate Senate Committees&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I’m deeply grateful to be where I am now, to have smoothly closed one chapter of work while opening another that I’m excited to inhabit for many years to come.&lt;/p&gt;
&lt;h2&gt;OSS&lt;/h2&gt;
&lt;p&gt;This year in OSS has been all about Hanami 2.0 development. By the end of 2019, I’d already made the broad strokes required to make dry-system the new core of the framework. 2020 was all about smoothing around the edges. I worked pretty consistently at this throughout the year, focusing on a new technique for frictionless, zero-boilerplate integrated components, view/action integration, a revised approach for configuration, and lately, support for code loading with Zeitwerk.&lt;/p&gt;
&lt;p&gt;Towards the beginning of the year, I decided to follow &lt;a href="https://solnic.codes/2020/03/02/open-source-status-update/"&gt;Piotr’s good example&lt;/a&gt; and &lt;a href="/writing/2020/03/27/open-source-status-update-march-2020/"&gt;write my own monthly OSS status updates&lt;/a&gt;. I published these monthly since, making for 9 in the year (and 10 once I do December’s, once this post is done!). I’m really glad I established this habit. It captures so much of the thinking I put into my work that would otherwise be lost with time, and in the case of the Hanami project, it’s a way for the community to follow along with progress. And I won’t lie, the thought of the upcoming post motivates me to squeeze just a little bit more into each month!&lt;/p&gt;
&lt;p&gt;This year I &lt;a href="/writing/2020/05/07/sharing-my-hanami-2-application-template/"&gt;shared my Hanami 2 application template&lt;/a&gt; as a way to try the framework while it’s still in development. We’re using it for three production services at work and it’s running well.&lt;/p&gt;
&lt;p&gt;Helping to rewrite Hanami for 2.0 has been the biggest OSS project I’ve undertaken, and work on this was a slog at times, but I’m happy I managed to keep a steady pace. I also rounded out the year by being able to catch up with Luca and Piotr for a couple of face to face discussions, which was a delight after so many months of text-only collaboration.&lt;/p&gt;
&lt;p&gt;On the conference side of things, given the travel restrictions, there was a lot less than normal, but I did have a great time at the one event I did attend, RubyConf AU back in February (which seems so long ago now). Sandwiched between the Australian summer bushfires and the onset of the coronavirus, the community here was amazingly lucky with the timing. Aside from this, the increase of virtual events meant I got to &lt;a href="/2020/07/14/philly-rb-talk-on-hanami-view-2-0"&gt;share a talk with Philly.rb&lt;/a&gt; and &lt;a href="/writing/2020/05/21/interviewed-on-githubs-open-source-friday-video-series/"&gt;appear on GitHub’s open source Friday livestream series&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I also &lt;a href="https://github.com/sponsors/timriley"&gt;joined the GitHub sponsors program&lt;/a&gt; in May. I only have a small group of sponsors (I’d love more!), but receiving notice of each one was a true joy.&lt;/p&gt;
&lt;p&gt;And in a first for me, I spent some time working on this year’s Advent of Code! I used it as an opportunity to develop some familiarity with Go. It was great fun! &lt;a href="https://github.com/timriley/aoc2020"&gt;Code is here&lt;/a&gt; if you’re interested.&lt;/p&gt;
&lt;h2&gt;Home &amp;amp; family&lt;/h2&gt;
&lt;p&gt;The ”stay at home” theme of 2020 was a blessing in many ways, because it meant more time with all the people I love. This year I got to join in on Clover learning to read, write, ride a bike, so many things! Iris is as gregarious as ever and definitely keen to begin her school journey this coming year.&lt;/p&gt;
&lt;p&gt;As part of making the most of being home, I also started working out at home in March (thanks, &lt;a href="https://www.youtube.com/user/thebodycoach1"&gt;PE with Joe!&lt;/a&gt;), which I managed to keep up at 5 days/week ever since! I haven’t felt this good in years.&lt;/p&gt;
&lt;p&gt;And to close out, a few other notables:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Started visiting the local library more, I enjoyed reading paper books again&lt;/li&gt;
&lt;li&gt;Some particularly good reads from the year: Stephen Baxter’s &lt;em&gt;Northland&lt;/em&gt; trilogy, Cixin Liu’s &lt;em&gt;The Supernova Era&lt;/em&gt;, Stephen Baxter’s &lt;em&gt;The Medusa Chronicles&lt;/em&gt;, Roger Levy’s &lt;em&gt;The Rig&lt;/em&gt;, Kim Stanley Robinson’s &lt;em&gt;Red Moon&lt;/em&gt;, Mary Robinette Kowal’s &lt;em&gt;The Relentless Moon&lt;/em&gt;, Kylie Maslen’s &lt;em&gt;Show Me Where it Hurts&lt;/em&gt;, and Hugh Howey’s &lt;em&gt;Sand&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Ted Lasso&lt;/em&gt; on Apple TV+ was just great&lt;/li&gt;
&lt;li&gt;Got a Nintendo Switch and played through &lt;em&gt;Breath of the Wild&lt;/em&gt;, a truly spectacular experience&lt;/li&gt;
&lt;li&gt;I think that’s all for now, see you next year!&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Open source status update, November 2020</title>
    <link rel="alternate" href="https://timriley.info/writing/2020/12/07/open-source-status-update-november-2020"/>
    <id>https://timriley.info/writing/2020/12/07/open-source-status-update-november-2020</id>
    <published>2020-12-07T10:50:00+00:00</published>
    <updated>2020-12-07T10:50:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Hello again, dear OSS enthusiasts. November was quite a fun month for me. Not only did I merge all the PRs I outlined in &lt;a href="/2020/11/03/open-source-status-update-october-2020"&gt;October’s status update&lt;/a&gt;, I also got to begin work on an area I’d been dreaming about for months: integrating Hanami/dry-system with Zeitwerk!&lt;/p&gt;
&lt;h2&gt;Added an autoloading loader to dry-system&lt;/h2&gt;
&lt;p&gt;&lt;a href="http://github.com/fxn/zeitwerk"&gt;Zeitwerk&lt;/a&gt; is a configurable autoloader for Ruby applications and gems. The “auto” in autoloader means that, once configured, you should never have to manually &lt;code&gt;require&lt;/code&gt; before referring to the classes defined in the directories managed by Zeitwerk.&lt;/p&gt;
&lt;p&gt;dry-system, on the other hand, was requiring literally every file it encountered, by design! The challenge here was to allow it to work with or without an auto-loader, making either mode a configurable option, ideally without major disruption to the library.&lt;/p&gt;
&lt;p&gt;Fortunately, many of the core &lt;code&gt;Dry::System::Container&lt;/code&gt; behaviours are already separate into individually configurable components, and in the end, all we needed was a new &lt;code&gt;Loader&lt;/code&gt; subclass implementing a 2-line method:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;module Dry
  module System
    class Loader
      # Component loader for autoloading-enabled applications
      #
      # This behaves like the default loader, except instead of requiring the given path,
      # it loads the respective constant, allowing the autoloader to load the
      # corresponding file per its own configuration.
      #
      # @see Loader
      # @api public
      class Autoloading &amp;lt; Loader
        def require!
          constant
          self
        end
      end
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This can be enabled for your container like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;require &amp;quot;dry/system/loader/autoloading&amp;quot;

class MyContainer &amp;lt; Dry::System::Container
  configure do |config|
    config.loader = Dry::System::Loader::Autoloading
    # ...
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Truth is, it did take a fair bit of doing to arrive at this simple outcome. &lt;a href="https://github.com/dry-rb/dry-system/pull/153"&gt;Check out the pull request&lt;/a&gt; for more detail. The biggest underlying change was moving the responsibility for requiring files out of &lt;code&gt;Container&lt;/code&gt; itself and into the &lt;code&gt;Loader&lt;/code&gt; (which is called via each &lt;code&gt;Component&lt;/code&gt; in the container). While I was in there, I took the chance to tweak a few other things too:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Clarified the &lt;code&gt;Container.load_paths!&lt;/code&gt; method by renaming it to &lt;code&gt;add_to_load_path!&lt;/code&gt; (since it is modifying Ruby’s &lt;code&gt;$LOAD_PATH&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Stopped automatically adding the &lt;code&gt;system_dir&lt;/code&gt; to the load path, since with Zeitwerk support, it’s now reasonable to run dry-system without &lt;em&gt;any&lt;/em&gt; of its managed directories being on the load path&lt;/li&gt;
&lt;li&gt;Added a new &lt;code&gt;component_dirs&lt;/code&gt; setting, defaulting to &lt;code&gt;[&amp;quot;lib&amp;quot;]&lt;/code&gt;, which is used to verify whether a given component is ”local” to the container. This check was previously done using the directories previously passed to &lt;code&gt;load_paths!&lt;/code&gt;, which we can’t rely upon now that we’re supporting autoloaders&lt;/li&gt;
&lt;li&gt;Added a new &lt;code&gt;add_component_dirs_to_load_path&lt;/code&gt; setting, defaulting to true, which will automatically add the configured &lt;code&gt;component_dirs&lt;/code&gt; to the load path in an after-configure hook. This will help ease the transition from the previous behaviour, and make dry-system still work nicely when not using an autoloader&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With all of this in place, a full working example with Zeitwerk looks like this. First, the container:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;require &amp;quot;dry/system/container&amp;quot;
require &amp;quot;dry/system/loader/autoloading&amp;quot;

module Test
  class Container &amp;lt; Dry::System::Container
    config.root = Pathname(__dir__).join(&amp;quot;..&amp;quot;).realpath
    config.add_component_dirs_to_load_path = false
    config.loader = Dry::System::Loader::Autoloading
    config.default_namespace = &amp;quot;test&amp;quot;
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then Zeitwerk setup:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;loader = Zeitwerk::Loader.new
loader.push_dir Test::Container.config.root.join(&amp;quot;lib&amp;quot;).realpath
loader.setup
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, given a component “foo_builder”, at lib/test/foo_builder.rb:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;module Test
  class FooBuilder
    def call
      # We can now referencing this constant without a require!
      Entities::Foo.new
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this in place, we can resolve &lt;code&gt;Test::Container[&amp;quot;foo_builder&amp;quot;]&lt;/code&gt;, receive an instance of &lt;code&gt;Test::FooBuilder&lt;/code&gt; as expected, then &lt;code&gt;.call&lt;/code&gt; it to receive our instance &lt;code&gt;Test::Foo&lt;/code&gt;. Tada!&lt;/p&gt;
&lt;p&gt;I’m very happy with how all this came together.&lt;/p&gt;
&lt;h2&gt;Next steps with dry-system&lt;/h2&gt;
&lt;p&gt;Apart from cracking the Zeitwerk nut, this project also gave me the chance to dive into the guts of dry-system after quite a while. There’s quite a bit of tidying up I’d still like to do, which is my plan for the next month or so. I plan to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Make it possible to configure all aspects of each component_dir via a single block passed to the container’s &lt;code&gt;config&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Remove the &lt;code&gt;default_namespace&lt;/code&gt; top-level container setting (since this will now be configured per-component_dir)&lt;/li&gt;
&lt;li&gt;Remove the &lt;code&gt;.auto_register!&lt;/code&gt; method, since our component-loading behaviour requires component dirs to be configured, and this method bypasses that step (until now, it’s only really worked by happenstance)&lt;/li&gt;
&lt;li&gt;Make Zeitwork usable without additional config by providing a plugin that can be activated by a simple &lt;code&gt;use :zeitwerk&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once these are done, I’ll hop up into the Hanami framework layer and get to work on passing the necessary configuration through to its own dry-system container so that it can also work with Zeitwerk out of the box.&lt;/p&gt;
&lt;h2&gt;Hanami core team meeting&lt;/h2&gt;
&lt;p&gt;This month I also had the (rare!) pleasure of catching up with Luca and Piotr in person to discuss our next steps for Hanami 2 development. &lt;a href="https://discourse.hanamirb.org/t/hanami-2-0-core-team-discussion-25-26-november-2020/580"&gt;Read my notes&lt;/a&gt; to learn more. If you’re at all interested in Hanami development (and if you’ve reached this point in my 9th straight monthly update, I assume you are), then this is well worth a read!&lt;/p&gt;
&lt;p&gt;Of particular relevance to the topics above, we’ve decided to defer the next Hanami 2 alpha release until the Zeitwerk integration is in place. This will ensure we have a smooth transition across releases in terms of code loading behaviour (if we released sooner, we’d need to document a particular set of rules for alpha2 but then half of those out the window for alpha3, which is just too disruptive).&lt;/p&gt;
&lt;h2&gt;Thank you to my sponsors!&lt;/h2&gt;
&lt;p&gt;After all this time, I’m still so appreciative of my tiny band of GitHub sponsors. This stuff is hard work, so &lt;a href="https://github.com/sponsors/timriley"&gt;I’d really appreciate your support&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;See you all again next month, by which point we’ll all have a Ruby 3.0 release!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Open source status update, October 2020</title>
    <link rel="alternate" href="https://timriley.info/writing/2020/11/03/open-source-status-update-october-2020"/>
    <id>https://timriley.info/writing/2020/11/03/open-source-status-update-october-2020</id>
    <published>2020-11-03T11:30:00+00:00</published>
    <updated>2020-11-03T11:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;October was the month! I finally got through the remaining tasks standing between me and an Hanami 2.0.0.alpha2 release. Let’s work through the list now!&lt;/p&gt;
&lt;h2&gt;Application views configured with application inflector&lt;/h2&gt;
&lt;p&gt;Now when you subclass &lt;code&gt;Hanami::View&lt;/code&gt; inside an Hanami app, it will now use the application’s configured inflector automatically. This is important because hanami-view uses the inflector to determine the class names for your &lt;a href="https://dry-rb.org/gems/dry-view/0.7/parts/#part-class-resolution"&gt;view parts&lt;/a&gt;, and it’s just plain table stakes for an framework to apply inflections consistently (especially if you’ve configured custom inflection rules).&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://github.com/hanami/view/pull/180"&gt;implementation within hanami-view&lt;/a&gt; was quite interesting, because it was the first time I had to adjust an &lt;code&gt;ApplicationConfiguration&lt;/code&gt; (this one being exposed as &lt;code&gt;config.views&lt;/code&gt; on the &lt;code&gt;Hanami::Application&lt;/code&gt; subclass) to &lt;em&gt;hide&lt;/em&gt; one of its base settings. In this case, it hides the &lt;code&gt;inflector&lt;/code&gt; setting because we know it will be configured with the application’s inflector as part of the &lt;code&gt;ApplicationView&lt;/code&gt; behaviour (to refresh your memory, &lt;code&gt;ApplicationView&lt;/code&gt; is a module that’s mixed in whenever &lt;code&gt;Hanami::View&lt;/code&gt; is subclassed within a namespace managed by a full Hanami application).&lt;/p&gt;
&lt;p&gt;Ordinarily, I’m all in favour of exposing as many settings as possible, but in this case, it didn’t make sense for a view-specific inflector to be independently configurable right alongside the application inflector itself.&lt;/p&gt;
&lt;p&gt;Rest assured, you don’t lose access to this setting entirely, so if you ever have reason to give your views a different inflector, you can go right ahead and directly assign it in a view class:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;module Main
  class View &amp;lt; Hanami::View
    # By default, the application inflector is configured

    # But you can also override it:
    config.inflector = MyCustomInflector
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There was a &lt;a href="https://github.com/hanami/hanami/pull/1081"&gt;counterpart hanami PR for this change&lt;/a&gt;, and I was quite happy to see it all done, because it means we now have consistent handling of both action and view settings: each gem provides their own &lt;code&gt;ApplicationConfiguration&lt;/code&gt; class, which is made accessible via &lt;code&gt;config.actions&lt;/code&gt; and &lt;code&gt;config.views&lt;/code&gt; respectively. This consistency should  make it easier to maintain both of these imported configurations going forward (and, one day, to devise a system for any third party gem to register application-level settings).&lt;/p&gt;
&lt;h2&gt;Application views have their template configured always&lt;/h2&gt;
&lt;p&gt;One aspect of the &lt;code&gt;ApplicationView&lt;/code&gt; behaviour is to automatically configure a template name on each view class. For example, a &lt;code&gt;Main::Views::Articles::Index&lt;/code&gt; would have its template configured as &lt;code&gt;&amp;quot;articles/index&amp;quot;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is great, but there was an missing piece from the implementation. It assumed that your view hierarchy would always include an abstract base class defined within the application:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;module Main
  # Abstract base view
  class View &amp;lt; Hanami::View
  end

  module Views
    module Articles
      # Concrete view
      class Index &amp;lt; View
      end
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Under this assumption, the base view would never have its template automatically configured. That makes sense in the above arrangement, but if you ever wanted to directly inherit from &lt;code&gt;Hanami::View&lt;/code&gt; for a single concrete view (and I can imagine cases where this would make sense), you’d lose the nice template name inference!&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/hanami/view/pull/181"&gt;With this PR&lt;/a&gt;, this limitation is no more: every &lt;code&gt;ApplicationView&lt;/code&gt; has a template configured in all circumstances.&lt;/p&gt;
&lt;h2&gt;Application views are configured with a Part namespace&lt;/h2&gt;
&lt;p&gt;Keeping with the theme of improving hanami-view integration, another gap I’d noticed was that application views are not automatically configured with a &lt;a href="https://dry-rb.org/gems/dry-view/0.7/parts/#defining-a-part-class"&gt;part namespace&lt;/a&gt;. This meant another wart if you wanted to use this feature:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;require &amp;quot;main/views/parts&amp;quot;

module Main
  class View &amp;lt; Hanami::View
    # Ugh, I have to _type_ all of this out, _by hand?_
    config.part_namespace = Views::Parts
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Not any more! &lt;a href="https://github.com/hanami/view/pull/182"&gt;As of this PR&lt;/a&gt;, we now have a &lt;code&gt;config.views.parts_path&lt;/code&gt; application-level setting, with a default value of &lt;code&gt;&amp;quot;views/parts&amp;quot;&lt;/code&gt;. When an &lt;code&gt;ApplicationView&lt;/code&gt; is activated, it will take this value, convert it into a module (relative to the view’s application or slice namespace), and assign it as the view’s &lt;code&gt;part_namespace&lt;/code&gt;. This would see any view defined in &lt;code&gt;Main&lt;/code&gt; having &lt;code&gt;Main::Views::Parts&lt;/code&gt; automatically set as its part namespace. Slick!&lt;/p&gt;
&lt;h2&gt;Security-related default headers restored&lt;/h2&gt;
&lt;p&gt;Sticking with configuration, but moving over to hanami-controller, &lt;code&gt;Hanami::Action&lt;/code&gt; subclasses within an Hanami app (that is, any &lt;code&gt;ApplicationAction&lt;/code&gt;) now have &lt;a href="https://github.com/hanami/controller/pull/336"&gt;these security-related headers configured out of the box&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;X-Frame-Options: DENY&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;X-Content-Type-Options: nosniff&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;X-XSS-Protection: 1; mode=block&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Content-Security-Policy:&lt;/code&gt; &lt;em&gt;&lt;a href="https://github.com/hanami/controller/pull/336/files#diff-fcfb514bd1756b038ab734a8223067a66d87794d98fb70c877376b95286b843b"&gt;it’s long, just go check the code&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are set on the &lt;code&gt;config.actions.default_headers&lt;/code&gt; application-level setting, which you can also tweak to suit your requirements.&lt;/p&gt;
&lt;p&gt;Previously, these were part of a bespoke one-setting-per-header arrangement in the &lt;code&gt;config.security&lt;/code&gt; application-level setting namespace, but I think this new arrangement is both easier to understand and much more maintainable, so I was happy to &lt;a href="https://github.com/hanami/hanami/pull/1085"&gt;drop that whole class from hanami&lt;/a&gt; as part of rounding this out this work.&lt;/p&gt;
&lt;h2&gt;Automatic cookie support based on configuration&lt;/h2&gt;
&lt;p&gt;The last change I made to hanami-controller was to move the &lt;code&gt;config.cookies&lt;/code&gt; application-level setting, which was defined in the hanami gem, directly into the &lt;code&gt;config.actions&lt;/code&gt; namespace, which is defined inside hamami-controller, much closer to the related behaviour.&lt;/p&gt;
&lt;p&gt;We now also automatically include the &lt;code&gt;Hanami::Action::Cookies&lt;/code&gt; module into any &lt;code&gt;ApplicationAction&lt;/code&gt; if cookies are enabled. This removes yet another implmentation detail and piece of boilerplace that users would otherwise need to consider when building their actions. I’m really happy with how the &lt;code&gt;ApplicationAction&lt;/code&gt; idea is enabling this kind of integration in such a clean way.&lt;/p&gt;
&lt;p&gt;Check out the finer details in &lt;a href="https://github.com/hanami/controller/pull/337"&gt;the PR to hanami-controller&lt;/a&gt; and witness the &lt;a href="https://github.com/hanami/hanami/pull/1086"&gt;corresponding code removal&lt;/a&gt; from hanami itself.&lt;/p&gt;
&lt;h2&gt;Released a minimal application template&lt;/h2&gt;
&lt;p&gt;It’s been a while now since I released my original &lt;a href="https://github.com/timriley/hanami-2-application-template"&gt;Hanami 2 application template&lt;/a&gt;, which still serves as a helpful base for traditional all-in-one web applications.&lt;/p&gt;
&lt;p&gt;But this isn’t the only good use for Hanami 2! I think it can serve as a helpful base for &lt;em&gt;any&lt;/em&gt; kind of application. When I had a colleague ask me on the viability of Hanami to manage a long-running system service, I wanted to demonstrate how it could look, so I’ve now released an &lt;a href="https://github.com/timriley/hanami-2-minimal-application-template"&gt;Hanami 2 &lt;em&gt;minimal&lt;/em&gt; application template&lt;/a&gt;. This one is fully stripped back: nothing webby at all, just a good old &lt;code&gt;lib/&lt;/code&gt; and a &lt;code&gt;bin/app&lt;/code&gt; to demonstrate an entry point. I think it really underscores the kind of versatility I want to achieve with Hanami 2. Go check it out!&lt;/p&gt;
&lt;h2&gt;Gave dry-types a nice require-time performance boost&lt;/h2&gt;
&lt;p&gt;Last but not least, one evening I was investigating just how many files were required as one of my applications booted. I noticed an unusually high number of concurrent-ruby files being required. Turns out this was an unintended consequence of requiring dry-types. &lt;a href="https://github.com/dry-rb/dry-types/pull/406"&gt;One single-line PR later&lt;/a&gt; and now a &lt;code&gt;require &amp;quot;dry/types&amp;quot;&lt;/code&gt; will load 242 fewer files!&lt;/p&gt;
&lt;h2&gt;Savouring this moment&lt;/h2&gt;
&lt;p&gt;It’s taken quite some doing to get to this moment, where an Hanami 2.0.0.alpha2 release finally feels feasible. As you’d detect from my previous posts, it’s felt tantalisingly close for every one of the last few months. As you’d also detect from &lt;em&gt;this&lt;/em&gt; post, the final stretch has involed a lot of focused, fiddly, and let’s face it, not all that exciting work. But these are just the kind of details we need to get right for an excellent framework experience, and I’m glad I could continue for long enough to get these done.&lt;/p&gt;
&lt;p&gt;I’m keenly aware that there’ll be much, much more of this kind of work ahead of us, but for the time being, I’m savouring this interstice.&lt;/p&gt;
&lt;p&gt;In fact, I’ve even given myself a treat: I’ve already started some early explorations of how we could adapt dry-system to fit with &lt;a href="http://github.com/fxn/zeitwerk"&gt;zeitwerk&lt;/a&gt; so that we can reliable autoloading a part of the core Hanami 2 experience. But more on that later ;)&lt;/p&gt;
&lt;h2&gt;Thank you to my sponsors!&lt;/h2&gt;
&lt;p&gt;I now have a &lt;a href="/sponsors"&gt;sponsors page&lt;/a&gt; on this here site, which contains a small list of people to whom I am very thankful. I’d really love for you to join their numbers and &lt;a href="https://github.com/sponsors/timriley"&gt;sustain my open source work&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As for the next month, new horizons await: I’ll start working out some alpha2 release notes (can you believe it’s been nearly 2 years of work?), as well as continuing on the zeitwerk experiment.&lt;/p&gt;
&lt;p&gt;See you all again, same place, same time!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Open source status update, September 2020</title>
    <link rel="alternate" href="https://timriley.info/writing/2020/10/06/open-source-status-update-september-2020"/>
    <id>https://timriley.info/writing/2020/10/06/open-source-status-update-september-2020</id>
    <published>2020-10-06T11:32:00+00:00</published>
    <updated>2020-10-06T11:32:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Well, didn’t September just fly by? &lt;a href="https://timriley.info/writing/2020/08/31/open-source-status-update-august-2020/"&gt;Last month&lt;/a&gt; I predicted I’d get through the remaining tasks standing in the way of an Hanami 2.0.0.alpha2 release, and while I made some inroads, I didn’t quite get there. At this point I’ve realised that after many consecutive months of really strong productivity on OSS work (which for me right now is done entirely on nights and weekends), a downtick of a couple of months was inevitable.&lt;/p&gt;
&lt;p&gt;Anyway, let’s take a look at what I did manage to achieve!&lt;/p&gt;
&lt;h2&gt;Reintroduced CSRF protection module to hanami-controller&lt;/h2&gt;
&lt;p&gt;Sometime during the upheaval that was hanami and hanami-controller’s initial rewrite for 2.0.0, we lost the important &lt;code&gt;CSRFProtection&lt;/code&gt; module. I’ve &lt;a href="https://github.com/hanami/controller/pull/327"&gt;brought it back now&lt;/a&gt;, this time locating it within hanami-controller instead of hanami, so it can live alongside the action classes that are meant to include it.&lt;/p&gt;
&lt;p&gt;For now, you can manually include it in your action classes:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;require &amp;quot;hanami/action&amp;quot;
require &amp;quot;hanami/action/csrf_protection&amp;quot;

class MyAction &amp;lt; Hanami::Action
  include Hanami::Action::CSRFProtection
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And if you need to manually opt out of the protections for any reason, you can implement this method in any one of your action classes:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;def verify_csrf_token?(req, res)
  false
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Either way, I encourage you to &lt;a href="https://github.com/hanami/controller/pull/327/files"&gt;check out the code&lt;/a&gt;; it’s a simple module and very readable.&lt;/p&gt;
&lt;h2&gt;Started on automatic enabling of CSRF protection&lt;/h2&gt;
&lt;p&gt;For a &lt;em&gt;batteries included&lt;/em&gt; experience, having to manually include the &lt;code&gt;CSRFProtection&lt;/code&gt; module isn’t ideal. So I’m currently working to make it so the module is automatically included when the Hanami application has sessions enabled. This is close to being done already, in this &lt;a href="https://github.com/hanami/controller/pull/332"&gt;hanami-controller PR&lt;/a&gt; and this &lt;a href="https://github.com/hanami/hanami/pull/1078"&gt;counterpart hanami PR&lt;/a&gt;. I’m also taking this an an opportunity to move all session-related config away from hanami and into hanami-controller, which I think is a more rational location both in terms of end-user understandability and future maintainability.&lt;/p&gt;
&lt;p&gt;We’ll see this one fully wrapped up in next month’s update :)&lt;/p&gt;
&lt;h2&gt;Improving preservation of state in dry/hanami-view context objects&lt;/h2&gt;
&lt;p&gt;This one was a doozy. It started with my &lt;a href="https://github.com/timriley/my-site/commit/fa72585bd73288c1824be1e2f35ac2025eeb42fb"&gt;fixing a bug in my site&lt;/a&gt; to do with missing page titles, and then realising that it only partially fixed the problem. I wasn’t doing anything particularly strange in my site, just following a pattern of setting page-specific titles in individual templates:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- page_title &amp;quot;Writing&amp;quot;

h1 Writing
  / ... rest of page
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then rendering the title within the layout:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;html
  head
    title = page_title
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both of these &lt;code&gt;page_title&lt;/code&gt; invocations called a single method on my view &lt;a href="https://dry-rb.org/gems/dry-view/0.7/context/"&gt;context object&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;def page_title(new_title = Undefined)
  if new_title == Undefined
    [@page_title, settings.site_title].compact.join(&amp;quot; | &amp;quot;)
  else
    @page_title = new_title
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pretty straightforward, right? However, because the context is reinitialized from a base object for each different rendering environment (first the template, and then the layout), that &lt;code&gt;@page_title&lt;/code&gt; we set in the template never goes anywhere else, so it’s not available afterwards in the layout.&lt;/p&gt;
&lt;p&gt;This baffled me for a quite a while, because I’ve written similar &lt;code&gt;content_for&lt;/code&gt;-style helpers in context classes and they’ve always worked without a hitch. Well, it turns out I got kinda lucky in those cases, because I was using a &lt;em&gt;hash&lt;/em&gt; (instead of a direct instance variable) to hold the provided pieces of content, and since hashes (like most objects in Ruby) are passed by reference, that just so happened to permit the same bits of content to be seen from all view context instances.&lt;/p&gt;
&lt;p&gt;Once I made this relisation, I first &lt;a href="https://github.com/timriley/my-site/commit/f9d029178dfeecd36586a7672ab17f1413f1145b"&gt;committed this egregious hack&lt;/a&gt; just to get my site properly showing titles again, and then I mulled over a couple of options for properly fixing this inside hanami-view.&lt;/p&gt;
&lt;p&gt;One option would be to acknowledge this particular use case and adjust the underlying gem to support it, ensuring that &lt;a href="https://github.com/hanami/view/pull/178"&gt;the template context is used to initialize the layout context&lt;/a&gt;. This works, and it’s certainly the smallest possible fix, but I think it papers over the fundamental issue here: the the creation of multiple context instances is a low-level implementation detail and should not be something the user needs to think about. I think a user &lt;em&gt;should&lt;/em&gt; feel free to set an ivar in a context instance and reasonably expect that it’ll be available at all points of the rendering cycle.&lt;/p&gt;
&lt;p&gt;So how do we fix this? The obvious way would be to ensure we create only a single context object, and have it work as required for rendering the both the template and the layout. The challenge here is that we require a different &lt;a href="https://github.com/hanami/view/blob/c4fb06a6b419cc83f2bb7f8b3027b07f03d3f199/lib/hanami/view/render_environment.rb"&gt;&lt;code&gt;RenderEnvironment&lt;/code&gt;&lt;/a&gt; for each of those, so the correct partials can be looked up, whether they’re called from within templates, or within part or scope classes. This is why we took the approach of creating those multiple context objects in the first place, so each one could have an appropriate &lt;code&gt;RenderEnvironment&lt;/code&gt; provided.&lt;/p&gt;
&lt;p&gt;So how do we keep a single context instance but somehow swap around the underlying environment? Well, as a matter of fact, &lt;em&gt;there’s a gem for that.&lt;/em&gt; After discovering this bug, I was inspired and stayed up to midnight &lt;a href="https://github.com/hanami/view/pull/179"&gt;spiking on an approach&lt;/a&gt; that relies upon &lt;a href="https://dry-rb.org/gems/dry-effects/"&gt;dry-effects&lt;/a&gt; and a &lt;a href="https://dry-rb.org/gems/dry-effects/0.1/effects/reader/"&gt;reader effect&lt;/a&gt; to provide the differing &lt;code&gt;render_environment&lt;/code&gt; to a single context object.&lt;/p&gt;
&lt;p&gt;(The other &lt;em&gt;effect&lt;/em&gt; I felt was the extreme tiredness the next day, I’m not the spritely youth I used to be!)&lt;/p&gt;
&lt;p&gt;Anyway, if you haven’t checked out dry-effects, I encourage you to do so: it may help you to discover some novel approaches to certain design challenges. In this case, all we need to do is include the effect module in our context class:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;module Hanami
  class View
    class Context
      # Instance methods can now expect a `render_env` to be available
      include Dry::Effects.Reader(:render_env)
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And ensure we’re wrapping a handler around any code expected to throw the effect:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;module Hanami
  class View
    module StandaloneView
      # This provides `with_render_env`, used below
      include Dry::Effects::Handler.Reader(:render_env)

      def call(format: config.default_format, context: config.default_context, **input)
        # ...

        render_env = self.class.render_env(format: format, context: context)
        template_env = render_env.chdir(config.template)

        # Anything including Dry::Effects.Reader(:render_env) will have access to the
        # provided `template_env` inside this handler block
        output = with_render_env(template_env) {
          render_env.template(config.template, template_env.scope(config.scope, locals))
        }

        # ...
      end
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this in place, we have a design that allows us to use a single context object only for entirety of the render lifecycle. For the simplicity to the user, I think this is a very worthwhile change, and I plan to spend time assessing it in detail this coming month. As Nikita (the author of dry-effects) points out, there’s a performance aspect to consider: although we’re saving ourselves some object allocations here, we now have to dispatch to the handler every time we throw the reader effect for the &lt;code&gt;render_env&lt;/code&gt;. Still, it feels like a very promising direction.&lt;/p&gt;
&lt;h2&gt;Filed issues arising from production Hanami 2 applications&lt;/h2&gt;
&lt;p&gt;Over the month at work, we put the finishing touches on two brand new services built with Hanami 2. This helped us to identify a bunch of rough edges that will need addressing before we’re done with the release. I filed them on our public Trello board:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://trello.com/c/rF8HgBxP"&gt;Need ability to insert middleware before framework-provided ones, e.g. rack_logger&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://trello.com/c/XoduxZxZ"&gt;Add all slice lib/ dirs to LOAD_PATH as early as possible&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://trello.com/c/qExli5cy"&gt;Allow application author to configure application container
Make a Types module automatically available (if possible) inside the Hanami.application.settings block&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://trello.com/c/JQG7oocI"&gt;(dry-system) How can we &lt;code&gt;use :foo&lt;/code&gt; from inside a slice-specific bootable file, when &lt;code&gt;:foo&lt;/code&gt; is an application-level bootable?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://trello.com/c/HDuJeazH"&gt;Properly support conditional loading of slices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://trello.com/c/dh4Wtjea"&gt;Body parsing should be built into hanami-controller, not be (solely) a standalone middleware&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://trello.com/c/KujFWMWw"&gt;&lt;code&gt;accept :json&lt;/code&gt; should be forgiving on requests that have no request bodies (or offer more expansive behavior, encompassing all request methods)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://trello.com/c/oPeUNfC5"&gt;Action enforce_accepted_mime_types callback should return 415 status code upon failure, not 406&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This goes to show how critical it is for frameworks like Hanami to have real-world testing, even at these very early stages of new release development. I’m glad I can also serve in this role, and grateful for keenness and patience of our teams in working with cutting edge software!&lt;/p&gt;
&lt;h2&gt;Fixed accidental memoization of dry-configurable setting values&lt;/h2&gt;
&lt;p&gt;Last but not least, I &lt;a href="https://github.com/dry-rb/dry-configurable/pull/99"&gt;fixed this bug in dry-configurable&lt;/a&gt; that arose from an earlier change I made to have it &lt;a href="https://github.com/dry-rb/dry-configurable/pull/95"&gt;evaluate settings immediately&lt;/a&gt; if a value was provided.&lt;/p&gt;
&lt;p&gt;This was a wonderful little bug to fix, and the perfect encapsulation of why I love programming: we started off with two potentially conflicting use cases, represented as two different test cases (one failing), and had to find a way to satisfy them both while still upholding the integrity of the gem’s overall design. I’m really happy with how this one turned out.&lt;/p&gt;
&lt;h2&gt;🙌🏼 Thanks to my sponsors!&lt;/h2&gt;
&lt;p&gt;This month I was honoured to have a new sponsor come on board. Thank you &lt;a href="https://github.com/svoop"&gt;Sven Schwyn&lt;/a&gt; for your support! If you’d like to give a boost to my open source work, &lt;a href="https://github.com/sponsors/timriley"&gt;please consider sponsoring me on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;See you all next month!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Open source status update, August 2020</title>
    <link rel="alternate" href="https://timriley.info/writing/2020/08/31/open-source-status-update-august-2020"/>
    <id>https://timriley.info/writing/2020/08/31/open-source-status-update-august-2020</id>
    <published>2020-08-31T12:25:00+00:00</published>
    <updated>2020-08-31T12:25:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Oh, hello there, has it been another month already? After my &lt;a href="https://timriley.info/writing/2020/08/03/open-source-status-update-july-2020"&gt;bumper month&lt;/a&gt; in July, August was a little more subdued (I had to devote more energy towards a work project), but I still managed to get a few nice things done.&lt;/p&gt;
&lt;h2&gt;Hanami session configuration back in action&lt;/h2&gt;
&lt;p&gt;In a nice little surprise, I realised that all the building blocks had fallen into place for Hanami’s standard session configuration to begin working again.&lt;/p&gt;
&lt;p&gt;So with a &lt;a href="https://github.com/jodosha/soundeck/pull/12"&gt;couple of lines of config uncommented&lt;/a&gt;, Luca’s “soundeck” demo app has working cookie sessions again. Anyone pulling from my &lt;a href="https://github.com/timriley/hanami-2-application-template"&gt;Hanami 2 application template&lt;/a&gt; will see the same config enabled after &lt;a href="https://github.com/timriley/hanami-2-application-template/commit/65f51083ae74a961f89a718c4bbc7f1d540e02e9"&gt;this commit&lt;/a&gt;, too.&lt;/p&gt;
&lt;h2&gt;Container auto-registration respects application inflector&lt;/h2&gt;
&lt;p&gt;Another small config-related changed I made was to [pass the Hanami 2 application inflector]((https://github.com/hanami/hanami/pull/1069) through to to the dry-system container handling component auto-registration.&lt;/p&gt;
&lt;p&gt;With this in place, if you configure a custom inflection for your app, e.g.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;module MyApp
  class Application &amp;lt; Hanami::Application
    config.inflector do |inflections|
      inflections.acronym &amp;quot;NBA&amp;quot;
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then it will be respected when your components are auto-registerd, so you can use your custom inflections as part of your module namespacing.&lt;/p&gt;
&lt;p&gt;With the setup above, if I had a file called &lt;code&gt;lib/my_app/nba_jam/cheat_codes.rb&lt;/code&gt;, the container would rightly expect it to define &lt;code&gt;MyApp::NBAJam::CheatCodes&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I’m delighed to see this in place. Having to deal with awkward namespaces (e.g. &lt;code&gt;SomeApi&lt;/code&gt; instead of &lt;code&gt;SomeAPI&lt;/code&gt;) purely because the framework wasn’t up to the task of handling it has long been an annoyance to me (these details &lt;em&gt;matter!&lt;/em&gt;), and I’m really glad that Hanami 2 will make this a piece of cake.&lt;/p&gt;
&lt;p&gt;This outcome is also a testament to the design approach we’ve taken for all the underpinning dry-rb gems. By ensuring important elements like an inflector were represented by a dedicated abstraction - and a configurable one at that - it was so easy for Hanami to provide its own inflector and see it used wherever necessary.&lt;/p&gt;
&lt;h2&gt;Customisable standard application components&lt;/h2&gt;
&lt;p&gt;Every Hanami 2 application will come with a few standard components, like a logger, inflector, and your settings. These are made available as registrations in your application container, e.g. &lt;code&gt;Hanami.application[&amp;quot;logger&amp;quot;]&lt;/code&gt;, to make them easy to auto-inject into your other application components as required.&lt;/p&gt;
&lt;p&gt;While it was my intention for these standard components to be replaceable by your own custom versions, what we learnt this month is that this was practically impossible! There was just no way to register your own replacements early enough for them to be seen during the application boot process.&lt;/p&gt;
&lt;p&gt;After spending a morning trying to get this to work, I decided that this situation was in fact pointing to a missing feature in dry-system. So I went ahead and added &lt;a href="https://github.com/dry-rb/dry-system/pull/151"&gt;support for multiple boot file directories&lt;/a&gt; in dry-system. Now you can configure an array of directories on this new &lt;code&gt;bootable_dirs&lt;/code&gt; setting:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;class MyContainer &amp;lt; Dry::System::Container
  config.bootable_dirs = [
    &amp;quot;config/boot/custom_components&amp;quot;,
    &amp;quot;config/boot/standard_components&amp;quot;
  ]
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the container locates a bootable component, it will work with these &lt;code&gt;bootable_dirs&lt;/code&gt; just like you’d expect your shell to work with its &lt;code&gt;$PATH&lt;/code&gt;: it will search the directories, in order, and the first found instance of your component will be used.&lt;/p&gt;
&lt;p&gt;With this in place, I &lt;a href="https://github.com/hanami/hanami/pull/1070"&gt;updated Hanami to to configure its own bootable_dirs&lt;/a&gt; and use its own directory for defining its standard components. The default directory is secondary to the directory specified for the application’s own bootable components, so this means if you want to replace Hanami’s standard &lt;code&gt;logger&lt;/code&gt;, you can just create a &lt;code&gt;config/boot/logger.rb&lt;/code&gt; and you’ll be golden!&lt;/p&gt;
&lt;h2&gt;Started rationalising flash&lt;/h2&gt;
&lt;p&gt;Last month when I was digging into some session-related details of the framework, I realised that the &lt;code&gt;flash&lt;/code&gt; we inherited from Hanami 1 was pretty hard to work with. It didn’t seem to behave in the same way we expect a flash to work, e.g. to automatically preserve added messages and make them available to the next request. The code was also too complex. This is a solved problem, so I looked around and started &lt;a href="https://github.com/hanami/controller/pull/326"&gt;rationalising the Hanami 2 flash system&lt;/a&gt; based on code from Roda’s flash plugin. I haven’t had the chance to finish this yet, but it’ll be first cab off the rank in September.&lt;/p&gt;
&lt;h2&gt;Plans for September&lt;/h2&gt;
&lt;p&gt;With a concerted effort, I think I could make September the month I knock off all my remaining tasks for a 2.0.0.alpha2 release. It’s been tantalisingly close for a while, but I think it could really happen!&lt;/p&gt;
&lt;p&gt;Time to get stuck into it.&lt;/p&gt;
&lt;h2&gt;🙌🏼 Thanks to my sponsors!&lt;/h2&gt;
&lt;p&gt;Lastly, my continued thanks to my little posse of GitHub sponsors for your continued support, especially &lt;a href="https://github.com/tak1n"&gt;Benjamin Klotz&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I’d really love for you to join the gang. If you care about a healthy, diverse future for Ruby application developers, please consider &lt;a href="https://github.com/sponsors/timriley"&gt;sponsoring my open source work&lt;/a&gt;!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Open source status update, July 2020</title>
    <link rel="alternate" href="https://timriley.info/writing/2020/08/03/open-source-status-update-july-2020"/>
    <id>https://timriley.info/writing/2020/08/03/open-source-status-update-july-2020</id>
    <published>2020-08-03T12:30:00+00:00</published>
    <updated>2020-08-03T12:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;July was a great month for my work on Hanami!&lt;/p&gt;
&lt;p&gt;After a feeling like I stalled a little &lt;a href="/writing/2020/06/28/open-source-status-update-june-2020/"&gt;in June&lt;/a&gt;, this time around I was able to get to the very end of my initial plans for application/action/view integration, as well as improve clarity around comes next for our 2.0 efforts overall.&lt;/p&gt;
&lt;h2&gt;Getting closer on extensible component configuration&lt;/h2&gt;
&lt;p&gt;Whenever I’ve worked on integrating the configuration of the standalone Hanami components (like hanami-controller or view) into the application core, I’ve asked myself, “if the app author chose &lt;em&gt;not&lt;/em&gt; to use this component, would the application-level configuration still make sense?” I wanted to avoid baking in too many assumptions about hanami-controller or hanami-view particulars into the config that you can access on &lt;code&gt;Hanami.application.config&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In the long term, I hope we can build a clean extensions API so that component gems can cleanly register themselves with the framework and expose their configuration that way. In the meantime, however, we need to take a practical, balanced approach, to make it easy for hanami-controller and hanami-view to do their job while still honouring that longer-term goal in spirit.&lt;/p&gt;
&lt;p&gt;I’m happy to report that I think I’ve found a pretty good arrangement for all of this! You can see it in action with we we load the &lt;code&gt;application.config.actions&lt;/code&gt; configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;module Hanami
  class Configuration
    attr_reader :actions

    def initialize(env:)
      # ...

      @actions = begin
        require_path = &amp;quot;hanami/action/application_configuration&amp;quot;
        require require_path
        Hanami::Action::ApplicationConfiguration.new
      rescue LoadError =&amp;gt; e
        raise e unless e.path == require_path
        Object.new
      end
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this approach, if the hanami-controller gem is available, then we’ll make its own &lt;code&gt;ApplicationConfiguration&lt;/code&gt; available as &lt;code&gt;application.config.actions&lt;/code&gt;. This means the hanami gem itself doesn’t need to know &lt;em&gt;anything else&lt;/em&gt; about how action configuration should be handled at the application level. This kind of detail makes much more sense to live in the hanami-controller gem, where those settings will actually be used.&lt;/p&gt;
&lt;p&gt;Let’s take a look at that:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;module Hanami
  class Action
    class ApplicationConfiguration
      include Dry::Configurable

      # Define settings that are _specific_ to application integration
      setting :name_inference_base, &amp;quot;actions&amp;quot;
      setting :view_context_identifier, &amp;quot;view.context&amp;quot;
      setting :view_name_inferrer, ViewNameInferrer
      setting :view_name_inference_base, &amp;quot;views&amp;quot;

      # Then clone all the standard settings from Action::Configuration
      Configuration._settings.each do |action_setting|
        _settings &amp;lt;&amp;lt; action_setting.dup
      end

      def initialize(*)
        super

        # Apply defaults to standard settings for use within an app
        config.default_request_format = :html
        config.default_response_format = :html
      end

      # ...
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This configuration class:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;(a) Defines settings specifically for the &lt;code&gt;Hanami::Action&lt;/code&gt; behaviour activated only when used within a full Hanami app&lt;/li&gt;
&lt;li&gt;(b) Clones the standard settings from &lt;code&gt;Hanami::Action::Configuration&lt;/code&gt; (which are there for standalone use) and makes them available&lt;/li&gt;
&lt;li&gt;(c) Then tweaks some of the default values of those standard settings, to make them fit better with the full application experience&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This feels like an ideal arrangement. It keeps the &lt;code&gt;ApplicationConfiguration&lt;/code&gt; close to the code in &lt;code&gt;ApplicationAction&lt;/code&gt;, which uses those new settings. It means that all the application integration code can live together and evolve in sync.&lt;/p&gt;
&lt;p&gt;Further, because &lt;code&gt;Hanami::Action::ApplicationConfiguration&lt;/code&gt; exposes a superset of the base &lt;code&gt;Hanami::Action::Configuration&lt;/code&gt; settings, we can make it so any &lt;code&gt;ApplicationAction&lt;/code&gt; (i.e. any action defined within an Hanami app) automatically &lt;em&gt;configures every aspect of itself&lt;/em&gt; based on whatever settings are available on the application!&lt;/p&gt;
&lt;p&gt;So for the application author, the result of all this groundwork should be a blessedly unsurprising experience: if they’re using hanami-controller, then they can go and tweak whatever settings they want right there on &lt;code&gt;Hanami.application.config.actions&lt;/code&gt;, both the basic action settings as well as the integration-specific settings (though most of the time, I hope the defaults should be fine!).&lt;/p&gt;
&lt;p&gt;When we do eventually implement an extensions API, we can at that point just remove the small piece special-case code from &lt;code&gt;Hanami::Application::Configuration&lt;/code&gt; and replace it with hanami-controller reigstering itself and making its settings available.&lt;/p&gt;
&lt;p&gt;If you’re interested in following these changes in more detail, check out &lt;a href="https://github.com/hanami/hanami/pull/1068"&gt;hanami/hanami#1068&lt;/a&gt; for the change from the framework side, and then &lt;a href="https://github.com/hanami/controller/pull/321"&gt;hanami/controller#321&lt;/a&gt; for the &lt;code&gt;ApplicationConfiguration&lt;/code&gt; and &lt;a href="https://github.com/hanami/controller/pull/320"&gt;hanami/controller#320&lt;/a&gt; for the self-configuring application actions. (I also took an initial pass at this in &lt;a href="https://github.com/hanami/hanami/pull/1065"&gt;hanami/hanami#1065&lt;/a&gt;, but that was surpassed by all the changes linked previously - I took small steps, and learnt along the way!)&lt;/p&gt;
&lt;p&gt;I also made matching changes to view configuration. All the same ideas apply: if you have hanami-view loaded, you’ll find an &lt;code&gt;Hanami.application.config.views&lt;/code&gt; with all the view settings you need, and then application views will self-configure themselves based on those values! Check out &lt;a href="https://github.com/hanami/hanami/pull/1066"&gt;hanami/hanami#1066&lt;/a&gt; and &lt;a href="https://github.com/hanami/view/pull/176"&gt;hanami/view#176&lt;/a&gt; for the implementation.&lt;/p&gt;
&lt;h2&gt;Fixed &lt;code&gt;handle_exception&lt;/code&gt; inside actions&lt;/h2&gt;
&lt;p&gt;One of the settings on Hanami::Action classes is its array of &lt;code&gt;config.handled_exceptions&lt;/code&gt;, which you can also supply one-by-one through the &lt;code&gt;config.handle_exception&lt;/code&gt; convenience method.&lt;/p&gt;
&lt;p&gt;It turns out another &lt;code&gt;handle_exception&lt;/code&gt; still existed as a class method, clearly an overhang of the previous action behaviour. &lt;a href="https://github.com/hanami/controller/pull/323"&gt;I took care of removing that&lt;/a&gt;, so now there should be no confusion whenever action authors configure this behaviour (especially since the old class-level method didn’t work with inheritence).&lt;/p&gt;
&lt;h2&gt;Automatically infer paired views for actions&lt;/h2&gt;
&lt;p&gt;Believe it or not, the work so far only took me about half-way through the month! This left enough time to roll through all my remaining “minimum viable action/view integration” tasks!&lt;/p&gt;
&lt;p&gt;First up was inferring paired views for actions. The idea here is that if you’re building an Hanami 2 app and following the sensible convention of matching your view and action names, then the framework can take care of auto-injecting an action’s view for you.&lt;/p&gt;
&lt;p&gt;So if you had an action class like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;class Main
  module Actions
    module Articles
      class Index &amp;lt; Main::Action
        include Deps[view: &amp;quot;views.articles.index&amp;quot;]

        def handle(request, response)
          response.render view
        end
      end
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, you can drop that &lt;code&gt;include Deps[view: &amp;quot;…&amp;quot;]&lt;/code&gt; line. A matching view will now automatically be available as the &lt;code&gt;view&lt;/code&gt; for the action!&lt;/p&gt;
&lt;p&gt;This works even for RESTful-style actions too. For example, an &lt;code&gt;Actions::Articles::Create&lt;/code&gt; action would have an instance of &lt;code&gt;Views::Articles::New&lt;/code&gt; injected, since that’s the view you’d want to re-render in the case of presenting a form with errors.&lt;/p&gt;
&lt;p&gt;If you need it, you can also configure your own custom view inference by providing your own &lt;code&gt;Hanami.application.config.actions.view_name_inferrer&lt;/code&gt; object.&lt;/p&gt;
&lt;p&gt;To learn more about the implementation, &lt;a href="https://github.com/hanami/controller/pull/322"&gt;check out the PR&lt;/a&gt; and then this &lt;a href="https://github.com/hanami/controller/pull/325"&gt;follow-up fix&lt;/a&gt; (in which I learnt I should &lt;em&gt;always&lt;/em&gt; write integration tests that exercise at least two levels of inheritance).&lt;/p&gt;
&lt;h2&gt;Automatically render an action’s view&lt;/h2&gt;
&lt;p&gt;With the paired view inference above, our action class is now looking like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;class Main
  module Actions
    module Articles
      class Index &amp;lt; Main::Action
        def handle(request, response)
          response.render view
        end
      end
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But we can do better. For simple actions like this, we shouldn’t have to write that “please render your own view” boilerplate.&lt;/p&gt;
&lt;p&gt;So how about just this?&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;class Main
  module Actions
    module Articles
      class Index &amp;lt; Main::Action
      end
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, any call to this action will automatically render its paired view, passing through all request params for the view to handle as required.&lt;/p&gt;
&lt;p&gt;And the beauty of this change was that, after all the groundwork laid so far, &lt;a href="https://github.com/hanami/controller/pull/324"&gt;it was only a single line of code!&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As Kent Beck &lt;a href="https://twitter.com/kentbeck/status/250733358307500032"&gt;has said&lt;/a&gt;, “for each desired change, make the change easy (warning: this may be hard), then make the easy change.” The easy change indeed. Moments like these are why I love being a programmer :)&lt;/p&gt;
&lt;h2&gt;Integrated, application-aware view context, and some helpers&lt;/h2&gt;
&lt;p&gt;Let’s keep going! This month I also gave the “automatic application integration” treatment to &lt;code&gt;Hanami::View::Context&lt;/code&gt;. Now when you inherit from this within your application, it’ll be all set up to accept the request/response details that &lt;code&gt;Hanami::Action&lt;/code&gt; is already passing through whenever you render a view from within an action.&lt;/p&gt;
&lt;p&gt;With these in place, we’re now providing useful methods like &lt;code&gt;session&lt;/code&gt; and &lt;code&gt;flash&lt;/code&gt; for use within your view-related classes and templates. If you want to add additional behaviour, you can now access &lt;code&gt;request&lt;/code&gt; and &lt;code&gt;response&lt;/code&gt; on your application’s view context class, too.&lt;/p&gt;
&lt;p&gt;While I was doing this, I also took the opportunity to hash out some initial steps towards a standard library of view context helpers with an &lt;code&gt;Hanami::View::ContextHelpers::ContentHelpers&lt;/code&gt; module. If you mix this into your app’s view context class, you’ll also have a convenient &lt;code&gt;content_for&lt;/code&gt; method that works like you’d expect. Longer term, I’ll look to move this into the hanami-helpers gem and update the existing helpers to work with the new views, including providing a nice way to opt in to whatever specific helpers you want your application to expose.&lt;/p&gt;
&lt;p&gt;In the meantime, &lt;a href="https://github.com/hanami/view/pull/177"&gt;check out all this fresh view context goodness here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Hanami 2.0 application template is up to date&lt;/h2&gt;
&lt;p&gt;After all of this, I took a moment to update my &lt;a href="https://github.com/timriley/hanami-2-application-template"&gt;Hanami 2 application template&lt;/a&gt;. If you create an app from this template today, all the features I’ve described above will be in place and ready for you to try. I also enabled rack session middleware in the app, because this is a requirement for the flash and session objects as well as CSRF protection.&lt;/p&gt;
&lt;h2&gt;Hanami 2.0 Trello board&lt;/h2&gt;
&lt;p&gt;Last but not least, as I was finally seeing some clear air ahead, I took a chance to bring our &lt;a href="https://trello.com/b/lFifnBti/hanami-20"&gt;Hanami 2.0 Trello board&lt;/a&gt; up to date!&lt;/p&gt;
&lt;p&gt;As it currently stands, I have just 7-8 items left before I think we’ll be ready for the long-awaited Hanami 2.0.0.alpha2 release.&lt;/p&gt;
&lt;p&gt;Beyond that, I hope the board will help everyone coordinate the remainder of our work in preparing 2.0.0. At very least, I’m already feeling much better knowing we’re a little more &lt;em&gt;oranized&lt;/em&gt;, with a single, up-to-date place where it’s easy to see what’s next as well as add new items whenever we think of them (I’ve no doubt that plenty more little things will crop up).&lt;/p&gt;
&lt;h2&gt;Plans for August&lt;/h2&gt;
&lt;p&gt;So that was July. What. A. Month.&lt;/p&gt;
&lt;p&gt;I tell you, I was &lt;em&gt;exceedingly&lt;/em&gt; happy to have finally completed my “get views and actions properly working together for the first time” list, which turns out to have taken the better past of &lt;strong&gt;five months&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For August, I plan to knock out as many of my remaining 2.0.0.alpha2 tasks. Some of them are pretty minor, but one or two are looming a little larger. We’ll see how many I can get through. One thing I’m accepting more and more is that when open sourcing across nights and weekends, patience is a virtue.&lt;/p&gt;
&lt;p&gt;Thanks for sticking with me through this journey so far!&lt;/p&gt;
&lt;h2&gt;🙌🏼 Thanks to my sponsors… could you be the next?&lt;/h2&gt;
&lt;p&gt;I’ve been working &lt;em&gt;really&lt;/em&gt; hard on preparing a truly powerful, flexible Ruby application framework. I’m in this for the long haul, but it’s not easy.&lt;/p&gt;
&lt;p&gt;If you’d like to help all of this come to fruition, I’d love for you to &lt;a href="https://github.com/sponsors/timriley"&gt;sponsor my open source work&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thanks especially to &lt;a href="https://github.com/tak1n"&gt;Benjamin Klotz&lt;/a&gt; for your continued support.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Philly.rb talk on hanami-view 2.0</title>
    <link rel="alternate" href="https://timriley.info/writing/2020/07/14/philly-rb-talk-on-hanami-view-2-0"/>
    <id>https://timriley.info/writing/2020/07/14/philly-rb-talk-on-hanami-view-2-0</id>
    <published>2020-07-14T10:35:00+00:00</published>
    <updated>2020-07-14T10:35:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Last month I had the honour of speaking at &lt;a href="https://www.meetup.com/Phillyrb/events/271085197/"&gt;Philly.rb’s first remote meetup&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;I took the opportunity to revise my &lt;a href="https://www.icelab.com.au/notes/tim-talks-views-from-the-top"&gt;year-ago talk about dry-view&lt;/a&gt; and update it for our current plans for hanami-view 2.0.&lt;/p&gt;
&lt;p&gt;You can watch along here:&lt;/p&gt;
&lt;iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/GDEbrJeIwfM" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;
&lt;br&gt;
&lt;p&gt;Doing a talk like this for a fully-remote audience was tricky. With its narrative structure and dramatic premise, I rely on audience cues to make sure I’m hitting the mark. Here, I just had to passionately project into the void… and hope it landed!&lt;/p&gt;
&lt;p&gt;Fortunately, the Philly.rb crew was great, and the “after show” was where the remote format really excelled. A bunch of great questions came up, and I was able to jump into screen-sharing mode and do an interactive session on how both view rendering and the broader framework will hang together for Hanami 2.0.&lt;/p&gt;
&lt;p&gt;All up, I really enjoyed the session. Thank you &lt;a href="http://twitter.com/etagwerker"&gt;Ernesto&lt;/a&gt; for facilitating it! With our limited travel possibilities right now, I’d love to do this a little more: please reach out if you’d be interested in me contributing to your remote conference or meet-up.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Open source status update, June 2020</title>
    <link rel="alternate" href="https://timriley.info/writing/2020/06/28/open-source-status-update-june-2020"/>
    <id>https://timriley.info/writing/2020/06/28/open-source-status-update-june-2020</id>
    <published>2020-06-28T11:05:00+00:00</published>
    <updated>2020-06-28T11:05:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;After &lt;a href="https://timriley.info/writing/2020/06/01/open-source-status-update-may-2020/"&gt;last month’s&lt;/a&gt; breakthroughs with the Hanami view rendering, I was looking forward to “rolling downhill… and collecting a bunch of quick wins.” I was unfortunately a little over-optimistic there, but in June I did manage to get a few nice things done. Let’s take a look.&lt;/p&gt;
&lt;h2&gt;Hanami application actions with deeper class hierarchies&lt;/h2&gt;
&lt;p&gt;To start off the month, I upgraded one of my work applications to use last month’s &lt;code&gt;Hanami::Action&lt;/code&gt; improvements. And it turns out, this revealed a shortcoming! In this application, we have multiple tiers of “base actions”, with one for the whole application, and then one for each slice:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;module MyApp
  # Application base action
  class Action &amp;lt; Hanami::Action
  end
end

module Main
  # Slice base action, inheriting from application base
  class Action &amp;lt; MyApp::Action
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;My first pass at the “application action” work didn’t account for this, and it resulted in the slice’s base action (&lt;code&gt;Main::Action&lt;/code&gt; in the example above) not receiving the proper application-specific behaviour.&lt;/p&gt;
&lt;p&gt;This is a great example of how important it is to “dogfood” in-development frameworks and libraries like this, and it’s why I made a conscious decision to be both a ”super user” &lt;em&gt;and&lt;/em&gt; core developer of all the tools we’ve been developing in dry-rb, rom-rb, and now Hanami. The feedback you get from &lt;em&gt;really&lt;/em&gt; using them is invaluable, and the ensuring feedback cycle means that we can fold in related improvements really quickly.&lt;/p&gt;
&lt;p&gt;In this case, I needed to make &lt;a href="https://github.com/hanami/controller/pull/316"&gt;this adjustment to hanami-controller&lt;/a&gt; and &lt;a href="https://github.com/hanami/hanami/pull/1058"&gt;this one over here to hanami&lt;/a&gt;. And with that, this application can now continue to run on the cutting edge :)&lt;/p&gt;
&lt;h2&gt;Class-configurable Hanami actions&lt;/h2&gt;
&lt;p&gt;With those changes done, I turned my attention to my major focus for the month. I adjusted &lt;code&gt;Hanami::Action&lt;/code&gt; so that it &lt;a href="https://github.com/hanami/controller/pull/318"&gt;is now class-configurable&lt;/a&gt;, just like &lt;code&gt;Hanami::View&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here’s how you can now configure an action:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;class MyAction &amp;lt; Hanami::Action
  config.default_response_format = :json
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this in place, when you instantiate the action, the configuration from its class will automatically apply:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;action = MyAction.new
action.(rack_env) # Default response format will be JSON
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What’s useful about this approach is that the configuration is inherited, which creates the opportunity for a base action to hold common configuration for all its subclasses:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;class BaseAction &amp;lt; Hanami::Action
  config.default_response_format = :json
end

class AnotherAction &amp;lt; BaseAction
end

action = AnotherAction.new
action.(rack_env) # Default response format will be JSON
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If &lt;a href="https://timriley.info/writing/2020/06/01/open-source-status-update-may-2020/"&gt;you’ve been following along&lt;/a&gt; with my recent updates, you’ll see where this is going. With this inheritable class-based configuration in place, we’ll be able to leverage the “Application Action” behavior that is seamlessly added to &lt;code&gt;Hanami::Action&lt;/code&gt; subclasses when they’re defined within a full Hanami application, and in this case, apply all the necessary action configuration from the framework.&lt;/p&gt;
&lt;p&gt;That’s the plan for July. We’ll see how far we can go. There’s still a few things to work out, like how we can allow any &lt;code&gt;Hanami::Action&lt;/code&gt; setting to be configured for the whole application, without having to duplicate every setting into the application-level &lt;code&gt;Hanami::Configuration&lt;/code&gt; and the related classes.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://github.com/hanami/controller/pull/318"&gt;pull request for this work&lt;/a&gt; is now done and should hopefully merge soon. One thing I was quite happy with was how I managed to make the switch from configuration being applied as an externally injected object to the class-based configuration without having to upend the entire test suite. I made this possible by keeping the injected configuration object, and making the class-based configuration its default parameter:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;module Hanami
  class Action
    module StandaloneAction
      def new(configuration: self.configuration.dup, **args)
        allocate.tap do |obj|
          obj.instance_variable_set(:@configuration, configuration.finalize!)

          # Other details snipped out... let me know if you'd like to hear the
          # story behind ths whole new/allocate dance :)
        end
      end
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Not only did this allow me to sidestep an overhaul of the test suite, but it also retained a wonderful flexibility: if for any reason you need a particular action &lt;em&gt;instance&lt;/em&gt; to behave differently from its class’ default configuration, you can still pass in your own configuration object. I call that a win!&lt;/p&gt;
&lt;h2&gt;An aside on component ”fit” within dry-system and Hanami 2&lt;/h2&gt;
&lt;p&gt;A big theme of my work so far with Hanami 2 has been making Hanami’s own components fit just right with dry-system, which manages the application and slice containers. With dry-system as it currently stands, this means:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Each source file should be entirely self-contained.&lt;/strong&gt; A single &lt;code&gt;require&lt;/code&gt; for that file should bring in enough of the outside world for the class defined therein to be fully functional.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Each class should work with a simple &lt;code&gt;.new&lt;/code&gt;.&lt;/strong&gt; Instead of requiring the container’s coponent loader to somehow satisfy a whole range of various initializers, each class should provide sensible defaults, such that a simple &lt;code&gt;.new&lt;/code&gt; is enough to get a working instance. This means a couple of things:&lt;/p&gt;
&lt;p&gt;Firstly, all injected dependencies should have working defaults. This is how &lt;a href="http://dry-rb.org/gems/dry-auto_inject/0.6/how-does-it-work/"&gt;dry-auto_inject&lt;/a&gt; (i.e. the &lt;code&gt;include Deps[&amp;quot;some_dep&amp;quot;]&lt;/code&gt; we’ll see inside Hanami application components) works: the specified dependencies are resolved from the container and effectively become the default arguments for the class’ &lt;code&gt;#initialize&lt;/code&gt; parameters.&lt;/p&gt;
&lt;p&gt;Next, any standard configuration should be already in place, without any additional argument passing. This is exactly why we moved the &lt;code&gt;Hanami::Action&lt;/code&gt; config onto the class, so that &lt;code&gt;SomeAction.new&lt;/code&gt; can already have the configuration it needs.&lt;/p&gt;
&lt;p&gt;If you’re already working with dry-system, or designing components to fit well with dry-system or eventually Hanami 2, these characteristics would be good to keep in mind.&lt;/p&gt;
&lt;h2&gt;dry-configurable settings eagerly evaluate their value&lt;/h2&gt;
&lt;p&gt;To preserve the behavior of various Hanami action settings, I needed to make a &lt;a href="https://github.com/dry-rb/dry-configurable/pull/95"&gt;small change to dry-configurable&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It’s been a few months now since Piotr &lt;a href="https://github.com/dry-rb/dry-configurable/pull/78"&gt;entirely rewrote dry-configurable&lt;/a&gt;, and in my view, the effort has been a smashing success: I think the code is far easier to understand and work with. Thanks Piotr!&lt;/p&gt;
&lt;p&gt;Since the rewrite, we’ve had to make a few little adjustments as we’ve discovered additional use cases out in the wild, and this was one of them. We needed to make it a setting’s constructor would run immediately when provided a value, ensure we could provide immediate feedback in the case of an invalid value.&lt;/p&gt;
&lt;p&gt;So given this setting:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;setting :default_response_format do |format|
  Utils::Kernel.Symbol(format) unless format.nil?
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can now expect an error to be raised if the provided value cannot be symbolized:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;# Will raise an exception!
config.default_response_format = 123
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thanks to the well-factored code of the rewrite, the &lt;a href="https://github.com/dry-rb/dry-configurable/pull/95"&gt;required change&lt;/a&gt; was very small, and now we have the best of both worlds when it comes to dry-configurable evaluating its settings: when a value is provided, the value will run through the setting’s constructor immediately, which provides the early feedback we want in situations like the above. When a value is not yet provided, the constructor doesn’t run, waiting until a value is later provided or until the whole configuration is finalized, which is useful behavior for when a configuration object takes some time to be fully prepared.&lt;/p&gt;
&lt;h2&gt;Thanks to my sponsors 🙌🏼&lt;/h2&gt;
&lt;p&gt;As of this week, I now have five GitHub sponsors! Thank you, sponsors: I’m ever grateful for all your support.&lt;/p&gt;
&lt;p&gt;If you’d like to pitch in and support my open source work, you can &lt;a href="https://github.com/sponsors/timriley"&gt;sponsor me here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thanks especially to &lt;a href="https://github.com/tak1n"&gt;Benjamin Klotz&lt;/a&gt; for your continued support.&lt;/p&gt;
&lt;h2&gt;Thanks for reading!&lt;/h2&gt;
&lt;p&gt;The winter solstice has now passed here in Australia, so while the nights are getting shorter, I’ll still be pushing hard through the evenings to try and reach this critical ”minimum viable actions/views” milestone for Hanami 2. See you all at the end of July! 👋🏼&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Rubyists, we must do better</title>
    <link rel="alternate" href="https://timriley.info/writing/2020/06/08/rubyists-we-must-do-better"/>
    <id>https://timriley.info/writing/2020/06/08/rubyists-we-must-do-better</id>
    <published>2020-06-08T05:05:00+00:00</published>
    <updated>2020-06-08T05:05:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Ruby: come for the language, stay for the community. That’s the story I tell people, anyway.&lt;/p&gt;
&lt;p&gt;Yesterday, however, we saw some of the worst in our community.&lt;/p&gt;
&lt;p&gt;An &lt;a href="https://github.com/rubocop-hq/rubocop/issues/8091"&gt;issue was raised&lt;/a&gt; with the RuboCop team about changing the name in light of the race-related police brutality issues (and more) being tackled in the USA right now.&lt;/p&gt;
&lt;p&gt;Bozhidar Batsov, the project author and lead maintainer, &lt;a href="https://github.com/rubocop-hq/rubocop/issues/8091#issuecomment-637963754"&gt;declined the request and closed the issue&lt;/a&gt;, noting how disruptive it would be to rename something so widely used, and claiming that he wanted to keep “politics out of software.” A few days later, several follow-up comments were made, taking various angles and urging him to reconsider his position. A &lt;a href="https://github.com/rubocop-hq/rubocop/issues/8091#issuecomment-640139376"&gt;couple&lt;/a&gt; &lt;a href="https://github.com/rubocop-hq/rubocop/issues/8091#issuecomment-640152525"&gt;of forks&lt;/a&gt; were made without notice. Everything argued until this point at least looked to be in good faith.&lt;/p&gt;
&lt;p&gt;Now, all of those recent events happened while Bozhidar was asleep (he’s in Bulgaria, while most of the commenters looked to be in the US). That’s a lot to wake up to, I’m sure (and a snarky tweet or two likely stung the most). I don’t know Bozhidar, though I’m sure it’s safe to say we both love Ruby, and we also both wear sunglasses in our social media avatars, which I appreciate. What I do know, however, is that he’s poured hundreds, maybe thousands, of hours of free labour into this project, so I can fully appreciate how shocking this must have felt to him. He &lt;a href="https://github.com/rubocop-hq/rubocop/issues/8091#issuecomment-640158172"&gt;shared this reaction on the GitHub issue&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;What happened next was truly horrifying: a vile, toxic pile-on, ruthlessly villifying the folks who were in good faith trying to make the software more welcoming (I won’t link to the comments in the issue, nor other comments on social media, many of which are equally irredeemable).&lt;/p&gt;
&lt;p&gt;This was online bullying, pure and simple. And online bullying kills. &lt;strong&gt;There is no place for this in our community.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Bozhidar eventually &lt;a href="https://github.com/rubocop-hq/rubocop/issues/8091#issuecomment-640192148"&gt;locked the issue&lt;/a&gt;, but as RuboCop lead, he’s yet to make any statement about the behaviour of some of those who spoke in “support” of him (I’m sure he has a lot going on, and I’ve reached out to him about this privately).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; Bozhidar &lt;a href="https://metaredux.com/posts/2020/06/08/the-rubocop-name-drama-redux.html"&gt;has now published his thoughts on the matter&lt;/a&gt;, and I’m grateful for his conscientious leadership here.&lt;/p&gt;
&lt;p&gt;As for me, let me also speak this, as one representative of the Ruby community: &lt;strong&gt;There is no place for this vitriol here. This behaviour is NOT welcome in ANY form.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There are many ills that plague the world right now, and whether or not you think software naming changes have a role to play in improving things — I do — it should at least be clear that one easy way to make a better world is to bring our best selves into every situation. And if you feel you can’t do this, consider just &lt;a href="https://signalvnoise.com/posts/3124-give-it-five-minutes"&gt;giving it five minutes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I notice that RuboCop, as of about 18 hours ago, &lt;a href="https://github.com/rubocop-hq/rubocop/commit/672e7fae4da6af6c1ff690dd4e2cc8a293e1ed3d"&gt;now has a Code of Conduct&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This document provides a few simple community guidelines for a safe, respectful, productive, and collaborative place for any person who is willing to contribute to the RuboCop community. It applies to all “collaborative spaces”, which are defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Participants will be tolerant of opposing views.&lt;/li&gt;
&lt;li&gt;Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.&lt;/li&gt;
&lt;li&gt;When interpreting the words and actions of others, participants should always assume good intentions.&lt;/li&gt;
&lt;li&gt;Behaviour which can be reasonably considered harassment will not be tolerated.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;Unforunately, there’s no clear recourse specified for those who do not abide by these guidelines, which I feel is a critical element for a Code of Conduct to ensure a truly welcoming space. However, even in the absence of this, I still sincerely hope that it brings about materially healthier interactions within the RuboCop community in future.&lt;/p&gt;
&lt;p&gt;To Bozhidar: thank you for everything you’ve contributed to the Ruby and other open source communities. You’ve personally made our community a better place. I’m sorry many aspects of this were hard for you, but I also hope you can help to visibly steer things back in a positive direction from here.&lt;/p&gt;
&lt;p&gt;To everyone else in the Ruby community: “Matz is nice so we are nice” is not a pithy aphorism. &lt;em&gt;It’s a bare minimum,&lt;/em&gt; a mere starting point. We can and should aim for better.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Open source status update, May 2020</title>
    <link rel="alternate" href="https://timriley.info/writing/2020/06/01/open-source-status-update-may-2020"/>
    <id>https://timriley.info/writing/2020/06/01/open-source-status-update-may-2020</id>
    <published>2020-06-01T06:30:00+00:00</published>
    <updated>2020-06-01T06:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;May was a breakthrough month in terms of the integration of the standalone components into Hanami 2. Let’s dig right in.&lt;/p&gt;
&lt;h2&gt;Seamless Hanami view integration&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://timriley.info/writing/2020/04/30/open-source-status-update-april-2020/"&gt;Last month&lt;/a&gt; I wrote about my first pass at integrating Hanami view classes with application they exist within. It looked like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;module Main
  # &amp;quot;Base&amp;quot; view class for `main` slice
  class View &amp;lt; Hanami::View[:main]
  end

  module Views
    class Articles
      # Class for a specific view, inheriting from base
      class Index &amp;lt; Main::View
      end
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this approach, inheriting from &lt;code&gt;Hanami::View[:main]&lt;/code&gt; would tell the subclass to apply its configuration using details from the &lt;code&gt;main&lt;/code&gt; slice. It worked fine, but there’s still some redundancy there:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;module Main                         # &amp;lt;- We're clearly in the `main` slice
  class View &amp;lt; Hanami::View[:main]  # &amp;lt;- So why do we have to repeat it here?
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In trying to get this stuff done for the next alpha release, Luca has definitely been encourating a pragmatic approach to getting things in place (“perfect is the enemy of good,” or in this case “shipped”). It’s the right way to go, but even still, this nagged at me, especially given our goal of reducing boilerplate as much as possible.&lt;/p&gt;
&lt;p&gt;After a while I realised that the &lt;em&gt;application itself&lt;/em&gt; could provide a facility to help us out in this situation. Given a class like &lt;code&gt;Main::View&lt;/code&gt;, and given that each slice ”owns” a specific namespace, there is enough information in the class name alone to infer which slice it belongs to. So now we have this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;Hanami.application.component_provider(Main::View)
#=&amp;gt; #&amp;lt;Hanami::Slice:0x00007fc2ae074568
# @application=Soundeck::Application,
# @booted=true,
# @container=Main::Container,
# @name=:main,
# @namespace=Main,
# @namespace_path=&amp;quot;main&amp;quot;,
# @root=#&amp;lt;Pathname:/Users/tim/Source/hanami/soundeck/slices/main&amp;gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pass in a class or instance, and get its slice. Easy. With this in place, we can use it from the &lt;code&gt;.inherited&lt;/code&gt; hook of &lt;code&gt;Hanami::View&lt;/code&gt; to get all the information we need for truly seamless integration:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;module Main
  class View &amp;lt; Hanami::View
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Look, zero boilerplate!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;But this is only half of the seamless view integration story: now that we can infer the slice that provides a given view, how do we add the slice-specific behaviour, especially given we’re still inheriting directly from &lt;code&gt;Hanami::View&lt;/code&gt;, which still needs to be able to provide the behaviour for &lt;em&gt;standalone&lt;/em&gt; (non-integrated) views?&lt;/p&gt;
&lt;p&gt;Our original approach for this was also pragmatic: when we need a subclass of a given Hanami component (like &lt;code&gt;Hanami::View&lt;/code&gt; or &lt;code&gt;Hanami::Action&lt;/code&gt;) to behave differently within an Hanami application versus when used standalone, we would just monkey patch the application-specific behaviour. Now, anyone who knows me would know this isn’t approach I would not tolerate for long. 😉 Even still, I was willing to do it for the sake of expedience. You can see the approach (and all my misgivings about it) in &lt;code&gt;lib/hanami/action/extensions/application_action.rb&lt;/code&gt; &lt;a href="https://github.com/hanami/hanami/pull/1049/files"&gt;in my proof of concept action integration PR&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But as is the theme of this section, it nagged at me. What we really needed here was for the patched methods providing the integration specialisations to be able to call &lt;code&gt;super&lt;/code&gt; to get to the standalone behaviour wherever they needed. With a monkey patch, this isn’t possible because you end up completely replacing the methods (or having to resort to hacky “alias method chain”-style approaches).&lt;/p&gt;
&lt;p&gt;One way to solve this would be to have a deeper inheritance chain (&lt;code&gt;Hanami::ApplicationView &amp;lt; Hanami::View&lt;/code&gt;) and using different superclasses for integrating views versus standalone views, but that bifurcates view usage in an unfriendly way, and more likely than not would make one of those two use cases more awkward than the other.&lt;/p&gt;
&lt;p&gt;At this point I realised Ruby gives us another option for this: modules! What we needed here were two different modules in the ancestor chain for a given view class, with the “nearest” one providing the application integration behaviour (e.g. &lt;code&gt;[Main::Views::Articles::Index, Main::View, ApplicationView, StandaloneView]&lt;/code&gt;), and the next one back providing the standard standalone behaviour. This way, the application integration module can add only the specialisations it requires, and can call &lt;code&gt;super&lt;/code&gt; whenever it needs.&lt;/p&gt;
&lt;p&gt;The final piece to this puzzle is to make it so that the &lt;code&gt;ApplicationView&lt;/code&gt; module can provide behaviour that’s &lt;em&gt;specific&lt;/em&gt; to a given slice. This is where the &lt;a href="https://dejimata.com/2017/5/20/the-ruby-module-builder-pattern"&gt;module builder pattern&lt;/a&gt; comes in. Instead of this &lt;code&gt;ApplicationView&lt;/code&gt; module being a plain old static module, we can &lt;em&gt;initialize&lt;/em&gt; it with the slice object that we get when we’re subclassing &lt;code&gt;Hanami::View&lt;/code&gt; in the first place.&lt;/p&gt;
&lt;p&gt;So with this in place, here’s what &lt;code&gt;Hanami::View&lt;/code&gt; and its &lt;code&gt;.inherited&lt;/code&gt; roughly look like:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;require_relative &amp;quot;view/application_view&amp;quot;
require_relative &amp;quot;view/standalone_view&amp;quot;
# ...

module Hanami
  class View
    include StandaloneView

    # ...

    def self.inherited(subclass)
      super

      # If inheriting directly from Hanami::View within an Hanami app, configure
      # the view for the application
      if subclass.superclass == View &amp;amp;&amp;amp; (provider = application_provider(subclass))
        subclass.include ApplicationView.new(provider)
      end
    end

    def self.application_provider(subclass)
      if Hanami.respond_to?(:application?) &amp;amp;&amp;amp; Hanami.application?
        Hanami.application.component_provider(subclass)
      end
    end
    private_class_method :application_provider
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And the resulting ancestors for an actual view class:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;Main::Views::Articles::Index.ancestors
# =&amp;gt; [
#   Main::Views::Home::Index,
#   Main::View,
#   #&amp;lt;Hanami::View::ApplicationView[main]&amp;gt;,
#   Hanami::View,
#   Hanami::View::StandaloneView::InstanceMethods,
#   Hanami::View::StandaloneView,
#   # ...
# ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These are exactly the number of different places we need to neatly slot in all the behaviour for our truly seamless view integration!&lt;/p&gt;
&lt;p&gt;The resulting arrangement has some other nice benefits, too, because the integration logic has now moved out from the hanami gem and over into the hanami-view gem itself:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It makes hanami-view easier to maintain, because the the standalone and integration view behaviours can be seen side by side and kept in sync.&lt;/li&gt;
&lt;li&gt;It also represents some first steps towards ensuring there isn’t just a single “blessed” view system, by making it clear that components should integrate &lt;em&gt;themselves&lt;/em&gt; with the application framework, rather than the other way around.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I was chuffed with how this all worked out, and I’m much, much happier with the overall arrangement now. Hats off to Ruby for being such a flexible language! &lt;a href="https://github.com/hanami/view/pull/173"&gt;Check out the full hanai-view PR for this new integration approach&lt;/a&gt;, as well as &lt;a href="https://github.com/hanami/hanami/pull/1052"&gt;the corresponding integration hooks (and reduction in view integration code!) inside the hanami gem&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;New &lt;code&gt;Hanami.application?&lt;/code&gt; check&lt;/h2&gt;
&lt;p&gt;A small subtle thing you might have noticed above was that check for &lt;code&gt;Hanami.application?&lt;/code&gt;. This is &lt;a href="https://github.com/hanami/hanami/pull/1052"&gt;another hook I added&lt;/a&gt; to make it easier for components to integrate (or not) with an Hanami application. Because many of the Hanami components can be used on their own (hanami-view, hanami-router, and hanami-controller in particular), the &lt;code&gt;Hanami&lt;/code&gt; namespace will definitely exist, but not necessarily a full &lt;code&gt;Hanami.application&lt;/code&gt;. This &lt;code&gt;Hanami.application?&lt;/code&gt; check provides a safe way to determine if an application has been defined before activating any integration code.&lt;/p&gt;
&lt;p&gt;Right now this is defined directly on the &lt;code&gt;Hanami&lt;/code&gt; module by the hanami gem, but we’ll also be adding it to hanami-utils so you can safely use it without having to require the full application gem.&lt;/p&gt;
&lt;h2&gt;Seamless view rendering within Hanami actions&lt;/h2&gt;
&lt;p&gt;All the polishing of the view integration was a warm-up for the main game this month: properly implementing the integration of view rendering into Hanami actions.&lt;/p&gt;
&lt;p&gt;This was the approach we agreed upon after &lt;a href="https://timriley.info/writing/2020/04/30/open-source-status-update-april-2020/"&gt;our experiments last month&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;class Index &amp;lt; Main::Action
  include Deps[view: &amp;quot;views.articles.index&amp;quot;]

  def handle(req, res)
    # Views are rendered by the response
    res.render view
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And as of a few days ago, &lt;a href="https://github.com/hanami/controller/pull/314"&gt;the work is complete!&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;That single &lt;code&gt;res.render view&lt;/code&gt; belies &lt;em&gt;a lot&lt;/em&gt; of underlying logic. What we do with this integration is provide the view with all the request-specific data that it might need to render itself, things like the current session, flash messages, CSRF token, etc. This is all set up automatically for you as soon as you inherit from &lt;code&gt;Hanami::Action&lt;/code&gt; within an existing Hanami application.&lt;/p&gt;
&lt;p&gt;Sound familiar? That’s because we follow the exact same integration approach for actions as we do for views. Hanami::Action now has a &lt;code&gt;StandaloneAction&lt;/code&gt; module providing the basic functionality, and an &lt;code&gt;ApplicationAction&lt;/code&gt; module that is initialized with the action’s slice, so it can pick up whatever details it needs from the slice or application to provide the view rendering integration.&lt;/p&gt;
&lt;p&gt;The crux of the integration is the action setting up a &lt;a href="https://dry-rb.org/gems/dry-view/0.7/context/"&gt;view context object&lt;/a&gt; with the request/response pair created when the action is called. This view context is automatically &lt;a href="https://dry-rb.org/gems/dry-view/0.7/context/#providing-the-context"&gt;passed to the view&lt;/a&gt; when &lt;code&gt;res.render&lt;/code&gt; is called. Having the request/response pair available to the context means that the context object can provide methods to make those details like the &lt;code&gt;flash&lt;/code&gt; available for use within the view &lt;a href="https://dry-rb.org/gems/dry-view/0.7/templates/#template-scope"&gt;templates&lt;/a&gt;, &lt;a href="https://dry-rb.org/gems/dry-view/0.7/scopes/#accessing-the-context"&gt;scopes&lt;/a&gt;, and &lt;a href="https://dry-rb.org/gems/dry-view/0.7/parts/#accessing-the-context"&gt;parts&lt;/a&gt; (these are links to dry-view documentation, since at this point, hanami-view unstable and dry-view 0.7 are effectively the same).&lt;/p&gt;
&lt;p&gt;This integration is eminently flexible. There are multiple points at which an application author can customise it. The first would be to add their own methods to the view context class within their application (I’ll show examples of this this next month). The next would be to override any of these methods within their base action class:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;#view_options&lt;/code&gt;, to pass additional options to every view as it is rendered&lt;/li&gt;
&lt;li&gt;&lt;code&gt;#view_context_options&lt;/code&gt;, to pass additional options to the view context&lt;/li&gt;
&lt;li&gt;&lt;code&gt;#build_response&lt;/code&gt;, to customize the response object that is prepared for passing to the action’s &lt;code&gt;#handle&lt;/code&gt; method (it’s unlikely this will need to be customised, but it’s nice to keep all options open)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I think these methods are a perfect example of the approach we’re taking with Hanami 2 development: conveniences by default, but &lt;em&gt;every possible measure&lt;/em&gt; available to adjust things when you need to diverge from the defaults.&lt;/p&gt;
&lt;h2&gt;Next steps for actions and views&lt;/h2&gt;
&lt;p&gt;This month was all about laying the proper groundwork for action and view integration. With this done, my plan for June is to roll through all these steps to round it out:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Making &lt;code&gt;Hanami::Action&lt;/code&gt; configuration class-based, like we have for view configuration, so we can auto-configure actions based on their slice&lt;/li&gt;
&lt;li&gt;Making actions automatically infer a paired view (think a “views.article.index” view for an &lt;code&gt;Actions::Articles::Index&lt;/code&gt;) so you don’t need to explicitly auto-inject them&lt;/li&gt;
&lt;li&gt;Then going a step further, making it so that actions automatically &lt;em&gt;render&lt;/em&gt; their paired view too, meaning you don’t not even need to implement &lt;code&gt;#handle&lt;/code&gt; for basic render-only actions&lt;/li&gt;
&lt;li&gt;Creating an &lt;code&gt;Hanami::View::ApplicationContext&lt;/code&gt; context class with a default set of helpful methods for use within all Hanami views, including those that require access to the request (as described above)&lt;/li&gt;
&lt;li&gt;Making the view context container identifier configurable&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With Hanami 2, while we’re building upon many years worth of open source efforts in terms of the existing libraries we’re pulling together, it’s become very clear to me through this process that doing good integration work is just as much effort all over again. Thanks for your patience while we work through this as best we can.&lt;/p&gt;
&lt;h2&gt;Thanks to my sponsors 🙌🏼&lt;/h2&gt;
&lt;p&gt;Earlier this month I announced that &lt;a href="https://timriley.info/writing/2020/05/21/sponsor-me-on-github/"&gt;I joined GitHub sponsors&lt;/a&gt;, and since then, two more kind people &lt;a href="https://github.com/sponsors/timriley"&gt;began sponsoring me&lt;/a&gt;. Thank you so much &lt;a href="https://github.com/ioquatix"&gt;Samuel Williams&lt;/a&gt; and &lt;a href="https://github.com/thomasklemm"&gt;Thomas Klemm&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;And a big shout out to &lt;a href="https://github.com/tak1n"&gt;Benjamin Klotz&lt;/a&gt; for your continuing support 😄&lt;/p&gt;
&lt;h2&gt;Thanks for reading!&lt;/h2&gt;
&lt;p&gt;This post turned out to be a big one! The fact that I had so much to say speaks to just how pivotal a month this was. I’m looking forward to the next few weeks of rolling downhill from here and collecting a bunch of quick wins. See you all again at the end of June! 👋🏼&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Sponsor me on GitHub</title>
    <link rel="alternate" href="https://timriley.info/writing/2020/05/21/sponsor-me-on-github"/>
    <id>https://timriley.info/writing/2020/05/21/sponsor-me-on-github</id>
    <published>2020-05-21T13:40:00+00:00</published>
    <updated>2020-05-21T13:40:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;&lt;a href="https://github.com/sponsors/timriley"&gt;I’ve joined GitHub’s sponsors program&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here’s my pitch:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I love Ruby. I’ve worked with Ruby for nearly 20 years, and even after all this time, it still brings me joy!&lt;/p&gt;
&lt;p&gt;My mission in open source is to ensure the joy of Ruby extends to the experience of writing real, maintainable applications, for teams of all sizes.&lt;/p&gt;
&lt;p&gt;For me, this comes from following a hybrid FP/OOP approach to writing Ruby, and for the last 6+ years, I’ve been helping to build tools to make this easy for application developers.&lt;/p&gt;
&lt;p&gt;I’m a co-founder and core team member of the dry-rb project, and a core team member of the rom-rb and Hanami projects.&lt;/p&gt;
&lt;p&gt;Right now, I’m heavily involved in the effort to build Hanami 2.0, which represents a new model for Ruby open source collaboration, and will provide a true framework of the future for Ruby application developers.&lt;/p&gt;
&lt;p&gt;I do the majority of my open source work as my “second job,” on nights and weekends. Your kind support will help sustain me in these efforts.&lt;/p&gt;
&lt;p&gt;Thank you! 🙏🏼&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I love contributing to the Ruby OSS ecosystem. &lt;strong&gt;But a labour of love is still a labour.&lt;/strong&gt; Right now I‘m working hard towards building Hanami 2, and have come to realise this is the single biggest effort I’ve made in OSS so far. There’s &lt;em&gt;a lot&lt;/em&gt; to do, and it’s taking many hours over evenings and weekends.&lt;/p&gt;
&lt;p&gt;This is where GitHub sponsorships come in. My plan for these is not to take my OSS work full-time or anything like that. I love the &lt;a href="https://www.cultureamp.com/"&gt;job I already have&lt;/a&gt; and I’ll be continuing my OSS contributions regardless. What the sponsorships will provide are small, tangible gestures of thanks that will go a long way towards making my OSS efforts feel sustainable.&lt;/p&gt;
&lt;p&gt;I’m delighted to report that these really do make a difference, because I already feel it from my first two sponsors! Thank you so much, &lt;a href="https://github.com/tak1n"&gt;Benjamin&lt;/a&gt; and &lt;a href="https://github.com/jasoncharnes"&gt;Jason&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;As an added means of giving back, I’m using this a motivation to write more here, both summarising the work I’m doing in OSS each month as well as delving deeper into some of the software design ideas I’ve been kicking around all these years.&lt;/p&gt;
&lt;p&gt;If any of this sounds good to you, &lt;a href="https://github.com/sponsors/timriley"&gt;I’d appreciate your support&lt;/a&gt;. Thank you very much!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Interviewed on GitHub’s “Open Source Friday” video series</title>
    <link rel="alternate" href="https://timriley.info/writing/2020/05/21/interviewed-on-githubs-open-source-friday-video-series"/>
    <id>https://timriley.info/writing/2020/05/21/interviewed-on-githubs-open-source-friday-video-series</id>
    <published>2020-05-21T12:45:00+00:00</published>
    <updated>2020-05-21T12:45:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Last week I appeared on GitHub’s fledgeling &lt;a href="https://www.twitch.tv/videos/621484153"&gt;“Open Source Friday” Twitch stream&lt;/a&gt;, where I joined &lt;a href="https://twitter.com/bdougieYO"&gt;Brian Douglas&lt;/a&gt; to discuss the dry-rb project. It was wide-ranging conversation: we discussed dry-rb in general, why I think it helps you write better code, and how to get started as a user and contributor. We also touched on the role dry-rb will play for Hanami 2, Ruby open source in general, and the many positive aspects of Ruby and its community that continue to bring me joy.&lt;/p&gt;
&lt;p&gt;It was a fun chat, and I think it gives a good sense about why we do everything that we do. &lt;a href="https://www.twitch.tv/videos/621484153"&gt;Go have a listen!&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Sharing my Hanami 2 application template</title>
    <link rel="alternate" href="https://timriley.info/writing/2020/05/07/sharing-my-hanami-2-application-template"/>
    <id>https://timriley.info/writing/2020/05/07/sharing-my-hanami-2-application-template</id>
    <published>2020-05-07T11:40:00+00:00</published>
    <updated>2020-05-07T11:40:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;We’ve been hard at work building Hanami 2 for a while now, but the truth is, it’ll be a while still until it’s truly ready.&lt;/p&gt;
&lt;p&gt;In the meantime, &lt;a href="https://github.com/timriley/hanami-2-application-template"&gt;I’ve put together an Hanami 2 application template&lt;/a&gt; that reflects how I’m currently using various Hanami 2 components within the applications I develop day-to-day.&lt;/p&gt;
&lt;p&gt;It currently consists of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Hanami 2 application core&lt;/li&gt;
&lt;li&gt;Hanami router&lt;/li&gt;
&lt;li&gt;Hanami controller and view manually integrated into the app (along with some helpers for rendering views within actions)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rom-rb.org"&gt;rom-rb&lt;/a&gt; configured to use a Postgres database&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;bin/run&lt;/code&gt; CLI with some extra commands to help you work with the database (&lt;code&gt;db create&lt;/code&gt;, &lt;code&gt;db migrate&lt;/code&gt;, &lt;code&gt;db create_migration&lt;/code&gt;, etc.)&lt;/li&gt;
&lt;li&gt;A little static assets manager built using Webpack, along with an &lt;code&gt;assets&lt;/code&gt; helper object for use within views&lt;/li&gt;
&lt;li&gt;A fully configured RSpec setup&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want to begin exploring the ideas and features of Hanami 2, all while the framework is still under active development, this template is a great way to get started! As the framework improves, I’ll keep this template updated alongside it.&lt;/p&gt;
&lt;p&gt;Fair warning: this template comes with no documentation and no support, but hopefully the code can serve as a helpful guide for those willing to dig around.&lt;/p&gt;
&lt;p&gt;Please go ahead and &lt;a href="https://github.com/timriley/hanami-2-application-template"&gt;&lt;strong&gt;check out the template&lt;/strong&gt;&lt;/a&gt;! I’m excited to share this early look into the Hanami 2 app development experience. Even at this early stage, it’s a framework that gives me great joy to use every day. 🌸&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Open source status update, April 2020</title>
    <link rel="alternate" href="https://timriley.info/writing/2020/04/30/open-source-status-update-april-2020"/>
    <id>https://timriley.info/writing/2020/04/30/open-source-status-update-april-2020</id>
    <published>2020-04-30T12:50:00+00:00</published>
    <updated>2020-04-30T12:50:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Hello again after another month! I’m excited to turn these monthly updates into a habit, and I’m equally as excited to share my progress with you!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hanami basic view integration&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I started off the month by merging the beginnings of my &lt;a href="https://github.com/hanami/hanami/pull/1040"&gt;Hanami 2 view integration work&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This makes it possible for views to &lt;em&gt;configure themselves&lt;/em&gt; based on the application and slice in which they’re situated, and the result is zero boilerplate view classes:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;module Main
  class View &amp;lt; Hanami::View[:main]
  end

  module Views
    class Articles
      class Index &amp;lt; Main::View
      end
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example, the &lt;code&gt;Views::Articles::Index&lt;/code&gt; view will have a matching template name configured (&lt;code&gt;&amp;quot;articles/index&amp;quot;&lt;/code&gt;), as well as the appropriate paths to find that template within the &lt;code&gt;Main&lt;/code&gt; slice’s directory.&lt;/p&gt;
&lt;p&gt;This is a great start, but it’s not the end of the view integration story for Hanami 2!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;View integration with Hanami actions&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The next step in this journey is making it nice to actually go ahead and &lt;em&gt;render&lt;/em&gt; these views within Hanami actions, as well as making sure that various request-specific details (like CSRF tokens, flash messages, and even just the request path) are available to views that require them.&lt;/p&gt;
&lt;p&gt;This was a tough nut to crack, but I think it also demonstrates just how grateful I am to be collaborating with Luca on all of this.&lt;/p&gt;
&lt;p&gt;I started off this effort by &lt;a href="https://github.com/hanami/hanami/pull/1043"&gt;copying over the very light-touch view/action integration&lt;/a&gt; that I’d already established within the Hanami 2 apps I’ve built so far. This consisted of a few extra helper methods inside the application’s base action, allowing you to render a view like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;module Main
  module Actions
    module Articles
      class Index &amp;lt; Main::Action
        include Deps[view: &amp;quot;views.articles.index&amp;quot;]

        def handle(req, res)
          render req, res, page: req.params[:page]
        end
      end
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This worked okay, but it felt awkward in a couple of ways. Having to pass the extra &lt;code&gt;req, res&lt;/code&gt; to every &lt;code&gt;render&lt;/code&gt; could quickly become laborious. It also didn’t fit well in PUT or POST actions, where you’re typically either redirecting (via &lt;code&gt;res.redirect_to &amp;quot;...&amp;quot;&lt;/code&gt;) or rendering (in this case via &lt;code&gt;render&lt;/code&gt;) based on the success of your action. Having to operate on the &lt;code&gt;res&lt;/code&gt; in one case and then via a helper directly on &lt;code&gt;self&lt;/code&gt; in the other just didn’t feel &lt;em&gt;balanced&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Luca encouraged me to find a better approach. After various circuitious discussions squeezed into our paltry ~1 hour Canberra/Rome daily overlapping time window, we decided it best to sketch out some complete-ish working implementations in code. This helped us land on this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;class Create &amp;lt; Main::Action
  include Deps[view: &amp;quot;views.articles.index&amp;quot;]

  def handle(req, res)
    if create_article_somehow(req.params[:article])
      res.redirect_to &amp;quot;/admin/articles&amp;quot;
    else
      res.render view, validation: errors_go_here
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This gives us a much nicer symmetry, with both redirecting and view rendering being a capability of the response (internally, the rendering just boils down to calling the view object and assigning its output to the its &lt;code&gt;body&lt;/code&gt;), and a little less line-noise, since we also established a way to ensure the &lt;code&gt;res&lt;/code&gt; object has all the request-specific details that a view may need.&lt;/p&gt;
&lt;p&gt;What I also like about this implementation is that action classes still very much retain their ”what you see is what you get” quality. They are very much &lt;em&gt;coordinators&lt;/em&gt; of application domain services, existing simply to handle the HTTP-related aspects of the web interface, and passing control to the other services as required. Views become just another element in this mix, handled like any other dependency of an action.&lt;/p&gt;
&lt;p&gt;It’s still very early days for this view/action integration, but we’re happy enough with the direction such that I can now begin finessing it and finishing it, hopefully over the month of May.&lt;/p&gt;
&lt;p&gt;The work so far (which is messy, be warned!) is spread across PRs in both &lt;a href="https://github.com/hanami/controller/pull/311"&gt;hanami/controller&lt;/a&gt; and &lt;a href="https://github.com/hanami/hanami/pull/1049"&gt;hanami/hanami&lt;/a&gt;. I’ve also made corresponding &lt;a href="https://github.com/jodosha/soundeck/pull/8"&gt;adjustments to our soundeck demo app&lt;/a&gt; to show this rendering approach in action, as well as various ways you can test actions that render views.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Various Hanami things: CLI env handling, dotenv improvements, middleware fix&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Last week I also &lt;a href="https://github.com/hanami/hanami/pull/1045"&gt;made some small fixes and tweaks&lt;/a&gt; to the nascent Hanami 2 CLI: I made it so the application boot process triggered via CLI actions was deferred late enough such that the &lt;code&gt;-e&lt;/code&gt; flag (to set the &lt;code&gt;HANAMI_ENV&lt;/code&gt;) is properly respected (important if you want to migrate your &lt;em&gt;actual&lt;/em&gt; test database!). Related, I expanded the range of files we load via Dotenv for application settings, so you can have your &lt;code&gt;.env.test&lt;/code&gt; and &lt;del&gt;eat it too&lt;/del&gt; have it actually respected when running CLI commands with &lt;code&gt;-e test&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I also &lt;a href="https://github.com/hanami/hanami/pull/1046"&gt;stumbled across and fixed&lt;/a&gt; a bug that prevented Rack middlewares from being mounted in the Hanami router if their &lt;code&gt;#initialize&lt;/code&gt; accepted only a single &lt;code&gt;app&lt;/code&gt; arg. This is why it’s good to have a real everyday app running on in-progress frameworks like this; such things get noticed and fixed much more quickly!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bonus OSS: development environment tooling&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;One more thing: I was glad to have the opportunity to bring several elements of Icelab’s former shared development environment into our new Culture Amp team:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Our &lt;a href="https://github.com/cultureamp/web-team-dotfiles"&gt;shared dotfiles&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Our &lt;a href="https://github.com/cultureamp/homebrew-web-team-devtools"&gt;Homebrew commands tap&lt;/a&gt;, which includes &lt;code&gt;bootstrap-developer-system&lt;/code&gt; to install the development environment, including the dotfiles, and &lt;code&gt;bootstrap-asdf&lt;/code&gt;, which takes care of installing the range of asdf-managed tools we use (including the finnicky verification of the nodejs package signatures)&lt;/li&gt;
&lt;li&gt;And our &lt;a href="https://github.com/cultureamp/web-team-scripts-to-rule-them-all"&gt;scripts to rule them all&lt;/a&gt;, which implements the normalized script pattern for every one of our apps, making it possible for the app to install&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After a decade of trying various shared development environments, and now working with this approach for the last couple of years, I’m still incredibly happy with this combo: it’s very light touch and the components are all very simple, making the whole thing a breeze to maintain. I’m happy it can live on and continue serve our team within a new company!&lt;/p&gt;
&lt;p&gt;Did I mention I also gave &lt;a href="https://github.com/timriley/dotfiles"&gt;my own dotfiles&lt;/a&gt; a refresh recently too?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Shout out to my GitHub sponsor!!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I’ll write about this in more detail soon, but here’s the short story: it dawned on me recently that helping to bring Hanami 2 into fruition will be a long slog, and that I could do with all the encouragement I could get. &lt;a href="https://github.com/sponsors/timriley"&gt;So I signed up for GitHub sponsors&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Within a week I already have one sponsor: thank you so much to &lt;a href="https://github.com/tak1n"&gt;Benjamin Klotz&lt;/a&gt; for your support! Receiving that first “You have a sponsor” email was truly a joy 😄.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;See you next month 👋🏼&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Well, that’s it for April! I’ll be spending May focused on getting that Hanami view/action integration done. It’s going to take some doing, but once it’s in place it’ll represent a major step forward for the 2.0 effort.&lt;/p&gt;
&lt;p&gt;See you back here again next month!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Open source status update, March 2020</title>
    <link rel="alternate" href="https://timriley.info/writing/2020/03/27/open-source-status-update-march-2020"/>
    <id>https://timriley.info/writing/2020/03/27/open-source-status-update-march-2020</id>
    <published>2020-03-27T11:48:00+00:00</published>
    <updated>2020-03-27T11:48:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Inspired by Piotr’s &lt;a href="https://solnic.codes/2020/03/02/open-source-status-update/"&gt;open source status update&lt;/a&gt;, I thought I’d begin a similar practice (and get back into the swing of blogging, since this year’s effectively a write-off for in-person meetings).&lt;/p&gt;
&lt;p&gt;I hope to do this monthly, but since this is the first one, let me recap the whole of the last year or so! Here are the highlights:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;dry-view 0.6&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In late 2018 and early 2019 I &lt;a href="https://dry-rb.org/news/2019/02/12/dry-view-0-6-0-an-introductory-talk-and-plans-for-1-0/"&gt;built and released dry-view 0.6.0&lt;/a&gt;, which contained all the abstractions I thought it needed for 1.0. Then I &lt;a href="https://www.icelab.com.au/notes/tim-talks-views-from-the-top"&gt;gave a talk&lt;/a&gt; about it and my philsophy on view layer design in general.&lt;/p&gt;
&lt;p&gt;Not much happened after that. I needed a bit of time to recover from my usual intense talk preparation and the blur of conference-driven-development that preceded it. Still, for the rest of the year I was a very satisfied user of dry-view 0.6 within my Icelab applications.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hanami core application/slices structure&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Later in 2019 I spent a while refining &lt;em&gt;Snowflakes&lt;/em&gt;, the ersatz framework we developed at Icelab for our own dry-rb/rom-rb/roda-based applications. Most of my work was about enshrining an application/sub-application structure and reducing the amount of boilerplate required for dry-system to provide this.&lt;/p&gt;
&lt;p&gt;This turned out to be a prescient effort, because I was able to pivot this approach into the &lt;a href="https://github.com/hanami/hanami/pull/1019"&gt;application and slices structure&lt;/a&gt; that will form the core of the Hanami framework for its upcoming 2.0 release.&lt;/p&gt;
&lt;p&gt;Working on this was a supremely rewarding experience, because it represented a true meeting of the minds between Luca, me, and the other dry-rb team members. We each brought our own experiences to bear on this problem, and through a good amount of back and forth and give and take, we found a way to make what I think will both be a convenient, low-ceremony framework structure that at the same time remains extremely powerful, flexible, and cleanly structured.&lt;/p&gt;
&lt;p&gt;We’re still working on it, and time will tell, but I’m feeling good about it!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hanami application settings&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Andrew Croome and I got together at Rails Camp Kyneton in November last year and plotted how to add first-class support for application settings to Hanami. Andrew made the broad strokes that weekend, and then I followed up with some adjustments, and I &lt;a href="https://github.com/hanami/hanami/pull/1029"&gt;merged our co-authored PR&lt;/a&gt; a couple of weeks ago.&lt;/p&gt;
&lt;p&gt;Around that same time, solnic released an amazingly simplified &lt;a href="https://github.com/dry-rb/dry-configurable/pull/78"&gt;complete rewrite of dry-configurable&lt;/a&gt;, and we realised it would serve as a useful underpinning for the Hanami application settings and could also open up some new possibilities like nested settings. I’ve been working since then to prepare the way for this, roughing the changes into Hanami and fixing a &lt;a href="https://github.com/dry-rb/dry-configurable/pull/85"&gt;couple&lt;/a&gt; of &lt;a href="https://github.com/dry-rb/dry-configurable/pull/87"&gt;things&lt;/a&gt; with dry-configurable along the way. There’s a few more things to do here, and we’re expecting these last changes to culminate with a release of dry-configurable 1.0.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;{dry,hanami}-view renaming and application integration&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I’ve temporarily put these dry-configurable adjustments on the backburner, however, so I can focus on some other things we more pressingly need for the next Hanami alpha release: integration of views and actions with the larger framework.&lt;/p&gt;
&lt;p&gt;For 2.0, we’ve always intended that dry-view would serve as the view layer for Hanami. The big news here is that we’re now &lt;em&gt;renaming&lt;/em&gt; dry-view to hanami-view! Yes, just as hanami-cli became dry-cli to improve the coherence of the gems that make up our two organisations, dry-view will be moving up into the Hanami org to sit alongside hanami-router and hanami-controller as the three main tentpoles of the web application stack.&lt;/p&gt;
&lt;p&gt;These renaming decisions have been very easily made, which I think goes to show just how aligned we all are on building an understandable and complementary ecosystem of next-gen Ruby OSS tools :)&lt;/p&gt;
&lt;p&gt;I’ve done the renaming and the new code is up in the &lt;a href="https://github.com/hanami/view/tree/master"&gt;master branch of hanami/view&lt;/a&gt;, with the default branch for the project being renamed to “1.x-master” until we’re done with the 2.0 development.&lt;/p&gt;
&lt;p&gt;Now I’m working on making it so that views can sit within an Hanami application with zero boilerplace. This is my &lt;a href="https://github.com/hanami/view/pull/172/files"&gt;current work-in-progress effort&lt;/a&gt;. The goal is to have it so that your view can be as simple as this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;module Main
  module Views
    class MyView &amp;lt; Main::View
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And work out of the box with all appropriate conventions and any required configurations pulled from its slice and the broader Hanami application.&lt;/p&gt;
&lt;p&gt;And like any well-behaved component within a dry-system application, it works with dry-system’s auto-registration, which means the view will be fully functional and behave consistently when resolved directly from the container:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;Main::Slice[&amp;quot;views.my_view&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or auto-injected into an action:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;module Main
  module Actions
    class MyAction &amp;lt; Main::Action
      include Deps[&amp;quot;views.my_view&amp;quot;]
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or even instantiated directly, like you might do in a test:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;RSpec.describe Main::Views::MyView do
  subject(:my_view) { described_class.new }
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href="https://github.com/hanami/view/pull/172/files"&gt;As you can see from the PR&lt;/a&gt;, there’s not actually all that much required to get the views integrated nicely. What’s left for me a this point is to tidy it up, consider what view configuration might need to exist at the application-level, and then make sure we’re doing everything we can to ensure hanami-view isn’t inextricably tied to the framework: I want to make sure that hanami-view could be switched out for some other view renderer and have the experience feel just as smooth.&lt;/p&gt;
&lt;p&gt;Once this is done, the approach for action integration will be very similar, so I hope I can roll through that one relatively quickly!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;See you next month 👋🏼&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Well, I think this has been a pretty good first effort at a monthly update! I hope it’s helpful for anyone wanting to know a little more about what’s going on in my little corner of the Ruby OSS world.&lt;/p&gt;
&lt;p&gt;I’m very excited by where things are going, and after a topsy turvy last 6 months for me personally (closing Icelab, staring a new job, etc.), I’m feeling well situated to resume regular work in the OSS sphere, so I’m hoping I’ll have a good few things to report on next month, too.&lt;/p&gt;
&lt;p&gt;See you then!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Speaking at RubyConf Thailand</title>
    <link rel="alternate" href="https://timriley.info/writing/2019/03/19/speaking-at-rubyconf-thailand"/>
    <id>https://timriley.info/writing/2019/03/19/speaking-at-rubyconf-thailand</id>
    <published>2019-03-19T10:00:00+00:00</published>
    <updated>2019-03-19T10:00:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;I have some very exciting news: I’ll be speaking at &lt;a href="https://rubyconfth.com"&gt;RubyConf Thailand&lt;/a&gt; in September! This will be the first ever Ruby conference in Bangkok, and it’s an honour to be part of it.&lt;/p&gt;
&lt;p&gt;It’s been a thrill every time I could join up with the Ruby community in the Asia Pacific region. With events like RedDotRubyConf currently taking a break, I’m relishing another chance to head back to warmer climes, to make new friends, and to learn about all the interesting work that people are doing.&lt;/p&gt;
&lt;p&gt;With &lt;a href="https://citizen428.net"&gt;Michael Kohl&lt;/a&gt; and crew running the conference, I have no doubt it will be a fantastic event. I hope to see you there! (Hint, the &lt;a href="https://www.papercall.io/rubyconfth"&gt;CFP is open&lt;/a&gt; until June)&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Podcast interview: Remote Ruby</title>
    <link rel="alternate" href="https://timriley.info/writing/2019/03/18/podcast-interview-remote-ruby"/>
    <id>https://timriley.info/writing/2019/03/18/podcast-interview-remote-ruby</id>
    <published>2019-03-18T10:55:00+00:00</published>
    <updated>2019-03-18T10:55:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;A few weeks back I had the pleasure of participating in my first podcast interview! I joined &lt;a href="https://jason.charn.es"&gt;Jason&lt;/a&gt; and &lt;a href="http://excid3.com"&gt;Chris&lt;/a&gt; for &lt;strong&gt;&lt;a href="https://remoteruby.transistor.fm/30"&gt;Episode 30 of Remote Ruby&lt;/a&gt;&lt;/strong&gt;. We talked about my getting started with programming, then some of the origins and motivations behind &lt;a href="https://icelab.com.au/"&gt;Icelab&lt;/a&gt;, &lt;a href="https://dry-rb.org/"&gt;dry-rb&lt;/a&gt;, and &lt;a href="https://rom-rb.org/"&gt;rom-rb&lt;/a&gt;. It was a lot of fun!&lt;/p&gt;
&lt;p&gt;In fact, while you’re there, I’d recommend just going ahead and subscribing to the whole &lt;a href="https://remoteruby.transistor.fm/"&gt;Remote Ruby&lt;/a&gt; podcast. Jason and Chris are gracious hosts, enthusiastic about Ruby and its future, and have a knack for getting interesting guests: every time a new episode comes out, it goes straight to the top of my queue.&lt;/p&gt;
&lt;p&gt;I first met Jason when I visited Nashville for &lt;a href="https://2017.southeastruby.com"&gt;Southeast Ruby in 2017&lt;/a&gt;. Even in the midst of his final-day conference preparations (and he was organizing this thing solo!), he took a couple of hours to hang out with me and nerd out on all things Ruby. This exemplifies the kind of person he is, and I’m really glad that he and Chris are bringing this same brand of hospitality to the community at large through their podcast.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Talking “Views, from the top”</title>
    <link rel="alternate" href="https://www.icelab.com.au/notes/tim-talks-views-from-the-top"/>
    <id>https://www.icelab.com.au/notes/tim-talks-views-from-the-top</id>
    <published>2019-03-17T10:55:00+00:00</published>
    <updated>2019-03-17T10:55:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>dry-view 0.6.0, an introductory talk, and plans for 1.0</title>
    <link rel="alternate" href="https://dry-rb.org/news/2019/02/12/dry-view-0-6-0-an-introductory-talk-and-plans-for-1-0/"/>
    <id>https://dry-rb.org/news/2019/02/12/dry-view-0-6-0-an-introductory-talk-and-plans-for-1-0/</id>
    <published>2019-02-12T09:00:00+00:00</published>
    <updated>2019-02-12T09:00:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Speaking at RubyConf AU 2019</title>
    <link rel="alternate" href="https://timriley.info/writing/2018/11/22/speaking-at-rubyconf-au"/>
    <id>https://timriley.info/writing/2018/11/22/speaking-at-rubyconf-au</id>
    <published>2018-11-22T10:00:00+00:00</published>
    <updated>2018-11-22T10:00:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;I’m thrilled to be speaking at next year’s &lt;a href="https://rubyconf.org.au/2019"&gt;RubyConf AU&lt;/a&gt; in Melbourne!&lt;/p&gt;
&lt;p&gt;I’ll be addressing the humble server-rendered view, in a talk entitled &lt;strong&gt;Views, from the top&lt;/strong&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;/Veni, vidi, view-ci/ – I came, I saw, I left the views in a mess.&lt;/p&gt;
&lt;p&gt;With server-rendered HTML still delivering most of the web, our views deserve more than a grab bag of helpers.&lt;/p&gt;
&lt;p&gt;Come along and learn the tools and techniques to conquer the design challenges that a complex view layer presents.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This topic has been brewing in my mind (and in various codebases) for a good two to three years now, so I’m especially excited to share it. I hope to present a compelling case for better architected views, and offer a good reminder that this is a layer in our web apps that is still ripe for improvements.&lt;/p&gt;
&lt;p&gt;The conference’s &lt;a href="https://rubyconf.org.au/2019/speakers"&gt;speaker lineup&lt;/a&gt; is still emerging, but it’s already packed with amazing-sounding talks. I hope to see you there!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Talking functional architecture at RedDotRubyConf</title>
    <link rel="alternate" href="https://www.icelab.com.au/notes/tim-talks-functional-architecture-at-reddotrubyconf"/>
    <id>https://www.icelab.com.au/notes/tim-talks-functional-architecture-at-reddotrubyconf</id>
    <published>2017-06-30T00:00:00+00:00</published>
    <updated>2017-06-30T00:00:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Icelab talks at RubyConf AU 2017</title>
    <link rel="alternate" href="https://www.icelab.com.au/notes/icelab-talks-at-rubyconf-au-2017"/>
    <id>https://www.icelab.com.au/notes/icelab-talks-at-rubyconf-au-2017</id>
    <published>2017-03-06T00:00:00+00:00</published>
    <updated>2017-03-06T00:00:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>A Rodakase retrospective: 1 year of dry-rb in production</title>
    <link rel="alternate" href="https://www.icelab.com.au/notes/a-rodakase-retrospective-1-year-of-dry-rb-in-production"/>
    <id>https://www.icelab.com.au/notes/a-rodakase-retrospective-1-year-of-dry-rb-in-production</id>
    <published>2017-02-23T00:00:00+00:00</published>
    <updated>2017-02-23T00:00:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>2016 in review</title>
    <link rel="alternate" href="https://timriley.info/writing/2017/1/9/2016-in-review"/>
    <id>https://timriley.info/writing/2017/1/9/2016-in-review</id>
    <published>2017-01-08T21:41:28+00:00</published>
    <updated>2017-01-08T21:41:28+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;I &lt;a href="http://openmonkey.com/blog/2010/01/07/2009-in-a-few-lists"&gt;had&lt;/a&gt; &lt;a href="http://openmonkey.com/blog/2011/01/05/2010-in-review"&gt;a&lt;/a&gt; &lt;a href="http://openmonkey.com/blog/2013/01/13/2011-in-review"&gt;good&lt;/a&gt; &lt;a href="http://openmonkey.com/blog/2013/01/01/2012-in-review"&gt;run&lt;/a&gt; of year in review posts, but fell off the bandwagon lately. It’s time to change that. Before I dive into 2016, here’s a recap of the intervening years:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2013:&lt;/strong&gt; Around the world tickets in hand, Misch and I worked, volunteered, and played Japan, Vietnam, Hong Kong, the USA, Finland, Germany, the UK, Spain, and Italy. An amazing time! Attended my first (and only) WWDC and had a blast. Started working on Decaf Sucks 2.0 (you’ll hear more about that &lt;em&gt;much&lt;/em&gt; later).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2014:&lt;/strong&gt; Settling back in Canberra and realising we could live for a long time in our (large by world standards) apartment, we renovated a little: new floors, paint, curtains. Made it feel like a whole new place. Misch and I gave birth to Clover, our best and most satisfying team effort yet.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2015:&lt;/strong&gt; Took our first with-kid overseas trip, and cruised through to Clover’s first birthday and our first parenting anniversary (which we celebrated with a giant bánh mì party). Icelab gathered for FarmLab, and we discussed alternatives-to-Rails for the first time. Our grandmothers both passed away, and we spent time with the extended family. Jojo and I held Rails Camp in Canberra in December, where we got to eat cake for Rails’ 10th birthday and watch Star Wars with 70 of our friends. Misch and I got pregnant again but sadly lost the little baby at 7 weeks.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Phew.&lt;/em&gt; That was some time. Now onto 2016.&lt;/p&gt;
&lt;p&gt;&lt;img src="/assets/95c63aea6f41-A236870E.jpg" alt="" /&gt;&lt;/p&gt;
&lt;h2&gt;At home with the (expanded!) family&lt;/h2&gt;
&lt;p&gt;Losing a baby at the end of 2016 was a big thing, but thankfully it came at a time when work and other demands scale back, so Misch and I spent some good quality time together and could regroup.&lt;/p&gt;
&lt;p&gt;We got a couple of big things done in the beginning of the year. First up, we bought a car! After two years of mostly car-free life, it was time for another way to get around the place. Our little Škoda Fabia does just that, and is fun to drive.&lt;/p&gt;
&lt;p&gt;Next, we renovated our bathroom! Knowing we’ll be living here for many years to come, this was a big and worthwhile upgrade to our home amenity. We splashed out and got a Toto washlet, too. I regret nothing.&lt;/p&gt;
&lt;p&gt;And in the last big thing for 2016, we became pregnant again and gave birth to baby Iris Persephone in October. This time around, the room at the Birth Centre was brimming with family. We wanted Clover there, so along came Misch’s parents too. Clover’s excited cry of “Baby!” upon seeing Iris come into the world is something I’ll always remember. Iris’ arrival brought another 6 weeks of time at home, which I enjoyed even more now that we’re a family of four.&lt;/p&gt;
&lt;h2&gt;Decaf Sucks 2.0&lt;/h2&gt;
&lt;p&gt;With Misch’s encouragement, I returned to my long stalled effort to release our all-new 2.0 version of Decaf Sucks. Turned out it didn’t need all that much; with just a couple of weeks of effort, Max and I got everything wrapped up and &lt;a href="https://www.icelab.com.au/notes/announcing-decaf-sucks-20"&gt;released it to the world&lt;/a&gt;. It was a weight from my shoulders and I’m happy to finally have it out there.&lt;/p&gt;
&lt;h2&gt;dry-rb&lt;/h2&gt;
&lt;p&gt;2016 brought a &lt;a href="https://www.icelab.com.au/notes/my-past-and-future-ruby/"&gt;seismic shift&lt;/a&gt; in how I write Ruby applications. After some experimentations with &lt;a href="http://rom-rb.org"&gt;rom-rb&lt;/a&gt; and Piotr Solnica’s &lt;a href="https://speakerdeck.com/timriley/functional-ruby-rodakase-and-another-world-of-ruby-web-applications"&gt;rodakase&lt;/a&gt; experiment late in 2015, I knew this was my future. So I dove in and contributed as much as I could to the fledgling set of libraries now known as &lt;a href="http://dry-rb.org/"&gt;dry-rb&lt;/a&gt;. And we got a lot done. We released a &lt;a href="http://github.com/dry-rb"&gt;whole bunch of gems&lt;/a&gt;, made things “official” with the launch of a &lt;a href="http://dry-rb.org/"&gt;website&lt;/a&gt; and &lt;a href="http://discuss.dry-rb.org/"&gt;discussion forum&lt;/a&gt;, and expanded the core team of developers to 5.&lt;/p&gt;
&lt;p&gt;Along with sharing code, I wanted to start sharing some of the &lt;em&gt;thinking&lt;/em&gt; behind the dry-rb style of Ruby app development, so I &lt;a href="https://www.icelab.com.au/notes/category/ruby"&gt;set about blogging&lt;/a&gt;, and managed to publish once a week for a good few months. This culminated with an &lt;a href="https://www.icelab.com.au/notes/next-generation-ruby-web-apps-with-dry-rb-rom-rb-and-roda-reddotrubyconf-2016"&gt;introductory talk I gave&lt;/a&gt; at &lt;a href="https://rdrc2016.herokuapp.com"&gt;RedDotRubyConf&lt;/a&gt; in Singapore. This was my first conference talk and I relished the opportunity to really polish a particular message. Luckily, I was able to build upon this a repeat performance at Rails Camp in Adelaide and at a Ruby community workshop over in Perth. No doubt, you can expect to hear plenty more from me about dry-rb in 2017 :)&lt;/p&gt;
&lt;h2&gt;Icelab&lt;/h2&gt;
&lt;p&gt;Icelab kicked off 2016 by &lt;a href="https://twitter.com/icelab/status/697179907097436164"&gt;celebrating our 10th birthday&lt;/a&gt;! I think we’ve built a remarkable little company and work-home to many good people, and I think the next 10 years will be even better.&lt;/p&gt;
&lt;p&gt;For me, most of 2016 at Icelab was spent getting us settled onto dry-rb and rom-rb as our preferred stack for server-side applications. We shipped &lt;a href="https://www.australianliterarystudies.com.au"&gt;our first production app&lt;/a&gt; with these all the way back in February, launched &lt;a href="https://www.icelab.com.au"&gt;our new website&lt;/a&gt; as an &lt;a href="https://github.com/icelab/berg"&gt;open source example app&lt;/a&gt; in June, and we have several more big sites that’ll see the light of day early in 2017. It took a little while to get over the knowledge and productivity hump, but I feel we’ve hit a good rhythm with the stack now, and given we’re the long-term maintainers of most of the things we ship, it’ll be something that I expect will pay dividends for many years to come.&lt;/p&gt;
&lt;p&gt;Open source was another big theme for the year. Along with our ongoing contributions to dry-rb, we took an “open source first” approach to any other standalone, reusable pieces of code we wrote. This small shift was a big help in making better design choices right from the beginning. You’ll be able to see some of this bear fruit when we take &lt;a href="https://github.com/icelab/formalist"&gt;our advanced form builder&lt;/a&gt; to 1.0 next year. It’s already been an incredibly useful tool across our client projects.&lt;/p&gt;
&lt;p&gt;I’m also proud that Icelab began contributing to the open source infrastructure that powers Ruby apps everywhere through our contributions to &lt;a href="https://rubytogether.org"&gt;Ruby Together&lt;/a&gt;, which we joined in 2016 as Australia’s first Emerald member.&lt;/p&gt;
&lt;h2&gt;And all the rest&lt;/h2&gt;
&lt;p&gt;And now I’ll collect everything else I could think of into a few broadly categorised lists:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Computer life:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I’ve removed Twitter apps from all my platforms. It’s helped me focus.&lt;/li&gt;
&lt;li&gt;Said goodbye to gentlyremind.me, the little Rails app I’ve been running to email me my Twitter favourites. Now that IFTTT can do that same thing, I’m happy to have one less running thing I have to worry about.&lt;/li&gt;
&lt;li&gt;Sometime in June I &lt;a href="https://twitter.com/timriley/status/741958757647716353"&gt;surpassed 250,000 all time Icelab chat messages&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Mulled many times with my co-workers on how we could run a better kind of tech meet-up in Canberra. Maybe this year!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Software development:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Continued my love/hate relationship with Docker, but I think now I’ve managed to find the right place for it in our development life: standardised production environments, and local dev only when we have to run something unusual.&lt;/li&gt;
&lt;li&gt;After uncountable years, I’m finally looking away from Heroku as our production environment of choice.&lt;/li&gt;
&lt;li&gt;Shipped a production iOS app built using &lt;a href="https://github.com/turbolinks/turbolinks-ios"&gt;Turbolinks for iOS&lt;/a&gt;, and it turned out rather nicely. I’d be happy to play with it some more.&lt;/li&gt;
&lt;li&gt;We settled on &lt;a href="https://github.com/choonkeat/attache"&gt;Attache&lt;/a&gt; as a standard handler for all our file uploads. I feel it is a smart architectural choice (and I was happy to meet its affable creator Choon Keat in Singapore!)&lt;/li&gt;
&lt;li&gt;We started to build &lt;a href="http://danger.systems"&gt;Danger&lt;/a&gt; into our CI builds. It’s already helpful, and I think we’re just scratching the surface.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/zverok/time_math2"&gt;time_math2&lt;/a&gt; is a great little Ruby library and a wonderful archetype for how “expressive” Ruby libraries can be made without Rails-style monkey patches.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Physical things:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;a href="http://www.hario.jp/seihin/productdetail.php?product=MCPN-14CBR"&gt;Mizudashi cold brew coffee pot&lt;/a&gt; I picked up to celebrate the launch of Decaf Sucks 2.0 makes amazing coffee and I’ve been putting it to good use ever since the weather warmed up. I’m aiming for 100% uptime of cold brew all summer long.&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://www.minaal.com/collections/your-future-gear/products/daily-bag"&gt;Minaal daily&lt;/a&gt; shipped from their Kickstarter campaign and it immediately became my every day carry. A great companion to their larger carry-on bag.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Apps:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;After trying innumerable things and never settling, I’ve finally found a home for &lt;em&gt;all&lt;/em&gt; my writing (pieces long, short, random or otherwise): it’s &lt;a href="https://www.ulyssesapp.com"&gt;Ulysses&lt;/a&gt;. What a great app.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://supertop.co/castro/"&gt;Castro 2&lt;/a&gt; came out with an ingenious new mechanic and I’m very happy to continue using it. It’s helped my jump onto a few new podcasts without the worry of managing them.&lt;/li&gt;
&lt;li&gt;CarPlay is great. I’ll readily admit this was a deciding factor in our new car choice and I wasn’t disappointed.&lt;/li&gt;
&lt;li&gt;Paw is now my one-stop shop for all my HTTP requestin’. Super polished.&lt;/li&gt;
&lt;li&gt;I’m back on good old Mail.app and happy to ignore all the we’ll-host-your-mail-and-your-passwords offerings that continue to swirl around.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Books, film, TV, etc.&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ripped through quite a bit of fiction as I waited for Clover to sleep (happily now she does this on her own). Highlights: &lt;em&gt;Seveneves&lt;/em&gt;, &lt;em&gt;Proxima&lt;/em&gt; &amp;amp; &lt;em&gt;Ultima&lt;/em&gt;, &lt;em&gt;The Prefect&lt;/em&gt;, &lt;em&gt;Aurora&lt;/em&gt; and the &lt;em&gt;Wool&lt;/em&gt; trilogy.&lt;/li&gt;
&lt;li&gt;I look at my &lt;a href="http://letterboxd.com/timriley/"&gt;Letterboxd profile&lt;/a&gt; and once again resolve to watch more cinema. Anyway, 2016’s highlights were &lt;em&gt;The Big Short&lt;/em&gt;, &lt;em&gt;Hunt for the Wilderpeople&lt;/em&gt;, &lt;em&gt;Easy A&lt;/em&gt;, &lt;em&gt;Arrival&lt;/em&gt;, &lt;em&gt;Crazy, Stupid Love&lt;/em&gt; and of course &lt;em&gt;Rogue One&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Subscribing to Netflix has been great. And fits perfectly well with our no-TV household.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Random:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cooked all the Filipino food I could think up. It was great to have this as a motivating theme behind all my cooking.&lt;/li&gt;
&lt;li&gt;And I tried &lt;a href="https://twitter.com/timriley/status/688506154335338496"&gt;toast and yoghurt&lt;/a&gt; for breakfast for the first time. Guess there’s always time for new firsts ¯_(ツ)_/¯.&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Announcing Decaf Sucks 2.0</title>
    <link rel="alternate" href="https://www.icelab.com.au/notes/announcing-decaf-sucks-20"/>
    <id>https://www.icelab.com.au/notes/announcing-decaf-sucks-20</id>
    <published>2016-09-07T00:00:00+00:00</published>
    <updated>2016-09-07T00:00:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Next-generation Ruby web apps with dry-rb, rom-rb, and Roda: RedDotRubyConf 2016</title>
    <link rel="alternate" href="https://www.icelab.com.au/notes/next-generation-ruby-web-apps-with-dry-rb-rom-rb-and-roda-reddotrubyconf-2016"/>
    <id>https://www.icelab.com.au/notes/next-generation-ruby-web-apps-with-dry-rb-rom-rb-and-roda-reddotrubyconf-2016</id>
    <published>2016-06-28T00:00:00+00:00</published>
    <updated>2016-06-28T00:00:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Conversational rom-rb, part 2: types, associations, and update commands</title>
    <link rel="alternate" href="https://www.icelab.com.au/notes/conversational-rom-rb-part-2-types-associations-and-update-commands"/>
    <id>https://www.icelab.com.au/notes/conversational-rom-rb-part-2-types-associations-and-update-commands</id>
    <published>2016-06-10T00:00:00+00:00</published>
    <updated>2016-06-10T00:00:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>A conversational introduction to rom-rb</title>
    <link rel="alternate" href="https://www.icelab.com.au/notes/a-conversational-introduction-to-rom-rb"/>
    <id>https://www.icelab.com.au/notes/a-conversational-introduction-to-rom-rb</id>
    <published>2016-06-03T00:00:00+00:00</published>
    <updated>2016-06-03T00:00:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Put HTTP in its place with Roda</title>
    <link rel="alternate" href="https://www.icelab.com.au/notes/put-http-in-its-place-with-roda"/>
    <id>https://www.icelab.com.au/notes/put-http-in-its-place-with-roda</id>
    <published>2016-05-24T00:00:00+00:00</published>
    <updated>2016-05-24T00:00:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>A change-positive Ruby web application architecture</title>
    <link rel="alternate" href="https://www.icelab.com.au/notes/a-change-positive-ruby-web-application-architecture"/>
    <id>https://www.icelab.com.au/notes/a-change-positive-ruby-web-application-architecture</id>
    <published>2016-05-19T00:00:00+00:00</published>
    <updated>2016-05-19T00:00:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Better code with an inversion of control container</title>
    <link rel="alternate" href="https://www.icelab.com.au/notes/better-code-with-an-inversion-of-control-container"/>
    <id>https://www.icelab.com.au/notes/better-code-with-an-inversion-of-control-container</id>
    <published>2016-05-10T00:00:00+00:00</published>
    <updated>2016-05-10T00:00:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Effective Ruby dependency injection at scale</title>
    <link rel="alternate" href="https://www.icelab.com.au/notes/effective-ruby-dependency-injection-at-scale"/>
    <id>https://www.icelab.com.au/notes/effective-ruby-dependency-injection-at-scale</id>
    <published>2016-05-03T00:00:00+00:00</published>
    <updated>2016-05-03T00:00:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Functional command objects in Ruby</title>
    <link rel="alternate" href="https://www.icelab.com.au/notes/functional-command-objects-in-ruby"/>
    <id>https://www.icelab.com.au/notes/functional-command-objects-in-ruby</id>
    <published>2016-04-26T00:00:00+00:00</published>
    <updated>2016-04-26T00:00:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Inactive records: the value objects your app deserves</title>
    <link rel="alternate" href="https://www.icelab.com.au/notes/inactive-records-the-value-objects-your-app-deserves"/>
    <id>https://www.icelab.com.au/notes/inactive-records-the-value-objects-your-app-deserves</id>
    <published>2016-04-19T00:00:00+00:00</published>
    <updated>2016-04-19T00:00:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>My past and future Ruby</title>
    <link rel="alternate" href="https://www.icelab.com.au/notes/my-past-and-future-ruby"/>
    <id>https://www.icelab.com.au/notes/my-past-and-future-ruby</id>
    <published>2016-04-14T00:00:00+00:00</published>
    <updated>2016-04-14T00:00:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>New podcasts</title>
    <link rel="alternate" href="https://timriley.info/writing/2014/11/6/new-podcasts"/>
    <id>https://timriley.info/writing/2014/11/6/new-podcasts</id>
    <published>2014-11-05T21:36:52+00:00</published>
    <updated>2014-11-05T21:36:52+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;I’ve enjoyed a couple of new podcasts lately:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.slate.com/articles/podcasts/working.html"&gt;Slate’s Working&lt;/a&gt; podcast finds people in interesting jobs and &lt;a href="http://www.slate.com/articles/life/working/2014/10/working_slate_s_new_podcast_on_how_americans_do_their_jobs.html"&gt;interviews them about their workdays&lt;/a&gt;. It’s brand new. The first episode – with Stephen Colbert – was fantastic. The show is short and dense. David Plotz as the host (along with some helpful editing, I’m sure) gets the guest talking (they usually have a lot to say) and then gets out of the way. I’ve appreciated finding another non-tech podcast to keep in my roster.&lt;/p&gt;
&lt;p&gt;I still have plenty of room for good tech podcasts, though. &lt;a href="http://thoughtbot.com"&gt;Thoughtbot&lt;/a&gt; have just launched another new podcast, called &lt;a href="http://bikeshed.fm"&gt;The Bike Shed&lt;/a&gt;, covering their general experiences in web development. This looks like it will be a discussion show with regular hosts Sean Griffin and Derek Prior. They seem humble and grounded, and the first show on &lt;a href="http://robots.thoughtbot.com/sandi-metz-rules-for-developers"&gt;Sandi Metz’ rules&lt;/a&gt; was thoughtful, and directly applicable to my work as a web developer. I’m still thinking over what they shared. I’m also appreciative they’ve kept the show to under 30 minutes. This makes it easy to cover on a walk into work!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Our Problem with Boxen</title>
    <link rel="alternate" href="https://timriley.info/writing/2014/5/18/our-problem-with-boxen"/>
    <id>https://timriley.info/writing/2014/5/18/our-problem-with-boxen</id>
    <published>2014-05-18T06:05:05+00:00</published>
    <updated>2014-05-18T06:05:05+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Over a year ago, we started using &lt;a href="https://boxen.github.com"&gt;Boxen&lt;/a&gt; at Icelab to manage our team’s development environments. It’s worked as advertised, but not well enough. I’m looking for something else.&lt;/p&gt;
&lt;p&gt;Our problem with Boxen is that it contains hundreds of different components, all of which are changing, &lt;em&gt;all the time.&lt;/em&gt; It needs constant tending. By the time a team is big enough to need something like Boxen, it’s paradoxically &lt;em&gt;too small&lt;/em&gt; to look after it properly: to have someone whose job it is to be “Boxenmaster,” someone who knows how these intricate parts all interact and can run the regular clean installs needed to make sure that the experience is smooth for new people (we’ve found it typical for a functional Boxen setup to stay working over repeated updates, while clean installs end up failing). We need a tool that we can set up once for a project and then have it still work reliably 6 months later when we revisit the project for further work.&lt;/p&gt;
&lt;p&gt;Boxen can be a trojan horse. If you don’t look after it properly, you risk creating two classes of developers within your team: those who were around to get Boxen working when you first rolled it out, now enjoying their nice functional development environments, and those who get started later, whose first experience in the team is one of frustration, non-productivity and a disheartening, unapproachable web of interconnected packages. And no one is safe: as soon as you want to upgrade that version of PHP, you could slip down there too.&lt;/p&gt;
&lt;p&gt;So while Boxen can indeed work as manager of personal development environments, and while Puppet is a serious and capable configuration manager, my recommendation is not to commit to Boxen half-heartedly. In fact, you may not even need something so rigorous and complex if your team is still small. I’m looking into such alternatives right now. I’m not settled on anything yet, but it’ll likely involve virtual machines, and perhaps use Docker as a way to keep it lightweight while working on multiple projects at a time. &lt;a href="http://orchardup.github.io/fig/"&gt;Fig&lt;/a&gt; seems like it could be a good fit. I’ll keep you posted.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Decaf Sucks 2: A New Old Design</title>
    <link rel="alternate" href="http://icelab.com.au/notes/decaf-sucks-2-a-new-old-design/"/>
    <id>http://icelab.com.au/notes/decaf-sucks-2-a-new-old-design/</id>
    <published>2013-08-23T23:18:33+00:00</published>
    <updated>2013-08-23T23:18:33+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Decaf Sucks 2: Starting Over</title>
    <link rel="alternate" href="http://icelab.com.au/notes/decaf-sucks-2-starting-over/"/>
    <id>http://icelab.com.au/notes/decaf-sucks-2-starting-over/</id>
    <published>2013-08-10T16:21:41+00:00</published>
    <updated>2013-08-10T16:21:41+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Decaf Sucks 2 Is Coming, 2 Hours at a Time</title>
    <link rel="alternate" href="http://icelab.com.au/notes/decaf-sucks-2-is-coming-2-hours-at-a-time/"/>
    <id>http://icelab.com.au/notes/decaf-sucks-2-is-coming-2-hours-at-a-time/</id>
    <published>2013-08-05T14:14:44+00:00</published>
    <updated>2013-08-05T14:14:44+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Sneaking Into the Everyday</title>
    <link rel="alternate" href="http://icelab.com.au/notes/sneaking-into-the-everyday/"/>
    <id>http://icelab.com.au/notes/sneaking-into-the-everyday/</id>
    <published>2013-07-18T16:04:35+00:00</published>
    <updated>2013-07-18T16:04:35+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Levelling Up With AngularJS: Building a Reusable Click to Edit Directive</title>
    <link rel="alternate" href="http://icelab.com.au/notes/levelling-up-with-angularjs-building-a-reusable-click-to-edit-directive/"/>
    <id>http://icelab.com.au/notes/levelling-up-with-angularjs-building-a-reusable-click-to-edit-directive/</id>
    <published>2013-07-01T05:36:19+00:00</published>
    <updated>2013-07-01T05:36:19+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Two i's</title>
    <link rel="alternate" href="https://timriley.info/writing/2013/6/16/the-next-most-interesting-thing"/>
    <id>https://timriley.info/writing/2013/6/16/the-next-most-interesting-thing</id>
    <published>2013-06-17T01:26:34+00:00</published>
    <updated>2013-06-17T01:26:34+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;I think it’s always good to be working on two things: The next most important thing, and the next most &lt;em&gt;interesting&lt;/em&gt; thing.&lt;/p&gt;
&lt;p&gt;– &lt;a href="http://37signals.com/svn/posts/3534-two-is"&gt;Jason Fried&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</content>
  </entry>
  <entry>
    <title>Things Get Fuzzy Fast: Examine Your Day So You Can Learn From It</title>
    <link rel="alternate" href="http://icelab.com.au/notes/things-get-fuzzy-fast-examine-your-day-so-you-can-learn-from-it/"/>
    <id>http://icelab.com.au/notes/things-get-fuzzy-fast-examine-your-day-so-you-can-learn-from-it/</id>
    <published>2013-06-04T03:01:55+00:00</published>
    <updated>2013-06-04T03:01:55+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>A Manageable Multi-Database Redis Development Setup</title>
    <link rel="alternate" href="http://icelab.com.au/notes/a-manageable-multi-database-redis-development-setup/"/>
    <id>http://icelab.com.au/notes/a-manageable-multi-database-redis-development-setup/</id>
    <published>2013-04-16T01:32:39+00:00</published>
    <updated>2013-04-16T01:32:39+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>There is No Lab</title>
    <link rel="alternate" href="http://icelab.com.au/notes/there-is-no-lab/"/>
    <id>http://icelab.com.au/notes/there-is-no-lab/</id>
    <published>2013-03-12T21:30:00+00:00</published>
    <updated>2013-03-12T21:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Announcing Decaf Sucks 1.1</title>
    <link rel="alternate" href="https://timriley.info/writing/2013/01/11/announcing-decaf-sucks-11"/>
    <id>https://timriley.info/writing/2013/01/11/announcing-decaf-sucks-11</id>
    <published>2013-01-10T08:00:00+00:00</published>
    <updated>2013-01-10T08:00:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;&lt;img src="/assets/f576e3f4d908-4749CD4B.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;It’s &lt;a href="http://icelab.com.au/notes/decaf-sucks-launch-countdown-a-restrospective/"&gt;been a while&lt;/a&gt; since we’ve talked about &lt;a href="http://decafsucks.com/"&gt;Decaf Sucks&lt;/a&gt; on the iPhone. Our first release has run smoothly for a year and a half, helping many thousands of iPhone owners find better coffee. Thanks to our well caffeinated contributors, nearly half of our reviews are now submitted from the app.&lt;/p&gt;
&lt;p&gt;On this note, we’re very excited to announce the release of Decaf Sucks version 1.1 &lt;a href="https://itunes.apple.com/au/app/decaf-sucks/id458958884?mt=8"&gt;in the App Store&lt;/a&gt;. This release makes it even easier to contribute reviews with the addition of Facebook login support (now available wherever Decaf Sucks exists).&lt;/p&gt;
&lt;p&gt;We’ve also added support for the iOS 6 and the larger iPhone 5 display, as well as giving the app some extra polish all around. In particular, our overseas friends can rest easy, because the distance to your favourite cafes is now reported in miles or kilometres based on the device’s region format setting.&lt;/p&gt;
&lt;p&gt;Decaf Sucks 1.1 is &lt;em&gt;still free&lt;/em&gt; and &lt;a href="https://itunes.apple.com/au/app/decaf-sucks/id458958884?mt=8"&gt;waiting for you in the App Store&lt;/a&gt;. Get it now and find some great coffee near you!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Read on for a full list of all the things we’ve changed.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;New features:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Login with Facebook to write reviews&lt;/li&gt;
&lt;li&gt;Support for the larger iPhone 5 display&lt;/li&gt;
&lt;li&gt;Distance units are localised into miles or kilometres based on the device’s settings&lt;/li&gt;
&lt;li&gt;iOS 6 compatibility updates&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Other improvements:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When setting a cafe’s location, show a callout above the map pin to make it clear that it can be dragged around.&lt;/li&gt;
&lt;li&gt;When setting a cafe’s location, ensure that the pin is actually draggable on the first tap.&lt;/li&gt;
&lt;li&gt;When setting a cafe’s location, always show a proper full address after dragging the pin around.&lt;/li&gt;
&lt;li&gt;Show a proper non-retina image for the back button’s pressed state.&lt;/li&gt;
&lt;li&gt;Fix a bug that sometimes caused the map view to start very zoomed out.&lt;/li&gt;
&lt;li&gt;When a review is completely empty, make sure the “Post” button stays disabled.&lt;/li&gt;
&lt;li&gt;Ensure the map for a single cafe encompasses the current location, if it is nearby.&lt;/li&gt;
&lt;li&gt;Fix a bug that led to some reviews being set to Hyderabad, India. &lt;em&gt;Yes, this was really weird.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://itunes.apple.com/au/app/decaf-sucks/id458958884?mt=8"&gt;Decaf Sucks 1.1 is available in the App Store now&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>My Boring Adventure</title>
    <link rel="alternate" href="https://timriley.info/writing/2013/01/06/my-boring-adventure"/>
    <id>https://timriley.info/writing/2013/01/06/my-boring-adventure</id>
    <published>2013-01-06T03:30:00+00:00</published>
    <updated>2013-01-06T03:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Austin Kleon’s &lt;a href="http://www.austinkleon.com/steal/"&gt;Steal Like An Artist&lt;/a&gt;, his manifesto for creativity in the digital age, is full of useful tips, with one that stands out for me:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Be boring. (It’s the only way to get work done.)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;While an inspired spark may be source of creative work, it’s the drudgery of thorough execution that’s necessary to bring it to completion. Even the smallest projects are an accumulation of many tiny details, each requiring time to get right. To do this, you need to show up and work, day after day. Being boring enables this.&lt;/p&gt;
&lt;p&gt;But you can be boring &lt;em&gt;and&lt;/em&gt; have adventure too. The way I’ve done this is to combine work and travel. Not the frenetic “I’ve got to squeeze as much as possible into my annual leave” kind of travel, but months at a time in a new place. This allows you the time to develop the routine needed for consistent, productive work, alongside many opportunities to explore your new home at a comfortable pace. Every lunch break affords you a chance to see something new, every outing for coffee the chance to become a regular in a new neighbourhood. Andy Warwick puts it excellently in this &lt;a href="http://www.quora.com/Living-Abroad/What-is-it-like-to-be-an-expat"&gt;Quora thread&lt;/a&gt;: “the mundane becomes an adventure when you live in a foreign land.”&lt;/p&gt;
&lt;p&gt;And when you wrap up your work for the night or for the week, there are hours and days ahead of you for adventure and discovery, which can inform and fuel your otherwise perfectly boring working life.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Written from Adelaide, Australia, where I’m living for two months.&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>2012 in Review</title>
    <link rel="alternate" href="https://timriley.info/writing/2013/01/01/2012-in-review"/>
    <id>https://timriley.info/writing/2013/01/01/2012-in-review</id>
    <published>2013-01-01T05:20:00+00:00</published>
    <updated>2013-01-01T05:20:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;h2&gt;A life abroad&lt;/h2&gt;
&lt;p&gt;This was a year marked by two new worlds: &lt;a href="http://subtletransition.com/"&gt;Garmisch&lt;/a&gt; and I spent six months &lt;a href="http://openmonkey.com/blog/2011/11/03/moving-to-the-philippines/"&gt;living in the Philippines&lt;/a&gt; and two &lt;a href="http://openmonkey.com/blog/2012/08/17/moved-to-hong-kong/"&gt;in Hong Kong&lt;/a&gt;. We experienced the full stretch of the urban spectrum; from the wide blue skies, leisurely pace (and mild chaos) of a provincial tropical island, through to Hong Kong’s unending bustle and its dense, concrete verticality.&lt;/p&gt;
&lt;p&gt;&lt;img src="/assets/60d143e80526-D90F7F60.jpg" alt="Legazpi Airport" /&gt;&lt;/p&gt;
&lt;p&gt;It was our first considerable stretch of time overseas. Living in the relative isolation did bring some challenges, but we overcame them, and in doing so we became a closer couple and hardier humans. And we’ve left the experience with &lt;a href="http://openmonkey.com/blog/2012/03/23/a-feeling-of-lightness/"&gt;a new perspective&lt;/a&gt; on what’s important, and on how we run our lives from here (Hint: there’s still a little more exploring to do!).&lt;/p&gt;
&lt;p&gt;This wasn’t the year’s only travel, either. The long stretches abroad were punctuated by visits elsewhere: Wellington in February for the magnificent &lt;a href="http://webstock.org.nz/"&gt;Webstock&lt;/a&gt;, Australia in April for a group hug with my workmates, Singapore in May for &lt;a href="http://reddotrubyconf.com/"&gt;RedDotRubyConf&lt;/a&gt;, and Tasmania in November for &lt;a href="http://railscamps.com/#portsorell_nov_2012"&gt;Railscamp 12&lt;/a&gt;. And outside all of this, with our own place rented out, we spent some quality time staying with our parents, firstly in Canberra and now in Adelaide.&lt;/p&gt;
&lt;p&gt;&lt;img src="/assets/52fd7284fc65-328BD8D0.jpg" alt="Hong Kong buildings" /&gt;&lt;/p&gt;
&lt;h2&gt;A bigger, better Icelab&lt;/h2&gt;
&lt;p&gt;It was a big year for &lt;a href="http://icelab.com.au/"&gt;Icelab&lt;/a&gt;. In January, we had five people: our Canberra office and Max on his own in Melbourne. In March, we &lt;a href="http://icelab.com.au/notes/icelab-gets-inventive-quadruples-melbourne-team/"&gt;merged with Inventive Labs&lt;/a&gt;, welcomed Narinda, Toby and Ally to the team, and assumed their office space in The Ironmongers on Brunswick Street. In August, &lt;a href="http://icelab.com.au/notes/melissa-kaulfuss-joins-icelab/"&gt;Melissa joined us&lt;/a&gt; in Melbourne as a project manager. In September, &lt;a href="http://icelab.com.au/notes/david-porter-joins-icelab/"&gt;David joined us&lt;/a&gt; in Canberra as a developer. In December, we moved out of our (then very crowded) office in Canberra into a bigger, much more comfortable space just a few blocks away. We finished the year with &lt;a href="http://icelab.com.au/about"&gt;nine amazing people&lt;/a&gt; and permanent offices in both Canberra and Melbourne. And all of this while I was mostly overseas. A distributed team can really work.&lt;/p&gt;
&lt;p&gt;The larger team meant we put out more work than ever. Plenty to be proud of, and plenty I &lt;em&gt;wish&lt;/em&gt; I could have been more involved in. That said, I did get to ship a few large projects this year:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PDF e-ticket support for &lt;a href="http://cornerhotel.com/"&gt;Ticketscout&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://thethousands.com.au/"&gt;The Thousands&lt;/a&gt;, with Max Wheeler &amp;amp; Anthony Kolber&lt;/li&gt;
&lt;li&gt;&lt;a href="http://youcamp.com/"&gt;Youcamp&lt;/a&gt;, with Toby Allder and Michael Honey, along with contributions from almost everyone in the lab
&lt;img src="/assets/fda2fe8d9620-AE6C1041.png" alt="Screenshots of The Thousands &amp;amp; Youcamp" /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Some other work endeavours&lt;/h2&gt;
&lt;p&gt;I had less time than usual this year to work on side-projects, but I did manage to ship &lt;a href="http://gentlyremind.me/"&gt;gentlyremind.me&lt;/a&gt; in February. It sends you a daily email of your recently favourited tweets. I use it everyday and it’s nice to see my friends do the same.&lt;/p&gt;
&lt;p&gt;I also found a little time to take a look into &lt;a href="http://www.rubymotion.com/"&gt;RubyMotion&lt;/a&gt; for building iOS apps. I put together &lt;a href="http://icelab.com.au/notes/rubymotion-and-rails-responders-at-the-canberra-ruby-crew/"&gt;an introductory talk&lt;/a&gt; and presented at a Canberra Ruby meeting and at Railscamp 12, where I also ported the bulk of the &lt;a href="http://decafsucks.com/"&gt;Decaf Sucks&lt;/a&gt; iOS app to RubyMotion in just a day or so. I expect I’ll spend a lot more time with this in the future.&lt;/p&gt;
&lt;h2&gt;Some new apps&lt;/h2&gt;
&lt;p&gt;A notable trend that emerged this year was my increased use of activity-tracking apps. Films I watched went in the lovely &lt;a href="http://letterboxd.com/"&gt;Letterboxd&lt;/a&gt;. My &lt;a href="http://decafsucks.com/people/1-timriley"&gt;Decaf Sucks&lt;/a&gt; page continued to grow as I explored new cities café-by-café. In Hong Kong, I started using &lt;a href="http://foursquare.com/timriley"&gt;Foursquare&lt;/a&gt; so I could record the eateries and other interesting places I visited, and it’s stuck with me since then. I tried to take note of my general activities using &lt;a href="http://dayoneapp.com/"&gt;Day One&lt;/a&gt; on both the Mac and iOS. It hasn’t quite become habitual yet, but anything I record there is a bonus. Finally, &lt;a href="http://www.rdio.com/people/tim_riley/"&gt;Rdio&lt;/a&gt; has become a daily source of musical wonder; my iTunes library is long since deleted.&lt;/p&gt;
&lt;h2&gt;Getting to the point&lt;/h2&gt;
&lt;p&gt;One of the freshest, foremost things in my mind about this year is a hard slog in the last six months; Icelab was growing, and there were was just a big backlog of work that &lt;em&gt;had to be done.&lt;/em&gt; To help, I bore down and worked harder and longer than ever before. We got past it, but it came at some personal cost. I lost opportunities for spending time with my wife, for exercise, exploring my new locations, and my own creative work.&lt;/p&gt;
&lt;p&gt;After all of that, though, I feel we’ve arrived in a good position to create a more sustainable workload in the future, and I haven’t lost any of my passion for creating things. If I still feel like this now, I know it won’t change, and I know that I’ll continue to put lots of time into it. What I &lt;em&gt;have&lt;/em&gt; learnt is how it feels to do it out of some kind of obligation, and I’ll do my best to ensure I don’t fall into that position again.&lt;/p&gt;
&lt;p&gt;But the biggest story, and the story worth remembering, is that I could do all of this — the hard work included — while still sharing wildly new experiences and building a stronger relationship with Garmisch. I couldn’t think of a better friend and travel companion, and I’m ever thankful for her company and support.&lt;/p&gt;
&lt;p&gt;I’ve finished the year with a stronger sense than ever of empowerment and direction. 2013 will be big!&lt;/p&gt;
&lt;p&gt;&lt;img src="/assets/355c6ef607a1-07C6A2CD.jpg" alt="Garmisch &amp;amp; me in 2012" /&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Out My Window</title>
    <link rel="alternate" href="https://timriley.info/writing/2012/09/16/out-my-window"/>
    <id>https://timriley.info/writing/2012/09/16/out-my-window</id>
    <published>2012-09-16T00:30:00+00:00</published>
    <updated>2012-09-16T00:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;&lt;img src="/assets/a8a24a9c9146-871A8B07.jpg" alt="The views out my windows" /&gt;&lt;/p&gt;
&lt;p&gt;The view out the window in my previous and current overseas offices: Talisay City (Negros Occidental, Philippines) and Sheung Wan (Hong Kong).&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>From a Good Idea and Persistence Came Sneakers</title>
    <link rel="alternate" href="https://timriley.info/writing/2012/09/11/from-a-good-idea-and-persistence-came-sneakers"/>
    <id>https://timriley.info/writing/2012/09/11/from-a-good-idea-and-persistence-came-sneakers</id>
    <published>2012-09-10T21:30:00+00:00</published>
    <updated>2012-09-10T21:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Stephen Tobolowsky shares some &lt;a href="http://www.slate.com/articles/arts/culturebox/2012/09/robert_redford_sidney_poitier_ben_kingsley_dan_aykroyd_what_it_was_like_shooting_the_movie_sneakers_.html"&gt;behind the scenes memories&lt;/a&gt; of &lt;em&gt;Sneakers&lt;/em&gt;, an all-time favourite film of mine, and finishes with his thoughts on scriptwriter Phil Alden Robinson:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Years later I ran into Phil at the symphony. I asked him I how he was able to come up with such a great script. He blushed and said he had worked on it for nine years. I know spending a long time writing something doesn’t guarantee success. But not giving up on a good idea almost always does.&lt;/p&gt;
&lt;/blockquote&gt;
</content>
  </entry>
  <entry>
    <title>Lost Cities for iOS</title>
    <link rel="alternate" href="https://timriley.info/writing/2012/08/29/lost-cities-for-ios"/>
    <id>https://timriley.info/writing/2012/08/29/lost-cities-for-ios</id>
    <published>2012-08-28T21:30:00+00:00</published>
    <updated>2012-08-28T21:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Lost Cities is one of my favourite card games, and over the weekend I was surprised to learn about the release of a &lt;a href="http://lostcitiesapp.com/"&gt;version for iOS&lt;/a&gt;. The app was released by &lt;a href="http://www.codingmonkeys.de/"&gt;TheCodingMonkeys&lt;/a&gt;, who previously built the &lt;a href="http://carcassonneapp.com/"&gt;Carcassonne app&lt;/a&gt;. I’m happy to report that Lost Cities is adapted just as delightfully for the iPhone.&lt;/p&gt;
&lt;p&gt;Lost Cities a two-player game, and while the mechanics don’t allow for the pass-and-play experience that Carcassonne offers, it’s still fun and responsive to play against friends, whether they’re in the same room or across the internet. The move to iOS also enhances the core experience of the game: that the scorekeeping and card counting is done for you allows you to focus solely on your tactics and execution. The single player challenges also encourage you to try different techniques and gameplay styles. This is a digital adaptation done right.&lt;/p&gt;
&lt;p&gt;The graphical polish on the app is amazing, and the music is &lt;em&gt;exquisite.&lt;/em&gt; Together, they really do create a feeling of daring and adventure, which is quite remarkable for a game that otherwise consists of stacks of cards. You want to try this game, if only to see how a card or board game &lt;em&gt;should&lt;/em&gt; be done on iOS.&lt;/p&gt;
&lt;p&gt;I was also interested to learn that TheCodingMonkeys were also the developers behind &lt;a href="http://www.codingmonkeys.de/subethaedit/"&gt;SubEthaEdit&lt;/a&gt;, the comparatively nerdy and (and definitely less game-like) networked collaborative text editor. Nice to see a company take their experience from one area of software development and use it to find success with some completely different products.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Fast Downscaling of Retina OS X Screenshots</title>
    <link rel="alternate" href="https://timriley.info/writing/2012/08/23/fast-downscaling-of-retina-os-x-screenshots"/>
    <id>https://timriley.info/writing/2012/08/23/fast-downscaling-of-retina-os-x-screenshots</id>
    <published>2012-08-22T21:30:00+00:00</published>
    <updated>2012-08-22T21:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Screenshots from the retina MacBook Pro look comically large when I share them with my &lt;em&gt;@1x&lt;/em&gt; teammates. This kind of fidelity is especially unnecessary when casually sharing annotated snapshots back and forth during the app development process.&lt;/p&gt;
&lt;p&gt;Automator makes it easy to build an app that will halve the dimensions of the images. Open Automator, choose to build a new &lt;em&gt;Application&lt;/em&gt;, then add these steps:&lt;/p&gt;
&lt;p&gt;&lt;img src="/assets/1a67e9a39885-D7902AC8.png" alt="Automator actions for retina image downscaling" /&gt;&lt;/p&gt;
&lt;p&gt;Save it to &lt;code&gt;/Applications&lt;/code&gt; and you’re done. Then, dragging an image onto the app’s icon will immediately copy it to your desktop and rescale it. Put it in your Dock for even easy access, or if you use &lt;a href="http://www.obdev.at/products/launchbar/index.html"&gt;LaunchBar&lt;/a&gt;, you can do it even faster with &lt;a href="http://www.obdev.at/resources/launchbar/help/index.php?chapter=InstantSend"&gt;InstantSend&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Saying Goodbye, Keeping Your Team</title>
    <link rel="alternate" href="https://timriley.info/writing/2012/08/21/saying-goodbye-keeping-your-team"/>
    <id>https://timriley.info/writing/2012/08/21/saying-goodbye-keeping-your-team</id>
    <published>2012-08-21T12:30:00+00:00</published>
    <updated>2012-08-21T12:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Noah Stokes &lt;a href="http://esbueno.noahstokes.com/post/29560226776/goodbye-san-francisco"&gt;shares his decision&lt;/a&gt; to leave San Francisco, and demonstrates that you can both move and take your colleagues with you:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Bold now shares a virtual office. Garrett is still in San Francisco, while Sam has moved to Austin, Texas and Charlie remains in Raleigh, North Carolina. It’s bittersweet to be virtual, but at the same time we all work to live, not live to work. So why not love where you live.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I’ve spent 9 of the past 12 months working remotely with my team at &lt;a href="http://icelab.com.au/"&gt;Icelab&lt;/a&gt;, and in this time, my wife and I have experienced life and adventures together in some &lt;a href="http://openmonkey.com/blog/2012/08/17/moved-to-hong-kong/"&gt;amazing&lt;/a&gt; and &lt;a href="http://openmonkey.com/blog/2011/11/03/moving-to-the-philippines/"&gt;personally significant&lt;/a&gt; parts of the world. It’s been a unique and precious period in our lives, and my work’s been a critical enabler for it.&lt;/p&gt;
&lt;p&gt;And when I &lt;em&gt;am&lt;/em&gt; at work, we’ve been as productive as ever. The time collaborating remotely has actually strengthened my understanding of my teammates. It takes a certain degree of thoroughness to explain yourself in text and over Skype. You can’t take shortcuts in your communication. It’s true that this has led to frustration at times, but it’s ultimately led to a more acute knowledge of their workings and motivations.&lt;/p&gt;
&lt;p&gt;Anyone who builds things knows that worthwhile creations take time to mature. One of the most worthwhile things you can work on is your team. If you’ve found a good one, do your best to keep it, no matter where you live.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Moved to Hong Kong</title>
    <link rel="alternate" href="https://timriley.info/writing/2012/08/17/moved-to-hong-kong"/>
    <id>https://timriley.info/writing/2012/08/17/moved-to-hong-kong</id>
    <published>2012-08-17T08:15:00+00:00</published>
    <updated>2012-08-17T08:15:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;As I write this, I look over Hollywood Road and watch the antique stores close down for the day. A stream of red-topped taxis buzz by. There’s still a waft of incense in the air from the nearby Man Mo temple. In a sweep of the eye, I can see hundreds of tightly packed residences stacked above each other. That’s right, I’m in Hong Kong!&lt;/p&gt;
&lt;p&gt;Time has really flown by since my wife and I &lt;a href="http://openmonkey.com/blog/2011/11/03/moving-to-the-philippines/"&gt;finished in the Philippines&lt;/a&gt; and came back to chilly Australia at the end of June. We spent a whirlwind seven weeks catching up with friends and work, and now we’ve already started the next chapter of our overseas adventures: two months in Hong Kong.&lt;/p&gt;
&lt;p&gt;&lt;img src="/assets/7441ce33f781-2443C9C1.png" alt="Map of Hong Kong" /&gt;&lt;/p&gt;
&lt;p&gt;Why Hong Kong? We’ve visited a couple of times and have really enjoyed it here, and after eight months in the provincial Philippines, it would be fun to experience the opposite end of the urban spectrum. Working remotely for &lt;a href="http://icelab.com.au/"&gt;Icelab&lt;/a&gt; has been no trouble, so we can go where our fancies take us.&lt;/p&gt;
&lt;p&gt;We’re staying in Sheung Wan, a fantastic neighbourhood. It’s an older area and still largely residential, so it’s quiet and charming, but just 10 minutes by foot to the bustle of Central. There are antique shops, galleries, boutiques and &lt;a href="http://decafsucks.com/search?q=sheung+wan"&gt;a bunch of decent cafés&lt;/a&gt;. Monocle has a &lt;a href="http://www.monocle.com/sections/affairs/Web-Articles/Neighbourhood-Sheung-Wan/"&gt;great video introduction&lt;/a&gt; to the neighbourhood.&lt;/p&gt;
&lt;h2&gt;Smarter the second time&lt;/h2&gt;
&lt;p&gt;Compared to the initial effort of preparing for the Philippines, moving here has been a breeze. All our affairs were already in order, so getting out again was merely a shuffling of what was in the suitcases. If you’ve made the effort to relocate for a significant period of time, you almost owe it to yourself to back it up with another trip.&lt;/p&gt;
&lt;p&gt;We’ve learnt from our first experience and have done a few things differently this time. Firstly, we’re staying in an apartment we found via &lt;a href="http://airbnb.com"&gt;Airbnb&lt;/a&gt;. The place is very comfortable and already well set up. Within a day of arriving, I was able to put in a productive day at work. For a shorter stay, it’s great not to worry about finding and establishing a new place to live.&lt;/p&gt;
&lt;p&gt;We also packed &lt;em&gt;far lighter&lt;/em&gt;. Here’s my packing list for 2 months:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Essentials&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;6 t-shirts&lt;/li&gt;
&lt;li&gt;1 pair of shorts&lt;/li&gt;
&lt;li&gt;1 pair of jeans&lt;/li&gt;
&lt;li&gt;1 pair of shoes (My trusty New Balance 574’s)&lt;/li&gt;
&lt;li&gt;1 pair of thongs&lt;/li&gt;
&lt;li&gt;Assorted underwear and socks (&lt;em&gt;much&lt;/em&gt; less than the Philippines)&lt;/li&gt;
&lt;li&gt;Toiletries&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Gear&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MacBook Pro&lt;/li&gt;
&lt;li&gt;iPad (mostly for reading)&lt;/li&gt;
&lt;li&gt;Jawbone Jambox&lt;/li&gt;
&lt;li&gt;4 external HDDs (2 x backup, 2 x media)&lt;/li&gt;
&lt;li&gt;Camera&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All up, 13kg in one still quite empty suitcase.&lt;/p&gt;
&lt;h2&gt;Say hello&lt;/h2&gt;
&lt;p&gt;We’ve a little over 6 weeks left here in Hong Kong. I’m hoping to make it out to a couple of tech meet-ups, but would love to meet anyone for lunch of coffee. &lt;a href="/contact"&gt;Say hello!&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>RubyMotion &amp; Rails Responders at the Canberra Ruby Crew</title>
    <link rel="alternate" href="https://timriley.info/writing/2013/01/13/rubymotion-rails-responders-at-the-canberra-ruby-crew"/>
    <id>https://timriley.info/writing/2013/01/13/rubymotion-rails-responders-at-the-canberra-ruby-crew</id>
    <published>2012-06-20T19:30:00+00:00</published>
    <updated>2012-06-20T19:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;This week the &lt;a href="http://canberraruby.com/"&gt;Canberra Ruby Crew&lt;/a&gt; meetings kicked off again after a long hiatus. Both Hugh and I gave presentations. Hugh spoke about our use of Rails responders for tidily offering layout-less content intended for display in modal popup windows. You won’t see his slides online just yet, but his use of &lt;a href="http://bartaz.github.com/impress.js"&gt;Impress.js&lt;/a&gt; with the demos embedded in iframes was &lt;em&gt;ingenious.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/assets/0d84ce63aa47-C0EF0098.png" alt="Hugh’s Rails Responders Slides" /&gt;&lt;/p&gt;
&lt;p&gt;I gave an introduction to &lt;a href="http://www.rubymotion.com/"&gt;RubyMotion&lt;/a&gt;, the impressive new Ruby implementation for developing iOS apps:&lt;/p&gt;
&lt;script async="async" class="speakerdeck-embed" data-id="4fe116fe324a6900220197d0" data-ratio="1.0778947368421052" src="//speakerdeck.com/assets/embed.js"&gt;&lt;/script&gt;
&lt;p&gt;I’ve played with all the other half-arsed attempts at bridging different languages or web tech with native iOS deployment, and none of them has left me convinced. RubyMotion is different. It feels &lt;em&gt;first-class,&lt;/em&gt; and in its brief couple of months since release, so many libraries have sprung up that look to make iOS development much more pleasant. I’m convinced enough that I’ll be porting &lt;a href="http://decafsucks.com/"&gt;Decaf Sucks&lt;/a&gt; across. I’ll let you know how it goes.&lt;/p&gt;
&lt;p&gt;If you’re interested in learning RubyMotion, here are some resources worth checking out:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://pragmaticstudio.com/screencasts/rubymotion"&gt;Free screencast&lt;/a&gt; from The Pragmatic Studio&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://github.com/HipByte/rubyMotionSamples"&gt;repository of example apps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The examples from iOS Programming Big Nerd Ranch 3rd ed, &lt;a href="https://github.com/jaimeiniesta/rubymotion-nerd"&gt;ported to RubyMotion&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;A long list of &lt;a href="https://github.com/railsfactory/rubymotion-cookbook/blob/master/projects/projects.list"&gt;open source RubyMotion projects&lt;/a&gt; (and &lt;a href="http://rubymotionapps.com/"&gt;another here&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Many thanks to Hugh and Matthew for organising this month’s meet. If you’re interested in coming along next time, &lt;a href="http://groups.google.com/group/canberra-ruby"&gt;join the mailing list&lt;/a&gt; and &lt;a href="http://twitter.com/rubyaustralia"&gt;follow @rubyaustralia on Twitter&lt;/a&gt;. See you then!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Icelab Singapore and RedDotRubyConf 2012</title>
    <link rel="alternate" href="http://icelab.com.au/notes/icelab-singapore-and-reddotrubyconf-2012/"/>
    <id>http://icelab.com.au/notes/icelab-singapore-and-reddotrubyconf-2012/</id>
    <published>2012-06-03T22:30:00+00:00</published>
    <updated>2012-06-03T22:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Wrapping Rack Middleware to Exclude Certain URLs (For Rails Streaming Responses)</title>
    <link rel="alternate" href="http://icelab.com.au/notes/wrapping-rack-middleware-to-exclude-certain-urls-for-rails-streaming-responses/"/>
    <id>http://icelab.com.au/notes/wrapping-rack-middleware-to-exclude-certain-urls-for-rails-streaming-responses/</id>
    <published>2012-05-21T02:35:00+00:00</published>
    <updated>2012-05-21T02:35:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>The Darker Side of Software Development</title>
    <link rel="alternate" href="https://timriley.info/writing/2012/05/09/the-darker-side-of-software-development"/>
    <id>https://timriley.info/writing/2012/05/09/the-darker-side-of-software-development</id>
    <published>2012-05-08T22:30:00+00:00</published>
    <updated>2012-05-08T22:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Jim Whimpey &lt;a href="http://icelab.com.au/notes/money-stress-and-the-cloud/"&gt;read my last post&lt;/a&gt; and &lt;a href="http://valhallaisland.com/blog/2012/tim-riley/"&gt;shared exactly how he feels&lt;/a&gt; when consumed with a software problem:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;After hours without progress you go to bed thinking about it. Thinking about it makes you angry and frustrated but &lt;em&gt;you can’t stop thinking about it.&lt;/em&gt; You spend so long at the computer you lose all perspective and follow paths that take you further from the solution. With each minute you spend banging your head against the problem you think less clearly and the possibility of solving the problem decreases. It is &lt;em&gt;frustrating.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is exactly what happened with me. I think there’s only so many times you can work like this before you burn out. It’s important to learn from those moments and develop a sense for when they begin to arise, so you know when it’s time to step back and short-circuit the cycle.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Money, Stress and The Cloud</title>
    <link rel="alternate" href="http://icelab.com.au/notes/money-stress-and-the-cloud/"/>
    <id>http://icelab.com.au/notes/money-stress-and-the-cloud/</id>
    <published>2012-05-06T20:30:00+00:00</published>
    <updated>2012-05-06T20:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Did you in-app purchase anything from the minibar, sir?</title>
    <link rel="alternate" href="http://icelab.com.au/notes/did-you-in-app-purchase-anything-from-the-minibar-sir/"/>
    <id>http://icelab.com.au/notes/did-you-in-app-purchase-anything-from-the-minibar-sir/</id>
    <published>2012-03-29T22:30:00+00:00</published>
    <updated>2012-03-29T22:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>A Feeling of Lightness</title>
    <link rel="alternate" href="https://timriley.info/writing/2012/03/23/a-feeling-of-lightness"/>
    <id>https://timriley.info/writing/2012/03/23/a-feeling-of-lightness</id>
    <published>2012-03-22T23:55:00+00:00</published>
    <updated>2012-03-22T23:55:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;In a recent Back to Work episode, Dan shared a line from &lt;em&gt;Fight Club&lt;/em&gt; that powerfully represents my feelings about clutter:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;As Tyler Durden said it best, “The things you own own you, man.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;a href="http://5by5.tv/b2w/56"&gt;Back to Work #56, One Giant Beholder Dinosaur&lt;/a&gt;, at 45:00.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I’ve removed clutter from my life in a big way. I did it by &lt;a href="http://openmonkey.com/blog/2011/11/03/moving-to-the-philippines/"&gt;moving overseas&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;What have I been doing here? Working, writing, exploring, drinking coffee, enjoying the company of my wife. Living life as usual. Nearly everything I do is in the 13 inches of my MacBook Pro. Everything I read is in my iPad. I have only a small collection of clothes (it’s the Philippines, after all). Effectively, I’ve been living with a single suitcase worth of things.&lt;/p&gt;
&lt;p&gt;Yes, we have bought some furniture and other things to make ourselves comfortable here, but I consider these entirely temporary. At the end of our sojourn here, I’ll not think twice about about parting with them.&lt;/p&gt;
&lt;p&gt;The result: an incredible feeling of lightness. A feeling of agility, and possibility. Our next steps are unhindered by our possessions. We’ve been thinking about where in the world we want to visit. About what places we want to get to know better. The future is exciting and ours to wield.&lt;/p&gt;
&lt;p&gt;This opening of the mind has extended far beyond geography. We’ve started to think more broadly than ever before about our ambitions for our work, what we create, and how we want to live. This experience will shape every aspect of our lives for years to come. &lt;em&gt;(And at the very least, I’ll certainly have a different mindset about how to populate a home, when we do settle down again)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You might consider packing up and moving overseas a rather extreme way to remove clutter from your life. I agree that it’s extreme, but it has the singular advantage that it facilitates a true mental decluttering along with the physical one. I think this makes it entirely worthwhile.&lt;/p&gt;
&lt;p&gt;Perhaps the change doesn’t need to be as extreme as ours. Maybe a house swap would get you away from most of your posessions long enough to give you a different perspective. Or maybe you could extend an overseas holiday and conduct some of your work there, so you can experience &lt;em&gt;ordinary life&lt;/em&gt; in another place. Or perhaps you really just need to throw some stuff out.&lt;/p&gt;
&lt;p&gt;Whatever you do, taking some concrete steps now may too bring you unexpected new feelings of lightness, feelings that only fold back into further positive action. It’s worth a try.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Derek Sivers on Doing Business</title>
    <link rel="alternate" href="https://timriley.info/writing/2012/03/18/derek-sivers-on-doing-business"/>
    <id>https://timriley.info/writing/2012/03/18/derek-sivers-on-doing-business</id>
    <published>2012-03-18T04:30:00+00:00</published>
    <updated>2012-03-18T04:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Derek Sivers answers “&lt;a href="http://derek.sivers.spillsthebeans.com/"&gt;Are business decisions for love or for money?&lt;/a&gt;” and summarises everything I want to do in business:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;For me? Love. The purpose of money is to trade for things that make you happy. So if you can bypass money and get directly to the happy, you’ve saved a lot of trouble. And it makes others happier, too, when you organize your business around non-monetary things.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If this jibes with you, then his book &lt;a href="http://sivers.org/a"&gt;Anything You Want&lt;/a&gt; is well worth a read.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>For the Love of Fika</title>
    <link rel="alternate" href="https://timriley.info/writing/2012/03/16/for-the-love-of-fika"/>
    <id>https://timriley.info/writing/2012/03/16/for-the-love-of-fika</id>
    <published>2012-03-16T05:30:00+00:00</published>
    <updated>2012-03-16T05:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;In Sweden, &lt;a href="http://nordiccoffeeculture.com/for-the-love-of-fika/"&gt;they have a custom I can definitely get into&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;While it’s true that many other countries also take coffee breaks, they aren’t appreciated in the same manner as they are here, nor are they mandated by law.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is from the &lt;a href="http://nordiccoffeeculture.com/"&gt;Nordic Coffee Culture Blog&lt;/a&gt;, which is a mainstay on my Flipboard home screen, along with &lt;a href="http://dearcoffeeiloveyou.com/"&gt;Dear Coffee, I Love You&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Export Trajectory Stories to CSV</title>
    <link rel="alternate" href="http://icelab.com.au/notes/export-trajectory-stories-to-csv/"/>
    <id>http://icelab.com.au/notes/export-trajectory-stories-to-csv/</id>
    <published>2012-03-14T12:30:00+00:00</published>
    <updated>2012-03-14T12:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Icelab gets inventive, quadruples Melbourne team</title>
    <link rel="alternate" href="http://icelab.com.au/notes/icelab-gets-inventive-quadruples-melbourne-team/"/>
    <id>http://icelab.com.au/notes/icelab-gets-inventive-quadruples-melbourne-team/</id>
    <published>2012-03-13T10:30:00+00:00</published>
    <updated>2012-03-13T10:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Roxette in Hong Kong</title>
    <link rel="alternate" href="https://timriley.info/writing/2012/03/13/roxette-in-hong-kong"/>
    <id>https://timriley.info/writing/2012/03/13/roxette-in-hong-kong</id>
    <published>2012-03-13T10:20:00+00:00</published>
    <updated>2012-03-13T10:20:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;&lt;img src="/assets/2024250d8164-533540A8.jpg" alt="Roxette in Hong Kong" /&gt;&lt;/p&gt;
&lt;p&gt;I saw Roxette play live in Hong Kong last week, something I thought would never happen, given Marie’s brain tumor, the band’s extended hiatus, and my location in the southern hemisphere. So it was a pleasure and privilege to see this childhood favourite band of mine in person, along with my eager wife.&lt;/p&gt;
&lt;p&gt;It was a great show. We were treated to new renditions of their songs (like a rock version of &lt;em&gt;Stars&lt;/em&gt;, Per singing &lt;em&gt;Crash! Boom! Bang!&lt;/em&gt;) and some masterful guitar work (like the epic extended solos in &lt;em&gt;7Twenty7&lt;/em&gt; and &lt;em&gt;The Look&lt;/em&gt;). But what stood out the most to me was the unadulterated passion and energy they all expressed. It was truly inspiring; I hope to feel and act the same way after the next 25 years of doing what I do.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Shawn Blanc on Benjamin Franklin’s Daily Schedule</title>
    <link rel="alternate" href="https://timriley.info/writing/2012/03/13/shawn-blanc-on-benjamin-franklins-daily-schedule"/>
    <id>https://timriley.info/writing/2012/03/13/shawn-blanc-on-benjamin-franklins-daily-schedule</id>
    <published>2012-03-13T09:55:00+00:00</published>
    <updated>2012-03-13T09:55:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;&lt;a href="http://shawnblanc.net/2012/03/franklin-schedule/"&gt;Why me must tend to how we spend our time&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I regularly come back to my own daily schedule to re-evaluate it and see if it is serving me as well as it should be. Because schedules, like finances, make excellent slaves but horrible masters.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Shawn also nicely summarises my feelings about time and the creative process:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;One big myth about creativity is that it cannot be harnessed. It is silly to believe that a creative person should live without routine or accountability or discipline.&lt;/p&gt;
&lt;/blockquote&gt;
</content>
  </entry>
  <entry>
    <title>Swipe to Go Back</title>
    <link rel="alternate" href="http://icelab.com.au/notes/swipe-to-go-back/"/>
    <id>http://icelab.com.au/notes/swipe-to-go-back/</id>
    <published>2012-03-02T05:30:00+00:00</published>
    <updated>2012-03-02T05:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>A Precious Hour</title>
    <link rel="alternate" href="https://timriley.info/writing/2012/03/02/a-precious-hour"/>
    <id>https://timriley.info/writing/2012/03/02/a-precious-hour</id>
    <published>2012-03-01T23:30:00+00:00</published>
    <updated>2012-03-01T23:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Rands on &lt;a href="http://www.randsinrepose.com/archives/2012/02/29/a_precious_hour.html"&gt;why being busy is no substitute for being creative&lt;/a&gt;. He’s building the habit of taking an hour each day to create and is already seeing the results. I &lt;a href="http://icelab.com.au/notes/the-benefits-of-waking-up-early/"&gt;did the same last year&lt;/a&gt; it was one of the major reasons that &lt;a href="http://icelab.com.au/notes/announcing-decaf-sucks-for-iphone/"&gt;Decaf Sucks for iPhone&lt;/a&gt; actually became a thing.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>A Webstock 2012 Recap</title>
    <link rel="alternate" href="http://icelab.com.au/notes/a-webstock-2012-recap/"/>
    <id>http://icelab.com.au/notes/a-webstock-2012-recap/</id>
    <published>2012-02-29T23:30:00+00:00</published>
    <updated>2012-02-29T23:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Announcing gentlyremind.me</title>
    <link rel="alternate" href="http://icelab.com.au/notes/announcing-gentlyremindme/"/>
    <id>http://icelab.com.au/notes/announcing-gentlyremindme/</id>
    <published>2012-02-27T22:30:00+00:00</published>
    <updated>2012-02-27T22:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Be More Than Your NDA</title>
    <link rel="alternate" href="https://timriley.info/writing/2012/02/27/be-more-than-your-nda"/>
    <id>https://timriley.info/writing/2012/02/27/be-more-than-your-nda</id>
    <published>2012-02-26T20:30:00+00:00</published>
    <updated>2012-02-26T20:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;This kind of conversation has happened to me a few times at various tech meetups.&lt;/p&gt;
&lt;p&gt;Me:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Great to meet you! So what kind of things do you work on?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Them:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Oh, I can’t tell you.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Me:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;…&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Where can you go from here? The weather? Sports?&lt;/p&gt;
&lt;p&gt;At meetups like this, the work is what binds us together. I understand that you can’t talk about your work if your employers impose an NDA, but that doesn’t have to mean the end of the conversation. How about a side project? Have you started one of those? It may only take a few hours on a weekend to get something running, and then you have something you can freely use to better connect with people. If not a side project, maybe there are a few libraries or tools you’ve been using that are interesting to you. Maybe you could talk about them.&lt;/p&gt;
&lt;p&gt;Participating in a tech meetup may initially be about hearing the presentations, but it doesn’t end there. You’ll want to give something back too. This means having something to talk about with your peers. Your NDA doesn’t have to stop that.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>500 Words before 8am</title>
    <link rel="alternate" href="https://timriley.info/writing/2012/02/24/500-words-before-8am"/>
    <id>https://timriley.info/writing/2012/02/24/500-words-before-8am</id>
    <published>2012-02-24T05:30:00+00:00</published>
    <updated>2012-02-24T05:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;&lt;a href="http://www.informationdiet.com/blog/read/500-words-before-8am"&gt;Wise advice&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I make sure to start every day as a producer, not a consumer.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Which reminds me of this, from &lt;a href="http://favstar.fm/users/_why/status/881768089"&gt;_why the lucky stiff&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;when you don’t create things, you become defined by your tastes rather than ability. your tastes only narrow &amp;amp; exclude people. so create.&lt;/p&gt;
&lt;/blockquote&gt;
</content>
  </entry>
  <entry>
    <title>Customising ActiveRecord's attribute formatting on inspect</title>
    <link rel="alternate" href="http://icelab.com.au/notes/customising-activerecords-attribute-formatting-on-inspect/"/>
    <id>http://icelab.com.au/notes/customising-activerecords-attribute-formatting-on-inspect/</id>
    <published>2012-02-02T10:30:00+00:00</published>
    <updated>2012-02-02T10:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Render Single-Line Markdown Text with Redcarpet</title>
    <link rel="alternate" href="http://icelab.com.au/notes/render-single-line-markdown-text-with-redcarpet/"/>
    <id>http://icelab.com.au/notes/render-single-line-markdown-text-with-redcarpet/</id>
    <published>2012-01-24T13:05:00+00:00</published>
    <updated>2012-01-24T13:05:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>New Relic, Heroku and Rails Streaming Responses</title>
    <link rel="alternate" href="http://icelab.com.au/notes/new-relic-heroku-and-rails-streaming-responses/"/>
    <id>http://icelab.com.au/notes/new-relic-heroku-and-rails-streaming-responses/</id>
    <published>2012-01-20T16:45:00+00:00</published>
    <updated>2012-01-20T16:45:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>2011 in Review</title>
    <link rel="alternate" href="https://timriley.info/writing/2013/01/13/2011-in-review"/>
    <id>https://timriley.info/writing/2013/01/13/2011-in-review</id>
    <published>2012-01-05T10:30:00+00:00</published>
    <updated>2012-01-05T10:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Each time I come to these posts, the year just past seems more activity-filled than ever. 2011 has been no exception. Another big year, with quite a few notable firsts for me:&lt;/p&gt;
&lt;h2&gt;A stake&lt;/h2&gt;
&lt;p&gt;In July I became a director and equal partner at &lt;a href="http://icelab.com.au/"&gt;Icelab&lt;/a&gt;, along with &lt;a href="http://michaelhoney.com"&gt;Michael&lt;/a&gt; and &lt;a href="http://makenosound.com/"&gt;Max&lt;/a&gt;. My time working at the lab so far has been immensely satisfying. I am privileged to work with such a &lt;a href="http://icelab.com.au/about"&gt;great team&lt;/a&gt;, and look forward to having Icelab as the foundation of many exciting future endeavours.&lt;/p&gt;
&lt;h2&gt;An app&lt;/h2&gt;
&lt;p&gt;For years I’d toyed with the idea of building an iOS app. It’s a platform I love to use, and one that will play an important role in the future of computing, and I wanted to be a part of it. Finally, with my attendance at Swipe Conference &lt;a href="http://icelab.com.au/notes/decaf-sucks-launch-countdown-starting-again-again/"&gt;as motivation&lt;/a&gt;, I learnt Objective-C and Cocoa, and built and released an app (also thanks to the tireless design work of &lt;a href="http://makenosound.com/"&gt;Max&lt;/a&gt;). Better still, it was an app for my favorite place on the internet, &lt;a href="http://decafsucks.com/"&gt;Decaf Sucks&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="/assets/552143fc9cd0-DF1310F1.png" alt="Decaf Sucks for iPhone" /&gt;&lt;/p&gt;
&lt;p&gt;We released the app in August, just after Decaf Sucks’ second birthday. Now, with a few months behind it, it looks like the release has been &lt;a href="http://icelab.com.au/notes/decaf-sucks-launch-countdown-a-restrospective/"&gt;been quite successful&lt;/a&gt;. Personally, it’s found me a bunch of new coffee to try, and helped to take me &lt;a href="http://decafsucks.com/people/1-timriley"&gt;well past 250 reviews&lt;/a&gt;. I hope it is a positive contribution for the coffee lovers of the world.&lt;/p&gt;
&lt;p&gt;With this big first release under my belt, I feel empowered to create a whole bunch of fun new experiences on iOS. Hopefully there’ll be a lot more to share on this front in 2012.&lt;/p&gt;
&lt;h2&gt;A talk&lt;/h2&gt;
&lt;p&gt;In September, the whole Icelab crew travelled up to Sydney for &lt;a href="http://south11.webdirections.org/"&gt;Web Directions South&lt;/a&gt;, where Michael and I spoke about &lt;a href="http://south11.webdirections.org/program/big-picture#web-or-native-smart-choices-for-smartphone-apps"&gt;smart choices for smartphone apps&lt;/a&gt; (i.e., &lt;em&gt;web or native, and tell me without the dogma&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;&lt;img src="/assets/a8225a92aadd-CF6B79BA.jpg" alt="Presenting at Web Directions South" /&gt;&lt;/p&gt;
&lt;p&gt;This was my first major conference presentation and I was honored to be part of such an inspirational event. I found the whole experience to be really rewarding. I enjoyed the singular focus that it encouraged for a good few weeks of thought, and the extra depths that it encouraged me to plumb in order to give a comprehensive overview of the topic. Its an experience I’d definitely like to repeat. Conferences of the world, who would like to have me in 2012? ;-)&lt;/p&gt;
&lt;h2&gt;A move&lt;/h2&gt;
&lt;p&gt;If all this wasn’t enough, my wife and I capped off our year by packing up our lives in Australia and &lt;a href="/2011/11/03/moving-to-the-philippines/"&gt;moving to the Philippines&lt;/a&gt;! I must say a little more about my &lt;a href="http://subtletransition.com/"&gt;amazing wife&lt;/a&gt; at this point: not only did she put up with me, look after me and keep me sane throughout my work on all of the above, she also shouldered most of the load for planning this move. I couldn’t have done half as much this year without her, and I’m lucky to be her husband.&lt;/p&gt;
&lt;p&gt;Anyway, as I write this now, I look out our front window onto the lush green of a small park, surrounded by palm trees, goats grazing, roosters crowing, and the kids of the neighbourhood running around. I’m definitely in a whole new place, and I look forward to the many adventures I will have here, away from the computer screen.&lt;/p&gt;
&lt;p&gt;[caption id=”” align=“alignnone” width=“1024.0”] &lt;img src="/assets/78690efcc4eb-6B63BEEC.jpg" alt="Approaching Sugar Beach, near Sipalay, Negros Occidenal" /&gt; Approaching Sugar Beach, near Sipalay, Negros Occidenal[/caption]&lt;/p&gt;
&lt;h2&gt;And more&lt;/h2&gt;
&lt;p&gt;In 2011, Icelab grew from three to five people. &lt;a href="http://hughevans.net/"&gt;Hugh&lt;/a&gt; and &lt;a href="http://www.andymccray.com/"&gt;Andy&lt;/a&gt; are good humans to see everyday and are invaluable as members of the team. I got to witness &lt;a href="http://moadoph.gov.au/"&gt;a lot&lt;/a&gt; of &lt;a href="http://mildenhall.moadoph.gov.au/"&gt;great&lt;/a&gt; &lt;a href="http://www.nma.gov.au/av/portmacquarie/"&gt;stuff&lt;/a&gt; &lt;a href="http://www.nma.gov.au/av/flemington/"&gt;come out&lt;/a&gt; &lt;a href="http://behindthelines.moadoph.gov.au/"&gt;of the lab&lt;/a&gt; this year, much of which I wish I could have worked on myself. I think this is a sign of a good workplace.&lt;/p&gt;
&lt;p&gt;I did get to make one big release. In November we launched the new &lt;a href="http://cornerhotel.com/"&gt;Corner Hotel&lt;/a&gt; site, along with a completely new ticket sales and box office management system, a Rails app that was practically my sole responsibility for the whole year. It’s one of Icelab’s biggest projects to date, and happily, everything’s gone pretty well since launch.&lt;/p&gt;
&lt;p&gt;Aside from shipping new projects, we also kept-on-shipping with our coffee subscription service, &lt;a href="http://dispatch.decafsucks.com/"&gt;Dispatch&lt;/a&gt;. It’s now &lt;a href="http://icelab.com.au/notes/dispatchs-first-birthday/"&gt;over a year in the running&lt;/a&gt;, and we’ve had a lot of fun sending coffee around the country.&lt;/p&gt;
&lt;p&gt;In the middle of 2012, my wife and I started &lt;a href="http://www.fitbomb.com/p/why-i-eat-paleo.html"&gt;eating paleo&lt;/a&gt;. It was a terrific decision. I’ve never felt healthier in my life, nor eaten better food. While it’s harder to follow closely here in the Philippines, we do our best, and I know that it will be an eating habit that I’ll keep for the long term.&lt;/p&gt;
&lt;p&gt;And in one final notable event, in April we were lucky to witness the wedding of our good friends Hugh &amp;amp; Tomomi. Since moving to Canberra a few years ago, the friends I’ve made there really make it feel like home. We’ll be back before too long.&lt;/p&gt;
&lt;p&gt;So that’s 2011. I finished the year feeling close to burnt out, but I’m proud of what I was able to achieve and I’ve now learnt the critical (if not already obvious) lesson that it’s best not to have many work commitments overlapping with an international house move. Now that we’ve settled here and had a short break over Christmas, I’m ready to go again and looking forward to a productive &amp;amp; venturesome 2012!&lt;/p&gt;
&lt;p&gt;&lt;img src="/assets/08920eb30162-BB764CF5.jpg" alt="Garmisch and me" /&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Decaf Sucks Launch Countdown: A Restrospective</title>
    <link rel="alternate" href="http://icelab.com.au/notes/decaf-sucks-launch-countdown-a-restrospective/"/>
    <id>http://icelab.com.au/notes/decaf-sucks-launch-countdown-a-restrospective/</id>
    <published>2011-12-23T09:30:00+00:00</published>
    <updated>2011-12-23T09:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Dispatch's First Birthday</title>
    <link rel="alternate" href="https://www.icelab.com.au/notes/dispatchs-first-birthday"/>
    <id>https://www.icelab.com.au/notes/dispatchs-first-birthday</id>
    <published>2011-12-09T22:30:00+00:00</published>
    <updated>2011-12-09T22:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Moving to the Philippines</title>
    <link rel="alternate" href="https://timriley.info/writing/2011/11/03/moving-to-the-philippines"/>
    <id>https://timriley.info/writing/2011/11/03/moving-to-the-philippines</id>
    <published>2011-11-03T11:20:00+00:00</published>
    <updated>2011-11-03T11:20:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;This Saturday, I am moving to the Philippines!&lt;/p&gt;
&lt;p&gt;For a long time, I’ve wanted to spend an extended time overseas, to learn a new place, culture and language, and tackle all the interesting challenges that go along with it. Now, it’s actually going to happen, thanks to my &lt;a href="http://profiles.ayad.com.au/AyadProfileDetail.aspx?AmbassadorID=5938"&gt;equally adventurous wife&lt;/a&gt; and &lt;a href="http://icelab.com.au/"&gt;flexible workplace&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://maps.google.com/maps?q=Bacolod+City,+Western+Visayas,+Philippines&amp;amp;hl=en&amp;amp;sll=12.071553,121.662598&amp;amp;sspn=9.101249,9.294434&amp;amp;vpsrc=0&amp;amp;hnear=Bacolod+City,+Negros+Occidental,+Western+Visayas,+Philippines&amp;amp;t=m&amp;amp;z=12"&gt;&lt;img src="/assets/83e2e859e310-DB51ED72.jpg" alt="Location of Bacolod within the Visayas" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I’ll be living in &lt;a href="http://en.wikipedia.org/wiki/Bacolod"&gt;Bacolod, Negros Occidental&lt;/a&gt; and continuing my work for Icelab as usual. I’m really looking forward to getting involved with the web/mobile development community over there, both in the Philippines and in SE Asia in general. So if you’re in the area, &lt;a href="/contact"&gt;please say hi!&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This also means that Icelab is much more easily available for hire in SE Asia. If you’re after some finely crafted web or mobile apps, &lt;a href="http://icelab.com.au/contact"&gt;get in touch&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It’s going to be a great adventure, and I can’t wait to get started. I’ll no doubt have plenty of interesting things to share, so be sure to follow me on &lt;a href="http://tumble.openmonkey.com/"&gt;Tumblr&lt;/a&gt; and &lt;a href="http://flickr.com/photos/timriley"&gt;Flickr&lt;/a&gt;, where I’ll be documenting my travel experiences.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Announcing Decaf Sucks for iPhone</title>
    <link rel="alternate" href="http://icelab.com.au/notes/announcing-decaf-sucks-for-iphone/"/>
    <id>http://icelab.com.au/notes/announcing-decaf-sucks-for-iphone/</id>
    <published>2011-08-30T00:25:00+00:00</published>
    <updated>2011-08-30T00:25:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Decaf Sucks Launch Countdown: Development Complete!</title>
    <link rel="alternate" href="http://icelab.com.au/notes/decaf-sucks-launch-countdown-development-complete/"/>
    <id>http://icelab.com.au/notes/decaf-sucks-launch-countdown-development-complete/</id>
    <published>2011-08-23T02:25:00+00:00</published>
    <updated>2011-08-23T02:25:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Decaf Sucks Launch Countdown: Un-Designing the App</title>
    <link rel="alternate" href="http://icelab.com.au/notes/decaf-sucks-launch-countdown-un-designing-the-app/"/>
    <id>http://icelab.com.au/notes/decaf-sucks-launch-countdown-un-designing-the-app/</id>
    <published>2011-08-06T06:50:00+00:00</published>
    <updated>2011-08-06T06:50:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Decaf Sucks Launch Countdown: Finishing the API</title>
    <link rel="alternate" href="http://icelab.com.au/notes/decaf-sucks-launch-countdown-finishing-the-api/"/>
    <id>http://icelab.com.au/notes/decaf-sucks-launch-countdown-finishing-the-api/</id>
    <published>2011-07-30T06:45:00+00:00</published>
    <updated>2011-07-30T06:45:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Decaf Sucks Launch Countdown: Getting to Work</title>
    <link rel="alternate" href="http://icelab.com.au/notes/decaf-sucks-launch-countdown-getting-to-work/"/>
    <id>http://icelab.com.au/notes/decaf-sucks-launch-countdown-getting-to-work/</id>
    <published>2011-07-23T04:50:00+00:00</published>
    <updated>2011-07-23T04:50:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Decaf Sucks Launch Countdown: Starting Again, Again</title>
    <link rel="alternate" href="http://icelab.com.au/notes/decaf-sucks-launch-countdown-starting-again-again/"/>
    <id>http://icelab.com.au/notes/decaf-sucks-launch-countdown-starting-again-again/</id>
    <published>2011-07-15T00:05:00+00:00</published>
    <updated>2011-07-15T00:05:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>RailsCamp 9</title>
    <link rel="alternate" href="http://icelab.com.au/notes/icelab-goes-camping/"/>
    <id>http://icelab.com.au/notes/icelab-goes-camping/</id>
    <published>2011-06-14T00:45:00+00:00</published>
    <updated>2011-06-14T00:45:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Run Your Own Piece of Heroku with Foreman</title>
    <link rel="alternate" href="http://icelab.com.au/notes/run-your-own-piece-of-heroku-with-foreman/"/>
    <id>http://icelab.com.au/notes/run-your-own-piece-of-heroku-with-foreman/</id>
    <published>2011-06-03T02:50:00+00:00</published>
    <updated>2011-06-03T02:50:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>What's New With Decaf Sucks</title>
    <link rel="alternate" href="http://icelab.com.au/notes/whats-new-with-decaf-sucks/"/>
    <id>http://icelab.com.au/notes/whats-new-with-decaf-sucks/</id>
    <published>2011-05-26T00:45:00+00:00</published>
    <updated>2011-05-26T00:45:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Canberra's Best Coffee</title>
    <link rel="alternate" href="https://timriley.info/writing/2011/05/11/canberras-best-coffee"/>
    <id>https://timriley.info/writing/2011/05/11/canberras-best-coffee</id>
    <published>2011-05-10T22:30:00+00:00</published>
    <updated>2011-05-10T22:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;&lt;em&gt;A feature article on &lt;a href="http://www.hercanberra.com.au/index.php/2011/05/11/best-beans/"&gt;HerCanberra&lt;/a&gt;, with the lowdown on the Canberra coffee scene.&lt;/em&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Whether it’s your morning pick-me-up, social lubricant, or lifelong passion, few things are more evocative than coffee. And as the Canberra days turn colder, the city’s cafés will see a growing flock of people come through their doors in search of a warming espresso beverage. Luckily for Canberrans, there’s no better time than now to find a great coffee and café experience in our city.&lt;/p&gt;
&lt;p&gt;Two years ago I created &lt;a href="http://decafsucks.com/"&gt;Decaf Sucks&lt;/a&gt; along with the rest of the caffeine-loving crowd at &lt;a href="http://icelab.com/"&gt;Icelab&lt;/a&gt; as a place for us all to record our coffee drinking journeys. It has since become a vibrant and considerably useful resource for location-based café and coffee reviews. It is a place for us all to help each other find the good coffee and avoid the bad.&lt;/p&gt;
&lt;p&gt;Since it is homegrown here in Canberra, it also has the internet’s best coverage of cafés in our city, with 66 people having contributed over 335 reviews for 174 different cafés. Today I’d like to take you on a tour of some of Canberra’s standout cafés, based on the opinions of the Decaf Sucks reviewers.&lt;/p&gt;
&lt;p&gt;The hottest café in Canberra right now would have to be &lt;a href="http://decafsucks.com/cafes/624-lonsdale-street-roasters"&gt;Lonsdale Street Roasters&lt;/a&gt; in Braddon. They’ve hit on a mix of great in-house roasted coffee, simple and well made food from a wood oven, and an always-bustling friendly atmosphere. Heading into Civic, we have &lt;a href="http://decafsucks.com/cafes/508-bean-in-the-city"&gt;Bean in the City&lt;/a&gt;, a hole in the wall that is big on coffee, as well as selling the best portuguese tarts in the city. Over in the west of the city, head into &lt;a href="http://decafsucks.com/cafes/379-harvest"&gt;Harvest&lt;/a&gt; (the newest member of the &lt;a href="http://decafsucks.com/cafes/307-kingston-grind"&gt;Kingston Grind&lt;/a&gt;, &lt;a href="http://decafsucks.com/cafes/29-group-seven"&gt;Group 7&lt;/a&gt; and &lt;a href="http://decafsucks.com/cafes/304-tonic"&gt;Tonic&lt;/a&gt; family) and give one of their syphon or cold press coffees a try.&lt;/p&gt;
&lt;p&gt;If you’re in the south of the city, be sure to try &lt;a href="http://decafsucks.com/cafes/298-lava"&gt;Lava&lt;/a&gt; in Weston, for what could be the best coffee south of the lake. Or if you’re after a meal and a place to hang out a while, head to &lt;a href="http://decafsucks.com/cafes/50-a-bite-to-eat-a-drink-as-well"&gt;A Bite To Eat, A Drink As Well&lt;/a&gt;, where you can enjoy the expansive space filled with raw brick, comfy lounges and 1970s-kitsch furniture, or soak up the sun in the courtyard out the back. It’s also a good place for a beer if your visit lingers into the afternoon.&lt;/p&gt;
&lt;p&gt;You needn’t despair if you ever find yourself hankering for a caffeine hit in Fyshwick, since you can just head to &lt;a href="http://decafsucks.com/cafes/462-ona-coffee"&gt;Ona Coffee&lt;/a&gt; on Wollongong St, where they are serious about good coffee. Heading back north, the Belconnen Fresh Food Markets offers a couple of good coffee options among the fruit and veg, in &lt;a href="http://decafsucks.com/cafes/500-beppes"&gt;Beppe’s Tuscan Kitchen&lt;/a&gt; and &lt;a href="http://decafsucks.com/cafes/713-as-nature-intended"&gt;As Nature Intended&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Finally, there’s no dearth of decent coffee if you’re heading for a drive outside Canberra. Try &lt;a href="http://decafsucks.com/cafes/165-cork-street-gallery-cafe"&gt;Cork Street Gallery Cafe&lt;/a&gt; in Gundaroo for a rustic outdoor dining area, where you can enjoy some delicious pizza with your coffee. In Bungendore, &lt;a href="http://decafsucks.com/cafes/675-the-provincial-pantry"&gt;The Provincial Pantry&lt;/a&gt; has received much high praise from the Decaf Sucks reviewers, and &lt;a href="http://decafsucks.com/cafes/701-the-albion"&gt;The Albion&lt;/a&gt; in Braidwood looks to be the pick of a town that is already bursting with tasty food and drink options.&lt;/p&gt;
&lt;p&gt;While that should certainly keep you busy for a while, we’ve only just scratched the surface. There’s plenty of other good coffee to discover in Canberra, and &lt;a href="http://decafsucks.com/"&gt;Decaf Sucks&lt;/a&gt; can help you do it. Visit the site in your desktop browser and you can enter a location to see all the reviews for cafés in that area. If you’re out and about, visit &lt;a href="http://decafsucks.com/"&gt;Decaf Sucks&lt;/a&gt; on your iPhone for an optimised experience that can show you the cafés around your precise location.&lt;/p&gt;
&lt;p&gt;We’d love to welcome you into the Decaf Sucks family! We made Decaf Sucks as simple as possible for people to contribute, so please consider &lt;a href="http://decafsucks.com/reviews/new"&gt;writing some reviews&lt;/a&gt;. All you need is an opinion to share and a star rating, nothing more. Better still, logging in is easy through connecting your Twitter or Facebook accounts. Your reviews will not only serve as a handy coffee diary, but also could be instrumental in helping introduce other people to fantastic new café experiences, whether in Canberra or further afield.&lt;/p&gt;
&lt;p&gt;For all those times when you’re happy to stay at home, we can also help you to drink great coffee. Last year we launched our coffee bean subscription service: &lt;a href="http://dispatch.decafsucks.com/"&gt;Dispatch, from Decaf Sucks&lt;/a&gt;. We’ve teamed up with the Lonsdale Street Roasters to deliver a fresh and tasty bag of coffee beans to your door every month. We introduce different beans every month, making Dispatch a great way to discover new coffee flavours. It’s an easy way to keep your cupboard stocked with fresh beans, and also makes for excellent gift idea. &lt;a href="http://dispatch.decafsucks.com/"&gt;Check it out&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This month we have a special offer for the readers of HerCanberra. Write at least one review on &lt;a href="http://decafsucks.com/"&gt;Decaf Sucks&lt;/a&gt;, and you could win a free month’s delivery of fresh Dispatch coffee beans. Just email us at hello(at)decafsucks.com to let us know and you’ll in the running. We look forward to reading your reviews!&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Tim Riley is a web developer who thinks almost as much about coffee as he does about building great experiences for the web and mobile devices. He lives in Braddon with his wife, and they love being just a stone’s throw from many great cafés. Tim works at &lt;a href="http://icelab.com.au/"&gt;Icelab&lt;/a&gt; and blogs at &lt;a href="http://openmonkey.com/"&gt;openmonkey.com&lt;/a&gt;. His coffee of choice is the macchiato.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Put Your Mac to Sleep With a Backup</title>
    <link rel="alternate" href="https://timriley.info/writing/2011/05/01/put-your-mac-to-sleep-with-a-backup"/>
    <id>https://timriley.info/writing/2011/05/01/put-your-mac-to-sleep-with-a-backup</id>
    <published>2011-05-01T06:10:00+00:00</published>
    <updated>2011-05-01T06:10:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;One aspect of my backup strategy is a full clone of my MacBook Pro’s hard drive using &lt;a href="http://www.shirt-pocket.com/SuperDuper/"&gt;SuperDuper&lt;/a&gt;. This is very useful, since it means that I have a complete, bootable duplicate of my hard drive that I can use to get back up and running quickly in the event of a hardware failure. The downside of backing up this way, however, is that it can become a “whenever I remember” approach, which is a recipe for quickly falling behind or forgetting altogether.~&lt;/p&gt;
&lt;p&gt;This is especially the case if you use a portable computer that isn’t always hooked in and running at a particular location (I carry mine to and from &lt;a href="http://icelab.com.au/"&gt;the lab&lt;/a&gt; each day).&lt;/p&gt;
&lt;p&gt;Here’s the solution that has worked for me: configure a SuperDuper backup schedule that commences whenever your external disk is connected, and finishes by putting your Mac to sleep:&lt;/p&gt;
&lt;p&gt;&lt;img src="/assets/f1261aa894fb-8745B36E.png" alt="SuperDuper" /&gt;&lt;/p&gt;
&lt;p&gt;My MacBook usually gets opened over the course of a normal evening at home. My rule is that the only way I can put it to sleep for the night is by connecting my backup drive and triggering the SuperDuper backup. It’s fast and simple; connecting a single cable to the USB port is no bother at all. The backup runs and then my Mac goes to sleep, and it means I can sleep easier too, with my data better protected.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;nb. If you adopt this approach, it’s useful to know the control+shift+eject keyboard shortcut, which you can use to turn off your Mac’s display once you’ve attached the backup drive.&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>The Benefits of Waking Up Early</title>
    <link rel="alternate" href="http://icelab.com.au/notes/the-benefits-of-waking-up-early/"/>
    <id>http://icelab.com.au/notes/the-benefits-of-waking-up-early/</id>
    <published>2011-04-18T04:45:00+00:00</published>
    <updated>2011-04-18T04:45:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Useful Heroku-Friendly Rewrites with rack-rewrite</title>
    <link rel="alternate" href="http://icelab.com.au/notes/useful-heroku-friendly-rewrites-with-rack-rewrite/"/>
    <id>http://icelab.com.au/notes/useful-heroku-friendly-rewrites-with-rack-rewrite/</id>
    <published>2011-04-06T06:45:00+00:00</published>
    <updated>2011-04-06T06:45:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>New site</title>
    <link rel="alternate" href="https://timriley.info/writing/2011/03/28/new-site"/>
    <id>https://timriley.info/writing/2011/03/28/new-site</id>
    <published>2011-03-27T21:00:00+00:00</published>
    <updated>2011-03-27T21:00:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Here I am again, with a new blog platform and site design. This time around, I have some interesting things to say about it. The reason for this particular rewrite was to unify my writing. Since I joined &lt;a href="http://icelab.com.au/"&gt;Icelab&lt;/a&gt;, I’ve published the bulk of my writing onto &lt;a href="http://icelab.com.au/notes/"&gt;the blog over there&lt;/a&gt;. This is entirely my intention, since it all helps to raise awareness of the good work that we’re doing, but it does leave my personal site looking rather unloved. Now I mave made it so all my posts are available in a single place, right here. Everything is available in snippet form on the home page, as well as in &lt;a href="/archives/"&gt;the archives&lt;/a&gt; and &lt;a href="http://feeds.feedburner.com/BlahBlahWoofWoof"&gt;the feed&lt;/a&gt;. When you choose to read a particular article, you will be taken back to the Icelab site for the articles originating from there, while my personal articles you will see in full here.&lt;/p&gt;
&lt;p&gt;This was all remarkably easy to achieve using the small but powerful &lt;a href="https://github.com/cloudhead/toto"&gt;toto&lt;/a&gt; Ruby blog engine. Eschewing the traditional database, Toto reads your articles from files on disk, and is happy to parse &lt;a href="http://daringfireball.net/projects/markdown"&gt;Markdown&lt;/a&gt; for you. Since we also use Markdown on the Icelab site, I can simply paste in copies of those articles verbatim, and toto is good to go. Then, if I add a &lt;code&gt;url&lt;/code&gt; attribute to the article’s metadata, Toto will link to that URL for viewing the article in full. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;title: Embrace the Metaclass and Extend Your ActiveModels
published_at: 2011-03-25 16:47:00
location: Canberra, Australia
url: http://icelab.com.au/notes/embrace-the-metaclass-and-extend-your-activemodels/

Part of the challenge in building [RentMonkey](http://rentmonkey.com.au/) is dealing with...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is possible through one minor extension to toto:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module Toto
  class Article
    alias_method :local_path, :path
    def path
      self[:url] ? self[:url] : local_path
    end

    alias_method :local_url, :url
    def url
      self[:url] ? self[:url] : local_url
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that is all the Ruby hacking I had to do. Rather nice!&lt;/p&gt;
&lt;p&gt;Also of interest is the new design. I’m using CSS media queries to create a &lt;a href="http://www.alistapart.com/articles/responsive-web-design/"&gt;responsive layout&lt;/a&gt; that works well at all window sizes and across different mobile devices. I’m also using &lt;a href="http://typekit.com/"&gt;Typekit&lt;/a&gt; to clothe the whole site in the lovely &lt;a href="http://typekit.com/fonts/ff-tisa-web-pro"&gt;FF Tisa Web Pro&lt;/a&gt; web font. I’m also finally hosting this site on &lt;a href="http://heroku.com"&gt;Heroku&lt;/a&gt;. The simple everything-is-in-git philosophy of Toto makes this a breeze. No comments here for now, but it’s easy to &lt;a href="/contact"&gt;contact me&lt;/a&gt;, and I’m always open to amending articles based on feedback.&lt;/p&gt;
&lt;p&gt;The design is my own doing, so naturally it is minimal (and contains approximately two colours), but I hope it gives the words (and code samples) room to breathe. I now have a much more prominent sidebar than ever before, because I have more to tell you than ever before. You know about &lt;a href="http://decafsucks.com/"&gt;Decaf Sucks&lt;/a&gt; and &lt;a href="http://dispatch.decafsucks.com/"&gt;Dispatch&lt;/a&gt;, right? Or that we’re aiming to change the real-estate scene with &lt;a href="http://rentmonkey.com.au/"&gt;RentMonkey&lt;/a&gt;? Hopefully my sidebar can do a little to help tell this story.&lt;/p&gt;
&lt;p&gt;As for the rest of the story, now it’s up to me to write it.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Embrace the Metaclass and Extend Your ActiveModels</title>
    <link rel="alternate" href="http://icelab.com.au/notes/embrace-the-metaclass-and-extend-your-activemodels/"/>
    <id>http://icelab.com.au/notes/embrace-the-metaclass-and-extend-your-activemodels/</id>
    <published>2011-03-25T06:15:00+00:00</published>
    <updated>2011-03-25T06:15:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>FactoryGirl and has_many associations</title>
    <link rel="alternate" href="http://icelab.com.au/notes/factorygirl-and-has-many-associations/"/>
    <id>http://icelab.com.au/notes/factorygirl-and-has-many-associations/</id>
    <published>2011-03-23T06:35:00+00:00</published>
    <updated>2011-03-23T06:35:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Taking the pain out of save_and_open_page</title>
    <link rel="alternate" href="http://icelab.com.au/notes/taking-the-pain-out-of-save-and-open-page/"/>
    <id>http://icelab.com.au/notes/taking-the-pain-out-of-save-and-open-page/</id>
    <published>2011-03-23T00:15:00+00:00</published>
    <updated>2011-03-23T00:15:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>TaskPaper and To-Do List Bankruptcy Without the Trauma</title>
    <link rel="alternate" href="https://timriley.info/writing/2011/02/02/taskpaper-and-to-do-list-bankruptcy-without-the-trauma"/>
    <id>https://timriley.info/writing/2011/02/02/taskpaper-and-to-do-list-bankruptcy-without-the-trauma</id>
    <published>2011-02-02T00:05:00+00:00</published>
    <updated>2011-02-02T00:05:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Let’s face it: you are an engaged, curious person, and you accumulate to-do list items like &amp;lt;your preferred simile here&amp;gt;: book and film recommendations, app and open source ideas, blog post ideas, restaurants and &lt;a href="http://decafsucks.com/"&gt;cafés&lt;/a&gt; to visit, things to see or do, online and off. These items are especially easy to accumulate if you use an app like &lt;a href="http://www.hogbaysoftware.com/products/taskpaper"&gt;TaskPaper&lt;/a&gt;, whose over-the-air sync enables universal capture of to-do items, whether at your Mac or while out and about on your iPhone.&lt;/p&gt;
&lt;p&gt;Before long, you’ll have more items on your list than you could conceivably do (even with all your ambition). These are great to keep around for reference or for that quiet weekend that needs filling, but in the end, they do little more than weigh you down and get in the way of the the important real-world things you &lt;em&gt;need&lt;/em&gt; to do. Your to-do list shouldn’t make you anxious.&lt;/p&gt;
&lt;p&gt;Fortunately, TaskPaper offers another feature that will help you manage this problem: its simple single-file, plain-text data format. With TaskPaper, you can spread your to-do lists across multiple files, and have them open them concurrently. This is largely its reason for being, and what sets it apart from the heavier duty task management apps like OmniFocus. The benefit of this single text file is that you can easily move it and rename it, enabling you to shift everything aside and declare &lt;em&gt;to-do list bankruptcy&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="/assets/87d043dbcd63-9B7CBFF0.png" alt="Before and after" /&gt;&lt;/p&gt;
&lt;p&gt;I’ve just done this myself, moving my &lt;code&gt;Things.taskpaper&lt;/code&gt; file to &lt;code&gt;Archive 2010.taskpaper&lt;/code&gt;. Bam: instant clean slate. Create a brand new &lt;code&gt;Things&lt;/code&gt; file in its place, copy across a select few important tasks, and you’re ready to go. You can even keep your archived to-do lists in your synced &lt;a href="http://www.hogbaysoftware.com/products/simpletext"&gt;SimpleText&lt;/a&gt; folder, so they remain accessible for reference on your iPhone as well as your Mac (There’s nothing wrong with a security blanket if it doesn’t get in the way).&lt;/p&gt;
&lt;p&gt;There’s much benefit to keeping your to-do list short. It helps you focus on the tasks that matter and actually achieve them. TaskPaper helps you balance these two by allowing you to contain and group your items by project and tags.[1] Sometimes, though, it helps to have the less important stuff completely out of the way. Declaring to-do list bankruptcy is a simple, pain-free way to get started on this path. It’s helping me to keep on top of the important things, and with publishing this post, I’ll have one less item in the list. Now, onto the next thing!&lt;/p&gt;
&lt;p&gt;[1] Another way would be to use a completely separate file for the “Someday” or “Maybe” tasks. A little more friction, but cleaner separation.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Dispatch #2: Your Coffee, Faster</title>
    <link rel="alternate" href="http://icelab.com.au/notes/dispatch-2-your-coffee-faster/"/>
    <id>http://icelab.com.au/notes/dispatch-2-your-coffee-faster/</id>
    <published>2011-01-26T23:30:00+00:00</published>
    <updated>2011-01-26T23:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>2010 in Review</title>
    <link rel="alternate" href="https://timriley.info/writing/2011/01/05/2010-in-review"/>
    <id>https://timriley.info/writing/2011/01/05/2010-in-review</id>
    <published>2011-01-05T00:05:00+00:00</published>
    <updated>2011-01-05T00:05:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;2010 was one of my most exciting and productive years yet, working with great people to build some products that I’m truly proud of. When I look back, I hope that this summary will help remind me that it was the start of something amazing.&lt;/p&gt;
&lt;h2&gt;I Shipped&lt;/h2&gt;
&lt;p&gt;This year I was able to enjoy a developer’s greatest pleasure, many times over: shipping.&lt;/p&gt;
&lt;p&gt;&lt;img src="/assets/429e2065515a-C22604A9.jpg" alt="RentMonkey and Dispatch screenshots" /&gt;&lt;/p&gt;
&lt;p&gt;First came the year’s most significant milestone for me: after 18 months of work on weekends and evenings, we finally launched &lt;a href="http://rentmonkey.com.au"&gt;RentMonkey&lt;/a&gt;. Some &lt;a href="http://twitter.com/mattallen/status/23864512564"&gt;called it&lt;/a&gt; “the best form on the internet,” &lt;a href="http://twitter.com/lachlanhardy/status/23862726827"&gt;others said&lt;/a&gt; it is “easily the most gorgeous and usable rental property leasing app you’ve ever seen!”, we think it is a great way to take back control of your biggest investments. We’re proud of what we’ve done so far and very excited to continue to build it in 2011. This is just the beginning; we’re in this for the long haul.&lt;/p&gt;
&lt;p&gt;With the first RentMonkey release behind me, I threw myself some long-overdue updates for our other baby, the café and coffee review site &lt;a href="http://decafsucks.com/"&gt;Decaf Sucks&lt;/a&gt;. Not long after it’s first birthday, we released a &lt;a href="http://icelab.com.au/notes/decaf-sucks-first-birthday-updates/"&gt;new design and a whole swag of improvements&lt;/a&gt;, including Facebook logins and a lovely infinite scroll. This helped increase interest in the site and now we’re just a touch shy of 1000 reviews!&lt;/p&gt;
&lt;p&gt;This wasn’t the only news on the internet-coffee-obsession front. Just before Christmas we embarked on a whole new business venture, coffee subscriptions. &lt;a href="http://dispatch.decafsucks.com/"&gt;Dispatch, from Decaf Sucks&lt;/a&gt; was conceived in one week and launched the very next (I &lt;a href="http://icelab.com.au/notes/wishing-you-a-happy-caffeinated-christmas/"&gt;wrote more here&lt;/a&gt; about that particular whirlwind). It’s been received really well and we’ve had a lot of fun sharing some coffee love around Australia.&lt;/p&gt;
&lt;h2&gt;Work at Icelab&lt;/h2&gt;
&lt;p&gt;Much of the energy and drive behind all of the above has risen from the inspirational team and environment at &lt;a href="http://icelab.com.au/"&gt;Icelab&lt;/a&gt;, where I’ve just finished my first full year of work. We saw our fair of client project launches, with the most notable for me being &lt;a href="http://explore.moadoph.gov.au"&gt;Exploring Democracy&lt;/a&gt; for Old Parliament House, which was the lab’s first big Rails app, and the &lt;a href="http://icelab.com.au/work/bureau-of-meteorology/"&gt;Water Storage&lt;/a&gt; iPhone app for the Bureau of Meteorology, which saw me get my hands dirty with Objective C for the first time. Icelab has been the perfect place for me to grow into a the well-rounded developer that I want to be.&lt;/p&gt;
&lt;p&gt;&lt;img src="/assets/625e782ffc2d-4E3B79B1.png" alt="Screenshot of the new Icelab site" /&gt;&lt;/p&gt;
&lt;p&gt;A few months ago we also released a new &lt;a href="http://icelab.com.au/"&gt;Icelab site&lt;/a&gt;, designed and built as beautifully as ever by &lt;a href="http://makenosound.com/"&gt;Max&lt;/a&gt;. It’s great to have a better platform for sharing the things that we get up to. I’ve taken to &lt;a href="http://icelab.com.au/notes/"&gt;blogging there with vigour&lt;/a&gt; (which explains the quietness here), and we’ve all been sure to keep our &lt;a href="http://icelab.com.au/work/"&gt;portfolio&lt;/a&gt; nice and fresh.&lt;/p&gt;
&lt;h2&gt;Open Source&lt;/h2&gt;
&lt;p&gt;Right before the 2010 Rails Rumble, I was able to extract the &lt;a href="https://github.com/intridea/omniauth"&gt;OmniAuth-based&lt;/a&gt; Twitter and Facebook logins from Decaf Sucks and release it as an open source Rails 3 engine called &lt;a href="http://github.com/icelab/omnisocial"&gt;OmniSocial&lt;/a&gt;. I &lt;a href="http://icelab.com.au/notes/welcome-to-the-omnisocial/"&gt;blogged about it&lt;/a&gt; and was lucky to have it featured on &lt;a href="http://5by5.tv/rubyshow/136"&gt;The Ruby Show&lt;/a&gt; and &lt;a href="http://ruby5.envylabs.com/episodes/121-episode-119-october-12-2010/stories/1040-omnisocial-omniauth-plus-twitter-and-facebook"&gt;Ruby5&lt;/a&gt;. This little publicity boost at its release has actually seen it adopted in a few apps, which has been fantastic to see.&lt;/p&gt;
&lt;p&gt;In 2011 I’d like to do a bit more of this: identifying useful patterns from my apps and extracting them into useful open source tools. John Nunemaker &lt;a href="http://railstips.org/blog/archives/2010/12/31/year-in-review/"&gt;is my inspiration&lt;/a&gt; in this regard.&lt;/p&gt;
&lt;h2&gt;Ruby Community&lt;/h2&gt;
&lt;p&gt;More on the Ruby front, I continued to run the &lt;a href="http://canberraruby.com/"&gt;Canberra Ruby Crew&lt;/a&gt; meetings in 2010, moving them to the more centrally-located Icelab. We had good attendance and a bunch of new faces, not to mention the usual pizza, beer and good times.&lt;/p&gt;
&lt;p&gt;Canberra hosted Australia’s seventh &lt;a href="http://railscamps.com"&gt;RailsCamp&lt;/a&gt; in April, where I was able to continue working on my nebulous iOS development skills (and have Happy Birthday sung to me by 100 drunken Rubyists). I also made a bunch of short talks at both the Canberra and Sydney Ruby groups, as well as at Canberra’s BarCamp:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://openmonkey.com/articles/2010/01/making-your-capistrano-recipe-book"&gt;Building your Capistrano Recipe Book&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://openmonkey.com/articles/2010/02/barcamp-canberra-introduction-to-sinatra"&gt;An Introduction to Sinatra&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Using Titanium to Build Mobile Apps with JavaScript (a lightning talk with Max, &lt;a href="http://cl.ly/3rEx"&gt;here’s an audio recording&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://openmonkey.com/articles/2010/04/javascript-testing-with-cucumber-capybara"&gt;JavaScript Testing with Capybara and Cucumber&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://roro-facebook-oauth.heroku.com/"&gt;Facebook Authentication with OAuth&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Google Maps JavaScript API v3&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Lastly, I ventured to Japan with a glut of Aussie Rubyists for my second RubyKaigi!&lt;/p&gt;
&lt;h2&gt;More Highlights&lt;/h2&gt;
&lt;p&gt;In short form: I wrote my first &lt;a href="http://rubyonrails.com.au/"&gt;Rails 3&lt;/a&gt; application, as well as more JavaScript than ever (made easy via &lt;a href="http://jashkenas.github.com/coffee-script/"&gt;CoffeeScript&lt;/a&gt;). I surpassed &lt;a href="http://decafsucks.com/people/1-timriley"&gt;100 café reviews on Decaf Sucks&lt;/a&gt;, averaging more than two reviews per week. I started a &lt;a href="http://metaloutsider.tumblr.com/"&gt;tumblelog about heavy metal&lt;/a&gt; with my brother-in-law, and it has been some of the most challenging writing I’ve done to date, but also a great way to discover new music. I got an iPad and iPhone 4, fantastic, life-changing devices both. I was motivated and inspired every week by the amazing stories and topics on the &lt;a href="http://5by5.tv/"&gt;5by5 podcasts&lt;/a&gt;. I started playing Ultimate Frisbee and working out three mornings per week. My laptop is &lt;em&gt;finally&lt;/em&gt; backed up regularly. I rediscovered the fun of photo-sharing through &lt;a href="http://instagr.am/"&gt;Instagram&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="/assets/4d566df7ba64-EA2B2817.jpg" alt="Misch and me in Japan" /&gt;&lt;/p&gt;
&lt;p&gt;Last, but never least, 2010 was a thoroughly enjoyable first year of marriage to my amazing wife. She has been the rock and support that allowed me to do all of the above. We had plenty of fun times together, including a weekend in the world’s biggest spa bath, a 3-week adventure across Japan, and a lovely first anniversary getaway.&lt;/p&gt;
&lt;p&gt;That’s it for a big year. In 2011 I will continue to &lt;a href="http://zenhabits.net/achieving/"&gt;get excited and do things&lt;/a&gt;. Watch this space.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Wishing You a Happy, Caffeinated Christmas</title>
    <link rel="alternate" href="http://icelab.com.au/notes/wishing-you-a-happy-caffeinated-christmas/"/>
    <id>http://icelab.com.au/notes/wishing-you-a-happy-caffeinated-christmas/</id>
    <published>2010-12-20T23:30:00+00:00</published>
    <updated>2010-12-20T23:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Announcing Dispatch, From Decaf Sucks</title>
    <link rel="alternate" href="http://icelab.com.au/notes/announcing-dispatch-from-decaf-sucks/"/>
    <id>http://icelab.com.au/notes/announcing-dispatch-from-decaf-sucks/</id>
    <published>2010-12-13T23:30:00+00:00</published>
    <updated>2010-12-13T23:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Welcome to the OmniSocial</title>
    <link rel="alternate" href="http://icelab.com.au/notes/welcome-to-the-omnisocial/"/>
    <id>http://icelab.com.au/notes/welcome-to-the-omnisocial/</id>
    <published>2010-10-10T23:30:00+00:00</published>
    <updated>2010-10-10T23:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>Decaf Sucks First Birthday Updates</title>
    <link rel="alternate" href="https://timriley.info/writing/2010/10/07/decaf-sucks-first-birthday-updates"/>
    <id>https://timriley.info/writing/2010/10/07/decaf-sucks-first-birthday-updates</id>
    <published>2010-10-06T23:30:00+00:00</published>
    <updated>2010-10-06T23:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;&lt;a href="http://decafsucks.com/"&gt;Decaf Sucks&lt;/a&gt;, our coffee and café review web site, recently turned 1 year old. For a site created over a single weekend for &lt;a href="http://railsrumble.com/"&gt;Rails Rumble&lt;/a&gt;, Decaf Sucks has been a great success. Over the last year, 143 different contributing reviewers have written 610 reviews for 502 cafés in 18 different countries. Outstanding!&lt;/p&gt;
&lt;p&gt;Here’s how we’ve travelled over time. The blue line is our cumulative review count, and the red is the number added each day.&lt;/p&gt;
&lt;p&gt;&lt;img src="/assets/326e7b8ae55f-27D16B64.png" alt="Decaf Sucks growth chart" /&gt;&lt;/p&gt;
&lt;p&gt;The site has worked amazingly well in its first incarnation. However, with the number of reviews growing, it has become time to make some changes. So, as a first birthday celebration, we recently pushed up a whole swag of improvements to the site. Here is the lowdown:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;New design:&lt;/strong&gt; Wider and shinier, the new design puts more information in front of you without sacrificing cleanliness.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Infinite scroll:&lt;/strong&gt; No more pagination! Scroll to the bottom of a café or review list, and another set of reviews will appear for you.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reviews grouped by cafe:&lt;/strong&gt; We’re starting to see 3 or 4 reviews for popular cafés, so it’s time to organise things better. Now we group together all the reviews for a cafe and present that cafe to you with the average rating across all its reviews. This reduces the clutter and gives you a better idea of which cafes are truly good and bad. It also makes the map of nearby cafes far more useful - no more duplicate entries!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Facebook logins:&lt;/strong&gt; This should help a much wider group of people share their experiences.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;People pages:&lt;/strong&gt; Meet our reviewers and follow their cafe adventures via convenient ATOM feed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance improvements:&lt;/strong&gt; We’ve started minifying and bundling all our CSS and JavaScript, which should help with performance particularly on the iPhone. We’ve also eliminated a bunch of jQuery plugins by using the Google Maps API directly.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Go &lt;a href="http://decafsucks.com/"&gt;check it out&lt;/a&gt; and &lt;a href="http://decafsucks.com/reviews/new"&gt;leave a review&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;&lt;a href="http://decafsucks.com/"&gt;&lt;img src="/assets/b1b46181d9bf-325B3241.png" alt="Decaf Sucks v2 Screenshots" /&gt;&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Easy server backups to Amazon S3 with duplicity</title>
    <link rel="alternate" href="http://icelab.com.au/notes/easy-server-backups-to-amazon-s3-with-duplicity/"/>
    <id>http://icelab.com.au/notes/easy-server-backups-to-amazon-s3-with-duplicity/</id>
    <published>2010-09-22T23:30:00+00:00</published>
    <updated>2010-09-22T23:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
  </entry>
  <entry>
    <title>JavaScript Testing with Cucumber and Capybara</title>
    <link rel="alternate" href="https://timriley.info/writing/2010/04/09/javascript-testing-with-cucumber-and-capybara"/>
    <id>https://timriley.info/writing/2010/04/09/javascript-testing-with-cucumber-and-capybara</id>
    <published>2010-04-09T04:55:00+00:00</published>
    <updated>2010-04-09T04:55:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;&lt;a href="http://github.com/jnicklas/capybara"&gt;Capybara&lt;/a&gt; is a Ruby DSL for easily writing integration tests for Rack applications. It is an alternative to Webrat and can easily replace it as a backend for your &lt;a href="http://cukes.info/"&gt;Cucumber&lt;/a&gt; features. Its power and utility lies in that it comes bundled with several different browser simulators, equipping you with a flexible toolkit for testing all parts of your application, from the simplest page to the most complex, JavaScript-heavy page.&lt;/p&gt;
&lt;p&gt;[caption id=”” align=“alignnone” width=“500.0”] &lt;img src="/assets/a7d850d3b04c-6D2622AC.jpg" alt="A photo of real capybaras." /&gt; A photo of real capybaras.[/caption]&lt;/p&gt;
&lt;p&gt;Using Capybara means you can now confidently write your Cucumber features as the first thing to drive the development of your application, regardless of how you plan to implement the feature. Another win for testing!&lt;/p&gt;
&lt;h2&gt;Using Capybara&lt;/h2&gt;
&lt;p&gt;Capybara, much like &lt;a href="http://github.com/brynary/webrat"&gt;Webrat&lt;/a&gt; before it, provides a broad set of steps you can use to test your application in Cucumber features:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Scenario: Creating an article
  Given I go to the new article page
  And I fill in &amp;quot;Title&amp;quot; with &amp;quot;JavaScript Testing with Cucumber and Capybara&amp;quot;
  And I fill in &amp;quot;Body&amp;quot; with &amp;quot;Here is my article&amp;quot;
  And I press &amp;quot;Create&amp;quot;
  Then I should see &amp;quot;Article created&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By default, it uses &lt;a href="http://github.com/brynary/rack-test"&gt;rack-test&lt;/a&gt; as the simulator to run your scenarios against your application. rack-test is the fastest of all Capybara’s drivers, and should be used whenever possible to keep down the time needed to run your entire suite of scenarios. It should have no trouble handling anything like the above scenario.&lt;/p&gt;
&lt;p&gt;You can use the rack-test driver according to one simple rule: no JavaScript, no problems. However, Cucumber does include a helper that allows you to use Capybara with rack-test to test the links with &lt;code&gt;onclick&lt;/code&gt; JavaScript handlers that Rails uses for deleting records:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;%= link_to 'Delete', article_path(@article), :method =&amp;gt; :destroy, :confirm =&amp;gt; 'Are you sure?' %&amp;gt;

Scenario: Deleting an article
  Given an article exists
  When I go to the article's page
  And I follow &amp;quot;Delete&amp;quot;
  Then I should see &amp;quot;Article deleted&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Testing JavaScript with Different Browser Simulators&lt;/h2&gt;
&lt;p&gt;Many parts of your application will be simple to test with scenarios like the above, but you will invariably have other parts that depend on complex interactions powered by JavaScript or AJAX requests. While these parts have historically been the hardest to test, their complexity in fact demands the strongest test coverage! Capybara will let you write the tests that these parts of your application deserve.&lt;/p&gt;
&lt;p&gt;Capybara comes with built-in drivers for several browser simulators that support JavaScript: &lt;a href="http://seleniumhq.org/"&gt;Selenium&lt;/a&gt;, &lt;a href="http://github.com/langalex/culerity"&gt;Culerity&lt;/a&gt; and &lt;a href="http://celerity.rubyforge.org/"&gt;Celerity&lt;/a&gt;. While Webrat has always allowed you to use Selenium as a browser simulator, it is an additional dependency that has at times been &lt;a href="http://twitter.com/schlick/status/10258959861"&gt;difficult&lt;/a&gt; to configure.&lt;/p&gt;
&lt;p&gt;With Capybara, using Selenium is as simple as putting a &lt;code&gt;@javascript&lt;/code&gt; tag above a scenario:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@javascript
Scenario: Endless pagination with AJAX
  Given 10 articles exist
  When I go to the articles page
  Then I should see 5 articles
  When I follow &amp;quot;More articles&amp;quot;
  Then I should see 10 articles
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This tests the kind of endless pagination that you see on &lt;a href="http://twitter.com/timriley"&gt;Twitter&lt;/a&gt;. Running the above scenario, you would see Firefox open up and automatically run through your steps on a working copy of your app. The AJAX request will fire and you’ll see the extra 5 articles appear on the page. Everything passes. Excellent!&lt;/p&gt;
&lt;p&gt;If you’d like to avoid a dependency on a GUI browser for your tests, you can try Celerity as a browser simulator, via the Culerity driver. Celerity is a JRuby wrapper for HtmlUnit, a Java headless browser with JavaScript support. Culerity is a wrapper for Celerity that allows any Rails app to use Celerity for testing, without the app having to run under JRuby. In practice, this is not as convoluted as it sounds. The end result is that Culerity will fire up a separate JRuby process to run tests against your app, while your app executes in its usual Ruby runtime.&lt;/p&gt;
&lt;p&gt;All you need to do to is put a &lt;code&gt;@culerity&lt;/code&gt; tag above your scenario:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@culerity
Scenario: Endless pagination with AJAX
  Given 10 articles exist
  When I go to the home page
  Then I should see 5 articles
  When I click &amp;quot;More articles&amp;quot;
  Then I should see 10 articles
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running this same scenario through Culerity, you’ll see each step pass as before, just without the browser running on-screen. This is useful if you’re looking to have your integration tests work nicely on a headless CI box or something similar.&lt;/p&gt;
&lt;p&gt;Just like you can use &lt;code&gt;@culerity&lt;/code&gt; to force a particular driver for a scenario or feature, you can explicitly require selenium by using the &lt;code&gt;@selenium&lt;/code&gt; tag, and rack-test with &lt;code&gt;@rack_test&lt;/code&gt;. The &lt;code&gt;@javascript&lt;/code&gt; tag is reserved for Capybara’s default JavaScript-supporting simulator. You can change this by creating a &lt;code&gt;features/support/capybara.rb&lt;/code&gt; file and including the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# :culerity or :selenium
Capybara.javascript_driver = :culerity
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also change the default driver for all scenarios:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Capybara.default_driver = :selenium
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Here’s One I Prepared Earlier&lt;/h2&gt;
&lt;p&gt;I’ve put together a &lt;a href="http://github.com/timriley/capybara-demo"&gt;Rails app&lt;/a&gt; that includes all the examples covered so far, ready for you to try yourself. The only thing you need is a recent version of &lt;a href="http://gembundler.com/"&gt;bundler 0.9&lt;/a&gt;. To clone the app and and set it up:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone git://github.com/timriley/capybara-demo.git capybara-demo
cd capybara-demo
bundle install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you can run the Cucumber features:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bundle exec rake cucumber:all
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And you can also start a server to verify the features manually:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./script/server
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Installing Cucumber with Capybara&lt;/h2&gt;
&lt;p&gt;Getting Cucumber and Capybara in your own app is easy.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gem install cucumber capybara
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you’re on the bundler, then just put the following in your &lt;code&gt;Gemfile&lt;/code&gt; and run &lt;code&gt;bundle install&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;group(:test) do
  gem 'cucumber'
  gem 'cucumber-rails'
  gem 'capybara'
  gem 'culerity'
  gem 'celerity', :require =&amp;gt; nil # JRuby only. Make it available but don't require it in any environment.
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then run the Cucumber generator with the &lt;code&gt;--capybara&lt;/code&gt; flag:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd /my_app
./script/generate cucumber --capybara
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running this generator will create a &lt;code&gt;web_steps.rb&lt;/code&gt; file in &lt;code&gt;features/step_definitions&lt;/code&gt;. This file is a mostly-compatible replacement for the &lt;code&gt;webrat_steps.rb&lt;/code&gt; that you may have had otherwise. It contains largely the same steps, and all of the following will work as you expect:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Given I am on the home page
When I go to the home page
When I press &amp;quot;Submit&amp;quot;
When I follow &amp;quot;New article&amp;quot;
When I fill in &amp;quot;Address&amp;quot; with &amp;quot;5 Smith Street&amp;quot; within &amp;quot;fieldset#home-address&amp;quot;
When I select &amp;quot;ACT&amp;quot; from &amp;quot;State&amp;quot; within &amp;quot;fieldset#home-address&amp;quot;
When I check &amp;quot;Remember Me&amp;quot;
When I uncheck &amp;quot;Remember Me&amp;quot;
When I choose &amp;quot;My Radio Button&amp;quot;
Then I should see &amp;quot;Article created&amp;quot; within &amp;quot;.flash&amp;quot;
Then I should not see &amp;quot;Error creating article&amp;quot;
Then the &amp;quot;Title&amp;quot; field should contain &amp;quot;My Title&amp;quot;
Then the &amp;quot;Title&amp;quot; field should not contain &amp;quot;My Title&amp;quot;
Then the &amp;quot;Remember Me&amp;quot; checkbox should be checked
Then the &amp;quot;Remember Me&amp;quot; checkbox should not be checked
Then I should be on the home page
Then show me the page
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While most of your features should run just fine, you may need to make some small tweaks to correct things, especially if you used some of the date selector steps that were present in &lt;code&gt;webrat_steps.rb&lt;/code&gt; but not in &lt;code&gt;web_steps.rb&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Installing Culerity Support&lt;/h3&gt;
&lt;p&gt;While the Selenium driver will work for you out of the box, you need &lt;a href="http://jruby.org/"&gt;JRuby&lt;/a&gt; installed in order to use Culerity &amp;amp; Celerity. If you are using &lt;a href="http://rvm.beginrescueend.com/"&gt;RVM&lt;/a&gt;, this is also easy:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rvm install jruby
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Culerity will look for a &lt;code&gt;jruby&lt;/code&gt; binary and use it to run Celerity. With RVM, you’ll need to make a &lt;code&gt;jruby&lt;/code&gt; symlink to the versioned binary that it installs:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd ~/.rvm/bin
ln -s jruby-1.4.0 jruby
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s it. You’ll note in the &lt;code&gt;Gemfile&lt;/code&gt; example above that we install the celerity gem for culerity to use, but don’t require it in any of the app’s environments, since your app (is most likely) not running under JRuby.&lt;/p&gt;
&lt;h2&gt;Go to it!&lt;/h2&gt;
&lt;p&gt;Since I’ve started using Capybara, I’ve been enjoying writing more integration for more parts of my applications, and it’s been a lot of fun. I hope this guide helps you follow the same path!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Rails Label Helpers with Blocks</title>
    <link rel="alternate" href="https://timriley.info/writing/2010/03/30/rails-label-helpers-with-blocks"/>
    <id>https://timriley.info/writing/2010/03/30/rails-label-helpers-with-blocks</id>
    <published>2010-03-30T03:20:00+00:00</published>
    <updated>2010-03-30T03:20:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Whenever I work closely with designers, I try to learn a trick or two. From my latest project with the inimitable &lt;a href="http://makenosound.com/"&gt;Max Wheeler&lt;/a&gt;, one of the things I picked up was his preferred strategy for building forms: nesting the inputs along with the label text inside a label tag. Something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;label&amp;gt;
  Name
  &amp;lt;input type=&amp;quot;text&amp;quot; name=&amp;quot;article[title]&amp;quot;/&amp;gt;
&amp;lt;/label&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This structure behaves as you would expect. Clicking the “name” text on the page will focus the input element nested within the same label. The benefit is that you no longer have to worry about synchronising the input’s DOM ID with the label’s &lt;code&gt;for&lt;/code&gt; attribute.&lt;/p&gt;
&lt;p&gt;Doing this using the ActionView form helpers is not currently possible. Fortunately, it is easy to roll your own solution using a custom FormBuilder.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class SmartLabelFormBuilder &amp;lt; ActionView::Helpers::FormBuilder
  def label(method, content_or_options_with_block = nil, options = {}, &amp;amp;block)
    if !block_given?
      # No block, use the standard label helper.
      super(method, content_or_options_with_block, options)
    else
      # We've got a block. This is where we want to do our business.
      options = content_or_options_with_block.is_a?(Hash) ? content_or_options_with_block.stringify_keys : {}

      if errors_on?(method)
        (options['class'] = options['class'].to_s + ' error').strip!
      end

      @template.content_tag(:label, options, &amp;amp;block)
    end
  end

  private

  def errors_on?(method)
    @object.respond_to?(:errors) &amp;amp;&amp;amp; @object.errors.respond_to?(:on) &amp;amp;&amp;amp; @object.errors.on(method.to_sym)
  end
end

ActionView::Base.field_error_proc = Proc.new do |html_tag, instance_tag|
  html_tag
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Include the above somewhere in your app (perhaps a file in &lt;code&gt;lib/&lt;/code&gt;) and now you can start building you forms like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;% form_for(@article, :builder =&amp;gt; SmartLabelFormBuilder) do |form| %&amp;gt;
  &amp;lt;% form.label(:title) do %&amp;gt;
    Title
    &amp;lt;%= form.text_field(:title) %&amp;gt;
  &amp;lt;% end %&amp;gt;
&amp;lt;% end %&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;See &lt;a href="http://gist.github.com/348707"&gt;this gist&lt;/a&gt; to fork or download these code examples.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The form builder will pass back to the regular &lt;code&gt;label&lt;/code&gt; method if you’re not using a block, so you can still create standalone labels if you need.&lt;/p&gt;
&lt;p&gt;It also takes care of displaying form errors. If a label contains a field with an error, then the helper will give it and &lt;code&gt;error&lt;/code&gt; class. Then you can use CSS selectors like &lt;code&gt;label.error&lt;/code&gt; and &lt;code&gt;label.error input@&lt;/code&gt;to change the appearance of your label text and inputs for these fields. For this to work nicely, I’ve overwritten ActionView’s &lt;code&gt;field_error_proc&lt;/code&gt; so that it does nothing by default to fields with errors.&lt;/p&gt;
&lt;p&gt;Handling labels and inputs in this way is also totally fine for testing tools. Capybara, for example, uses &lt;a href="http://github.com/jnicklas/capybara/blob/5661d67ae9458890ac458cb6bbb2ac45513fac2a/lib/capybara/xpath.rb#L139"&gt;a bunch of different xpaths&lt;/a&gt; to locate fields, including some that support labels with nested inputs. So a Cucumber step like this works exactly as you expect:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;When I fill in &amp;quot;Title&amp;quot; with &amp;quot;Rails Label Helpers with Blocks&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You might also want to check out this &lt;a href="https://rails.lighthouseapp.com/projects/8994/tickets/3645-let-label-helpers-accept-blocks"&gt;patch for rails 3&lt;/a&gt; that adds block support to labels. It’s in need of a few testers and “+1” comments so that it can get incorporated!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>BarCamp Canberra 2010 and an Introduction to Sinatra</title>
    <link rel="alternate" href="https://timriley.info/writing/2010/02/15/barcamp-canberra-2010-and-an-introduction-to-sinatra"/>
    <id>https://timriley.info/writing/2010/02/15/barcamp-canberra-2010-and-an-introduction-to-sinatra</id>
    <published>2010-02-14T22:00:00+00:00</published>
    <updated>2010-02-14T22:00:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Earlier this month was &lt;a href="http://barcamp.org/BarCampCanberra"&gt;BarCamp Canberra 2010&lt;/a&gt;, this city’s third BarCamp event. Having never been to one, I thought it was high time I checked it out. Given that most of the attendees would be unfamiliar with Ruby, it was also a prime opportunity to give an introductory-level talk. I chose to talk about Sinatra, since it was small enough to allow for a good overview in the 20-minute time slot.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://bcc2010-sinatra.heroku.com/"&gt;&lt;img src="/assets/35b47adf56c0-2C134651.png" alt="Sinatra title slide" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This was my first long-form presentation, and I’m happy with how it went. I had an interested audience with some good questions, and we were even able to fit in a live deployment of a sample app to &lt;a href="http://heroku.com/"&gt;Heroku&lt;/a&gt;. Hopefully I also attracted a few more people to our &lt;a href="http://canberraruby.com/"&gt;Canberra Ruby Crew&lt;/a&gt; meets!&lt;/p&gt;
&lt;p&gt;The talk was written in &lt;a href="http://github.com/schacon/showoff"&gt;showoff&lt;/a&gt;, Scott Chacon’s Sinatra application for building browser-based slides. I suggest you give showoff a go for your next talk; I thoroughly enjoyed the process of using it to create mine. It freed me from the burden of colours, transitions, layout, and positioning, and let me concentrate on the most important aspect of my talk, its &lt;em&gt;content&lt;/em&gt;. It also means that you can &lt;a href="http://bcc2010-sinatra.heroku.com/"&gt;view the slides&lt;/a&gt; online (thanks, Heroku!) as well as their &lt;a href="http://github.com/timriley/bcc2010-sinatra-talk"&gt;source code&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The BarCamp Canberra organisers did a great job: everything went smoothly and the turnout was excellent. It was interesting for me to see the goings-on outside the Ruby circuit that I usually frequent. &lt;a href="http://makenosound.com/"&gt;Max&lt;/a&gt; and I were also able to showoff our little web/iPhone hybrid app we built using &lt;a href="http://symphony-cms.org/"&gt;Symphony&lt;/a&gt; and &lt;a href="http://appcelerator.com/"&gt;Titanium&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The proclaimed outcome of the day (according to &lt;a href="http://twitter.com/#search?q=%23bcc2010"&gt;twitter&lt;/a&gt;, anyway) was an increased motivation to go out and &lt;em&gt;do&lt;/em&gt;, &lt;a href="http://barcamp.org/BarCampCanberra2010"&gt;the program of talks&lt;/a&gt; was a little light on the kind of hands-on technical topics that equip people to take action and create something. In this way, I think that Ruby hackers or other web or application developers are in a position to make an important contribution to BarCamps or other similar events. It’s a great opportunity to revisit the basics (or even hit on something advanced) and share it with others.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Making Your Capistrano Recipe Book</title>
    <link rel="alternate" href="https://timriley.info/writing/2010/01/19/making-your-capistrano-recipe-book"/>
    <id>https://timriley.info/writing/2010/01/19/making-your-capistrano-recipe-book</id>
    <published>2010-01-19T04:45:00+00:00</published>
    <updated>2010-01-19T04:45:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;If you’ve deployed more than one application with Capistrano, then you’ve probably repeated yourself in most of the deploy scripts. Heck, you’ve probably copied and pasted from one to another just to get things going. Moving your common deployment logic into a gem will prevent this duplication and allow you to codify your best techniques in a single place. It’s building a toolbelt where before the tools were scattered over the garage floor.&lt;/p&gt;
&lt;p&gt;Most importantly, removing the onerous job of writing lengthy deploy scripts means you can keep focused on the business value of your application. Basic deployment should never get in the way of that.&lt;/p&gt;
&lt;p&gt;I gave a presentation on this topic at the January 2010 Sydney &lt;a href="http://rubyonrails.com.au/"&gt;RORO&lt;/a&gt; meeting. Below are the slides and in this article I’ll write in more detail about the technique and why it is useful.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.slideshare.net/timriley/making-your-capistrano-recipe-book" title="Making Your Capistrano Recipe Book"&gt;Making Your Capistrano Recipe Book&lt;/a&gt;&lt;object style="margin:0px" width="425" height="355"&gt;&lt;param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=capistranorecipebook-100118175359-phpapp01&amp;amp;rel=0&amp;amp;stripped_title=making-your-capistrano-recipe-book"&gt;&lt;/p&gt;
&lt;param name="allowFullScreen" value="true"&gt;
&lt;param name="allowScriptAccess" value="always"&gt;
&lt;embed src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=capistranorecipebook-100118175359-phpapp01&amp;amp;rel=0&amp;amp;stripped_title=making-your-capistrano-recipe-book" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"&gt;&lt;/embed&gt;&lt;/object&gt;
&amp;nbsp;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;I’ve just finished two years working in an applications development team at the &lt;a href="http://www.amc.org.au/"&gt;Autralian Medical Council&lt;/a&gt; in Canberra. While I was there, we built many tightly-focused Rails apps. Currently there are eight in production and a number more under development. In order to keep things easy to manage, we did a lot of things to simplify the deployment process.&lt;/p&gt;
&lt;p&gt;The first step we took was to automate the provision of homogenous virtual servers. I wrote a few thousand lines of bash, tied it to xen-tools, and hey presto, we could build new servers in one command and a couple of minute’s wait. Now that we had a standard server environment for each application, we saw another issue appear: lots of duplication in their Capistrano &lt;code&gt;deploy.rb&lt;/code&gt; scripts. So the second measure was to extract all the common Capistrano tasks and configuration into a gem.&lt;/p&gt;
&lt;h2&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;Here’s the kind of deploy script you might have for a simple application. First, you’ll have the standard information about your application, its repo, and the target host for deployment.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;set :application, 'myapp.mycorp.com'
set :user, 'deployer'
set :deploy_to, &amp;quot;/home/deployer/deployments/#{application}&amp;quot;
set :use_sudo, false

role :app, '192.168.0.1'
role :web, '192.168.0.1'
role :db, '192.168.0.1', :primary =&amp;gt; true

set :scm, :git
set :repository, 'git@git.mycorp.net:myapp.git'
set :branch, 'master'
set :deploy_via, :remote_cache

default_run_options[:pty] = true
set :ssh_options, { :forward_agent =&amp;gt; true }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you’ll want to add some deployment tasks that work with Passenger:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;namespace :deploy do
  desc &amp;quot;Restarting mod_rails with restart.txt&amp;quot;
  task :restart, :roles =&amp;gt; :app, :except =&amp;gt; { :no_release =&amp;gt; true } do
    run &amp;quot;touch #{current_path}/tmp/restart.txt&amp;quot;
  end

  [:start, :stop].each do |t|
    desc &amp;quot;#{t} task is a no-op with mod_rails&amp;quot;
    task t, :roles =&amp;gt; :app do ; end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, you might have some custom tasks and callbacks to set up your “Thinking Sphinx”:http://freelancing-god.github.com/ts/en/ installation.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;before 'deploy:setup', 'sphinx:create_db_dir'
before 'deploy:setup', 'sphinx:generate_yaml'
after 'deploy:update_code', 'sphinx:symlink'

namespace :sphinx do
  desc 'Create a directory to store the sphinx indexes'
  task :create_db_dir, :roles =&amp;gt; :app do
    run &amp;quot;mkdir -p #{shared_path}/sphinx&amp;quot;
  end

  desc 'Generate a config yaml in shared path'
  task :generate_yaml, :roles =&amp;gt; :app do
    sphinx_yaml = &amp;lt;&amp;lt;-EOF
development: &amp;amp;base
  morphology: stem_en
  config_file: #{shared_path}/config/sphinx.conf
test:
  &amp;lt;&amp;lt;: *base
production:
  &amp;lt;&amp;lt;: *base
EOF
    run &amp;quot;mkdir -p #{shared_path}/config&amp;quot;
    put sphinx_yaml, &amp;quot;#{shared_path}/config/sphinx.yml&amp;quot;
  end

  desc 'Symlink the sphinx yml and config files, and the db directory for storage of indexes'
  task :symlink, :roles =&amp;gt; :app do
    run &amp;quot;ln -nfs #{shared_path}/sphinx #{release_path}/db/sphinx&amp;quot;
    run &amp;quot;ln -nfs #{shared_path}/config/sphinx.yml #{release_path}/config/sphinx.yml&amp;quot;
    run &amp;quot;ln -nfs #{shared_path}/config/sphinx.conf #{release_path}/config/sphinx.conf&amp;quot;
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Creating a Gem Heirarchy&lt;/h2&gt;
&lt;p&gt;To extract some of the common patterns from the above deploy script, we’ll need to create a heirarchy of files that we’ll eventually turn into our gem. Here’s how it should look:&lt;/p&gt;
&lt;p&gt;&lt;img src="/assets/4771b5db9900-580205E7.png" alt="Capistrano gem structure" /&gt;&lt;/p&gt;
&lt;h2&gt;Capistrano Boilerplate&lt;/h2&gt;
&lt;p&gt;You’ll need something like the following wrapped around any Capistrano code that you use in your gem.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;unless Capistrano::Configuration.respond_to?(:instance)
  abort &amp;quot;capistrano/ext/multistage requires Capistrano 2&amp;quot;
end

Capistrano::Configuration.instance.load do
  # stuff here...
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above will only work on Capistrano version 2 or greater (which should be no problem, now that we’re up to 2.5.13).&lt;/p&gt;
&lt;p&gt;You could also do the following if you wanted something that works in all versions, including those before 2:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;configuration = Capistrano::Configuration.respond_to?(:instance) ?
  Capistrano::Configuration.instance(:must_exist) :
  Capistrano.configuration(:must_exist)

configuration.load do
  # stuff here...
end
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Extracting Deployment Code into your Gem&lt;/h2&gt;
&lt;p&gt;OK, so here’s some of the basic deploy script stuff moved into the &lt;code&gt;lib/mycorp/base.rb&lt;/code&gt; file in the gem:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;require 'capistrano/mycorp/common'

configuration = Capistrano::Configuration.respond_to?(:instance) ?
  Capistrano::Configuration.instance(:must_exist) :
  Capistrano.configuration(:must_exist)

configuration.load do

# User details
_cset :user, 'deployer'
_cset(:group) { user }

# Application details
_cset(:app_name) { abort &amp;quot;Please specify the short name of your application, set :app_name, 'foo'&amp;quot; }
set(:application) { &amp;quot;#{app_name}.mycorp.com&amp;quot; }
_cset(:runner) { user }
_cset :use_sudo, false

# SCM settings
_cset(:appdir) { &amp;quot;/home/#{user}/deployments/#{application}&amp;quot; }
_cset :scm, 'git'
set(:repository) { &amp;quot;git@git.mycorp.net:#{app_name}.git&amp;quot; }
_cset :branch, 'master'
_cset :deploy_via, 'remote_cache'
set(:deploy_to) { appdir }

# Git settings for Capistrano
default_run_options[:pty] = true # needed for git password prompts
ssh_options[:forward_agent] = true # use the keys for the person running the cap command to check out the app

end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These lines are lifted directly from the original &lt;code&gt;deploy.rb&lt;/code&gt;, with one notable exception: many of the original @set@ calls have been replaced with &lt;code&gt;_cset&lt;/code&gt;. &lt;code&gt;_cset&lt;/code&gt; is a method used by the Capistrano’s &lt;a href="http://github.com/capistrano/capistrano/blob/cb38c68bf9ec079590e6124728a1027668914f68/lib/capistrano/recipes/deploy.rb#L5"&gt;internal &lt;code&gt;deploy.rb&lt;/code&gt; file&lt;/a&gt;, which we have put inside &lt;code&gt;common.rb&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def _cset(name, *args, &amp;amp;block)
  unless exists?(name)
    set(name, *args, &amp;amp;block)
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What &lt;code&gt;_cset&lt;/code&gt; allows you to do is provide &lt;em&gt;overrideable defaults&lt;/em&gt; for any Capistrano option. This is a powerful thing because it allows us to set conventions in our gem. As any Rails developer would know, conventions save time and make it easier for multiple people to work with the same tools or on the same code.&lt;/p&gt;
&lt;p&gt;For example, take the &lt;code&gt;user&lt;/code&gt; option from above. We’ve set it to ‘deployer’ by default. Because we’ve done it using &lt;code&gt;_cset&lt;/code&gt;, any deploy script using this gem can choose to either accept this default or provide its own custom user details.&lt;/p&gt;
&lt;p&gt;Note that whenever &lt;code&gt;_cset&lt;/code&gt; (or &lt;code&gt;set&lt;/code&gt;, for that matter) is called with the a block, it is lazily evaluated. This is important to do if you are including another value provided by a &lt;code&gt;set&lt;/code&gt; or &lt;code&gt;_cset&lt;/code&gt; call (like &lt;code&gt;:app_dir&lt;/code&gt; above that uses the &lt;code&gt;application&lt;/code&gt; setting).&lt;/p&gt;
&lt;p&gt;Finally, take note of the @abort@ call in the block for setting &lt;code&gt;app_name&lt;/code&gt;. You can use &lt;code&gt;abort&lt;/code&gt; in your gem whenever there are settings that must be defined by the individual deploy scripts.&lt;/p&gt;
&lt;h2&gt;Tasks &amp;amp; Callbacks&lt;/h2&gt;
&lt;p&gt;The Thinking Sphinx tasks and callbacks can go straight into the gem without any modification:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;configuration = Capistrano::Configuration.respond_to?(:instance) ?
  Capistrano::Configuration.instance(:must_exist) :
  Capistrano.configuration(:must_exist)

configuration.load do

_cset(:app_name) { abort &amp;quot;Please specify the short name of your application, set :app_name, 'foo'&amp;quot; }

before 'deploy:setup', 'sphinx:create_db_dir'
before 'deploy:setup', 'sphinx:generate_yaml'
after 'deploy:update_code', 'sphinx:symlink'

namespace :sphinx do
  desc 'Create a directory to store the sphinx indexes'
  task :create_db_dir, :roles =&amp;gt; :app do
    run &amp;quot;mkdir -p #{shared_path}/sphinx&amp;quot;
  end

  desc 'Generate a config yaml in shared path'
  task :generate_yaml, :roles =&amp;gt; :app do
    sphinx_yaml = &amp;lt;&amp;lt;-EOF
development: &amp;amp;main_settings
  config_file: #{shared_path}/config/sphinx.conf
  pid_file: #{shared_path}/pids/sphinx.pid
production:
  &amp;lt;&amp;lt;: *main_settings
EOF
    put sphinx_yaml, &amp;quot;#{shared_path}/config/sphinx.yml&amp;quot;
  end

  desc 'Symlink the sphinx yml and config files, and the db directory for storage of indexes'
  task :symlink, :roles =&amp;gt; :app do
    run &amp;quot;ln -nfs #{shared_path}/sphinx #{release_path}/db/sphinx&amp;quot;
    run &amp;quot;ln -nfs #{shared_path}/config/sphinx.yml #{release_path}/config/sphinx.yml&amp;quot;
    run &amp;quot;ln -nfs #{shared_path}/config/sphinx.conf #{release_path}/config/sphinx.conf&amp;quot;
  end
end

end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example, I’ve included the callbacks as well as the tasks. Any deploy script that requires this file from the gem will have these sphinx tasks run automatically. This is useful to keep your deploy scripts concise and your deployment behaviour consistent, but it may not suit everyone. The alternative, to include only the tasks in the gem and require the callbacks to be in the deploy script, would make more sense if you’re planning to offer your gem to a wide audience with varying deployment requirements.&lt;/p&gt;
&lt;h2&gt;Building the Gem&lt;/h2&gt;
&lt;p&gt;Building the gem is easy with &lt;a href="http://github.com/technicalpickles/jeweler"&gt;Jeweler&lt;/a&gt;. First, create a &lt;code&gt;Rakefile&lt;/code&gt; with something like the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;begin
  require 'jeweler'
  Jeweler::Tasks.new do |gemspec|
    gemspec.name = &amp;quot;capistrano-mycorp&amp;quot;
    gemspec.summary = &amp;quot;MyCorp recipes for Capistrano&amp;quot;
    gemspec.description = &amp;quot;MyCorp recipes for Capistrano&amp;quot;
    gemspec.email = &amp;quot;tim@openmonkey.com&amp;quot;
    gemspec.homepage = &amp;quot;http://github.com/timriley/capistrano-mycorp&amp;quot;
    gemspec.authors = [&amp;quot;Tim Riley&amp;quot;]
  end
rescue LoadError
  puts &amp;quot;Jeweler not available. Install it with: gem install jeweler&amp;quot;
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then generate a version number:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;echo &amp;quot;0.0.1&amp;quot; &amp;gt; VERSION
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And now you can run &lt;code&gt;rake gemspec&lt;/code&gt; and &lt;code&gt;rake build&lt;/code&gt; to generate a &lt;code&gt;.gem&lt;/code&gt; file that you can install on your system. Jeweler also provides easy ways to get your gem onto &lt;a href="http://gemcutter.org/"&gt;Gemcutter&lt;/a&gt; or &lt;a href="http://rubyforge.org/"&gt;Rubyforge&lt;/a&gt;, so take a look at its &lt;a href="http://github.com/technicalpickles/jeweler/blob/master/README.markdown"&gt;README&lt;/a&gt; for more information.&lt;/p&gt;
&lt;h2&gt;The Result&lt;/h2&gt;
&lt;p&gt;Now that your gem is installed, you can take to your deploy script with a machete:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;set :app_name, 'myapp'

role :app, '192.168.0.1'
role :web, '192.168.0.1'
role :db, '192.168.0.1', :primary =&amp;gt; true

require 'capistrano/mycorp/base'
require 'capistrano/mycorp/thinking_sphinx'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s it! The result is a beautifully concise script that contains just the information that is pertinent to the application. It is easy to write and easy to read.&lt;/p&gt;
&lt;h2&gt;Suggested Uses&lt;/h2&gt;
&lt;p&gt;Building a gem of Capistrano recipes would be useful for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Internal teams wanting to standardise their deployment process and remove duplicate code from multiple deploy scripts.&lt;/li&gt;
&lt;li&gt;Agencies or freelancers wanting to reduce the amount of overhead required to start new projects.&lt;/li&gt;
&lt;li&gt;Individuals wanting to share their Capistrano tricks with the community.&lt;/li&gt;
&lt;li&gt;Hosting companies wanting to give their users a set of tasks for easily deploying to their servers.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Find Out More&lt;/h2&gt;
&lt;p&gt;You can see all my example code in the &lt;a href="http://github.com/timriley/capistrano-mycorp"&gt;timriley/capistrano-mycorp&lt;/a&gt; repository on GitHub.&lt;/p&gt;
&lt;p&gt;I hope this comes in handy!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>2009 in a Few Lists</title>
    <link rel="alternate" href="https://timriley.info/writing/2010/01/07/2009-in-a-few-lists"/>
    <id>https://timriley.info/writing/2010/01/07/2009-in-a-few-lists</id>
    <published>2010-01-07T04:50:00+00:00</published>
    <updated>2010-01-07T04:50:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;First, the once-in-a-lifetimes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Garmisch and I bought our first home and moved in. Inner-city living is great!&lt;/li&gt;
&lt;li&gt;We organised our wedding&lt;/li&gt;
&lt;li&gt;And &lt;a href="http://www.t8photography.com/GarmischAndTim"&gt;got married!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Then had an exciting honeymoon in Phú Quốc, Sài Gòn and Hong Kong.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These were without doubt the highlights of the year, and together they form fond memories I will hold for years to come, and the backdrop to what I know will be an wonderful married life with Misch.&lt;/p&gt;
&lt;p&gt;&lt;img src="/assets/1b34936153f0-F264EE54.jpg" alt="Garmisch and me on our wedding day" /&gt;&lt;/p&gt;
&lt;p&gt;Plenty of other stuff happened. For starters, I accepted an offer to join the team at &lt;a href="http://icelab.com.au/"&gt;Icelab&lt;/a&gt; as Senior Developer. I’m excited to start to work with the talented folk there in just a few weeks! This meant that in December I wrapped up my couple of years at the &lt;a href="http://www.amc.org.au/"&gt;AMC&lt;/a&gt;. I got to do some pretty neat things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Built a payments service that has since processed over $10M and handles the credit card payments for four different public applications&lt;/li&gt;
&lt;li&gt;Built a new redundant virtual server infrastructure to handle the AMC’s eight Rails apps, thanks to Xen, 3000 lines of custom bash scripts for automated provision (if only Chef came out earlier), haproxy and nginx for load balancers, MySQL replication, NFS shares backed by a distributed block device, and a gem chock full of shared capistrano recipes. Plenty of moving parts but things have run pretty smoothly since!&lt;/li&gt;
&lt;li&gt;Came back fired up from RailsConf and managed to rejig (with the vital help of the team) the development methodology for my final project: pair programming all the time; tests first all the time; cucumber features first wherever possible; weekly iterations and user stories written and prioritised by the business.&lt;/li&gt;
&lt;li&gt;Finally started building the simplest things that could first possibly work.&lt;/li&gt;
&lt;li&gt;Put a lot of energy into helping build a positive and passionate team culture.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let’s not forget the fun outside of work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Went overseas for my first major web development conferences: RailsConf in Vegas and Ruby Kaigi in Tokyo.&lt;/li&gt;
&lt;li&gt;Entered the Rails Rumble with &lt;a href="http://makenosound.com/"&gt;Max&lt;/a&gt; and &lt;a href="http://hughevans.net/"&gt;Hugh&lt;/a&gt; and built &lt;a href="http://decafsucks.com/"&gt;Decaf Sucks&lt;/a&gt;. It is still going strong with over 300 reviews.&lt;/li&gt;
&lt;li&gt;Presented Decaf Sucks at &lt;a href="http://webjam.com.au/webjam10"&gt;WebJam 10&lt;/a&gt; in Sydney.&lt;/li&gt;
&lt;li&gt;Founded a startup with three others and got busy working on our product.&lt;/li&gt;
&lt;li&gt;Did a little &lt;a href="http://github.com/timriley"&gt;open source stuff&lt;/a&gt;, most notably &lt;a href="http://github.com/timriley/acts-as-importable"&gt;acts-as-importable&lt;/a&gt;, &lt;a href="http://github.com/timriley/campfire-bot"&gt;campfire-bot&lt;/a&gt;, &lt;a href="http://github.com/timriley/unfuddle-helpdesk"&gt;unfuddle-helpdesk&lt;/a&gt; and &lt;a href="http://github.com/timriley/openmonkey.com"&gt;openmonkey.com&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;2010 is looking like it will be a ripper!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>The Atomic Shell Script</title>
    <link rel="alternate" href="https://timriley.info/writing/2009/11/4/the-atomic-shell-script"/>
    <id>https://timriley.info/writing/2009/11/4/the-atomic-shell-script</id>
    <published>2009-11-04T06:25:00+00:00</published>
    <updated>2009-11-04T06:25:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;The single file is the fundamental building block of unix and the smallest unit that you work with when configuring servers. So, when scripting your repetitive server tasks, keeping your code contained within a similarly singular, atomic file is a worthy goal for the simplicity and convenience that it brings. A way to achieve this for complex scripts may not always be immediately clear, but it usually both possible and worth the effort.&lt;/p&gt;
&lt;p&gt;[caption id=”” align=“alignnone” width=“500.0”] &lt;img src="/assets/163643b2426d-7C5EA0BC.jpg" alt="Image from freakdog." /&gt; Image from freakdog.[/caption]&lt;/p&gt;
&lt;p&gt;Here’s my example. When we started using &lt;a href="http://github.com/wvanbergen/request-log-analyzer/"&gt;request-log-analyzer&lt;/a&gt; to periodically analyse the logs from one of our Rails apps, it looked like I would need to use multiple files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A Ruby script to call request-log-analyzer with the appropriate log file and output the data we want.&lt;/li&gt;
&lt;li&gt;Another Ruby file to contain the definition for our custom log analysis rules (this is a requirement for request-log-analyzer).&lt;/li&gt;
&lt;li&gt;An SQLite database file for storing the parsed data from the log files.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What I initially was hoping to do in a single script had become a tenuous concert of interdependent files that would no doubt require documentation for my teammates to install and maintain. With a little creativity, I overcame the issue and reduced everything to a single script, achieving simplicity of installation and use. First, I’ll show you the script (abridged for this article), and then the explanation of my technique:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env ruby

require 'time'
require 'fileutils'
require 'tempfile'

require 'rubygems'
require 'activesupport'
require 'sqlite3'
require 'ruport'

Tempfile.class_eval do
  # Remove the dashes that Tempfile would otherwise put in the tmpname.
  def make_tmpname(basename, n)
    case basename
    when Array
      prefix, suffix = *basename
    else
      prefix, suffix = basename, ''
    end

    t = Time.now.strftime(&amp;quot;%Y%m%d&amp;quot;)
    path = &amp;quot;#{prefix}#{t}#{$$}#{rand(0x100000000).to_s(36)}#{n}#{suffix}&amp;quot;
  end

  def klassify
    File.basename(self.path, '.rb').gsub(/\/(.?)/) { &amp;quot;::&amp;quot; + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
  end
end

LIB_FILE = Tempfile.new(['file_format', '.rb'])
DB_FILE = Tempfile.new(['questions', '.sqlite3'])
LOG_FILES = ARGV.blank? ? Dir[&amp;quot;/var/log/rails/rails-questions-production/rails-questions-production-#{Time.now.yesterday.strftime('%Y%m%d')}*&amp;quot;].first : ARGV.join(' ')

LIB_FILE.puts &amp;quot;class #{LIB_FILE.klassify} &amp;lt; RequestLogAnalyzer::FileFormat::Rails\n&amp;quot;
LIB_FILE.puts &amp;lt;&amp;lt;'EOF'
  line_definition :current_user do |line|
    line.regexp = /Logged in as: (.+)/
    line.captures &amp;lt;&amp;lt; { :name =&amp;gt; :email, :type =&amp;gt; :string }
  end
end
EOF
LIB_FILE.flush

unless system(&amp;quot;request-log-analyzer -f #{LIB_FILE.path} -d #{DB_FILE.path} #{LOG_FILES} &amp;gt; /dev/null&amp;quot;)
  exit &amp;quot;Error running request-log-analyzer&amp;quot;
end

db = SQLite3::Database.new(DB_FILE.path)

all_logins_query = &amp;lt;&amp;lt;EOQ
SELECT email AS user_email FROM current_user_lines GROUP BY user_email;
EOQ

all_logins_rows = db.execute2(all_logins_query)
all_logins_table = Ruport::Data::Table.new(:column_names =&amp;gt; all_logins_rows.shift, :data =&amp;gt; all_logins_rows)

puts
puts &amp;quot;All Logins&amp;quot;
puts all_logins_table.as(:text, :ignore_table_width =&amp;gt; true)

# Cleanup
LIB_FILE.close!
DB_FILE.close!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The trick here is that the single ruby script creates all the auxiliary files that it needs, every time it runs! Ruby’s &lt;code&gt;Tempfile&lt;/code&gt; is your friend, since it will take care of creating unique temporary files for you, and clean them up again when you call &lt;code&gt;#close!&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The other trick is getting request-log-analyzer to accept a Tempfile for its custom file format definition. It expects a single class to be defined inside this file, with a name matching the name of the file. Nothing a little monkey patch to Tempfile couldn’t handle. I overwrote &lt;code&gt;make_tempname&lt;/code&gt; to ensure no dashes were used in the file name (these aren’t allowed in the names of Ruby classes), and then I added a &lt;code&gt;#klassify&lt;/code&gt; instance method to convert the filename into the kind of class name that request-log-analyzer would expect.&lt;/p&gt;
&lt;p&gt;I’m happy with the way this his script turned out. It may be a little more involved, but the result is far more explicit, without any dependence on other moving parts. This singular, atomic script is easy to install and maintain, and requires no documentation apart from its own source code.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Complex Nested Forms with Rails and Unobtrusive jQuery</title>
    <link rel="alternate" href="https://timriley.info/writing/2009/10/13/complex-nested-forms-with-rails-and-unobtrusive-jquery"/>
    <id>https://timriley.info/writing/2009/10/13/complex-nested-forms-with-rails-and-unobtrusive-jquery</id>
    <published>2009-10-13T00:20:00+00:00</published>
    <updated>2009-10-13T00:20:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;I came across Ryan Bates’ &lt;a href="http://github.com/ryanb/complex-form-examples/"&gt;complex-form-examples&lt;/a&gt; project when I needed to build a complex form recently. It’s an excellent educational reference for building these complex forms in a Rails app. You know the kind of forms, the ones where you want to add or remove an arbitrary number of child associations of a parent record. True to form, the latest version of Ryan’s example app uses Rails’ new &lt;a href="http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html#M002132"&gt;&lt;code&gt;accepts_nested_attributes_for&lt;/code&gt;&lt;/a&gt;, which lets you create, edit or delete the collection of associated child objects just by passing through the appropriate attributes to the parent record.&lt;/p&gt;
&lt;p&gt;The example app works beautifully, but a couple of its approaches didn’t match how I wanted to build my app:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It uses inline JavaScript. The JavaScript triggers and code to dynamically add and remove parts of the form for child objects are mixed in with the rest of my page markup, not in &lt;code&gt;application.js&lt;/code&gt; or another file dedicated to my JavaScript and behaviour.&lt;/li&gt;
&lt;li&gt;The JavaScript function to add fields to the form takes a string argument that contains &lt;em&gt;all&lt;/em&gt; the HTML markup for these forms. Now, I’m no expert, but this just didn’t seem as clean or efficient as possible.
[caption id=”” align=“alignnone” width=“500.0”] &lt;img src="/assets/6fa184048ecd-A1862768.jpg" alt="Image from carsten_tb." /&gt; Image from carsten_tb.[/caption]&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So when I incorporated Ryan’s examples into my application, I made a couple of changes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;All the JavaScript is unobtrusive. The behaviour is kept in my &lt;code&gt;application.js&lt;/code&gt; along with the rest of my JavaScript.&lt;/li&gt;
&lt;li&gt;This means the page markup is cleaner. The links to add and remove child elements have no inline JS, just a certain class name for jQuery to hook onto.&lt;/li&gt;
&lt;li&gt;The JavaScript functions that add new fields to the form don’t get given the markup for those fields in an argument. The fields are already in the form, wrapped in a div that is hidden with a &lt;code&gt;display: none&lt;/code&gt; rule. The JavaScript function finds these fields in the DOM and then duplicates them and inserts them into the right place. In essence, the hidden fields act as a template.&lt;/li&gt;
&lt;li&gt;For the form to work with multiple child objects, the links to add new fields include an HTML5 &lt;a href="http://dev.w3.org/html5/spec/Overview.html#custom-data-attribute"&gt;custom data attribute&lt;/a&gt; called &lt;code&gt;data-association&lt;/code&gt; to store the appropriate association name for the child. The JavaScript uses this field to find the right hidden template fields (see above). Neat!&lt;/li&gt;
&lt;li&gt;All the JavaScript uses &lt;a href="http://jquery.com"&gt;jQuery&lt;/a&gt;, because that matched the rest of my application. No big deal.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I applied these changes to an &lt;a href="http://github.com/timriley/complex-form-examples/tree/unobtrusive-jquery"&gt;unobtrusive-jquery branch&lt;/a&gt; in my fork of complex-form-examples. Please clone or fork it to take a look and make any improvements! I hope it can come in handy. Thanks to &lt;a href="http://www.workingwithrails.com/person/6491-ryan-bates"&gt;Ryan Bates&lt;/a&gt; for his excellent work in building the example application.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Decaf Sucks, and a Rails Rumble Redux</title>
    <link rel="alternate" href="https://timriley.info/writing/2009/09/02/decaf-sucks-and-a-rails-rumble-redux"/>
    <id>https://timriley.info/writing/2009/09/02/decaf-sucks-and-a-rails-rumble-redux</id>
    <published>2009-09-02T06:35:00+00:00</published>
    <updated>2009-09-02T06:35:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;h2&gt;Decaf Sucks&lt;/h2&gt;
&lt;p&gt;48 hours. 3 hackers (&lt;a href="http://makenosound.com"&gt;Max&lt;/a&gt;, &lt;a href="http://hughevans.net"&gt;Hugh&lt;/a&gt; and &lt;a href="http://openmonkey.com/about"&gt;yours truly&lt;/a&gt;), 1 web application built from scratch. Allow me to introduce you to &lt;a href="http://decafsucks.com/"&gt;&lt;strong&gt;Decaf Sucks&lt;/strong&gt;&lt;/a&gt;, our Rails Rumble entry and a site for helping you find great coffee.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://decafsucks.com/"&gt;&lt;img src="/assets/5ac1bd7cfb18-013B89C4.jpg" alt="Screenshot of Decaf Sucks" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We hate bad coffee. We hate the way it tastes, the way it smells, and most of all we hate paying for it. Decaf Sucks is about helping each other to find the good cafés and avoid the bad ones.&lt;/p&gt;
&lt;p&gt;So &lt;a href="http://decafsucks.com/"&gt;check it out&lt;/a&gt; and &lt;a href="http://decafsucks.com/reviews/new"&gt;leave a review&lt;/a&gt;. Also be sure to try the site on your iPhone. It uses the GPS to enable you to find the best cafés nearby, and write a review as you bask in the sweet glow of a just finished cup of coffee.&lt;/p&gt;
&lt;h2&gt;Lessons Learnt Starting Up Fast&lt;/h2&gt;
&lt;p&gt;We’ve been trying to build this app for over a year. We would have spent well in excess of 48 hours together working on our first prototype. The trouble was that we dreamt big and started to build an app that would do everything at once. Finally, it took the &lt;a href="http://railsrumble.com/"&gt;Rails Rumble&lt;/a&gt; to get us somewhere. We were forced focus on a core experience and how we could implement it quickly. I’m very pleased with the result, and would like to share the things I have learnt in competing over the last three Rumbles.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Build something you want to use.&lt;/strong&gt; There is no stronger motivation than this.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Say no,&lt;/strong&gt; and do it early. Define the core experience your application will provide and build that well. Save any other ideas for later.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Have a designer on your team.&lt;/strong&gt; This is the single biggest improvement to our previous Rumble entries. It was great to have the talented &lt;a href="http://makenosound.com/"&gt;Max&lt;/a&gt; on the team. First impression and overall polish counts for a lot. Allow your app to make the best impression!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Track your outstanding tasks in meatspace.&lt;/strong&gt; Try a whiteboard or a large sheet of butcher’s paper. This is an easy technique to keep the group mindful of what is left to do. It’s also very satisfying to write a big tick or strike through a completed task.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Work in the same place.&lt;/strong&gt; This is the fun part: hack together, share your meals and enjoy the weekend. This also makes it easy to discuss your app, form consensus quickly, and get back to building.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Don’t plan much for Monday.&lt;/strong&gt; When it comes to Sunday afternoon and you realise you still have many hours left of work to do, you don’t want anything to get in the way. The last two years we have worked through Sunday night to finish our apps. Each time it has been worth it. If you are an employee and working in any kind of IT role, tell your boss that this is 40 hours of the most intense, practical and relevant training anyone could receive, and hopefully they see reason and give you the time off. &lt;em&gt;(This only applies if you are in a timezone similar to GMT +10)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Have someone focus solely on user experience.&lt;/strong&gt; This doesn’t necessarily have to be your designer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep most of your site accessible without a login.&lt;/strong&gt; You’ve worked hard. Minimise the number of hurdles required for people to admire it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Outsource your authentication.&lt;/strong&gt; When you do need authentication, outsource it. Use &lt;a href="http://apiwiki.twitter.com/OAuth-FAQ"&gt;Twitter&lt;/a&gt;, &lt;a href="http://developers.facebook.com/connect.php"&gt;Facebook&lt;/a&gt;, or &lt;a href="http://openid.net/"&gt;OpenID&lt;/a&gt;. Your choice will depend on the kind of audience you’re seeking.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use only one or two models.&lt;/strong&gt; Key models at least. Any more and the chances are that you will have more pages to build than your weekend will allow.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use good plugins.&lt;/strong&gt; These will save you so much time! The heroes for us this year were &lt;a href="http://github.com/mbleigh/twitter-auth/tree/master"&gt;twitter-auth&lt;/a&gt; and &lt;a href="http://geokit.rubyforge.org/"&gt;geokit&lt;/a&gt;. Both are well built and well documented, with a great out of the box experience. They offered no unpleasant surprises, meaning we could integrate them easily.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Refer to &lt;a href="http://railscasts.com"&gt;Railscasts&lt;/a&gt;!&lt;/strong&gt; This site is an invaluable resource. If you’re trying something new with Rails, chances are that there is already a screencast about it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Build your server early.&lt;/strong&gt; Towards the end of the weekend, you’ll be focused on completing your application and deploying often. Don’t let an outstanding server build get in the way.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Build your server fast.&lt;/strong&gt; Don’t let your server build consume too much time either. Use an automated provision tool like &lt;a href="http://github.com/benschwarz/passenger-stack"&gt;passenger-stack&lt;/a&gt;. If you predict that you will have an involved setup process, buy a &lt;a href="http://slicehost.com/"&gt;slice&lt;/a&gt; for a week or two before the competition, build the server to your specifications, and document your process.&lt;/p&gt;
&lt;h2&gt;Something Different&lt;/h2&gt;
&lt;p&gt;Now for some things that I would try differently next time:&lt;/p&gt;
&lt;p&gt;Implement a working &lt;a href="http://v3.jasonsantamaria.com/archive/2004/05/24/grey_box_method.php"&gt;grey box&lt;/a&gt; design first. I find it a lot easier to build an application backend if it is at least for a prototypical frontend.&lt;/p&gt;
&lt;p&gt;Write Cucumber integration tests for the application’s “happy path.” As the last long night wore on, we were making plenty of changes that had the potential to break things. It would have been good to have some tests confirm everything still worked as expected. I don’t think it’s feasible over the weekend to build your app via TDD/BDD, but a small set of integration tests would help without being too burdensome.&lt;/p&gt;
&lt;p&gt;Write these Cucumber features ahead of time. I don’t think this would break the competition rules and it would be useful to have these from the beginning. This would also help you and your teammates crystallise the application’s functionality before the build weekend.&lt;/p&gt;
&lt;h2&gt;Context&lt;/h2&gt;
&lt;p&gt;One of the reasons I like competing in the Rails Rumble is that it provides sweet, glorious &lt;em&gt;context&lt;/em&gt;. If you’re working as a programmer somewhere that requires your attention in many places, frequent context shifts are a fast way to swap happy productivity for overall malaise. The Rails Rumble is a way to rejuvenate yourself. Gustavo Duarte &lt;a href="http://duartes.org/gustavo/blog/post/lucky-to-be-a-programmer"&gt;captures the experience&lt;/a&gt; well:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Few things are better than spending time in a creative haze, consumed by ideas, watching your work come to life, going to bed eager to wake up quickly and go try things out. I am not suggesting that excessive hours are needed or even advisable; a sane schedule is a must except for occasional binges. The point is that programming is an intense creative pleasure, a perfect mixture of puzzles, writing, and craftsmanship.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;While it is indeed one of those occasional binges, this year’s Rails Rumble was a timely personal reminder of why I play this game: to focus on creating new and helpful things in interesting ways. See you next year.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Show Me the Page!</title>
    <link rel="alternate" href="https://timriley.info/writing/2009/08/06/show-me-the-page"/>
    <id>https://timriley.info/writing/2009/08/06/show-me-the-page</id>
    <published>2009-08-06T00:15:00+00:00</published>
    <updated>2009-08-06T00:15:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Get this in your Cucumber steps.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Then /^show me the page$/ do
  save_and_open_page
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thanks to the magic of &lt;a href="http://wiki.github.com/brynary/webrat"&gt;webrat’s&lt;/a&gt; lesser known &lt;a href="http://github.com/brynary/webrat/blob/273e8c541a82ddacf91f4f68ab6166c16ffdc9c5/lib/webrat/core/save_and_open_page.rb"&gt;save_and_open_page&lt;/a&gt;, this will save the current page to a temporary file and open it in your browser. Be sure to &lt;code&gt;gem install launchy&lt;/code&gt; for it to work.&lt;/p&gt;
&lt;p&gt;Here’s a sample of &lt;em&gt;show me the page&lt;/em&gt; in a scenario:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Scenario: Signing in
  When I go to the home page
  And I fill in &amp;quot;username&amp;quot; with &amp;quot;john&amp;quot;
  And I fill in &amp;quot;password&amp;quot; with &amp;quot;john&amp;quot;
  Then show me the page
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Very useful for debugging.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Machinist and Paperclip</title>
    <link rel="alternate" href="https://timriley.info/writing/2009/07/25/machinist-and-paperclip"/>
    <id>https://timriley.info/writing/2009/07/25/machinist-and-paperclip</id>
    <published>2009-07-25T03:55:00+00:00</published>
    <updated>2009-07-25T03:55:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Once you’ve found a comfortable fixture replacement for your tests, there is no going back. For many, that’s something like &lt;a href="http://thoughtbot.com/projects/factory_girl"&gt;Factory Girl&lt;/a&gt;. For me, it is Pete Yandell’s &lt;a href="http://github.com/notahat/machinist"&gt;Machinist&lt;/a&gt;. So when I wanted to write some &lt;a href="http://cukes.info/"&gt;Cucumber&lt;/a&gt; scenarios for a model using &lt;a href="http://thoughtbot.com/projects/paperclip"&gt;Paperclip&lt;/a&gt; for file attachments, I definitely needed a factory for creating it!&lt;/p&gt;
&lt;p&gt;In Machinist, building factories (called &lt;em&gt;blueprints&lt;/em&gt;) is easy for standard ActiveRecord attributes and associations. Check out the &lt;a href="http://github.com/notahat/machinist"&gt;comprehensive README&lt;/a&gt; in the repository. Here is the start of a &lt;code&gt;blueprints.rb&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;require 'faker'

Sham.title { Faker::Lorem.sentence }

AttachedDocument.blueprint do
  title
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As it turns out, it is equally easy to add support for models using Paperclip. Say our model looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class AttachedDocument &amp;lt; ActiveRecord::Base
  has_attached_file :document
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then your blueprint should incorporate the attached file like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;require 'faker'

Sham.title { Faker::Lorem.sentence }
Sham.document { Tempfile.new('the attached document') }

AttachedDocument.blueprint do
  title
  document
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using a &lt;a href="http://ruby-doc.org/stdlib/libdoc/tempfile/rdoc/index.html"&gt;Tempfile&lt;/a&gt; instance to represent the paperclip attachments works without a hitch, and the implementation of Tempfile will ensure there are no file duplicates. It will also mean that the other methods that paperclip sets up (such as &lt;code&gt;document_file_name&lt;/code&gt;, &lt;code&gt;document_content_type&lt;/code&gt; and &lt;code&gt;document_file_size&lt;/code&gt;) are all accessible on the generated object. Now get going and test!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Fast GitHub Clone Bash Function Using the OS X Clipboard</title>
    <link rel="alternate" href="https://timriley.info/writing/2009/07/21/fast-github-clone-bash-function-using-the-os-x-clipboard"/>
    <id>https://timriley.info/writing/2009/07/21/fast-github-clone-bash-function-using-the-os-x-clipboard</id>
    <published>2009-07-21T06:40:00+00:00</published>
    <updated>2009-07-21T06:40:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;h2&gt;Bash Functions Save Time&lt;/h2&gt;
&lt;p&gt;The keen programmer never has any shortage of new and interesting open source projects to inspect, and for me, GitHub is increasingly the place where the ones I want to see are hosted.&lt;/p&gt;
&lt;p&gt;Whenever I want to have a good close look at something, I’ll make a clone of the project to my computer and use TextMate to poke around the source. If I want to keep the source around for reference and have no intention to modify it, I’ll clone the project to &lt;code&gt;~/Code/sources/&amp;lt;user_name&amp;gt;-&amp;lt;repository_name&amp;gt;&lt;/code&gt;. I started using this naming convention to help me quickly identify where again to find the upstream copy of the project when I return to it.&lt;/p&gt;
&lt;p&gt;I’ve been following this pattern for quite some months now, and in my ongoing effort to decrease keystrokes, I made a bash function to take care of it for me:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function ghclone {
  gh_url=${1:-`pbpaste`}
  co_dir=${HOME}/Code/sources/$(echo $gh_url | sed -e 's/^git:\/\/github.com\///; s/\//-/; s/\.git$//')

  if [-d $co_dir]; then
    cd $co_dir &amp;amp;&amp;amp; git pull origin master
  else
    git clone &amp;quot;${gh_url}&amp;quot; &amp;quot;${co_dir}&amp;quot; &amp;amp;&amp;amp; cd &amp;quot;${co_dir}&amp;quot;
  fi
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Optional Arguments and Clipboard Access Save Even More Time&lt;/h2&gt;
&lt;p&gt;This &lt;code&gt;ghclone&lt;/code&gt; function accepts a single argument, the URL of the repository on GitHub. If it does not exist as a local clone, it will clone it and leave you in its directory. If you’ve already cloned it before, it will take you to the clone and update it.&lt;/p&gt;
&lt;p&gt;This single function argument is optional, thanks to Bash’s cool &lt;a href="http://tldp.org/LDP/abs/html/parameter-substitution.html#PARAMSUBREF"&gt;parameter substitution&lt;/a&gt; capabilities. The &lt;code&gt;gh_url&lt;/code&gt; variable is set from the function argument if it is passed, or from OS X’s clipboard:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gh_url=${1:-`pbpaste`}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For those of us who are used to more expressive languages like Ruby, it is akin to this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gh_url = ARGV[0] || `pbpaste`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;pbaste&lt;/code&gt; utility is shipped by default with Mac OS X, and returns the current value in the system clipboard. This is cool! It means that, with this shortcut function, I can clone GitHub repositories &lt;em&gt;very&lt;/em&gt; quickly:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Click the copy button next to the repository URL on GitHub.&lt;/li&gt;
&lt;li&gt;Open the terminal and type &lt;code&gt;ghclone&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Start exploring!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Throw the function into your &lt;code&gt;~/.bash_profile&lt;/code&gt; to get started. If &lt;code&gt;pbpaste&lt;/code&gt; is interesting to you, be sure to check its counterpart, &lt;code&gt;pbcopy&lt;/code&gt;, which you can use to populate the clipboard from the shell.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Thinking Sphinx RSpec Matchers</title>
    <link rel="alternate" href="https://timriley.info/writing/2009/07/19/thinking-sphinx-rspec-matchers"/>
    <id>https://timriley.info/writing/2009/07/19/thinking-sphinx-rspec-matchers</id>
    <published>2009-07-18T23:40:00+00:00</published>
    <updated>2009-07-18T23:40:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;How do you use &lt;a href="http://rspec.info/"&gt;RSpec&lt;/a&gt; to drive the design of your models that will use &lt;a href="http://ts.freelancing-gods.com/"&gt;Thinking Sphinx&lt;/a&gt; for search? Say you’re already using &lt;a href="http://cukes.info/"&gt;Cucumber&lt;/a&gt; for integration tests to verify your index builds correctly and searches return the results you expect. For your models’ specs, you’ll want something that is lighter but doesn’t sacrifice your overall test coverage.&lt;/p&gt;
&lt;p&gt;To achieve this, I wrote a couple of small RSpec matchers that inspect the Thinking Sphinx index object on your model to ensure that it contains the fields and attributes that you expect.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Spec::Matchers.define(:index) do |*field_names|
  description do
    &amp;quot;have a search index for #{field_names.join('.')}&amp;quot;
  end
  match do |model|
    all_fields = field_names.dup
    first_field = all_fields.pop

    model.sphinx_indexes.first.fields.select { |field|
      field.columns.length == 1 &amp;amp;&amp;amp;
        field.columns.first.__stack == all_fields.map { |s| s.to_sym } &amp;amp;&amp;amp;
        field.columns.first.__name == first_field.to_sym
    }.length == 1
  end
end

Spec::Matchers.define(:have_attribute) do |*attr_names|
  description do
    &amp;quot;have a search attribute for #{attr_names.join('.')}&amp;quot;
  end
  match do |model|
    all_attrs = attr_names.dup
    first_attr = all_attrs.pop

    model.sphinx_indexes.first.attributes.select { |attr|
      attr.columns.length == 1 &amp;amp;&amp;amp;
        attr.columns.first.__stack == all_attrs.map { |s| s.to_sym } &amp;amp;&amp;amp;
        attr.columns.first.__name == first_attr.to_sym
    }.length == 1
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Put these matchers in your &lt;code&gt;spec_helper.rb&lt;/code&gt; or somewhere else handy, and then you can use them in your model specs:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;describe Question do
  it { should index(:topic) }
  it { should have_attribute(:state) }
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;They read quite nicely in the single-line format above, and the matchers provide a readable description when you run the spec:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Question
- should have a search index for topic
- should have a search attribute for legacy_mastery
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While these matchers work well for me, I feel that @sphinx_indexes@ is perhaps an object I should leave alone, and not something I can rely on having continued access to. Please leave a comment if you have any suggestions for doing this more cleanly!&lt;/p&gt;
&lt;p&gt;What I did learn, however, is how simple it was to write custom matchers for RSpec. If you haven’t tried it before, I strongly suggest you give it a go! RSpec’s matcher DSL is straightforward, and the documentation has &lt;a href="http://rspec.rubyforge.org/rspec/1.2.8/classes/Spec/Matchers.html"&gt;everything you need&lt;/a&gt; to get started.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Using RSpec Ordered Message Expectations to Tighten your Specs</title>
    <link rel="alternate" href="https://timriley.info/writing/2009/07/01/using-rspec-ordered-message-expectations-to-tighten-your-specs"/>
    <id>https://timriley.info/writing/2009/07/01/using-rspec-ordered-message-expectations-to-tighten-your-specs</id>
    <published>2009-07-01T13:35:00+00:00</published>
    <updated>2009-07-01T13:35:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;I quite enjoy the competitive undercurrent of ping pong pair programming. As the person writing the implementation code, it is fun to write something that will turn a test green, but still not necessarily do what my partner was expecting. Taking this approach has also been helpful for improving our specs. Take this example controller spec:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;describe ArticlesController do
  describe &amp;quot;handling create&amp;quot; do
    before(:each) do
      @article = mock_model(Article, :save =&amp;gt; nil)
      Article.stub(:new).and_return(@article)

      @user = mock_model(User)
      controller.stub(:current_user).and_return(@user)
    end

    it &amp;quot;should build a new article from posted data&amp;quot; do
      Article.should_receive(:new).with('title' =&amp;gt; 'Test Post')
      post :create, :article =&amp;gt; {:title =&amp;gt; 'Test Post'}
    end

    it &amp;quot;should assign the current user as the article's author&amp;quot; do
      @article.should_receive(:author=).with(@user)
      post :create
    end

    it &amp;quot;should save the article&amp;quot;
      @article.should_receive(:save)
      post :create
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This looks like a reasonable set of concise, clear examples, but you can easily make them all pass and without building a controller action that does what you expect:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class ArticlesController &amp;lt; ApplicationController
  def create
    @article = Article.new(params[:article])
    @article.save
    @article.author = current_user
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This satisfies the examples, but saving the article &lt;em&gt;before&lt;/em&gt; assigning the current user as author isn’t what we would have intended. Enter RSpec’s &lt;a href="http://rspec.info/documentation/mocks/message_expectations.html"&gt;ordered message expectations&lt;/a&gt;. These allow you to specify the order in which you expect an object to receive message calls.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;describe ArticlesController do
  describe &amp;quot;handling create&amp;quot; do
    it &amp;quot;should save the article after assigning the current user as author&amp;quot;
      @article.should_receive(:author=).with(@user).ordered
      @article.should_receive(:save).ordered
      post :create
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This example would fail with the above controller action, and force us to write it properly:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class ArticlesController &amp;lt; ApplicationController
  def create
    @article = Article.new(params[:article])
    @article.author = current_user
    @article.save
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The result is a controller that does what you expect, a stronger set of specs, and an increased capacity for true behaviour driven development. Win, win, win!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>De-@wip Your Cucumber Stories</title>
    <link rel="alternate" href="https://timriley.info/writing/2009/06/29/de-wip-your-cucumber-stories"/>
    <id>https://timriley.info/writing/2009/06/29/de-wip-your-cucumber-stories</id>
    <published>2009-06-29T06:55:00+00:00</published>
    <updated>2009-06-29T06:55:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;We’ve been really hitting the &lt;a href="http://cukes.info/"&gt;Cucumber&lt;/a&gt; hard in the recent few iterations of our current project. Now that we’re &lt;a href="http://en.wikipedia.org/wiki/Pair_programming#Ping_pong_pair_programming"&gt;ping pong pair programming&lt;/a&gt;, the Cucumber story is the first thing we write, and we revisit regularly during the &lt;a href="http://jamesshore.com/Blog/Red-Green-Refactor.html"&gt;red-green-refactor&lt;/a&gt; cycle.&lt;/p&gt;
&lt;p&gt;To make this easy, we place a &lt;code&gt;@wip&lt;/code&gt; &lt;em&gt;work in progress&lt;/em&gt; &lt;a href="http://wiki.github.com/aslakhellesoy/cucumber/tags"&gt;tag&lt;/a&gt; at the top of the current stories:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Feature: Make coffee

  # This one is done.
  Scenario: First coffee of the day

  # This is the one we're working on.
  @wip
  Scenario: Afternoon perk up
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we use &lt;code&gt;cucumber -t wip&lt;/code&gt; to run just the stories in progress. Once the stories are green and we’re happy to move on, we often forget to remove the &lt;code&gt;@wip&lt;/code&gt; tags before committing. I wrote a little sed command in a bash alias to make this easier:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Remove any @wip tags from Cucumber features.
alias dewip=&amp;quot;sed -E -i '' -e '/^[[:blank:]]*@wip$/d;s/,[[:blank:]]*@wip//g;s/@wip,[[:blank:]]*//g' features/**/*.feature&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Throw it in your &lt;code&gt;.bash_profile&lt;/code&gt; for ease of access! Here’s a &lt;a href="http://gist.github.com/136294"&gt;gist&lt;/a&gt; for your forking pleasure.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Rails Templates as the New Geek Code</title>
    <link rel="alternate" href="https://timriley.info/writing/2009/05/20/rails-templates-as-the-new-geek-code"/>
    <id>https://timriley.info/writing/2009/05/20/rails-templates-as-the-new-geek-code</id>
    <published>2009-05-20T02:30:00+00:00</published>
    <updated>2009-05-20T02:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Anyone who’s been around the Internet long enough should remember &lt;a href="http://www.geekcode.com/"&gt;The Geek Code&lt;/a&gt;. This meme sought to provide a - however obsbcure - succinct textual distillation of the attributes and interests of any geek.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GED/J d-- s:++&amp;gt;: a-- C++(++++) ULU++ P+ L++ E---- W+(-) N+++ o+ K+++ w---
  O- M+ V-- PS++&amp;gt;$ PE++&amp;gt;$ Y++ PGP++ t- 5+++ X++ R+++&amp;gt;$ tv+ b+ DI+++ D+++
  G+++++ e++ h r-- y++**
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This geek code block represents someone trained in education and law who is tall and dresses casually, knows his way around Linux and Ultrix pretty well, hates emacs, but loves indulging in a little Dilbert. &lt;a href="http://www.geekcode.com/geek.html"&gt;Look it up&lt;/a&gt;, it’s elaborate.&lt;/p&gt;
&lt;p&gt;Today, a Rails developer can provide a &lt;a href="http://m.onkey.org/2008/12/4/rails-templates"&gt;template&lt;/a&gt; for generating new apps that can uniquely embody all their development tools and preferences in a single place.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gem 'haml'
gem 'mislav-will_paginate', :lib =&amp;gt; 'will_paginate', :source =&amp;gt; 'http://gems.github.com'
gem 'chriseppstein-compass', :lib =&amp;gt; 'compass', :source =&amp;gt; 'http://gems.github.com'
gem 'thoughtbot-paperclip', :lib =&amp;gt; 'paperclip', :source =&amp;gt; 'http://gems.github.com' if yes?('Paperclip gem?')
gem 'authlogic' if yes?('Authlogic gem?')
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You’re a haml guy? Right on. Plus compass for CSS! You must like really things semantic.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;file '.testgems',
%q{config.gem 'rspec'
config.gem 'rspec-rails'
config.gem 'notahat-machinist', :lib =&amp;gt; 'machinist', :source =&amp;gt; 'http://gems.github.com'
config.gem 'ianwhite-pickle', :lib =&amp;gt; 'pickle', :source =&amp;gt; 'http://gems.github.com'
config.gem 'webrat'
config.gem 'cucumber'
}
run 'cat .testgems &amp;gt;&amp;gt; config/environments/test.rb &amp;amp;&amp;amp; rm .testgems'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;RSpec along with Cucumber backed by Machinist and Pickle for test data factories. You must be Australian. But that’s cool, that’s a pretty helpful combo for tests.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git :init
git :add =&amp;gt; '.'
git :commit =&amp;gt; '-a -m &amp;quot;Initial commit from AMC Rails template&amp;quot;'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Building a complete Rails template has been really useful for us at the &lt;a href="http://www.amc.org.au/"&gt;AMC&lt;/a&gt;. We’re often asked to bring up quick, single-purpose apps alongside the many components that we’re building in our mostly service-oriented architecture.&lt;/p&gt;
&lt;p&gt;Our template is &lt;a href="http://github.com/timriley/amc-rails-template"&gt;available on GitHub&lt;/a&gt; for your perusal and reuse. It does everything from setting up plugins and gem dependencies, to generating a deploy script using our custom capistrano extensions, to including a default layout and stylesheet. This is how we roll, captured in a single file.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Importing Legacy Data in Rails</title>
    <link rel="alternate" href="https://timriley.info/writing/2009/05/02/importing-legacy-data-in-rails"/>
    <id>https://timriley.info/writing/2009/05/02/importing-legacy-data-in-rails</id>
    <published>2009-05-01T22:10:00+00:00</published>
    <updated>2009-05-01T22:10:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;h2&gt;How Did we Get Here?&lt;/h2&gt;
&lt;p&gt;Our current &lt;a href="http://www.amc.org.au/"&gt;work&lt;/a&gt; project is a long-overdue rebuild of a critical business system as a modern Rails web app. We’re building this thing according to agile practices to the best of our ability. Each week we provide a new, working release that is an incremental improvement on the last. We’re not looking to replace the current system in a single swoop, and expect the new system to work alongside the old one for quite a while.&lt;/p&gt;
&lt;p&gt;This means that we’ll need to maintain the same data in both systems for the duration of their lives together. We don’t want the new Rails app to access the data directly from the old app’s database, since this would prevent us from following the conventions that makes working with Rails so pleasant. Instead, we’ve come up with a way to import the legacy data into a new database.&lt;/p&gt;
&lt;p&gt;In doing this, I’ve built a Rails plugin to make the experience easier. It is called &lt;em&gt;Acts as Importable&lt;/em&gt; and it is now &lt;a href="http://github.com/timriley/acts-as-importable"&gt;available on GitHub&lt;/a&gt;. This article will show our technique for importing the legacy data and how Acts as Importable helps.&lt;/p&gt;
&lt;h2&gt;Accessing the Legacy Data&lt;/h2&gt;
&lt;p&gt;We use ActiveRecord to access the legacy data. It takes a bit of legwork to shoehorn the legacy schema into ActiveRecord models, but once that is done, we have satisfactory access to the data we want to import.&lt;/p&gt;
&lt;p&gt;The first thing we do is provide a &lt;code&gt;Legacy::Base&lt;/code&gt; model from which all other legacy models can inherit. This provides a single place to define access to the legacy database.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Legacy::Base &amp;lt; ActiveRecord::Base
  self.abstract_class = true
  establish_connection &amp;quot;legacy_#{Rails.env}&amp;quot;

  acts_as_importable
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can setup the extra database connections in &lt;code&gt;config/database.yml&lt;/code&gt;, like below. Our legacy connection is to an MS SQL server via ODBC. Tim Lucas has &lt;a href="http://toolmantim.com/articles/getting_rails_talking_to_sqlserver_on_osx_via_odbc"&gt;an excellent tutorial&lt;/a&gt; on how to get that set up.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;legacy_development:
  adapter: sqlserver
  mode: odbc
  dsn: LEGACY
  autocommit: true
  host: localhost
  username: NTDOMAIN\username
  password: password
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, here are a couple of the legacy models that inherit from &lt;code&gt;Legacy::Base&lt;/code&gt; to access the legacy database. For the sake of this article, consider the code examples to come from a quiz application with models for categories, questions, and responses.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Legacy::Question &amp;lt; Legacy::Base
  set_table_name 'quiz_questions'
  set_primary_key 'QuestionNumber'

  belongs_to :category,
             :class_name =&amp;gt; 'Legacy::Category',
             :foreign_key =&amp;gt; 'CategoryCode'
end

class Legacy::Category &amp;lt; Legacy::Base
  set_table_name 'quiz_categories'
  set_primary_key 'Code'
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These are simple examples, but you get the idea. &lt;code&gt;set_table_name&lt;/code&gt; and &lt;code&gt;set_primary_key&lt;/code&gt; are your friends when you have table names and keys that defy Rails’ conventions. If you want to use ActiveRecord associations to access your legacy data, you will also want to become familiar with the options available for specifying things like the class, join table and column names. Check the rdocs for &lt;code&gt;has_many&lt;/code&gt;, &lt;code&gt;belongs_to&lt;/code&gt; and &lt;code&gt;has_and_belongs_to_many&lt;/code&gt; and look out for options like &lt;code&gt;:class_name&lt;/code&gt;, &lt;code&gt;:foreign_key&lt;/code&gt;, &lt;code&gt;:join_table&lt;/code&gt;, and &lt;code&gt;:association_foreign_key&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Converting the Legacy Data&lt;/h2&gt;
&lt;p&gt;Each of the legacy models provides a &lt;code&gt;to_model&lt;/code&gt; method that returns a new model ready to be saved into the new, non-legacy database.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# New model
class Category &amp;lt; ActiveRecord::Base
end

# Legacy model
class Legacy::Category &amp;lt; Legacy::Base
  set_table_name 'quiz_categories'
  set_primary_key 'Code'

  def to_model
    ::Category.new do |c|
      c.name = self.Description
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Importing the Legacy Data&lt;/h2&gt;
&lt;p&gt;Now that the import rules are defined for all the legacy models we care about, how do we get them all into the new database? This is where &lt;a href="http://github.com/timriley/acts_as_importable"&gt;Acts as Importable&lt;/a&gt; comes in. This plugin helps to smoothen the import of an entire system’s worth of legacy data. You saw it enabled above in the &lt;code&gt;Legacy::Base&lt;/code&gt; class:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Legacy::Base &amp;lt; ActiveRecord::Base
  self.abstract_class = true
  establish_connection &amp;quot;legacy_#{Rails.env}&amp;quot;

  acts_as_importable
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Acts as importable will let you import your legacy models in different ways:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Import a single question
Legacy::Question.import(123)
# or
Legacy::Question.find(123).import

# Import all questions
Legacy::Question.import_all

# Import all the questions, 1000 at a time
Legacy::Question.import_all_in_batches
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These are all different ways of instantiating your legacy models, calling the &lt;code&gt;#to_model&lt;/code&gt; method, and saving the returned object. The value of the plugin is that it adds a little bit of extra smarts to this basic premise.&lt;/p&gt;
&lt;h3&gt;belongs_to Relationships &amp;amp; Caching&lt;/h3&gt;
&lt;p&gt;Acts as Importable provides a &lt;code&gt;#lookup&lt;/code&gt; class method for finding the new ID of an imported legacy model. When each legacy model is imported using the above methods, Acts as Importable saves the legacy model’s class name and ID along with the rest of the data in new model (be sure to provide &lt;code&gt;legacy_class&lt;/code&gt; and &lt;code&gt;legacy_id&lt;/code&gt; columns for this to work). The first time you &lt;code&gt;lookup&lt;/code&gt; a legacy record, it uses &lt;code&gt;ActiveRecord::Base#find&lt;/code&gt; with the appropriate values for &lt;code&gt;legacy_class&lt;/code&gt; and &lt;code&gt;legacy_id&lt;/code&gt; as conditions. It will save the ID in a lookup hash in memory, so that the next time you want to lookup from the same ID, the result is returned without a trip to the database.&lt;/p&gt;
&lt;p&gt;This is really best seen in action:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# New models
class Question &amp;lt; ActiveRecord::Base
  belongs_to :category
  has_many :responses, :dependent =&amp;gt; :destroy
end
class Category &amp;lt; ActiveRecord::Base
  has_many :questions
end

# Legacy model
class Legacy::Question &amp;lt; Legacy::Base
  def to_model
    ::Question.new do |q|
      # import the category association
      q.category_id = Legacy::Category.lookup(self.category.try(:id__))
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Setting the id for associations directly like this is important for conserving trips to the database importing a model’s &lt;code&gt;belongs_to&lt;/code&gt; relationships, just like the one above. Be wary when using lookups for large data sets, however, as it will likely consume quite a bit of memory to store the ID mappings.&lt;/p&gt;
&lt;p&gt;As an aside, we use &lt;code&gt;#try(:id__)&lt;/code&gt; on the legacy model to provide the ID for the lookup, because &lt;code&gt;#id&lt;/code&gt; on a nil value actually returns its &lt;code&gt;#object_id&lt;/code&gt;. If the associated object doesn’t exist, we don’t want to pass a bogus ID. Acts as Importable provides &lt;code&gt;Object#try&lt;/code&gt; and the &lt;code&gt;ActiveRecord::Base#id__&lt;/code&gt; to &lt;code&gt;id&lt;/code&gt; alias for you.&lt;/p&gt;
&lt;p&gt;The lookups will work automatically if the class name of the model you’re importing is the same as the legacy model’s, eg. &lt;code&gt;Legacy::Question&lt;/code&gt; to &lt;code&gt;Question&lt;/code&gt;. If the class name of the model you’re creating is different, you can tell that to Acts as Importable so that the lookups can continue to work:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# New model
class DifferentThing &amp;lt; ActiveRecord::Base
end

# Legacy model
class Legacy::Thing
  acts_as_importable :to =&amp;gt; 'DifferentThing'

  def to_model
    ::DifferentThing.new
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;has_many Relationships &amp;amp; Building&lt;/h3&gt;
&lt;p&gt;You can import &lt;code&gt;has_many&lt;/code&gt; relationships quite easily, using the &lt;code&gt;#build&lt;/code&gt; method on the association proxy. Here it is in action, expanding on the Question’s &lt;code&gt;#to_model&lt;/code&gt; method from above:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# New models
class Question &amp;lt; ActiveRecord::Base
  belongs_to :category
end
class Response &amp;lt; ActiveRecord::Base
  belongs_to :question
end

# Legacy model
class Legacy::Question &amp;lt; Legacy::Base
  def to_model
    ::Question.new do |q|
      q.category_id = Legacy::Category.lookup(self.category.try(:id__))

      # Build the responses
      (1..5).each do |i|
        q.responses.build(:body =&amp;gt; self.send(:&amp;quot;response_#{i}&amp;quot;))
      end
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you save the new model, the newly built associated models are saved too.&lt;/p&gt;
&lt;h3&gt;Importing Large Sets of Legacy Records&lt;/h3&gt;
&lt;p&gt;We ran into some slowness importing legacy models with large numbers of records, or with other models with large amounts of data in particular fields. To get around this, we use &lt;code&gt;#import_all_in_batches&lt;/code&gt;, which only retrieves 1000 models at a time for processing. This is based on Jamis Buck’s technique for &lt;a href="http://weblog.jamisbuck.org/2007/4/6/faking-cursors-in-activerecord"&gt;faking cursors in ActiveRecord&lt;/a&gt;, and as such, it requires a numeric primary key for the legacy models (you’d normally expect this to be the case, but it isn’t for a few of our legacy tables).&lt;/p&gt;
&lt;h3&gt;Idempotence of Imports&lt;/h3&gt;
&lt;p&gt;As I mentioned in the introduction, the legacy app we’re replacing will remain in use as we incrementally build the new system. We will need to continue to synchronise the legacy data with the new system during this time. We’ll therefore need our import process to be &lt;a href="http://en.wikipedia.org/wiki/Idempotence"&gt;idempotent&lt;/a&gt;, meaning that multiple imports can run and result in the same set of data at the other end. Mostly, this just means that we’ll want to avoid creation of duplicate records in the new database.&lt;/p&gt;
&lt;p&gt;We approach this pretty simply. Each night the old records are deleted and new ones re-imported in their place. You’ll want to pick an approach that best suits your situation.&lt;/p&gt;
&lt;p&gt;There is one complication, however, insofar as the new system is used to create some entirely new content that relates to the imported models. If models are deleted and re-imported from the legacy system every night, their IDs would be different each time. To get around this, we hard-code the ID of certain imported models to the same value as their respective legacy model’s ID. This is very simply done:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Legacy::Question &amp;lt; Legacy::Base
  def to_model
    ::Question.new do |q|
      # Hard-code the ID
      q.id = self.id

      q.category_id = Legacy::Category.lookup(self.category.try(:id__))

      (1..5).each do |i|
        q.responses.build(:body =&amp;gt; self.send(:&amp;quot;response_#{i}&amp;quot;))
      end
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Final steps&lt;/h2&gt;
&lt;p&gt;There are two final pieces to our import system. Firstly, a way to control the order of the imports:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Legacy::Importer
  def self.run
    Legacy::Category.import_all
    Legacy::Question.import_all_in_batches

    # Flush all the lookup tables
    Legacy::Category.flush_lookups!
    Legacy::Question.flush_lookups!
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is important to control the order if your imported models are going to relate to each other. Some records will need to exist before the others can link to them.&lt;/p&gt;
&lt;p&gt;And finally, a rake task:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;namespace :legacy do
  desc &amp;quot;Import the legacy data.&amp;quot;
  task :import =&amp;gt; :environment do
    Legacy::Importer.run
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s it! This approach certainly works to our liking, but I would love to hear your thoughts on this issue. Please feel free to post a comment below.&lt;/p&gt;
&lt;h2&gt;Further Reading&lt;/h2&gt;
&lt;p&gt;These articles were of great use in getting our legacy imports up and running:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://pragdave.blogs.pragprog.com/pragdave/2006/01/sharing_externa.html"&gt;Sharing External ActiveRecord Connections&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.pathf.com/blogs/2008/03/using-activerec/"&gt;Using ActiveRecord to Migrate Legacy Data&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Adaptive script/console Shell Alias for both Rails and Sinatra</title>
    <link rel="alternate" href="https://timriley.info/writing/2009/03/06/adaptive-script-console-shell-alias-for-both-rails-and-sinatra"/>
    <id>https://timriley.info/writing/2009/03/06/adaptive-script-console-shell-alias-for-both-rails-and-sinatra</id>
    <published>2009-03-06T05:25:00+00:00</published>
    <updated>2009-03-06T05:25:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Like many keystroke-efficient Rails hackers, I’ve long had a line in my &lt;code&gt;.bash_profile&lt;/code&gt; file to alias &lt;code&gt;sc&lt;/code&gt; to &lt;code&gt;script/console&lt;/code&gt;, along with a &lt;a href="http://gist.github.com/74761"&gt;host of other tricks&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This shortcut was more than sufficient until recently, when I started writing Sinatra apps. The minimal framework that it is, Sinatra doesn’t provide a console script like Rails, but I found you can easily achieve the same effect by running &lt;code&gt;irb -r your_sinatra_app.rb&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Not wanting my fingers to have to deviate from habits long held, I changed my &lt;code&gt;sc&lt;/code&gt; alias into a full-blown bash function that will drop you into a Rails console, Sinatra console or just a plain irb console based on your location within the filesystem:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function sc {
  if [-x script/console]; then
    script/console
  else
    sinatra_rb=`egrep -l &amp;quot;^require.+sinatra.$&amp;quot; *.rb 2&amp;gt;/dev/null`
    if [-e $sinatra_rb]; then
      irb -r $sinatra_rb
    else
      irb
    fi
  fi
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Throw it in your &lt;code&gt;.bash_profile&lt;/code&gt; and have fun!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Cucumber steps for testing page URLs and redirects</title>
    <link rel="alternate" href="https://timriley.info/writing/2009/03/04/cucumber-steps-for-testing-page-urls-and-redirects"/>
    <id>https://timriley.info/writing/2009/03/04/cucumber-steps-for-testing-page-urls-and-redirects</id>
    <published>2009-03-03T23:35:00+00:00</published>
    <updated>2009-03-03T23:35:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;With the new project at &lt;a href="http://www.amc.org.au/"&gt;work&lt;/a&gt;, we’re making sure we follow the best practices we know. This means building the app in weekly iterations according to user stories that we review and schedule with the business owners. It also means being strict about writing tests first, and so far we’re doing pretty well.&lt;/p&gt;
&lt;p&gt;We’re using the shiny new &lt;a href="http://github.com/aslakhellesoy/cucumber"&gt;Cucumber&lt;/a&gt; as much as possible for our high-level integration testing. Along with Cucumber’s &lt;a href="http://github.com/aslakhellesoy/cucumber/blob/6feabc03c3ffd5e7c8b5d0fa82225d712f48d564/rails_generators/cucumber/templates/webrat_steps.rb"&gt;webrat steps&lt;/a&gt;, it is easy to write features that, among other things, can request pages, fill in forms, and check for text on the returned pages. When we add in Ian White’s &lt;a href="http://github.com/ianwhite/pickle"&gt;pickle&lt;/a&gt;, we also have &lt;a href="http://github.com/ianwhite/pickle/blob/2c7cd1bc81bf3762f754b602414f907a1c35ea2a/rails_generators/pickle/templates/paths.rb"&gt;integration&lt;/a&gt; with Rails’ named routes to keep your features readable and steps DRY.&lt;/p&gt;
&lt;p&gt;We often use this one webrat step for visiting a page:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;When /^I go to (.+)$/ do |page_name|
  visit path_to(page_name)
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the only step that webrat provides relating to pages and paths. We found that we needed a couple more:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Then /^I should be on the (.+?) page$/ do |page_name|
  request.request_uri.should == send(&amp;quot;#{page_name.downcase.gsub(' ','_')}_path&amp;quot;)
  response.should be_success
end

Then /^I should be redirected to the (.+?) page$/ do |page_name|
  request.headers['HTTP_REFERER'].should_not be_nil
  request.headers['HTTP_REFERER'].should_not == request.request_uri

  Then &amp;quot;I should be on the #{page_name} page&amp;quot;
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With these steps, we can properly check the URL of the page we’ve been returned, and whether or not we have been redirected during that request cycle. This was important for us to have the fullest coverage possible for our authorization features:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Feature: Users cannot access to the system without logging in
  In order to protect the system from unauthorized access
  An anonymous user
  Should not have access to the system

  Scenario: Visiting the login page
    Given an anonymous user
    When I go to the new login page
    Then I should be on the new login page

  Scenario: Redirecting to login page
    Given an anonymous user
    When I go to the home page
    Then I should be redirected to the new login page
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first scenario ensures that we don’t end up in a redirect loop for an anonymous user directly visiting the login page, and the second scenario ensures that an anonymous user accessing any page in the system is appropriately blocked from access and redirected to the login page.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Business Cards and Hacker Groups</title>
    <link rel="alternate" href="https://timriley.info/writing/2009/02/28/business-cards-and-hacker-groups"/>
    <id>https://timriley.info/writing/2009/02/28/business-cards-and-hacker-groups</id>
    <published>2009-02-27T23:00:00+00:00</published>
    <updated>2009-02-27T23:00:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;I’m very happy to announce that this year I’ll be attending my first software development conference, &lt;a href="http://en.oreilly.com/rails2009/"&gt;RailsConf 2009&lt;/a&gt;, thanks to the generous sponsorship of my employers, the &lt;a href="http://www.amc.org.au/"&gt;Australian Medical Council&lt;/a&gt;. This fantastic opportunity has brought forward some thought on what I had already planned for this year: to make some business cards.&lt;/p&gt;
&lt;p&gt;I like the idea of having my key contact details ready on a single card to hand to someone new if we’ve started a conversation that we’d like to continue later. I can see the utility particularly in situations where I meet people in a business context or in opportunities for discussing freelance work. It means we can talk as much as possible about whatever we like without needing to worry about the clumsy manual transmission of contact information.&lt;/p&gt;
&lt;p&gt;[caption id=”” align=“alignnone” width=“500.0”] &lt;img src="/assets/6e03efed6a37-D9DA8E7B.jpg" alt="Image from cjc." /&gt; Image from cjc.[/caption]&lt;/p&gt;
&lt;p&gt;I’m not so sure about the value of business cards in the context of this conference and a big group of hackers. I am keenly looking forward to the chance to meet many new people at RailsConf, but I do not know how to most easily preserve contact with them. For example, when I meet people at &lt;a href="http://www.rubyonrails.com.au/"&gt;RORO&lt;/a&gt; events, I typically ask them if they are on the twitter, then get out my iPhone and jot their name down into the notes app. I can foresee this becoming difficult to scale to larger groups.&lt;/p&gt;
&lt;p&gt;I’d like to ask your advice. Would it be useful to have my twitter/web contacts down on a card for giving away in these same situations in the future? Is this acceptable in this community? Or should I just take things as they come: not worry about remembering the details of everyone I meet, accept that they will take much the same approach, and throw away the idea of business cards for these situations? How do &lt;em&gt;you&lt;/em&gt; use business cards?&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Handy Shell Alias for Restarting your Rack Apps</title>
    <link rel="alternate" href="https://timriley.info/writing/2009/02/25/handy-shell-alias-for-restarting-your-rack-apps"/>
    <id>https://timriley.info/writing/2009/02/25/handy-shell-alias-for-restarting-your-rack-apps</id>
    <published>2009-02-25T06:50:00+00:00</published>
    <updated>2009-02-25T06:50:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;pre&gt;&lt;code&gt;alias r='touch tmp/restart.txt'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Most useful if you’re using the &lt;a href="http://modrails.com/"&gt;Phusion Passenger&lt;/a&gt; and its &lt;a href="http://www.fngtps.com/passenger-preference-pane"&gt;Preference Pane&lt;/a&gt; to serve your apps through Apache on OS X.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Installing Gitosis on Dreamhost</title>
    <link rel="alternate" href="https://timriley.info/writing/2009/02/25/installing-gitosis-on-dreamhost"/>
    <id>https://timriley.info/writing/2009/02/25/installing-gitosis-on-dreamhost</id>
    <published>2009-02-25T01:00:00+00:00</published>
    <updated>2009-02-25T01:00:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Hugh’s &lt;a href="http://hughevans.net/2009/02/22/sinatra-on-dreamhost"&gt;forays back into shared hosting&lt;/a&gt; over the last week have reminded me that I could make better use of the cheap &lt;a href="http://dreamhost.com/"&gt;Dreamhost&lt;/a&gt; account that I have had languishing for the last few years. I am going to migrate the few things I have running on my more expensive Slicehost slice. Quick way to save a bit of money each month, particularly with the equally languishing Australian Dollar.&lt;/p&gt;
&lt;p&gt;&lt;img src="/assets/61a73383d000-16A37E81.jpg" alt="Dreamhost stickers" /&gt;&lt;/p&gt;
&lt;p&gt;First of these is Gitosis, which is a very handy way to securely store and share your private Git repositories. Since most accounts on Dreamhost are on shared host, you’ll need to install it inside your home directory. Turns out this is quite simply done, and Marco Borromeo &lt;a href="http://blog.marcoborromeo.com/how-to-install-gitosis-on-a-dreamhost-shared-account"&gt;provides a good tutorial&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I had to take an additional couple of steps in order to get things to work with the Python 2.4 installation on my particular host, so I have provided a complete list here. This should hopefully work on any of the Dreamhost servers. Please leave a note in the comments if you have to do anything differently.&lt;/p&gt;
&lt;p&gt;The first step you’ll need to do is create a dedicated user account for the gitosis installation. I also chose to create a dedicated subdomain at the same time. Then, all you’ll need to do is follow these steps:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 1. Create dirs for unpacking your source code and installing your apps
mkdir $HOME/src
mkdir $HOME/apps

# 2. Install the latest version of git
cd $HOME/src
wget http://kernel.org/pub/software/scm/git/git-1.6.1.3.tar.gz
tar zxvf git-1.6.1.3.tar.gz
cd git-1.6.1.3
./configure --prefix=$HOME/apps NO_MMAP=1
make &amp;amp;&amp;amp; make install

# 3. Create dir for local python modules
mkdir -p $HOME/apps/lib/python2.4/site-packages
export PYTHONPATH=$HOME/apps/lib/python2.4/site-packages

# 4. Install setuptools python module
cd $HOME/src
wget http://peak.telecommunity.com/dist/ez_setup.py
python2.4 ez_setup.py --prefix=$HOME/apps

# 5. Install gitosis
cd $HOME/src
git clone git://eagain.net/gitosis.git
cd gitosis
python2.4 setup.py install --prefix=$HOME/apps

# 6. Add new paths to shell environment
echo 'export PATH=$HOME/apps/bin:$PATH' &amp;gt;&amp;gt; $HOME/.bashrc
echo 'export PATH=$HOME/apps/bin:$PATH' &amp;gt;&amp;gt; $HOME/.bash_profile
echo 'export PYTHONPATH=$HOME/apps/lib/python2.4/site-packages' &amp;gt;&amp;gt; $HOME/.bashrc
echo 'export PYTHONPATH=$HOME/apps/lib/python2.4/site-packages' &amp;gt;&amp;gt; $HOME/.bash_profile
. ~/.bash_profile

# 7. Paste your public SSH key into a temporary file on your server. I'll assume it to be '$HOME/id_rsa.pub'

# 8. Initialise gitosis with your public key
gitosis-init &amp;lt; $HOME/id_rsa.pub
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once you’re done with these, follow the second half of &lt;a href="http://vafer.org/blog/20080115011413"&gt;this excellent gitosis introduction&lt;/a&gt; to get started hosting your repositories on Dreamhost!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Image courtesy of &lt;a href="http://www.flickr.com/photos/guder/924253586/"&gt;Patrick Havens&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Cleaner Sinatra integration with Compass 0.4</title>
    <link rel="alternate" href="https://timriley.info/writing/2009/02/15/cleaner-sinatra-integration-with-compass-04"/>
    <id>https://timriley.info/writing/2009/02/15/cleaner-sinatra-integration-with-compass-04</id>
    <published>2009-02-15T12:55:00+00:00</published>
    <updated>2009-02-15T12:55:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;&lt;a href="http://acts-as-architect.blogspot.com/"&gt;Chris Eppstein&lt;/a&gt; has been working hard on &lt;a href="http://compass-style.org/"&gt;Compass&lt;/a&gt; lately to improve its integration with application frameworks. This gives me the pleasure of updating the code from my &lt;a href="http://log.openmonkey.com/post/73462983/using-compass-for-css-in-your-sinatra-application"&gt;earlier post about integrating Sinatra and Compass&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gem 'chriseppstein-compass', '~&amp;gt; 0.4'
require 'compass'

configure do
  Compass.configuration do |config|
    config.project_path = File.dirname( __FILE__ )
    config.sass_dir = File.join('views', 'stylesheets')
  end
end

get &amp;quot;/stylesheets/screen.css&amp;quot; do
  content_type 'text/css'

  # Use views/stylesheets &amp;amp; blueprint's stylesheet dirs in the Sass load path
  sass :&amp;quot;stylesheets/screen&amp;quot;, :sass =&amp;gt; Compass.sass_engine_options
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above is everything you need for your &lt;a href="http://sinatrarb.com/"&gt;Sinatra&lt;/a&gt; app to use Compass 0.4 to render your CSS. The biggest change in 0.4 is that Compass now comes with a configuration singleton. I set it up above in Sinatra’s &lt;code&gt;configure&lt;/code&gt; block and tell it that I keep my Sass stylesheets in &lt;code&gt;views/stylesheets&lt;/code&gt; inside the application directory. Keeping the Sass configuration separate from the working application code keeps your render calls short and concise, like the rest of your well-crafted Sinatra app.&lt;/p&gt;
&lt;p&gt;Thanks to Chris for his hard work and for providing the example code for the Sinatra integration.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>A Cycle Helper for Sinatra</title>
    <link rel="alternate" href="https://timriley.info/writing/2009/02/13/a-cycle-helper-for-sinatra"/>
    <id>https://timriley.info/writing/2009/02/13/a-cycle-helper-for-sinatra</id>
    <published>2009-02-12T22:20:00+00:00</published>
    <updated>2009-02-12T22:20:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;&lt;a href="http://sinatrarb.com/"&gt;Sinatra’s&lt;/a&gt; minimalist tack encourages you to build just the number of helpers that is required for your app. In doing so, it’s also a chance to improve your Ruby fu. While the source for the helpers that come with Rails provides an excellent starting point for your particular subset, they’re often built to keep all comers happy. You can do something a lot slimmer for your narrowly focused Sinatra app.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.flickr.com/photos/ickypoo/510063218/"&gt;&lt;img src="/assets/2e7cfa9d7295-F9EC3C71.jpg" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here’s &lt;a href="http://github.com/timriley/unfuddle-helpdesk/blob/46ed4c40f7a217a3bd465c9f7783f065e4462d01/unfuddle_helpdesk.rb#L43"&gt;what I came up with&lt;/a&gt; for a cycle helper to alternately colour table rows via cycling their CSS classes:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Uppublished_at:&lt;/strong&gt; check the end of this post for some improved solutions!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;helpers do
  def cycle
    @_cycle ||= reset_cycle
    @_cycle = [@_cycle.pop] + @_cycle
    @_cycle.first
  end

  def reset_cycle
    @_cycle = %w(even odd)
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For reference, &lt;a href="http://github.com/rails/rails/blob/ff3fb6c5f3b2a0592189545f6f24ef759df6a12e/actionpack/lib/action_view/helpers/text_helper.rb#L379"&gt;this is the source&lt;/a&gt; for Rails’ equivalent set of helpers, comprising about one hundred or so lines of code. Now, here are my &lt;a href="http://github.com/timriley/unfuddle-helpdesk/blob/46ed4c40f7a217a3bd465c9f7783f065e4462d01/views/ticket_report.haml#L35"&gt;helpers in use&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;%table.tickets{:cellpadding =&amp;gt; 0, :cellspacing =&amp;gt; 0}
  %thead
    %tr
      %th No.
      %th Summary
      %th Reporter
      %th Assignee
      %th Updated
  %tbody
    - reset_cycle
    - group.tickets.each do |ticket|
      %tr{:class =&amp;gt; &amp;quot;#{ticket.out_of_bounds? ? 'out-of-bounds' : 'unassigned'} #{cycle}&amp;quot;}
        = partial('ticket_row', :locals =&amp;gt; {:ticket =&amp;gt; ticket})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nothing like writing your own helpers for a nice sense of achievement! And now a question for you? Can you think of a neater way to cycle through a series of strings? Would love to hear your feedback.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Uppublished_at:&lt;/strong&gt; Oceanic Ruby guru &lt;a href="http://smartbomb.com.au/"&gt;Lachie Cox&lt;/a&gt; has forked my helpers and provided some smarter versions. Thanks Lachie!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;helpers do
  def cycle
    %w{even odd}[@_cycle = ((@_cycle || -1) + 1) % 2]
  end

  CYCLE = %w{even odd}
  def cycle_fully_sick
    CYCLE[@_cycle = ((@_cycle || -1) + 1) % 2]
  end
end
&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <title>Caching and Expring Stylesheets and Javascripts in Sinatra</title>
    <link rel="alternate" href="https://timriley.info/writing/2009/02/09/caching-and-expring-stylesheets-and-javascripts-in-sinatra"/>
    <id>https://timriley.info/writing/2009/02/09/caching-and-expring-stylesheets-and-javascripts-in-sinatra</id>
    <published>2009-02-08T21:30:00+00:00</published>
    <updated>2009-02-08T21:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;&lt;em&gt;The code examples in this article were extracted from &lt;a href="http://toolmantim.com/"&gt;Tim Lucas&lt;/a&gt;’s &lt;a href="http://github.com/toolmantim/toolmantim/"&gt;toolmantim.rb&lt;/a&gt;, a weblog app that inspired me to start playing with Sinatra. Thanks to Tim for his good work!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;One of the most rewarding things I’ve found while playing with &lt;a href="http://www.sinatrarb.com/"&gt;Sinatra&lt;/a&gt; so far it encourages me to learn more about the implementation of the underlying mechanics of a modern web application. This is a framework that doesn’t coddle you: the things that you get for free in &lt;a href="http://rubyonrails.com/"&gt;Rails&lt;/a&gt; and the other larger web frameworks are nowhere to be seen. What’s left is lean &amp;amp; mean, and ready to be shaped into whatever form you fancy!&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.flickr.com/photos/tartanna/52648670/"&gt;&lt;img src="/assets/54b06666e12f-A6048C01.jpg" alt="Expiry stamp" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So let’s talk about stylesheets and javascript files. These are assets that don’t change as often as the pages in your web app. Your javascript files are more than likely to be served directly from the filesystem. Your stylesheets may also be static files, but Sinatra also provides excellent support for generating CSS from Sass templates.&lt;/p&gt;
&lt;p&gt;If you’re using Sass, you’ll no doubt have something like this in your Sinatra app:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;get &amp;quot;/stylesheets/screen.css&amp;quot; do
  content_type 'text/css'
  sass :&amp;quot;stylesheets/screen&amp;quot;
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When someone requests the &lt;code&gt;/stylesheets/screen.css&lt;/code&gt; file, the above action is run and the generated CSS is sent back. You won’t want the very same thing happening for the next page request. You can fix this by setting the ‘Expiry’ header in the responses you send to stylesheet requests:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;get &amp;quot;/stylesheets/screen.css&amp;quot; do
  content_type 'text/css'
  response['Expires'] = (Time.now + 60*60*24*356*3).httpdate
  sass :&amp;quot;stylesheets/screen&amp;quot;
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Setting the ‘&lt;a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.21"&gt;Expires&lt;/a&gt;’ header in the above action will encourage client proxies or browsers to cache your generated stylesheet.&lt;/p&gt;
&lt;p&gt;This is all pretty great, but this technique still leaves a couple of gaps. Any static files (like javascripts) are not served through Sinatra, so we can’t manually set an Expires header for them. Further, for the stylesheets that Sinatra &lt;em&gt;does&lt;/em&gt; generate, we’ll need a way to force the clients to refresh them whenever we modify their source Sass files. We can address these gaps with a couple of Sinatra helpers:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;helpers do
  def versioned_stylesheet(stylesheet)
    &amp;quot;/stylesheets/#{stylesheet}.css?&amp;quot; + File.mtime(File.join(Sinatra::Application.views, &amp;quot;stylesheets&amp;quot;, &amp;quot;#{stylesheet}.sass&amp;quot;)).to_i.to_s
  end
  def versioned_javascript(js)
    &amp;quot;/javascripts/#{js}.js?&amp;quot; + File.mtime(File.join(Sinatra::Application.public, &amp;quot;javascripts&amp;quot;, &amp;quot;#{js}.js&amp;quot;)).to_i.to_s
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use these helpers to load your javascripts and stylesheets in the appropriate places in your layout file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;!!! Strict
%html{:xmlns =&amp;gt;'http://www.w3.org/1999/xhtml', 'xml:lang' =&amp;gt; 'en', :lang =&amp;gt; 'en'}
  %head
    %meta{'http-equiv' =&amp;gt; 'Content-Type', :content =&amp;gt; 'text/html; charset=utf-8'}/
    %title My Sinatra App

    %link{:href =&amp;gt; versioned_stylesheet('screen'), :media =&amp;gt; 'screen', :rel =&amp;gt; 'stylesheet', :type =&amp;gt; 'text/css'}/
    %script{:src =&amp;gt; versioned_javascript('application'), :type =&amp;gt; 'text/javascript'}/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These helpers will generate links with a timestamp appended, like so: &lt;code&gt;stylesheets/screen.css?1233119990&lt;/code&gt;. This timestamp is derived from the file modification time of the source Sass files for your stylesheets (and in the case of javascripts, the static javascript files themselves), which means that a new timestamp will appear in your page source automatically after you make modifications. This will trigger the clients to request the newest stylesheets, which will in turn stay cached until the next change and minimise the total download for each request to your Sinatra application.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Truism</title>
    <link rel="alternate" href="https://timriley.info/writing/2009/02/05/truism"/>
    <id>https://timriley.info/writing/2009/02/05/truism</id>
    <published>2009-02-05T04:45:00+00:00</published>
    <updated>2009-02-05T04:45:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;&lt;a href="http://designaday.tumblr.com/post/75496791/truism"&gt;designaday&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Broken gets fixed. Shoddy lasts forever”&lt;/p&gt;
&lt;p&gt;One of the developers I work with said this after I complained about a lingering issue in one of our products. It rings true. When deadlines are tight, and there is more work to get done than there are developers or hours in the schedule, it’s not the squeaky wheel, but the jammed one that gets the grease. The lesson, then, is to make sure it gets done right the first time. You never know when you’ll have the opportunity to revisit it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is all too true. I’m especially mindful of this given that I’m just now embarking on a fresh rewrite of something that was particularly broken. We’re making sure the customer understands that we’re taking the time necessary to do things right the first time (or in this case, the second time).&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Using Compass for CSS in your Sinatra application</title>
    <link rel="alternate" href="https://timriley.info/writing/2009/01/27/using-compass-for-css-in-your-sinatra-application"/>
    <id>https://timriley.info/writing/2009/01/27/using-compass-for-css-in-your-sinatra-application</id>
    <published>2009-01-27T11:30:00+00:00</published>
    <updated>2009-01-27T11:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Stage left: &lt;a href="http://sinatra.github.com/"&gt;Sinatra&lt;/a&gt;, the hottest new Ruby DSL for expressive, singular web application development. Stage right: &lt;a href="http://compass-style.org/"&gt;Compass&lt;/a&gt;, the CSS &lt;em&gt;metaframework&lt;/em&gt; that takes the ease of use of &lt;a href="http://www.blueprintcss.org/"&gt;Blueprint&lt;/a&gt;’s grids and &lt;a href="http://haml.hamptoncatlin.com/"&gt;Sass&lt;/a&gt;’ syntax, and lets you combine them in a manner semantic &amp;amp; modular, just how you like it.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.flickr.com/photos/damonabnormal/2228863689/"&gt;&lt;img src="/assets/44bfc49ebf79-A49A7E7A.jpg" alt="Sinatra graffiti" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Until now, these actors could never meet. With the release of the 0.9 series of Sinatra, however, it has become a &lt;em&gt;breeze&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The key change occurred with the restructure of Sinatra’s rendering logic. When you call &lt;code&gt;sass&lt;/code&gt; to render a Sass stylesheet, you can now pass options that go directly to the Sass engine. See this excerpt from &lt;code&gt;lib/sinatra/base.rb&lt;/code&gt; in the codebase:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def sass(template, options={}, &amp;amp;block)
  require 'sass' unless defined? ::Sass
  options[:layout] = false
  render :sass, template, options
end

def render_sass(template, data, options, &amp;amp;block)
  engine = ::Sass::Engine.new(data, options[:sass] || {})
  engine.render
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See that &lt;code&gt;options[:sass]&lt;/code&gt; hash going to &lt;code&gt;::Sass::Engine.new&lt;/code&gt;? That’s how we’ll get Compass to work. All it needs are some extra load paths to be passed to &lt;code&gt;Sass::Engine&lt;/code&gt;, which you can do when you render your Sass stylesheet. Compass also conveniently provides a configuration singleton to let you do this just once in the &lt;code&gt;configure&lt;/code&gt; block of your application:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gem 'chriseppstein-compass', '~&amp;gt; 0.4'
require 'compass'

configure do
  Compass.configuration do |config|
    config.project_path = File.dirname( __FILE__ )
    config.sass_dir = File.join('views', 'stylesheets')
  end
end

get &amp;quot;/stylesheets/screen.css&amp;quot; do
  content_type 'text/css'

  # Use views/stylesheets &amp;amp; blueprint's stylesheet dirs in the Sass load path
  sass :&amp;quot;stylesheets/screen&amp;quot;, :sass =&amp;gt; Compass.sass_engine_options
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This stylesheet action at the bottom of this example will parse your views/stylesheets/screen.sass file and your Compass inclusions and mixins will output exactly the CSS that you expect. All done!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update (2009-02-15):&lt;/strong&gt; The code above was updated for the improved Sass configuration support in Compass 0.4. See &lt;a href="http://log.openmonkey.com/post/78482055/cleaner-sinatra-integration-with-compass-0-4"&gt;my post on the 0.4 release&lt;/a&gt; for an explanation of the improvements. Before this release, your stylesheet actions would have looked a lot more ungainly:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;get &amp;quot;/stylesheets/screen.css&amp;quot; do
  content_type 'text/css'

  # Use views/stylesheets &amp;amp; blueprint's stylesheet dirs in the Sass load path
  sass :&amp;quot;stylesheets/screen&amp;quot;, { :sass =&amp;gt; { :load_paths =&amp;gt; (
    [File.join(File.dirname( __FILE__ ), 'views', 'stylesheets')] +
    Compass::Frameworks::ALL.map { |f| f.stylesheets_directory })
  } }
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Of course, you won’t want Sass loading up all the Compass files and re-rendering your CSS with every page request, so you will want to cache this generated CSS file. This is another handy Sinatra trick I’ve come across, but I’ll save that for the next entry.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Accessing Basecamp's writeboards programatically with Ruby</title>
    <link rel="alternate" href="https://timriley.info/writing/2009/01/08/accessing-basecamps-writeboards-programatically-with-ruby"/>
    <id>https://timriley.info/writing/2009/01/08/accessing-basecamps-writeboards-programatically-with-ruby</id>
    <published>2009-01-08T13:25:00+00:00</published>
    <updated>2009-01-08T13:25:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;&lt;a href="http://basecamphq.com/"&gt;Basecamp’s&lt;/a&gt; writeboards provide a great facility for versioned, collaborative document editing. However, any user of Basecamp will be able to tell you that they don’t fit in as neatly as the other components of the system. Going to a writeboard takes you to an interim “loading” page before displaying the writeboard outside the regular interface of Basecamp.&lt;/p&gt;
&lt;p&gt;To the developer, the difference between writeboards and the rest of Basecamp is highlighted further because writeboards are the only part of the app &lt;a href="http://developer.37signals.com/basecamp/"&gt;without an API&lt;/a&gt;. The enterprising Ruby developer, however, sees this as no barrier! So without further ado, I bring you &lt;a href="http://github.com/timriley/writeboard-rb/"&gt;writeboard-rb&lt;/a&gt;, a little Ruby class that allows you to access the contents of a writeboard programatically:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wb = Basecamp::Writeboard.new(
  :url =&amp;gt; 'http://bigcorp.updatelog.com/W1234567',
  :username =&amp;gt; 'user',
  :password =&amp;gt; 'pass',
  :cookie_jar =&amp;gt; '/tmp/foo',
  :use_ssl =&amp;gt; true
)

puts wb.contents
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Behind the scenes, no fewer than &lt;em&gt;five&lt;/em&gt; calls to the curl command line utility are made, along with a wee bit of Hpricot screen scraping to facilitate a form post otherwise made with JavaScript during the normal writeboard loading page. Using curl is necessary here because we need to preserve session cookies for &lt;em&gt;both&lt;/em&gt; the Basecamp and Writeboard domains during the process of fetching the writeboard’s contents.&lt;/p&gt;
&lt;p&gt;Anyway, please go &lt;a href="http://github.com/timriley/writeboard-rb/"&gt;check out the code on github&lt;/a&gt;, and feel free to fork it to make any improvements or extensions! I hope it can come in handy for you. It was a fun little challenge to take it this far.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Capistrano task to selectively update crontabs</title>
    <link rel="alternate" href="https://timriley.info/writing/2008/11/25/capistrano-task-to-selectively-update-crontabs"/>
    <id>https://timriley.info/writing/2008/11/25/capistrano-task-to-selectively-update-crontabs</id>
    <published>2008-11-25T02:25:00+00:00</published>
    <updated>2008-11-25T02:25:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;In a lot of our Rails projects, we need to throw a few entries into a user’s crontab, for things like scheduled rake tasks or thinking sphinx updates.&lt;/p&gt;
&lt;p&gt;However, we don’t want to overwrite the entire crontab file whenever we want to add new entries or change the details of the existing ones.&lt;/p&gt;
&lt;p&gt;Thus, I’ve devised a little technique to allow selective updates of the crontab. Take this demo and apply it where necessary in your capistrano deploy.rb file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;namespace :my_rake_task do
  task :add_cron_job, :roles =&amp;gt; :app, :except =&amp;gt; { :no_release =&amp;gt; true } do
    tmpname = &amp;quot;/tmp/appname-crontab.#{Time.now.strftime('%s')}&amp;quot;
    # run crontab -l or echo '' instead because the crontab command will fail if the user has no pre-existing crontab file.
    # in this case, echo '' is run and the cap recipe won't fail altogether.
    run &amp;quot;(crontab -l || echo '') | grep -v 'rake my_rake_task' &amp;gt; #{tmpname}&amp;quot;
    run &amp;quot;echo '12 1,11,18 * * * cd #{current_path} &amp;amp;&amp;amp; RAILS_ENV=production rake my_rake_task' &amp;gt;&amp;gt; #{tmpname}&amp;quot;
    run &amp;quot;crontab #{tmpname}&amp;quot;
    run &amp;quot;rm #{tmpname}&amp;quot;
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To step you through it, this task will dump the current crontab to a file, excluding the command that you care about, then add the command back to the bottom of the file, and install the crontab again, with all the other contents preserved. This will allow you to change the particular entry without clobbering the entire file. Nifty!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Using Markov Chains to provide English language seed data for your Rails application</title>
    <link rel="alternate" href="https://timriley.info/writing/2008/10/23/using-markov-chains-to-provide-english-language-seed-data-for-your-rails-application"/>
    <id>https://timriley.info/writing/2008/10/23/using-markov-chains-to-provide-english-language-seed-data-for-your-rails-application</id>
    <published>2008-10-22T14:10:00+00:00</published>
    <updated>2008-10-22T14:10:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;I spent a portion of last weekend’s Rails Rumble preparing a script that would seed our application’s database with test data. Among other things, having a well populated database is useful to fully test all the parts of the application’s interface that might not come into play when using a smaller data set. It also gives you the ability to get a true sense of how your app will look and feel after the real users start to pour in content (hopefully!).&lt;/p&gt;
&lt;p&gt;For the Rumble project, I used the &lt;a href="http://faker.rubyforge.org/"&gt;faker&lt;/a&gt; Ruby gem, which provides methods for generating realistic names, domains and email addresses. It also provides a text generator that pulls random strings from Lorem Ipsum. However, while the app may feel have felt fully populated, seeing the Latin everywhere didn’t make it feel &lt;em&gt;right&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;So for the next project, I will generate random English language strings for the seed data. Rather than pulling words ad hoc from a dictionary or something, we can use &lt;a href="http://en.wikipedia.org/wiki/Markov_chain"&gt;Markov Chains&lt;/a&gt; to generate reasonably realistically structured text for us. A bit of searching revealed a &lt;a href="http://rubyquiz.com/quiz74.html"&gt;Ruby Quiz page&lt;/a&gt; with an implementation of a text generator using Markov Chains. It explains that the generator:&lt;/p&gt;
&lt;p&gt;bq. read[s] some text document(s), making note of which characters commonly follow which characters or which words commonly follow other words (it works for either scale). Then, when generating text, you just select a character or word to output, based on the characters or words that came before it.&lt;/p&gt;
&lt;p&gt;I took implementation provided on this page, and added a couple of convenience methods to easily fetch sequences of words or whole sentences:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Courtesy of http://rubyquiz.com/quiz74.html

class MarkovChain
  def initialize(text)
    @words = Hash.new
    wordlist = text.split
    wordlist.each_with_index do |word, index|
    add(word, wordlist[index + 1]) if index &amp;lt;= wordlist.size - 2
   end
  end

  def add(word, next_word)
    @words[word] = Hash.new(0) if !@words[word]
    @words[word][next_word] += 1
  end

  def get(word)
    return &amp;quot;&amp;quot; if !@words[word]
    followers = @words[word]
    sum = followers.inject(0) {|sum,kv| sum += kv[1]}
    random = rand(sum)+1
    partial_sum = 0
    next_word = followers.find do |word, count|
      partial_sum += count
      partial_sum &amp;gt;= random
    end.first
    next_word
  end

  # Convenience methods to easily access words and sentences

  def random_word
    @words.keys.rand
  end

  def words(count = 1, start_word = nil)
    sentence = ''
    word = start_word || random_word
    count.times do
      sentence &amp;lt;&amp;lt; word &amp;lt;&amp;lt; ' '
      word = get(word)
    end
    sentence.strip.gsub(/[^A-Za-z\s]/, '')
  end

  def sentences(count = 1, start_word = nil)
    word = start_word || random_word
    sentences = ''
    until sentences.count('.') == count
      sentences &amp;lt;&amp;lt; word &amp;lt;&amp;lt; ' '
      word = get(word)
    end
    sentences
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, in my seed data script, I prime a MarkovChain instance with some good literature (I chose Sir Arthur Conan Doyle’s &lt;em&gt;&lt;a href="http://gutenberg.org/etext/139"&gt;The Lost World&lt;/a&gt;&lt;/em&gt; from Project Gutenburg), and then use it to populate my records:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mc = MarkovChain.new(File.read(&amp;quot;#{RAILS_ROOT}/db/populate_source.txt&amp;quot;))

# create_or_update method taken from http://railspikes.com/2008/2/1/loading-seed-data

1.upto(100) do |comment_id|
  Comment.create_or_update(
    :id =&amp;gt; comment_id,
    :title =&amp;gt; mc.words(3).titleize,
    :body =&amp;gt; mc.sentences(3)
  )
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Done! While the text isn’t the kind of fluent prose you’d expect from the real-life internet commenters, it does look much more realistic than Lorem Ipsum, and often provides cause for a bit of a chuckle. An example of the kind of stuff you’ll get from The Lost World is below. Feel free to choose your favourite text as the source :)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Luxurious voyage I had a solemnity as one would no sneer would have just after two surviving Indians in a precipice, and were accompanying to his thumb over his shoulders, the effort.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For some background on how to load seed data into your Rails app, see &lt;a href="http://railspikes.com/2008/2/1/loading-seed-data"&gt;this page&lt;/a&gt; on the Rail Spikes blog for a good introduction.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>For the win!</title>
    <link rel="alternate" href="https://timriley.info/writing/2008/10/20/for-the-win"/>
    <id>https://timriley.info/writing/2008/10/20/for-the-win</id>
    <published>2008-10-20T10:20:00+00:00</published>
    <updated>2008-10-20T10:20:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;In the last 48 hours, two friends and colleagues and I laboured over a brand new web app, for our entry in this year’s &lt;a href="http://railsrumble.com/" title="Rails Rumble"&gt;Rails Rumble&lt;/a&gt;. The result is something we’re proud to share with you: &lt;strong&gt;&lt;a href="http://ideasftw.com/" title="Ideas FTW"&gt;Ideas FTW!&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="/assets/a6eea9536481-B4153799.jpg" alt="Ideas FTW!" /&gt;&lt;/p&gt;
&lt;p&gt;We all have plenty of ideas, most of which we never have the opportunity to act on. Ideas FTW is an idea &lt;em&gt;incubator&lt;/em&gt; that allows you to see your ideas come to life. You can vote on ideas, post your own, take up an idea and start an effort to achieve it on your own or in a group.&lt;/p&gt;
&lt;p&gt;The site is simple and colourful, and will only become more fun to use as more ideas get added. So, please do sign up with your &lt;a href="http://openid.net/" title="OpenID"&gt;OpenID&lt;/a&gt; and empty your overflowing brains! I’m looking forward to seeing some interesting ideas fill the site.&lt;/p&gt;
&lt;p&gt;Of course, the app is far from perfect yet. A few bugs slipped through and there are certainly a couple more key features we would like to add. If you have any suggestions for improving Ideas FTW, please feel free to &lt;a href="http://ideasftw.com/efforts/1" title="Ideas FTW effort page"&gt;leave a comment on our effort page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://hughevans.net/" title="Hugh Evans"&gt;Hugh&lt;/a&gt;, &lt;a href="http://starclass.com.au/" title="Michael MacDonald"&gt;Michael&lt;/a&gt; and I all had a great time working together to create this site. We learnt a great many new things over the weekend and I am sure that I can speak for the others to say the lack of sleep was worth it. If you enjoy the site, please feel free to &lt;a href="http://railsrumble.com/teams/all-caps" title="Rails Rumble team page"&gt;vote for us&lt;/a&gt; when the tallies open in a couple of days.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Configuring god to monitor Sphinx's searchd</title>
    <link rel="alternate" href="https://timriley.info/writing/2008/09/10/configuring-god-to-monitor-sphinxs-searchd"/>
    <id>https://timriley.info/writing/2008/09/10/configuring-god-to-monitor-sphinxs-searchd</id>
    <published>2008-09-10T12:55:00+00:00</published>
    <updated>2008-09-10T12:55:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;&lt;a href="http://god.rubyforge.org/"&gt;God&lt;/a&gt; is a server monitoring tool helpful for starting your server processes and keeping them running, all the while making sure they don’t misbehave. In the Rails world, it appears to be used most commonly with packs of mongrels or thins. However, this is not to say it can’t be used to monitor other software. For one of my recent work projects, we’ve been using God to monitor the searchd process that &lt;a href="http://sphinxsearch.com/"&gt;Sphinx&lt;/a&gt; uses to serve results to search queries.&lt;/p&gt;
&lt;p&gt;The configuration required for this can be based mostly on what you see around the place for thins or mongrels:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;require 'yaml'

app_config = YAML.load(File.open(File.dirname( __FILE__ ) + &amp;quot;/config.yml&amp;quot;))['production']

God.watch do |w|
  w.group = &amp;quot;#{app_config['app_name']}-sphinx&amp;quot;
  w.name = w.group + &amp;quot;-1&amp;quot;

  w.interval = 30.seconds

  w.uid = app_config['user']
  w.gid = app_config['group']

  w.start = &amp;quot;searchd --config #{app_config['app_root']}/config/sphinx.conf&amp;quot;
  w.start_grace = 10.seconds
  w.stop = &amp;quot;searchd --config #{app_config['app_root']}/config/sphinx.conf --stop&amp;quot;
  w.stop_grace = 10.seconds
  w.restart = w.stop + &amp;quot; &amp;amp;&amp;amp; &amp;quot; + w.start
  w.restart_grace = 15.seconds

  w.pid_file = File.join(app_config['app_root'], &amp;quot;tmp/pids/sphinx.pid&amp;quot;)

  w.behavior(:clean_pid_file)

  w.start_if do |start|
    start.condition(:process_running) do |c|
      c.interval = 5.seconds
      c.running = false
    end
  end

  w.restart_if do |restart|
    restart.condition(:memory_usage) do |c|
      c.above = 100.megabytes
      c.times = [3, 5] # 3 out of 5 intervals
    end
  end

  w.lifecycle do |on|
    on.condition(:flapping) do |c|
      c.to_state = [:start, :restart]
      c.times = 5
      c.within = 5.minutes
      c.transition = :unmonitored
      c.retry_in = 10.minutes
      c.retry_times = 5
      c.retry_within = 2.hours
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are a number of other changes required for this configuration to work properly. Firstly, you will require a &lt;code&gt;config/config.yml&lt;/code&gt; file, containing something like the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;development: &amp;amp;production_settings
  app_name: myapp
  user: deployer
  group: deployer
  app_root: &amp;quot;/home/deployer/deployments/myapp.amc.org.au/current&amp;quot;
test:
  &amp;lt;&amp;lt; *production_settings
production:
  &amp;lt;&amp;lt; *production_settings
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This config file sets the app’s short name, the user and group to run the searchd process, and the path to the rails application root. We keep these settings in a yaml file because they are also used in a number of other places besides here. If you don’t have this requirement, you can put all of these settings directly into your god config file.&lt;/p&gt;
&lt;p&gt;The other requirement is that we make Sphinx use a configuration file that has a predictable name. The default Sphinx config file, when using &lt;a href="http://freelancing-gods.com/"&gt;Pat Allen’s&lt;/a&gt; awesome &lt;a href="http://ts.freelancing-gods.com/"&gt;Thinking Sphinx&lt;/a&gt; plugin, has a file name contains the name of the current Rails environment. God can’t use this to launch searchd, because it is does not run within the context of the Rails environment. To set location and name of the sphinx configuration file, set up a config/sphinx.yml file that contains something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;development: &amp;amp;production_settings
  config_file: /home/deployer/deployments/myapp.amc.org.au/current/config/sphinx.conf
  pid_file: /home/deployer/deployments/myapp.amc.org.au/current/tmp/pids/sphinx.pid
test:
  &amp;lt;&amp;lt; *production_settings
production:
  &amp;lt;&amp;lt; *production_settings
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thinking Sphinx respects these settings and uses them when it generates the Sphinx config file.&lt;/p&gt;
&lt;p&gt;So that’s about it. Make sure you’ve run &lt;code&gt;rake thinking_sphinx:index&lt;/code&gt; at least once in your Rails app, to generate the Sphinx configuration and indexes, and then you are ready to start god and have your searchd automatically started and monitored!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Lessons learnt the hard way: Don't use script/console --sandbox on production apps</title>
    <link rel="alternate" href="https://timriley.info/writing/2008/09/10/dont-use-script-console-sandbox-on-production-apps"/>
    <id>https://timriley.info/writing/2008/09/10/dont-use-script-console-sandbox-on-production-apps</id>
    <published>2008-09-10T02:05:00+00:00</published>
    <updated>2008-09-10T02:05:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Don’t use &lt;code&gt;script/console -s&lt;/code&gt; or &lt;code&gt;script/console --sandbox&lt;/code&gt; on your live, running Rails application. The built-in transactions that roll back on exit are nifty, but if your database’s transactions use any kind of locking, then you will get locks on the tables for any models that you use in the console. This will most likely stop your application from working.&lt;/p&gt;
&lt;p&gt;Moral of the story? If you need to use the console on a live application, just be careful. You’re smart, you’re cautious, you don’t need the sandbox. It will only cause more trouble than it is worth.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Beijing Olympic medal tally for our Campfire bot</title>
    <link rel="alternate" href="https://timriley.info/writing/2008/08/10/beijing-olympic-medal-tally-for-our-campfire-bot"/>
    <id>https://timriley.info/writing/2008/08/10/beijing-olympic-medal-tally-for-our-campfire-bot</id>
    <published>2008-08-10T08:00:00+00:00</published>
    <updated>2008-08-10T08:00:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;I’ve recently been working on a bot framework for 37signals’ &lt;a href="http://campfirenow.com/"&gt;Campfire&lt;/a&gt; web chat system. We use Campfire at work and it’s been great for having fun together and increasing team cohesiveness.&lt;/p&gt;
&lt;p&gt;Having a bot in the room is also good for random quotes, Chuck Norris facts and comic strips. We also plan to add a few useful work-related things, such as git &amp;amp; subversion commit messages and server monitoring alerts.&lt;/p&gt;
&lt;p&gt;The bot is plugin-oriented and easy to extend. Check out the &lt;a href="http://github.com/timriley/campfire-bot/tree/master"&gt;code on github&lt;/a&gt;. It’s still in flux and I would love to hear any feedback.&lt;/p&gt;
&lt;p&gt;This afternoon I hacked together a little plugin to report the Beijing olympics medal tally, thanks to an idea of Sean’s. The code is highly specific, but it shows how easy (and fun!) it is to add a command to the bot and scrape data off the web using hpricot.&lt;/p&gt;
&lt;p&gt;Here it is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;require 'open-uri'
require 'hpricot'

class BeijingTally &amp;lt; PluginBase

  on_command 'tally', :tally

  def tally(msg)
    output = &amp;quot;#{'Pos.'.rjust(6)} - #{'Country'.ljust(25)} - G - S - B - Total\n&amp;quot;
    rows = ((Hpricot(open('http://results.beijing2008.cn/WRM/ENG/INF/GL/95A/GL0000000.shtml'))/'//table')[1]/'tr')[2..-1]
    rows.each_with_index do |row, i|
      cells = row/'td'
      output += &amp;quot;#{strip_tags_or_zero(cells[0].inner_html).rjust(6)} - &amp;quot; # position
      output += &amp;quot;#{((i == rows.length - 1) ? '' : strip_tags_or_zero(cells[1].inner_html)).ljust(25)} - &amp;quot; # country
      output += &amp;quot;#{strip_tags_or_zero(cells[-5].inner_html).rjust(3)} - &amp;quot; # gold
      output += &amp;quot;#{strip_tags_or_zero(cells[-4].inner_html).rjust(3)} - &amp;quot; # silver
      output += &amp;quot;#{strip_tags_or_zero(cells[-3].inner_html).rjust(3)} - &amp;quot; # bronze
      output += &amp;quot;#{strip_tags_or_zero(cells[-2].inner_html).rjust(3)}\n&amp;quot; # total
    end

    paste(output)
  end

  private

  # Take away the HTML tags from the string and insert a '0' if it is empty
  def strip_tags_or_zero(str)
    (out = str.gsub(/&amp;lt;\/?[^&amp;gt;]*&amp;gt;/, &amp;quot;&amp;quot;).strip).blank? ? '0' : out
  end
end
&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <title>Mum's Savoury Mince Pockets</title>
    <link rel="alternate" href="https://timriley.info/writing/2008/06/28/mums-savoury-mince-pockets"/>
    <id>https://timriley.info/writing/2008/06/28/mums-savoury-mince-pockets</id>
    <published>2008-06-28T04:00:00+00:00</published>
    <updated>2008-06-28T04:00:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;These are easy to make and taste excellent, and are also good cold or reheated the next day! Thanks, Mum.&lt;/p&gt;
&lt;p&gt;Ingredients:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1 kg good (beef) mince&lt;/li&gt;
&lt;li&gt;2 carrots, peeled and diced&lt;/li&gt;
&lt;li&gt;1 onion, diced&lt;/li&gt;
&lt;li&gt;1 tsp garlic&lt;/li&gt;
&lt;li&gt;2 tsp curry powder&lt;/li&gt;
&lt;li&gt;1 cup frozen peas and corn&lt;/li&gt;
&lt;li&gt;Tomato sauce&lt;/li&gt;
&lt;li&gt;Sweet chilli sauce&lt;/li&gt;
&lt;li&gt;2 cups cooked rice&lt;/li&gt;
&lt;li&gt;Lebanese bread, around 3-4 pieces&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Fry the mince with the garlic and curry powder.&lt;/li&gt;
&lt;li&gt;Once the mince is getting close to done, add the peas and corn.&lt;/li&gt;
&lt;li&gt;Add tomato sauce and sweet chilli sauce to taste.&lt;/li&gt;
&lt;li&gt;Add the cooked rice and mix it all up.&lt;/li&gt;
&lt;li&gt;Cut the lebanese bread in half to form pockets and fill with the mince and rice mixture.&lt;/li&gt;
&lt;li&gt;Put the filled pockets in an oven at 180°C for 5-10 minutes, or until crisp.&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  <entry>
    <title>Displaying both local and HTTP remote images in Prince XML generated PDFs</title>
    <link rel="alternate" href="https://timriley.info/writing/2008/06/27/displaying-both-local-and-http-remote-images-in-prince-xml-generated-pdfs"/>
    <id>https://timriley.info/writing/2008/06/27/displaying-both-local-and-http-remote-images-in-prince-xml-generated-pdfs</id>
    <published>2008-06-27T04:30:00+00:00</published>
    <updated>2008-06-27T04:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;In our Rails apps, we use the awesome &lt;a href="http://princexml.com/"&gt;Prince XML&lt;/a&gt; to generate PDFs. We interact with the prince command line application using the &lt;a href="http://sublog.subimage.com/articles/2007/05/29/html-css-to-pdf-using-ruby-on-rails"&gt;Ruby library and Rails helper&lt;/a&gt; from the guys over at subimage interactive.&lt;/p&gt;
&lt;p&gt;When using their helper to generate a PDF from a Rails template, all image tags have the src attribute altered so they point to paths that are relative to the local filesystem, not just the root of your application.&lt;/p&gt;
&lt;p&gt;However, this breaks any images that you are loading from remote locations over HTTP. For us, this ended up breaking the &lt;a href="http://code.google.com/apis/maps/documentation/staticmaps/"&gt;static Google Maps&lt;/a&gt; that we were generating.&lt;/p&gt;
&lt;p&gt;So here’s an updated make_pdf helper that only modifies the image paths if they are local. This lets us use both local and HTTP-hosted images on the same PDF!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# We use this chunk of controller code all over to generate PDF files.
#
# To stay DRY we placed it here instead of repeating it all over the place.
#
module PdfHelper
  require 'prince'

  private
    # Makes a pdf, returns it as data...
    def make_pdf(template_path, pdf_name, landscape=false)
      prince = Prince.new()
      # Sets style sheets on PDF renderer.
      prince.add_style_sheets(
        &amp;quot;#{RAILS_ROOT}/public/stylesheets/application.css&amp;quot;,
        &amp;quot;#{RAILS_ROOT}/public/stylesheets/print.css&amp;quot;,
        &amp;quot;#{RAILS_ROOT}/public/stylesheets/prince.css&amp;quot;
      )
      prince.add_style_sheets(&amp;quot;#{RAILS_ROOT}/public/stylesheets/prince_landscape.css&amp;quot;) if landscape
      # Render the estimate to a big html string.
      # Set RAILS_ASSET_ID to blank string or rails appends some time after
      # to prevent file caching, and messing up local-disk requests.
      ENV[&amp;quot;RAILS_ASSET_ID&amp;quot;] = ''
      html_string = render_to_string(:template =&amp;gt; template_path, :layout =&amp;gt; 'document')
      # Make all paths relative to the file systemm, but only if they don't have http(s):// at the start.
      html_string.gsub!(%r{(src=&amp;quot;)([^h][^t][^t][^p][^s]?[^:][^/]*)}, &amp;quot;src=\&amp;quot;#{RAILS_ROOT}/public\\2&amp;quot;)
      # Send the generated PDF file from our html string.
      return prince.pdf_from_string(html_string)
    end

    # Makes and sends a pdf to the browser
    #
    def make_and_send_pdf(template_path, pdf_name, landscape=false)
      send_data(
        make_pdf(template_path, pdf_name, landscape),
        :filename =&amp;gt; pdf_name,
        :type =&amp;gt; 'application/pdf'
      )
    end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And just to be precise, here is the diff between the helpers:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;--- pdf_helper.rb 2008-06-27 15:05:44.000000000 +1000
+++ new_pdf_helper.rb 2008-06-27 15:05:54.000000000 +1000
@@ -18,11 +18,11 @@
       prince.add_style_sheets(&amp;quot;#{RAILS_ROOT}/public/stylesheets/prince_landscape.css&amp;quot;) if landscape
       # Render the estimate to a big html string.
       # Set RAILS_ASSET_ID to blank string or rails appends some time after
- # to prevent file caching, fucking up local - disk requests.
+ # to prevent file caching, and messing up local-disk requests.
       ENV[&amp;quot;RAILS_ASSET_ID&amp;quot;] = ''
       html_string = render_to_string(:template =&amp;gt; template_path, :layout =&amp;gt; 'document')
- # Make all paths relative, on disk paths...
- html_string.gsub!(&amp;quot;src=\&amp;quot;&amp;quot;, &amp;quot;src=\&amp;quot;#{RAILS_ROOT}/public&amp;quot;)
+ # Make all paths relative to the file systemm, but only if they don't have http(s):// at the start.
+ html_string.gsub!(%r{(src=&amp;quot;)([^h][^t][^t][^p][^s]?[^:][^/]*)}, &amp;quot;src=\&amp;quot;#{RAILS_ROOT}/public\\2&amp;quot;)
       # Send the generated PDF file from our html string.
       return prince.pdf_from_string(html_string)
     end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I’d also love it if you could propose a better way to handle the regular expression inside the gsub! call. Leave a comment!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Loading the ActiveRecord SQL Server adapter in a Rails 2.1 app</title>
    <link rel="alternate" href="https://timriley.info/writing/2008/06/18/loading-the-activerecord-sql-server-adapter-in-a-rails-21-app"/>
    <id>https://timriley.info/writing/2008/06/18/loading-the-activerecord-sql-server-adapter-in-a-rails-21-app</id>
    <published>2008-06-18T03:30:00+00:00</published>
    <updated>2008-06-18T03:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;It’s pretty simple. In your config/environment.rb:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;config.gem 'activerecord-sqlserver-adapter', :source =&amp;gt; 'http://gems.rubyonrails.org', :lib =&amp;gt; 'active_record/connection_adapters/sqlserver_adapter'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then run rake gems:install and away you go.&lt;/p&gt;
&lt;p&gt;Hugh has &lt;a href="http://hughevans.net/2008/05/25/rails-ubuntu-odbc"&gt;some more tips&lt;/a&gt; about the packages and configuration required to connect to an SQL Server from a Linux platform.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Enabling a non-interactive install of Blackdown's j2re1.4 on Ubuntu or Debian</title>
    <link rel="alternate" href="https://timriley.info/writing/2008/06/12/enabling-a-non-interactive-install-of-blackdowns-j2re14-on-ubuntu-or-debian"/>
    <id>https://timriley.info/writing/2008/06/12/enabling-a-non-interactive-install-of-blackdowns-j2re14-on-ubuntu-or-debian</id>
    <published>2008-06-12T05:45:00+00:00</published>
    <updated>2008-06-12T05:45:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;When you &lt;code&gt;apt-get install&lt;/code&gt; the &lt;code&gt;j2re1.4&lt;/code&gt; Java package in Debian or Ubuntu, it displays a few ncurses-style dialogs to configure the software and to require your acceptance of the license agreement. Working with these dialogs is fine if you are installing the software with apt-get in a typical interactive shell session. If you are installing without this capacity to interact (like in a script), you will run into problems. Here’s how to fix it.&lt;/p&gt;
&lt;p&gt;If you install the package with the noninteractive frontend for dpkg, then you’ll get an error like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# DEBIAN_FRONTEND=noninteractive /usr/bin/apt-get --yes --force-yes install j2re1.4

Reading package lists... Done
Building dependency tree
Reading state information... Done
Suggested packages:
  mozilla-browser mozilla-firefox galeon ttf-kochi-gothic ttf-kochi-mincho
Recommended packages:
  gsfonts-x11 libx11-6 libxext6 libxi6
The following NEW packages will be installed:
  j2re1.4
0 upgraded, 1 newly installed, 0 to remove and 23 not upgraded.
Need to get 0B/22.5MB of archives.
After unpacking 60.3MB of additional disk space will be used.
Preconfiguring packages ...
j2re1.4 failed to preconfigure, with exit status 10
Selecting previously deselected package j2re1.4.
(Reading database ... 26639 files and directories currently installed.)
Unpacking j2re1.4 (from .../j2re1.4_1.4.2.02-1ubuntu3_i386.deb) ...
dpkg: error processing /var/cache/apt/archives/j2re1.4_1.4.2.02-1ubuntu3_i386.deb (--unpack):
 subprocess pre-installation script returned error exit status 10
Errors were encountered while processing:
 /var/cache/apt/archives/j2re1.4_1.4.2.02-1ubuntu3_i386.deb
E: Sub-process /usr/bin/dpkg returned an error code (1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The package fails to install because it requires the acceptance of the licence agreement, which it will only allow in an interactive installation.&lt;/p&gt;
&lt;p&gt;The typical way to fix this is to pre-seed the debconf database (using @debconf-set-selections@) with the answers to the questions that the j2re1.4 package requires. However, this doesn’t seem to satisfy j2re1.4, and the package still displays the dialog or fails in non-interactive mode. Why is it java that always gives me these hairy problems?&lt;/p&gt;
&lt;p&gt;Anyway, here is the way to fix it. If you manually append the values &lt;em&gt;directly&lt;/em&gt; to the debconf database file, it will work:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cp /var/cache/debconf/config.dat /var/cache/debconf/config.dat-old

cat &amp;lt;&amp;lt; E_O_DEBCONF &amp;gt;&amp;gt; /var/cache/debconf/config.dat

Name: j2re1.4/jcepolicy
Template: j2re1.4/jcepolicy
Value:
Owners: j2re1.4
Flags: seen

Name: j2re1.4/license
Template: j2re1.4/license
Value: true
Owners: j2re1.4
Flags: seen

Name: j2re1.4/stopthread
Template: j2re1.4/stopthread
Value: true
Owners: j2re1.4
Flags: seen

E_O_DEBCONF
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;(For reference, I found these values by making a copy of the config.dat file, running &lt;code&gt;dpkg-preconfigure&lt;/code&gt; on the j2re1.4 package, and then running a diff between the updated config.dat file and my copy.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Now you’ll be able to successfully install the package in noninteractive mode. This means, for us, one step closer to fully automating our Xen builds!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Automatic Saving Of Invalid Resources in Rails While Maintaining a Clean RESTful Interface</title>
    <link rel="alternate" href="https://timriley.info/writing/2008/06/09/automatic-saving-of-invalid-resources-in-rails-while-maintaining-a-clean-restful-interface"/>
    <id>https://timriley.info/writing/2008/06/09/automatic-saving-of-invalid-resources-in-rails-while-maintaining-a-clean-restful-interface</id>
    <published>2008-06-09T13:35:00+00:00</published>
    <updated>2008-06-09T13:35:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;or, &lt;strong&gt;How To Change Your World In One Line Of Code&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;One of the cool things that we’re doing at the AMC is building a large collection of loosely coupled Rails applications that communicate using REST. This is slightly unusual, as rails is predominantly used to build single apps that operate in isolation. In our experiences, we’ve picked up a number of tricks that we’d like to share. Here’s the first, on how a single this single line of code has saved us weeks of time and effort. Here’s the line in question:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if @bank_transaction.save &amp;amp;&amp;amp; @bank_transaction.valid?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read on to find out how this helps!&lt;/p&gt;
&lt;h2&gt;RESTful Resources&lt;/h2&gt;
&lt;p&gt;This line is part of a new Rails app we’re developing to centrally handle all online payments for our software systems at the AMC. In this payments application, we expose two key resources over REST: (1) line items, which the other apps create with the details of the items to purchase, and (2) bank transactions, which are passed line item IDs and credit card details for the purchase.&lt;/p&gt;
&lt;p&gt;Naturally, the models behind these resources have a bunch of validation rules that ensure certain conditions are met before they can be saved successfully. If any of these requirements are not met, then the model fails to save and the error hash is returned to the client app.&lt;/p&gt;
&lt;p&gt;For most resources, these error hashes are returned in the usual Rails-like way. Let’s look at how &lt;code&gt;LineItemsController&lt;/code&gt; does it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class LineItemsController &amp;lt; ApplicationController
  # POST /line_items.xml
  def create
    @line_item = LineItem.new params[:line_item]
    respond_to do |format|
      if @line_item.save
        format.xml { render :xml =&amp;gt; @line_item, :status =&amp;gt; :created, :location =&amp;gt; @line_item }
      else
        format.xml { render :xml =&amp;gt; @line_item.errors, :status =&amp;gt; :unprocessable_entity }
      end
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To paraphrase: if the items save successfully, return success and the line item in XML, otherwise return the error hash. Nice and predictable, nothing exciting here.&lt;/p&gt;
&lt;h2&gt;Saving Invalid Resources&lt;/h2&gt;
&lt;p&gt;One of the resources in the payments app is different. These are the @BankTransactions@, which are about as “mission critical” as we get. Let’s talk about the successful case first: during the creation of the &lt;code&gt;BankTransaction&lt;/code&gt; model, if all the validations have passed, a before_create callback is triggered that will talk to the bank (using ActiveMerchant, of course) and ask to make the transaction there. If this succeeds, the model is saved to the database and an XML representation of the saved model is passed back to the client application with a success code.&lt;/p&gt;
&lt;p&gt;However, if the transaction with the bank fails, this is still information we care about. A failed transaction could be an indication of a larger problem, and also needs to be recorded for customer service purposes. It makes sense to save every failed transaction as well as every successful one. To this end, the callback that communicates with the bank always returns true, which allows the save continue, and records for both successful and unsuccessful transactions to be kept in the database.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(Another approach to this problem would be to create a separate @TransactionLog@ model to store the transaction data, but this approach requires extra work. Having the &lt;code&gt;BankTransactions&lt;/code&gt; save every time is essentially free. Excellent.)&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Keeping the REST API Simple&lt;/h2&gt;
&lt;p&gt;While the payments app is saving unsuccessful transactions, the client apps do not want to keep these records around: all they care about is if a transaction is successful or not. The easiest way to make it simple for the clients is to make the creation of a bank transaction resource behave the same way as creating any other resource over REST in rails. This means that if a transaction with the bank fails, then it should &lt;em&gt;appear&lt;/em&gt; to the clients as if the save also failed.&lt;/p&gt;
&lt;p&gt;This will require the controller to generate an errors hash if the transaction fails. This means that the model should be invalid at this point. Given that we save to the database even for failed transactions, the model should therefore be invalid after the save:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class BankTransaction &amp;lt; ActiveRecord::Base
  validate :must_be_successful_if_saved
  before_create :transact

  private

  def transact
    # talk to the bank here, and set self.success to true or false pending the results
    # return true to make sure a save always occurs
    true
  end

  def must_be_successful_if_saved
    errors.add_to_base(&amp;quot;failed to transact successfully with the bank&amp;quot;) if !new_record? &amp;amp;&amp;amp; !success?
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then, in the &lt;code&gt;BankTransactionsController&lt;/code&gt;, that one magic line:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class BankTransactionsController &amp;lt; ApplicationController
  # POST /bank_transactions.xml
  def create
    @bank_transaction = BankTransaction.new params[:bank_transaction]
    respond_to do |format|
      if @bank_transaction.save &amp;amp;&amp;amp; @bank_transaction.valid?
        format.xml { render :xml =&amp;gt; @bank_transaction, :status =&amp;gt; :created, :location =&amp;gt; @bank_transaction }
      else
        format.xml { render :xml =&amp;gt; @bank_transaction.errors, :status =&amp;gt; :unprocessable_entity }
      end
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unlike the standard behaviour shown in the &lt;code&gt;LineItemsController&lt;/code&gt; above, we only return a successfully created model if the save is successful AND it is still valid afterwards. A saved model for a failed transaction with the bank will be invalid at this point, so it will return the errors hash. To the client app, this looks like the same resource it tried to create initially, and so it can proceed as usual to display the errors, ask for corrections if necessary, and try to save again. In the background, the payments app has saved every failed transaction for safe keeping.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>God init script for Debian/Ubuntu systems</title>
    <link rel="alternate" href="https://timriley.info/writing/2008/05/27/god-init-script-for-debianubuntu-systems"/>
    <id>https://timriley.info/writing/2008/05/27/god-init-script-for-debianubuntu-systems</id>
    <published>2008-05-26T14:45:00+00:00</published>
    <updated>2008-05-26T14:45:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;To compliment the Red Hat &lt;a href="http://pastie.caboo.se/110483"&gt;init script&lt;/a&gt; that is available on the net, here’s the init script we use to launch &lt;a href="http://god.rubyforge.org/"&gt;god&lt;/a&gt; on our Ubuntu systems.&lt;/p&gt;
&lt;p&gt;To use, save as &lt;code&gt;/etc/init.d/god&lt;/code&gt;, make the file executable, then run &lt;code&gt;update-rc.d god defaults&lt;/code&gt;. You’re done.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/sh

### BEGIN INIT INFO
# Provides: god
# Required-Start: $all
# Required-Stop: $all
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: God
### END INIT INFO

NAME=god
DESC=god

set -e

# Make sure the binary and the config file are present before proceeding
test -x /usr/bin/god || exit 0

# Create this file and put in a variable called GOD_CONFIG, pointing to
# your God configuration file
test -f /etc/default/god &amp;amp;&amp;amp; . /etc/default/god
[$GOD_CONFIG] || exit 0

. /lib/lsb/init-functions

RETVAL=0

case &amp;quot;$1&amp;quot; in
  start)
    echo -n &amp;quot;Starting $DESC: &amp;quot;
    /usr/bin/god -c $GOD_CONFIG -P /var/run/god.pid -l /var/log/god.log
    RETVAL=$?
    echo &amp;quot;$NAME.&amp;quot;
    ;;
  stop)
    echo -n &amp;quot;Stopping $DESC: &amp;quot;
    kill `cat /var/run/god.pid`
    RETVAL=$?
    echo &amp;quot;$NAME.&amp;quot;
    ;;
  restart)
    echo -n &amp;quot;Restarting $DESC: &amp;quot;
    kill `cat /var/run/god.pid`
    /usr/bin/god -c $GOD_CONFIG -P /var/run/god.pid -l /var/log/god.log
    RETVAL=$?
    echo &amp;quot;$NAME.&amp;quot;
    ;;
  status)
    /usr/bin/god status
    RETVAL=$?
    ;;
  *)
    echo &amp;quot;Usage: god {start|stop|restart|status}&amp;quot;
    exit 1
    ;;
esac

exit $RETVAL
&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <title>Testing for elements in ActiveRecord's XML output</title>
    <link rel="alternate" href="https://timriley.info/writing/2008/04/18/testing-for-elements-in-activerecords-xml-output"/>
    <id>https://timriley.info/writing/2008/04/18/testing-for-elements-in-activerecords-xml-output</id>
    <published>2008-04-18T03:25:00+00:00</published>
    <updated>2008-04-18T03:25:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Following on from my previous post about &lt;a href="http://log.openmonkey.com/post/31406090"&gt;customising ActiveRecord’s to_xml output&lt;/a&gt;, I have had to write specs to make sure a custom attribute I have added to a model’s XML serialisation actually appears as expected.&lt;/p&gt;
&lt;p&gt;ActiveSupport’s Hash.from_xml class method makes this a piece of cake. Instead of testing against the XML as a string or parsing it manually, you can turn it into a hash and get directly to the attribute you want. Behold:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;describe Product do
  before(:each) do
    @product = Product.new
  end

  # net_price is the custom attribute I have added to the XML serialisation
  it &amp;quot;should include net price in XML serialisation&amp;quot; do
    @product.attributes = valid_product_attributes
    Hash.from_xml(@product.to_xml)['product']['net_price'].should == @product.net_price
  end
end
&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <title>Activating the screensaver with Quicksilver in OS X</title>
    <link rel="alternate" href="https://timriley.info/writing/2008/04/12/activating-the-screensaver-with-quicksilver-in-os-x"/>
    <id>https://timriley.info/writing/2008/04/12/activating-the-screensaver-with-quicksilver-in-os-x</id>
    <published>2008-04-11T14:45:00+00:00</published>
    <updated>2008-04-11T14:45:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;There are a &lt;a href="http://www.rousette.org.uk/blog/archives/quicksilver-activate-screensaver-snippet/"&gt;few&lt;/a&gt; &lt;a href="http://leafraker.com/2007/09/14/start-the-screen-saver-with-quicksilver/"&gt;posts&lt;/a&gt; around the net that describe some ways to launch the OS X screensaver using a keyboard shortcut with &lt;a href="http://www.blacktree.com/"&gt;Quicksilver&lt;/a&gt;. However, common to all of these approaches is using a code snippet that is launched by a trigger.&lt;/p&gt;
&lt;p&gt;Quicksilver triggers can only be launched by global keybindings or mouse gestures. That is, you can’t access them through the main quicksilver interface. A simple way around this is to use Automator to create a custom screensaver launch application:&lt;/p&gt;
&lt;p&gt;&lt;img src="/assets/cc0da2d4473f-0A90392F.jpg" alt="Automator workflow for screensaver launch app" /&gt;&lt;/p&gt;
&lt;p&gt;Save the workflow as an app called, let’s say, “Screensaver”, put it in your Applications folder, and you can then use Quicksilver to activate your screensaver like you would launch any ordinary app.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Setting default arguments for to_xml for your ActiveRecord model</title>
    <link rel="alternate" href="https://timriley.info/writing/2008/04/11/setting-default-arguments-for-toxml-for-your-activerecord-model"/>
    <id>https://timriley.info/writing/2008/04/11/setting-default-arguments-for-toxml-for-your-activerecord-model</id>
    <published>2008-04-11T02:45:00+00:00</published>
    <updated>2008-04-11T02:45:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Rails provides you with a lot of &lt;a href="http://api.rubyonrails.com/classes/ActiveRecord/Serialization.html#M001137"&gt;scope for customising&lt;/a&gt; the XML serialisation of models with to_xml. Among other things, you can exclude attributes, include objects that are first-level associations, and include the results of any custom methods for your model.&lt;/p&gt;
&lt;p&gt;However, most of the examples for these only show this customisation taking place in the scope of the controller, where the arguments are passed to a single call of to_xml. This is not very DRY if you want to customise the default to_xml output for your model to include or exclude some information.&lt;/p&gt;
&lt;p&gt;Customising the default behaviour is pretty easy. Say I have an extra method inside a model that combines price and tax to formulate a net price, and I want this to be included in the XML serialisation every time. Here is what to do:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Product &amp;lt; ActiveRecord::Base
  def net_price
    self.price + self.tax
  end

  alias_method :ar_to_xml, :to_xml

  def to_xml(options = {}, &amp;amp;block)
    default_options = { :methods =&amp;gt; [:net_price]}
    self.ar_to_xml(default_options.merge(options), &amp;amp;block)
  end
end
&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <title>New Job at the Australian Medical Council</title>
    <link rel="alternate" href="https://timriley.info/writing/2008/03/27/new-job-australian-medical-council"/>
    <id>https://timriley.info/writing/2008/03/27/new-job-australian-medical-council</id>
    <published>2008-03-27T11:40:00+00:00</published>
    <updated>2008-03-27T11:40:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Today I began working in a new Rails development role at the &lt;a href="http://amc.org.au/"&gt;Australian Medical Council&lt;/a&gt;. This is the second job I have taken since I have moved to Canberra, and I am looking forward to digging in my heels in here.&lt;/p&gt;
&lt;p&gt;Shortly after arriving here, I took up a web systems administration position at the &lt;a href="http://www.dbcde.gov.au/"&gt;Department of Broadband, Communications and the Digital Economy&lt;/a&gt;. This was a good opportunity to see the inner working of government, and I enjoyed working with a fun and motivated team.&lt;/p&gt;
&lt;p&gt;It’s refreshing to be back into a hands-on programming role at the AMC. Along with my friend &lt;a href="http://hughevans.net/"&gt;Hugh&lt;/a&gt;, we’ll be introducing some new, agile development practices while moving most of the organisation’s VB-based internal systems to the web with Rails. It’s going to be a lot of fun and I sense that it will be a good opportunity for growth and learning. I’ll be sure to pass along anything useful that I come across. Watch this space!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Mocking out your Rails helpers in helper specs</title>
    <link rel="alternate" href="https://timriley.info/writing/2008/03/19/mocking-out-your-rails-helpers-in-helper-specs"/>
    <id>https://timriley.info/writing/2008/03/19/mocking-out-your-rails-helpers-in-helper-specs</id>
    <published>2008-03-19T10:35:00+00:00</published>
    <updated>2008-03-19T10:35:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;&lt;a href="http://rspec.info/"&gt;RSpec&lt;/a&gt; provides &lt;a href="http://rspec.info/documentation/mocks/"&gt;some pretty good tools&lt;/a&gt; for mocking your objects in Rails test specs.&lt;/p&gt;
&lt;p&gt;Mocks allow you to set expectations on what kind of messages are passed to your objects, and what values they pass back in return. Among other things, this brings the advantage that your test/example can be truly focused on one unit of code, because the data that the mocks (or stubs) return does not rely on the implementation of the objects or methods that they imitate.&lt;/p&gt;
&lt;p&gt;In most cases, mocking with RSpec in your Rails tests is pretty straightforward - you just create the mock object and set your expectations of it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# mock_model is a convenience provided by rspec_on_rails
my_mock = mock_model(User)
my_mock.should_receive(:authenticate).with('user', 'pass').and_return(true)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alternatively, you can create partial mocks or stubs on real objects or classes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# partial stub for a real object
u = User.new
u.stub!(:name).and_return('Phred')

# partial mock for a class method
User.should_receive(:find).and_return(u)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In all of these cases, there is a clear recipient for the mocking or stubbing treatment - a mock object, or real object, or a class. This is what you will mostly use in your model and controller specs.&lt;/p&gt;
&lt;p&gt;However, if you are in the specs for your helpers, it’s not self evident what the recipient should be. Here’s an example for a helpers file with two helpers, one calling the other:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module ApplicationHelper
  def user_list(users)
    users.each { |u| user_summary(u) }
  end

  def user_summary(user)
    open :div, {:class =&amp;gt; 'user_summary'} do
      open :p, user.name
      open :p, user.birthday
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These helper methods all belong inside a module, and are not attached to any object or class like the ActiveRecord models in the above examples. So how do we mock a helper? Check it out:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;require File.dirname( __FILE__ ) + '/../../spec_helper'

describe ApplicationHelper, &amp;quot;user printing helpers&amp;quot; do
  include ActionView::Helpers
  include Haml::Helpers

  it &amp;quot;should call the user_summary helper for each user object in the array passed to user_list&amp;quot; do
    user = mock_model(User) do |u|
      u.stub!(:name).and_return('Phred')
      u.stub!(:birthday).and_return('April')
    end
    users = []

    5.times do
      users &amp;lt;&amp;lt; user
    end

    # This is the clincher!
    self.should_receive(:user_summary).exactly(5).times

    user_list(users)
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In your helper specs, the &lt;code&gt;self&lt;/code&gt; object is what you can use to mock and stub your helpers. It’s that simple. What is &lt;code&gt;self&lt;/code&gt;? I’m not really quite sure. It looks like a dynamically created subclass of &lt;code&gt;Spec::Rails::Example::HelperExampleGroup&lt;/code&gt; that is particular to the spec example currently being run. Whatever it is, it gets the job done for us.&lt;/p&gt;
&lt;p&gt;If you also need to mock or stub your helpers in view specs, you can use the &lt;code&gt;@controller.template&lt;/code&gt; object:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@controller.template.stub!(:a_helper_method).and_return(true)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check out &lt;a href="http://jakescruggs.blogspot.com/2007/03/mockingstubbing-partials-and-helper.html"&gt;Jack Scrugg’s article&lt;/a&gt; for more detail about this.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>jQuery datepickers with constrained ranges in Rails forms</title>
    <link rel="alternate" href="https://timriley.info/writing/2008/03/18/jquery-datepickers-with-constrained-ranges-in-rails-forms"/>
    <id>https://timriley.info/writing/2008/03/18/jquery-datepickers-with-constrained-ranges-in-rails-forms</id>
    <published>2008-03-18T11:20:00+00:00</published>
    <updated>2008-03-18T11:20:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;One of the first things I’ve done since joining the AMC is dive into one of the projects currently in development and start fixing small bugs, to familiarise myself with the codebase and the kind of problems they are solving here. It’s also been a good chance to pick up a few things.&lt;/p&gt;
&lt;p&gt;One of these has been &lt;a href="http://jquery.com/"&gt;jQuery&lt;/a&gt;. One of the places we are using it is to provide &lt;a href="http://docs.jquery.com/UI/Datepicker"&gt;datepickers&lt;/a&gt; in a form to create new &lt;em&gt;Exam&lt;/em&gt; records. Here’s the view for the form partial:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;%p
  = f.label :name
  %br/
  = f.text_field :name

%p
  = f.label :exam_date
  %br/
  = f.text_field :exam_date, {:value =&amp;gt; full_date(@exam.exam_date)}

%h2 Registration Period

%p
  = f.label :open_date, 'From: '
  %br/
  = f.text_field :open_date, {:value =&amp;gt; full_date(@exam.open_date)}
  %br/
  = f.label :close_date, 'To: '
  %br/
  = f.text_field :close_date, {:value =&amp;gt; full_date(@exam.close_date)}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Mmm, &lt;a href="http://haml.hamptoncatlin.com/"&gt;haml&lt;/a&gt;. Also note the complete lack of JavaScript inside this view! This is because we are making use of &lt;em&gt;unobtrusive&lt;/em&gt; JavaScript, a technique which separates the JavaScript code from the presentation layer and places importance on allowing the views to degrade gracefully (that is, continue to work fully even when JavaScript support is not available).&lt;/p&gt;
&lt;p&gt;In this way, our JavaScript is kept inside another file: &lt;code&gt;application.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$(document).ready(function() {
  ////
  // ui.datepicker fields
  $('#exam_exam_date').datepicker();
  $('#exam_open_date').datepicker();
  $('#exam_close_date').datepicker();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code initialises the datepickers and attaches them to the text fields with IDs of exam_exam_date, exam_open_date, and exam_close_date. The datepickers will appear when you click or focus any of these fields. They will let you select the a date from an interactive calendar and insert a string representation of that date into the text field.&lt;/p&gt;
&lt;p&gt;All very good, but we have another requirement: that the dates available for selection inside the datepickers are constrained. Specifically, the open and close dates should not be after the exam date, and the close date should not be before the open date. These constraints can be set up by passing a function name to the datepickers when they are initialised:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$(document).ready(function() {
  ////
  // ui.datepicker fields
  $('#exam_exam_date').datepicker();
  $('#exam_open_date').datepicker({beforeShow: customRange});
  $('#exam_close_date').datepicker({beforeShow: customRange});

  function customRange(input) {
    return {
      // 8640000 is the number of milliseconds in a day
      // set the maxDate for registrations to be the day _before_ the exam date, or no limit if there is no exam date yet
      maxDate: $('#exam_exam_date').datepicker('getDate') ? new Date($('#exam_exam_date').datepicker('getDate') - 86400000) : null,
      // set the minDate for registration close to be the day _after_ registration open
      minDate: input.id == 'exam_close_date' ? ($('#exam_open_date').datepicker('getDate') ? new Date(new Date($('#exam_open_date').datepicker('getDate')).getTime() + 86400000) : null) : null
    }
  }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The customRange function above is called every time before the datepickers appear. It sets the maxDate and minDate properties of the datepicker. This allows the constraints for one field to vary depending on the dates the user has chosen for the others.&lt;/p&gt;
&lt;p&gt;When you press save, all of the dates will be sent to the controller and saved to your record. However, if validation fails for some reason and the form is reloaded, and extra step is necessary to make sure the constraints continue to behave properly.&lt;/p&gt;
&lt;p&gt;You see, the datepicker widgets are kind of dumb. If they are attached to a field that already contains a textual representation of a date (such as when your form is reloaded after failed validation), they will not set themselves to that date. You must do this manually. Here it is, in the final incarnation of &lt;code&gt;application.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;////
// Behaviours
$(document).ready(function() {
  ////
  // ui.datepicker fields
  $('#exam_exam_date').datepicker();
  $('#exam_open_date').datepicker({beforeShow: customRange});
  $('#exam_close_date').datepicker({beforeShow: customRange});

  // initialise the date in the datepickers from the text in the input fields
  // this is necessary for the page reload that occurs after a failed validation
  $('#exam_exam_date').datepicker('setDate', new Date($('#exam_exam_date').attr('value')));
  $('#exam_open_date').datepicker('setDate', new Date($('#exam_open_date').attr('value')));
  $('#exam_close_date').datepicker('setDate', new Date($('#exam_close_date').attr('value')));

  function customRange(input) {
    return {
      // 8640000 is the number of milliseconds in a day
      // set the maxDate for registrations to be the day _before_ the exam date, or no limit if there is no exam date yet
      maxDate: $('#exam_exam_date').datepicker('getDate') ? new Date($('#exam_exam_date').datepicker('getDate') - 86400000) : null,
      // set the minDate for registration close to be the day _after_ registration open
      minDate: input.id == 'exam_close_date' ? ($('#exam_open_date').datepicker('getDate') ? new Date(new Date($('#exam_open_date').datepicker('getDate')).getTime() + 86400000) : null) : null
    }
  }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What does this all give you?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;JavaScript code that is separated from your views, keeping everything nice and clean&lt;/li&gt;
&lt;li&gt;Datepickers that will appear when their text fields are focused&lt;/li&gt;
&lt;li&gt;Constraints that prevent the user from selecting dates that are out of bounds&lt;/li&gt;
&lt;li&gt;Properly initialised datepickers after a form reload, such that they will display the correct date for the fields when clicked, and still adhere to the proper constraints&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;All up, this amounts to a pretty solid set of date selection widgets. Thanks, jQuery.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Generating semi-private, obfuscated resource sharing URLs in Rails</title>
    <link rel="alternate" href="https://timriley.info/writing/2008/03/11/generating-semi-private-obfuscated-resource-sharing-urls-in-rails"/>
    <id>https://timriley.info/writing/2008/03/11/generating-semi-private-obfuscated-resource-sharing-urls-in-rails</id>
    <published>2008-03-11T12:25:00+00:00</published>
    <updated>2008-03-11T12:25:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Recently I was working on an app that required semi-private, obfuscated URLs for sharing pages and feeds with non-registered members, much like Backpack does for its page feeds. Specifically, I wanted a URL with a long code in it of some kind that would make it difficult to guess.&lt;/p&gt;
&lt;p&gt;I could not find anything on the net addressing this kind of requirement, much less in a RESTful way, so I rolled up my sleeves and built one independently. Here’s how I did it.&lt;/p&gt;
&lt;p&gt;In this example, I want to share &lt;em&gt;Activity&lt;/em&gt; resources, which I am exposing in the typical way in Rails’ routes.rb:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;map.resources :users do |user|
  user.resources :activities, :path_prefix =&amp;gt; '/:user_id'
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This routes provides access to the 7 standard CRUD actions in the ActivitiesController: index, show, new, create, edit, update, destroy. The only one of these that relates to listing multiple activities is index, but that action is already used to display activities to logged in users. So, a new action is needed, which we shall called “shared”. I will create 2 new named routes to provide access to this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;map.shared_activities '/:user_id/activities/shared/:key',
  :controller =&amp;gt; 'activities', :action =&amp;gt; 'shared', :conditions =&amp;gt; { :method =&amp;gt; :get }

map.formatted_shared_activities '/:user_id/activities/shared/:key.:format',
  :controller =&amp;gt; 'activities', :action =&amp;gt; 'shared', :conditions =&amp;gt; { :method =&amp;gt; :get }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Take note of the &lt;code&gt;:key&lt;/code&gt; component of these paths. This is the private code required to ‘unlock’ the shared activity pages. The key belongs to Sharing objects:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Sharing &amp;lt; ActiveRecord::Base
  belongs_to :user
  validates_uniqueness_of :key
  after_create :create_key

  private

  def create_key
    self.key = Digest::SHA1.hexdigest(Time.now.to_s.split(//).sort_by {rand}.join)
    self.save
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, if a user wants to share his activities, he can create a sharing object, which generates the key for the URL that they can share with their non-registered friends. One of these URLs will look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;http://myapp.com/tim/activities/shared/a258f423366a2a07ffd3afec8c07f1bed8e07ba9
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will load the &lt;code&gt;shared&lt;/code&gt; action in the activities controller, which is also protected by a before_filter that will only allow access if the key in the URL is valid for the user.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class ActivitiesController &amp;lt; ApplicationController
  before_filter :login_required, :except =&amp;gt; :shared
  before_filter :get_user
  before_filter :sharing_required, :only =&amp;gt; :shared

  def shared
    @activities = @user.activities.paginate(:order =&amp;gt; 'created_at DESC', :page =&amp;gt; params[:page])
  end

  private

  def get_user
    @user = User.find_by_login(params[:user_id])
  end

  def sharing_required
    # pull the key out of the URL and verify that the user has one to match
    unless Sharing.find(:first, :conditions =&amp;gt; ['user_id = ? AND key = ?', @user.id, params[:key] ])
      flash[:error] = 'You do not have permission to view this page'
      redirect_to '/'
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To flesh out the implementation, all you need to do is the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add views for the shared action in the activities controller&lt;/li&gt;
&lt;li&gt;Write a builder view to output the sharing action in RSS (&lt;a href="http://www.railsjitsu.com/now-feeding-rss-in-rails-2-0"&gt;explained here&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Include a sharing resource (nested under the user resource) in routes.rb and add a CRUD interface for creating and manipulating Sharing objects&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Canberra Ruby Crew February meeting wrap-up</title>
    <link rel="alternate" href="https://timriley.info/writing/2008/02/23/canberra-ruby-crew-february-meeting-wrap-up"/>
    <id>https://timriley.info/writing/2008/02/23/canberra-ruby-crew-february-meeting-wrap-up</id>
    <published>2008-02-23T08:35:00+00:00</published>
    <updated>2008-02-23T08:35:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;The second Canberra Ruby Crew meeting was a success. Our number grew to seven, and all had a good time chatting, eating, playing guitar hero and sharing ideas and code. Thanks to everyone who came along.&lt;/p&gt;
&lt;p&gt;Some things we did:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://hughevans.net/"&gt;Hugh&lt;/a&gt; introduced some of the crew to the wonders of &lt;a href="http://haml.hamptoncatlin.com/"&gt;Haml and Sass&lt;/a&gt; and bringing them together with &lt;a href="http://staticmatic.rubyforge.org/"&gt;StaticMatic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://rails.nomad-labs.com/"&gt;Shoaib&lt;/a&gt; ran us through how to get started with &lt;a href="http://git.or.cz/"&gt;git&lt;/a&gt; and &lt;a href="http://gitorious.org/"&gt;gitorious&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="allen.com.au"&gt;Matt&lt;/a&gt; showed off his use of the &lt;a href="http://stencil.rubyforge.org/svn/trunk/"&gt;Stencil&lt;/a&gt; plugin and his rocking &lt;a href="http://teethgrinder.co.uk/open-flash-chart/"&gt;Open Flash Charts&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The next meeting will be on Friday the 21st of March. Keep your eyes on the &lt;a href="http://groups.google.com/group/rails-oceania/"&gt;Rails Oceania google group&lt;/a&gt; for an announcement closer to the date!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edit:&lt;/strong&gt; The next meeting will actualy be on Friday 28th March, beginning a last-Friday-of-the-month pattern.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Naming schemes</title>
    <link rel="alternate" href="https://timriley.info/writing/2008/02/11/naming-schemes"/>
    <id>https://timriley.info/writing/2008/02/11/naming-schemes</id>
    <published>2008-02-11T04:30:00+00:00</published>
    <updated>2008-02-11T04:30:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;Garrett Murray writes about &lt;a href="http://log.maniacalrage.net/post/26015986"&gt;his history with naming schemes&lt;/a&gt; for hardware and networks.&lt;/p&gt;
&lt;p&gt;My favourite scheme so far was in one of my former offices, where we named everything after Street Fighter characters. A few of the notable names:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;ehonda&lt;/em&gt; was the network file server - big, fat and full of data&lt;/li&gt;
&lt;li&gt;&lt;em&gt;guile&lt;/em&gt; was the backup server - being a millitary man, he &lt;a href="http://www.urbandictionary.com/define.php?term=watch+my+six"&gt;covered our six&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;blanka&lt;/em&gt; - courtesy of his electric shock powers - was the firewall&lt;/li&gt;
&lt;li&gt;&lt;em&gt;vega&lt;/em&gt; was my workstation, because I could speak Spanish&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Another benefit of the scheme was that, given the proliferation of Street Fighter sequels, it was unlikely we were going to run out of names very quickly. The scheme I use at home is characters from &lt;a href="http://en.wikipedia.org/wiki/Azumanga_Daioh"&gt;Azumanga Daioh&lt;/a&gt;. My iMac is Chiyo, and a rarely leave home without Tadakichi-san, my MacBook.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>The Canberra Ruby Crew and getting involved</title>
    <link rel="alternate" href="https://timriley.info/writing/2008/01/21/the-canberra-ruby-crew-and-getting-involved"/>
    <id>https://timriley.info/writing/2008/01/21/the-canberra-ruby-crew-and-getting-involved</id>
    <published>2008-01-21T02:55:00+00:00</published>
    <updated>2008-01-21T02:55:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;&lt;a href="http://www.flickr.com/photos/sabman/2201755530/"&gt;&lt;img src="/assets/73f4e0d4bce4-7A629E20.jpg" alt="Matt and I working on migrating an app to Rails 2." /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Last Friday night we ran the inaugural CRC (&lt;em&gt;Canberra Ruby Crew&lt;/em&gt;) meeting. It was a good time; we ate, wrote some code, debugged some code and played some Guitar Hero. There were just three of us in attendance, but there should be at least five for the next month’s meeting.&lt;/p&gt;
&lt;p&gt;I’ve been to a couple of Rails nights in Adelaide before I moved here, but this meet left my truly inspired. I put this down firstly to the setting that allowed us to dig our heels into some code, and also the news, energy and wider perspective of the Rails scene that &lt;a href="http://allen.com.au/" title="Matt Allen's blog"&gt;Matt&lt;/a&gt; brought from his involvement in the large and active Sydney community. I’ll be definitely visiting their next gathering.&lt;/p&gt;
&lt;p&gt;This brings me to my plans for 2008. I’ve been meaning to write for some time about what I plan to achieve this year, and I will start with a &lt;em&gt;themeword&lt;/em&gt;, a notion I picked up in a &lt;a href="http://factoryjoe.com/blog/2008/01/02/kicking-off-2008-with-a-themeword/" title="Chris Messina's themeword post"&gt;post from Chris Messina&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;My themeword for 2008 is &lt;em&gt;involvement&lt;/em&gt;. I wrote in my notebook recently that I want to:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Give myself fully to what I am doing at any time, without worrying or getting distracted by the other components of my life.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So this year I plan to get involved in a number of ways: dive fully into my hacking, web projects and Tagalog study, and allow myself to completely unwind when I take myself away from these.&lt;/p&gt;
&lt;p&gt;I will also get involved in the community in a way I have never done before. Until now, I haven’t fully engaged in local web and tech communities, forsaking this involvement largely to pursue volleyball ambitions. This year, however, I want to involve myself in and contribute to these communities. Last week’s CRC meeting has already shown me how rewarding this activity is. I’m looking forward to a fruitful and productive year!&lt;/p&gt;
&lt;p&gt;Oh, and I must also buy Guitar Hero.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>ActionMailer templates in Rails 2.0</title>
    <link rel="alternate" href="https://timriley.info/writing/2008/01/20/actionmailer-templates-in-rails-20"/>
    <id>https://timriley.info/writing/2008/01/20/actionmailer-templates-in-rails-20</id>
    <published>2008-01-10T07:15:00+00:00</published>
    <updated>2008-01-10T07:15:00+00:00</updated>
    <author>
      <name>Tim Riley</name>
    </author>
    <content type="html">&lt;p&gt;When upgrading to Rails 2.0, it seems you cannot rename all of our &lt;code&gt;.rhtml&lt;/code&gt; templates to &lt;code&gt;.html.erb&lt;/code&gt; - it seems ActionMailer will refuse to find templates with this extension. Make sure you rename them to just &lt;code&gt;.erb&lt;/code&gt;.&lt;/p&gt;
</content>
  </entry>
</feed>
