<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://tekin.co.uk/atom.xml" rel="self" type="application/atom+xml" /><link href="https://tekin.co.uk/" rel="alternate" type="text/html" /><updated>2026-07-21T16:02:47+00:00</updated><id>https://tekin.co.uk/atom.xml</id><title type="html">tekin.co.uk</title><subtitle>This is the personal blog of Tekin Süleyman, Manchester UK based Ruby on Rails developer available for hire.</subtitle><author><name>tekin</name></author><entry><title type="html">10 Things You Might Not Know About Rails i18n</title><link href="https://tekin.co.uk/2026/07/10-things-you-might-not-know-about-rails-i18n" rel="alternate" type="text/html" title="10 Things You Might Not Know About Rails i18n" /><published>2026-07-13T00:00:00+00:00</published><updated>2026-07-13T00:00:00+00:00</updated><id>https://tekin.co.uk/2026/07/10-things-you-might-not-know-about-rails-i18n</id><content type="html" xml:base="https://tekin.co.uk/2026/07/10-things-you-might-not-know-about-rails-i18n"><![CDATA[<meta property="og:video" content="https://www.youtube-nocookie.com/embed/X1u8ZWCSuA0?start=23" />

<div class="video-container">
  <iframe title="10 Things You Might Not Know About Rails i18n" width="560" height="315" src="https://www.youtube-nocookie.com/embed/X1u8ZWCSuA0?start=76" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
</div>

<p>A lightning talk exploring i18n in Rails and how it can be helpful to us even when we’re not translating our applications into other languages.</p>

<p>I gave this talk at <a href="https://brightonruby.com/2026/10-cool-things-internationalisation-tekin-suleyman/">Brighton Ruby in 2026</a>.</p>

<p><a href="https://speakerdeck.com/tekin/10-things-you-might-not-know-about-rails-i18n">Slides</a></p>]]></content><author><name>Tekin Süleyman</name></author><category term="Ruby &amp; Rails" /><category term="speaking" /><summary type="html"><![CDATA[A talk exploring i18n in Rails, and how it can be useful even when we're not translating our applications into other languages. I gave this talk at Brighton Ruby in 2026.]]></summary></entry><entry><title type="html">The Ruby community has a DHH problem</title><link href="https://tekin.co.uk/2025/09/the-ruby-community-has-a-dhh-problem" rel="alternate" type="text/html" title="The Ruby community has a DHH problem" /><published>2025-09-21T00:00:00+00:00</published><updated>2025-09-21T00:00:00+00:00</updated><id>https://tekin.co.uk/2025/09/the-ruby-community-has-a-dhh-problem</id><content type="html" xml:base="https://tekin.co.uk/2025/09/the-ruby-community-has-a-dhh-problem"><![CDATA[<p><a href="https://davidcel.is">David Celis</a> recently published a <a href="https://davidcel.is/articles/rails-needs-new-governance">thoughtful
piece</a> on Rails governance in response to the latest troubling
<a href="https://world.hey.com/dhh/as-i-remember-london-e7d38e64">blog post</a> from DHH, the creator of
Rails. Like David, I’ve also been troubled by DHH’s recent output and the harm it is causing to the Ruby community.</p>

<p>I think it’s worth taking a moment to analyse DHH’s post in more detail and make it clear exactly why it’s so
problematic.</p>

<p>In his post, DHH complains that London is no longer a city he wants to live in because it is now only a third “native
Brit”. His use of “native Brit” is as a proxy for “White British”. The implication is clear: if you are not White, you
are not British.</p>

<p><em>(Update: for the avoidance of doubt that David is talking about whiteness here, when he makes his claim that London is
only a third “native Brit” he references <a href="https://en.wikipedia.org/wiki/Ethnic_groups_in_London">this Wikipedia post on London ethnicity
statistics</a> where the most recent census data shows 36.8% of
individuals identifying as White British, excluding all those that identify as either Asian or Black British from his
made up  “native Brit” classification.)</em></p>

<p>In the same post he praises Tommy Robinson (actual name Stephen Christopher Yaxley-Lennon), a right-wing agitator with
several <a href="https://en.wikipedia.org/wiki/Tommy_Robinson#Criminal_offences">convictions</a> for violent offences and a long
history of association with far-right groups such as the <a href="https://en.wikipedia.org/wiki/English_Defence_League">English Defence
League</a> and the <a href="https://en.wikipedia.org/wiki/British_National_Party">British Nationalist
Party</a>. He then goes on to describe those that attended last
weekend’s far-right rally in London as “perfectly normal, peaceful Brits” protesting against the
“demographic nightmare” that has enveloped London, despite the <a href="https://www.bbc.co.uk/news/articles/cwydezxl0xlo">violence and disorder they caused</a>.</p>

<p>To all of that he adds a dash of Islamophobia, citing “Pakistani rape gangs” as one of the reasons for the unrest,
repeating a weaponised trope borne from a long since <a href="https://policinginsight.com/feature/analysis/when-bad-evidence-is-worse-than-no-evidence-quilliams-grooming-gangs-report-and-its-legacy/">discredited
report</a>
from the Quilliam Foundation, an organisation with ties to both the the <a href="https://medium.com/insurge-intelligence/the-quilliam-foundation-is-financed-by-tea-party-conservatives-investigated-by-sam-harris-1e43d54f0bee">US Tea
Party</a>,
and <a href="https://www.theguardian.com/uk-news/2013/oct/12/tommy-robinson-quilliam-foundation-questions-motivation">Tommy
Robinson</a> himself. A
trope that exists despite the fact that the <a href="https://novaramedia.com/2019/06/12/child-sexual-exploitation-asian-grooming-gangs-and-the-left/">overwhelming majority of convicted child sex offenders are white
men</a>, with Asian men
in fact under-represented.</p>

<p>According to DHH, there is nothing racist or xenophobic in saying “Britain primarily a united kingdom for the Brits”,
which again let’s be absolutely clear; by “Brits” he means White people.</p>

<h2 id="im-tired">I’m tired</h2>

<p>As a non-white British citizen born and raised in London, I can’t explain just how painful it is to hear this sort of
toxic rhetoric being promoted by one of the most prominent and visible leaders of the Ruby community. I experienced my
fair share of racism growing up
in London in the eighties and nineties from the very people that DHH is lionising in his post. White men who questioned my right to
exist. Places and spaces where I didn’t feel welcome or safe because of my ethnic origin. And despite the recent rise
in far-right politics, for the most part things are much better today. Anyone who has spent even the smallest amount of time
in the great city of London knows that it is precisely its rich multiculturalism that makes it great. The migration issue in
Britain is <a href="https://bsky.app/profile/robjimfleming.bsky.social/post/3lzbp5gcbsc2m">complicated</a> and
<a href="https://migrationobservatory.ox.ac.uk/resources/briefings/uk-public-opinion-toward-immigration-overall-attitudes-and-level-of-concern/">multi-layered</a>
and DHH wades into it with all the subtlety and nuance of a bull in china shop, sharing the same <a href="https://www.youtube.com/watch?v=tKEsyIuTrO8">old and tired xenophobic tropes</a>.</p>

<p>Unfortunately it isn’t just migrants and non-white people that have come under fire in DHH’s recent writing and social
media posts. There have been multiple examples of <a href="https://world.hey.com/dhh/bad-therapy-08849dc9">anti-trans rhetoric</a>.
There was the post where he described an ad featuring a plus-sized Black women as <a href="https://world.hey.com/dhh/the-beauty-of-ideals-b3dccf72">“grotesque”</a>
and <a href="https://world.hey.com/dhh/you-expect-principles-but-should-wish-for-none-531988ec">celebrated</a> the ads being
replaced with ones featuring “blond babies” (what is it about the baby’s blondness that is relevant I wonder?). There
have been the posts veering into puritanical
<a href="https://world.hey.com/dhh/the-parental-dead-end-of-consent-morality-e4e8a8ee">Pronatalism</a>,
another favourite subject of the nationalist right. Whether it’s his direct intention or not, there has been a
consistent theme of othering and stigmatising in his recent writing. The message is clear: if you are trans, a migrant,
black, overweight, childless, have <a href="https://world.hey.com/dhh/cold-reading-an-adhd-affliction-44163793">ADHD</a>, then in
DHH’s view you are somehow inferior. Instead of raising up and supporting the marginalised and vulnerable, DHH choses
to exclude and punch down. This is not something the Ruby community can continue to simply ignore.</p>

<h2 id="i-️-ruby">I ❤️ Ruby</h2>

<p>I love the Ruby community dearly. It’s a community that has given me much joy, and a successful career doing work I
love. It’s also a community I’ve done my fair share to help grow and prosper. Through running my <a href="https://nwrug.org">local Ruby
meetup</a>, <a href="https://tekin.co.uk/speaking/">speaking at conferences</a>, organising <a href="https://railsgirls.com/manchester">Rails Girls
workshops</a>, helping multiple people enter the industry through mentorship, and contributing to
open source, including <a href="https://contributors.rubyonrails.org/contributors/tekin-suleyman/commits">Rails itself</a>. And in
the over fifteen years I’ve been part of the community I’ve met many kind, thoughtful and compassionate people, few of
whom I would ever imagine sharing DHH’s increasingly reactionary and toxic views. But from the safety of his blue-tick
echo chamber, and despite many in the community voicing their concern and disgust, DHH is able to spin a narrative that
any opposition to his views are nothing more than noise from <a href="https://nitter.net/dhh/status/1969362639500324985#m">“radical, violence-condoning nut jobs”</a>.</p>

<h2 id="power-and-the-complicity-of-silence">Power and the complicity of silence</h2>

<p>My sense is that since the great <a href="https://www.theverge.com/2021/4/27/22406673/basecamp-political-speech-policy-controversy">Basecamp
implosion</a>, rather than
reflect on how he and Jason may have been at fault for what happened, DHH found
solace in ideas that allowed him to frame himself as victim. DEI and “wokeness” were the actual problem, not him.
The recent state of US politics has only further entrenched him in these ideas and emboldening him to fully mask-off.
All that is to say I don’t imagine DHH is likely to change. Too much of his self image is now tied up in the misguided
moral high ground of these right-wing ideals. So what can be done?</p>

<p>DHH holds an incredible amount of power. He has the trademarks to Rails, and sits as the chair of the Rails
Foundation. He is also a board member at Shopify, a company that contributes a huge amount to the Ruby ecosystem, both
financially, and through the incredible work of the Ruby and Rails infrastructure team. Of the twelve Rails Core
members, almost half are directly employed by either Basecamp or Shopify. DHH and Basecamp drives much of the
direction for new features that make it into Rails. Unfortunately DHH isn’t going anywhere.</p>

<p>At the same time, if we want the Ruby community to grown and thrive and for the next generation of Rubyists to
feel welcome, we cannot afford to let the damage he is causing by sharing his harmful and toxic ideas go unchecked. We
need many more voices to speak up and make it abundantly clear that his views are not widely held. Most importantly,
DHH needs to hear directly from the other leaders in our community that his behaviour is not OK and is causing
serious harm. That means the <a href="https://rubycentral.org/about/#directors">leadership at Ruby Central</a>.
That means the members of <a href="https://rubyonrails.org/community#core">Rails Core</a>. That means the many podcasters and
conference organisers that have a platform and voice that the community listens to. That means Matz himself.</p>

<p>Silence and appeasement is only emboldening DHH to ratchet up the toxicity and allowing the damage to continue.
Perhaps if enough of those in positions of power call him out on his bullshit he’ll at at least follow his own
advice and <a href="https://world.hey.com/dhh/make-politics-private-again-9b47aaaf">keep his politics to himself</a>. More
importantly though, it will send a clear message to the wider Ruby community (and those who may be considering joining
it) that the majority does not stand with DHH and his toxic views.</p>

<h2 id="update-9-october-2025">Update (9 October 2025)</h2>

<p>Since I wrote this post others have also written on the topic. Here’s a selection in case you need further convincing:</p>

<ul>
  <li><a href="https://jakelazaroff.com/words/dhh-is-way-worse-than-i-thought/">DHH Is Way Worse Than I Thought</a></li>
  <li><a href="https://christianheilmann.com/2025/09/25/as-i-remember-london/">As I remember London</a></li>
  <li><a href="https://paulbjensen.co.uk/2025/09/17/on-dhhs-as-i-remember-london.html">On DHH’s “As I Remember London”</a></li>
  <li><a href="https://victorwynne.com/dhh/">Ruby deserves better leadership than DHH</a></li>
  <li><a href="https://johan.hal.se/wrote/2025/09/26/david-please-stop-posting/">David, please stop posting</a></li>
</ul>

<p>And of course if you haven’t watched it already, you really should watch <a href="https://tomstu.art/the-dhh-problem">Tom’s five minute talk from
2014</a>. As relevant today as it was then.</p>]]></content><author><name>Tekin Süleyman</name></author><category term="Ruby &amp; Rails" /><summary type="html"><![CDATA[The Ruby community can no longer afford to stand silent in the face of DHH and his toxic ideas.]]></summary></entry><entry><title type="html">Yearnotes 2024</title><link href="https://tekin.co.uk/2025/01/yearnotes-2024" rel="alternate" type="text/html" title="Yearnotes 2024" /><published>2025-01-06T00:00:00+00:00</published><updated>2025-01-06T00:00:00+00:00</updated><id>https://tekin.co.uk/2025/01/yearnotes-2024</id><content type="html" xml:base="https://tekin.co.uk/2025/01/yearnotes-2024"><![CDATA[<p>After a four year absence it’s time to get back on the yearnotes train!</p>

<h2 id="work">Work</h2>

<h3 id="join-together">Join Together</h3>

<p>2024 was a good year for <a href="https://jointogether.online">Join Together</a>. Our main focus was a chunky project for the <a href="https://neu.org.uk">National Education Union</a> (NEU). As well as replacing their online join process, we also built them a bespoke service for upgrading existing student members to full membership after graduation.</p>

<p>The NEU are one of the largest (and more tech-savvy) unions in the UK, so it was a pretty big deal for us to win the project. And despite a fair amount of complexity and esoteric requirements, we totally nailed it, leaving the folks at the NEU over the moon with what we delivered.</p>

<p>We also shipped projects and updates of varying sizes for a good number of our existing union clients. It’s become clear that updates and ongoing development with existing clients will make up a healthy chunk of our revenue, which is helpful, as bringing new unions onboard can be a long and arduous process (it took a whole year from first contact to signed contract with NEU).</p>

<h3 id="side-gig">Side gig</h3>

<p>By the tail end of the year Join Together’s projects were wrapping up and a previous client of mine got in touch asking if I’d be available for a short contract. They needed urgent help shipping some major updates in a short space of time. With no new Join Together projects starting until the new year I was able to take them up on their offer, and spent the last couple months of 2024 working on their mission. It turned into quite an intense project, working mostly solo to make significant changes to their user modelling (primarily separating admin-related code/authentication from their generic <code class="language-plaintext highlighter-rouge">User</code> model<sup id="user-model-src"><a href="#user-model">(1)</a></sup>). In the end it all went smoothly, which was very pleasing, and I was wrapped up in time for Christmas.</p>

<p>I imagine this won’t be the last time I take on short contracts outside of my work on Join Together. Although we’re busy when we have projects on, the in-between time tends to be quiet as the platform mostly takes care of itself. As long as my <a href="https://jointogether.online/team/">partners in unionisation</a> are not also on outside contracts, and can take care of urgent issues, it’ll be a good way to keep busy and the bank balance healthy.</p>

<h3 id="maintainable-ruby-podcast">Maintainable Ruby Podcast</h3>

<p>This summer I made an appearance on the <a href="https://maintainable.fm/episodes/tekin-suleyman-balancing-complexity-and-team-size">Maintainable Software podcast</a>. I really enjoyed chatting with Robby and sharing my reckons about software, teams and some of the things I believe helps keep software maintainable. Some people have said nice things about the episode, so that’s good.</p>

<h3 id="conferences">Conferences</h3>

<p>I was sad to miss <a href="https://haggisruby.co.uk">Haggis Ruby</a> (I got Covid), hopefully they’ll be back in 2025. I did make it to Brighton Ruby tho (ten for ten!), which is always one of the highlights of the year. Andy puts on an incredible conference (and has great taste in cocktail flights). The talks are generally good, but for me it’s about spending time and catching up with Ruby friends, old and new, in lovely Brighton. Have you got your tickets for <a href="https://ti.to/goodscary/brightonruby-2025">Brighton Ruby 2025</a> yet?</p>

<p>I did submit one <a href="https://speakerline.io/proposals/7761">talk proposal</a> this year, to both Brighton Ruby and RailsConf, but was unsuccessful. It’s for a talk on i18n in Rails. My thesis is that everyone writing Rails code should be making more use of i18n, even when the app isn’t actually being localised into other languages, and that following the less well known Rails’ conventions for i18n can lead to simpler, clearer and more maintainable code. I’d still like to develop this talk at some point, perhaps to share with <a href="https://nwrug.org">NWRUG</a>.</p>

<h2 id="life">Life</h2>

<p>Welcome to the world baby Ela! That’s four nieces now, my siblings sticking to what they know.</p>

<figure>
  <img class="post-image" src="/images/20250106/baby-ela.jpg" alt="A picture of a baby looking at the camera" />
  <figcaption>My niece Ela Mulvey, born July 2024</figcaption>
</figure>

<h3 id="travels">Travels</h3>

<p>Myself and Lauren had an Easter break in Malaga. What we hadn’t realised was that Easter is a big deal in that part of Spain. Our arrival coincided perfectly with Holly Week (Semana Santa), when various brotherhoods parade through the streets wearing unfortunately-appropriated pointy hoods and carrying massive Jesus-themed floats. It’s quite a thing to behold.</p>

<p><img class="post-image" src="/images/20250106/jesus-float.jpg" alt="A gold float with Jesus on a donkey being carried by dozens of robed men through the streets of Malaga" /></p>

<p>In August we went on a trip to Copenhagen by train, stopping off in Brussels and Hamburg on the way. Earlier in the year I’d managed to snag a table at <a href="https://noma.dk/nomathreepointzero/">Noma</a> for their final Vegetable Season before they close as a permanent restaurant. An eye-watering amount of money for a meal, but as an experience it was one I won’t forget in a hurry and one of my main highlights of the year.</p>

<figure>
  <img class="post-image" src="/images/20250106/noma-food.jpg" alt="A grid of photos showing various dishes from the set menu at Noma, Copenhagen" />
  <figcaption>The entirely-vegetarian food from Noma’s Vegetable Season menu</figcaption>
</figure>

<p>Copenhagen itself is a beautiful city and checks many of my travel boxes: great food and coffee everywhere; lots of interesting art and culture; plenty of things to do. The main highlights included: Swimming in the pristine harbour; Wandering around Freetown Christiana; The Design Musuem; and <a href="https://louisiana.dk/en/">Louisiana</a>, a beautiful modern art gallery just outside Copenhagen with notable work by Franz Gerscht, William Kentridge, Louise Bourgeois and Yayoi Kusama.</p>

<p><img class="post-image" src="/images/20250106/yayoi-room.jpg" alt="A photo from inside Yoyoi Kusama’s Gleaming Lights of the Souls" /></p>

<p>We were only there for a day, but Hamburg is also somewhere I’d love to go back to and spend more time exploring.</p>

<figure>
  <img class="post-image" src="/images/20250106/hamburg-friends.jpg" alt="Myself, Lauren and our friends in Hamburg" />
  <figcaption>Hanging out with pals in Hamburg</figcaption>
</figure>

<h3 id="walking-club">Walking club</h3>

<p>Although once again we failed to make it to Snowdon (due to untimely illness), it was another solid showing for our Walking Club this year with big walks every month except August. The highlight for me was an epic hike up Scafell Pike on a glorious sunny day in June. Get out in the wilds people, it’s good for the soul.</p>

<p><img class="post-image" src="/images/20250106/walking-club.jpg" alt="View into a valley from near the peak of Scafell Pike" /></p>

<h3 id="djing">DJing</h3>

<p>I started DJing regularly again towards the end of the year with a bimonthly residency at <a href="https://odioba.com">Ōdiōba</a>, a lovely little listening bar in Stockport. It’s been great fun playing records out again, and it’s got me reconnecting with my record collection. The sets will be making their way onto <a href="https://www.mixcloud.com/tekin/">my Mixcloud</a> if you’re interested (December’s should be up shortly). I also DJ’d the launch for my friend John’s new book <a href="https://the-modernist.org/collections/books/products/a-time-a-place">A Time ⋅ A Place</a>, which I would encourage you to buy as it’s great, but it’s also sold out, sorry.</p>

<h3 id="garden-makeover">Garden makeover</h3>

<p>Almost ten years after moving in it was finally time to sort out our garden. I’m prone to procrastination, especially when it comes to major house projects like this, as personal head space tends to be in short supply. Thankfully Lauren forced matters this spring by calling various trades people for quotes on paving, kicking off a short stint as a garden designer. It’s a small garden (not much bigger than a yard really) so designing it became a careful balancing act between space for planting whilst maintaining enough room for general hanging out when the Manchester weather permits. Here’s the obligatory before and after shots for you:</p>

<figure>
  <img class="post-image" src="/images/20250106/garden-before.jpg" alt="A lawn and various shrubs in an urban garden" />
  <figcaption>Garden before</figcaption>
</figure>

<figure>
  <img class="post-image" src="/images/20250106/garden-after.jpg" alt="A partially paved garden with triangular beds to the left and right, and garden furniture" />
  <figcaption>Garden after</figcaption>
</figure>

<p>I’m pretty pleased with how it turned out. It’s a much more useable space, and we were able to make the most of it this summer before winter kicked in. I’m looking forward to more planting and outdoor fun times in 2025.</p>

<h2 id="film-tv-and-theatre">Film, TV and Theatre</h2>

<p>Three films stuck out for me this year: <em><a href="https://www.youtube.com/watch?v=QzZBbX5A1FA">Perfect Days</a></em>, <em><a href="https://www.youtube.com/watch?v=r-vfg3KkV54">The Zone of Interest</a></em>, and <em><a href="https://www.youtube.com/watch?v=_RwLdIiZk_8">Soundtrack to a Coup D’etat</a></em>. Each was striking and moving in their own way, and I’d recommend all three as must-see films. Honourable mention to <em>Civil War</em>, <em>Dune 2</em>, <em>Kneecap</em>, <em>Longlegs</em> and <em>The Substance</em>.</p>

<p>On the TV front I don’t think it was a vintage year. Post strike/Covid/ZIRP slump? <em>Shōgun</em>, <em>Industry</em> and <em>The Penguin</em> stood out. <em>House of the Dragon</em>, <em>The Bear</em>, <em>Slow Horses</em>, <em>Black Doves</em>, <em>English Teacher</em> and <em>Agatha All Along</em> were all serviceable fun.  The show I enjoyed the most however was a reality TV show on Netflix: <em>Culinary Class Wars</em>, a thoroughly entertaining and over the top cross between MasterChef and Squid Games.</p>

<p>We got to the theatre a couple of times, and the most memorable thing for me was <em><a href="https://www.instagram.com/darknoonshow/">Dark Noon</a></em>, a fantastic and intense retelling of the birth of modern America by a predominantly black South African cast. If you live in Australia anywhere near Sydney (and happen to be reading this in January 2025) I would highly recommend you check it out at the <a href="https://www.sydneyfestival.org.au/events/dark-noon">Sydney Festival</a>.</p>

<h2 id="music">Music</h2>

<p>I thought it might have been a quiet one for new music this year, but looking back through the releases I picked up there are some standouts for sure. Two of my favourites come from friends of mine: <em><a href="https://wearerhythmsection.bandcamp.com/album/desire">Private Joy’s Desire! EP</a></em> and <em><a href="https://jamiefinlay.bandcamp.com/album/sun-dogs">Jamie Finlay’s Sun Dogs album</a></em>. Other releases that stood out were new albums from <em>Nala  Sinephro</em>, <em>Don Glori</em>, <em>Shabaka</em>, <em>Thandi Ntuli and Carlos Nino</em>, <em>Greg Foat</em> ,<em>Lady Wray</em>, <em>Sam Gandel &amp; Sam Wilkes</em>, <em>Allysha Joy</em>, <em>Bricknasty</em> and <em>Hiatus Kaiyote</em>.</p>

<p>Aside from new music, I’ve continued to dig out old gems in various record shops and fairs on my travels, including at <a href="https://www.recordplanet.nl/en">Record Planet</a> in Holland, one fo the biggest record fairs there is. Imagine two aircraft hangers full of stalls selling vinyl from all over the world. Two days digging through the crates was exhausting, but loads of fun, and I managed to come back with a modest haul of tasty records, including some I’ve been trying to hunt down for some time. It also happens to coincide with <a href="https://leguesswho.com">Le Guess Who?</a>, which is a fantastic music festival.</p>

<p>Speaking of festivals, <a href="https://weoutherefestival.com">We Out Here</a> was, as always, a wonderful weekend of sunshine, dancing and incredible music. I’m already looking forward to 2025.</p>

<p>Other live music that hit the spot this year included gigs by <em>Shabaka</em>, <em>Lady Wray</em>, <em>Bricknasty</em>. And not forgetting <a href="https://www.instagram.com/manchester_music_for_gaza/">Manchester Music for Gaza</a>, a moving day of music, talks and activities that raised over £10,000 for <a href="https://www.map.org.uk">Medical Aid for Palestinians</a>. Well done Ian, Yousef, Zoe, and all involved. Free Palestine! 🇵🇸</p>

<h2 id="games">Games</h2>

<p>I didn’t find a huge amount of time for games this year. The highlight was probably the <em>Resident Evil 4 remake</em>, which I thoroughly enjoyed: spooky, cinematic, and difficult in the right sort of way. <em>Animal Well</em> was also enjoyable, if a little baffling and opaque. I lost interest in uncovering all the eggs after getting to the end.</p>

<p>I also picked up <em>Cyberpunk 2077</em> after the PS5 update hoping to get lost in an epic adventure. And whilst the graphics and world building are very impressive, it took too much effort (and more than a few YouTube tutorials) to wrap my head around all the systems and mechanics enough to feel vaguely competent in the game. And by the time I was in my stride it felt like I’d already seen most of the content. With that said I apparently put over 70 hours into it, so I guess it was alright?</p>

<p>I’ve only got a little way into <em>Astrobot</em> and so far it’s a joyful and fun experience that manages to capture some of Nintendo’s magic sauce. I’m looking forward to spending more time with it in 2025.</p>

<p>Honourable mention to <em>Overcooked! All You Can Eat</em>, which several years later continues to eat up countless hours as myself and my friend Lucy wring every last drop of fun out of it trying to 4-star all the levels<sup id="all-the-levels-src"><a href="#all-the-levels">(2)</a></sup>. No co-operative game has come close to its combination of chaotic fun and charm. It’s a shame Ghost Town have given up on further expansions as we’d buy them in a heart beat. Here’s hoping their recently-announced <a href="https://youtu.be/T_i0xhr4utk">new co-operative game</a> is able to replicate some of the same magic.</p>

<h2 id="looking-ahead-to-2025">Looking ahead to 2025</h2>

<p>After a couple years of various random issues I’m aiming to get my health and fitness back on track, and generally have a more relaxed and mindful approach to life in 2025. More time for some creative projects. And of course Snowdon must finally be conquered!</p>

<h2 id="footnotes">Footnotes</h2>

<p><a id="user-model" href="#user-model-src">1</a>: Perhaps I should blog separately about the patterns and techniques I used to do this iteratively and without any downtime?</p>

<p><a id="all-the-levels" href="#all-the-levels-src">2</a>: Unfortunately this may actually be impossible with just the two of us as the target score on some levels appears calibrated for three or four players. Still we plough on…</p>]]></content><author><name>Tekin Süleyman</name></author><summary type="html"><![CDATA[Here's what happened in my 2024.]]></summary></entry><entry><title type="html">Different ways to use “–patch” in Git</title><link href="https://tekin.co.uk/2024/08/the-many-uses-for-git-patch" rel="alternate" type="text/html" title="Different ways to use “–patch” in Git" /><published>2024-08-23T00:00:00+00:00</published><updated>2024-08-23T00:00:00+00:00</updated><id>https://tekin.co.uk/2024/08/the-many-uses-for-git-patch</id><content type="html" xml:base="https://tekin.co.uk/2024/08/the-many-uses-for-git-patch"><![CDATA[<p>I’ve written previously about <a href="http://localhost:4000/2017/03/git-tips-you-possibly-did-not-know-you-needed#3-interactively-stage-changes-with---patch">using <code class="language-plaintext highlighter-rouge">--patch</code> to interactively stage changes</a>. But did you know that you can use <code class="language-plaintext highlighter-rouge">--patch</code> (aka <code class="language-plaintext highlighter-rouge">-p</code>) to similar effect with other Git commands? Let’s take a look…</p>

<h2 id="selectively-stashing-changes">Selectively stashing changes</h2>

<p><code class="language-plaintext highlighter-rouge">git stash</code> is great for temporarily stashing changes that you want to apply later, and handily it also supports selectively stashing changes with the <code class="language-plaintext highlighter-rouge">--patch</code> flag:</p>

<pre class="terminal"><code>  $ git stash --patch

  diff --git a/spec/sidekiq/upload_job_spec.rb b/spec/sidekiq/upload_job_spec.rb
  index b5f1d04e..e3b6227d 100644
  --- a/spec/sidekiq/upload_job_spec.rb
  +++ b/spec/sidekiq/upload_job_spec.rb
  <span class="light-blue">@@ -7,6 +7,7 @@</span> describe '#perform' do
       context 'the application has already been uploaded' do
         let(:membership_application) { create :forsa, :uploaded }

  <span class="diff-green">+      # Here is change one of two</span>
         it 'lets us know via Appsignal' do
           allow(Appsignal).to receive(:report_error)
           job.perform(membership_application.id)

  <span class="diff-blue">(1/2) Stash this hunk [y,n,q,a,d,j,J,g,/,e,p,?]?</span>
</code></pre>

<p>Bonus <code class="language-plaintext highlighter-rouge">git stash</code> tip: you can also selectively stash entire files using <code class="language-plaintext highlighter-rouge">--</code> to disambiguate the command from the paths you want stashing:</p>

<pre class="terminal"><code>  $ git stash -- path/to/file.rb</code></pre>

<h2 id="selective-discarding-changes-from-your-current-work-tree">Selective discarding changes from your current work tree</h2>

<p>You can use the <code class="language-plaintext highlighter-rouge">git restore</code> command to discard local changes and restore files to their last committed state. It can also be called with the <code class="language-plaintext highlighter-rouge">--patch</code> flag to interactively select specific hunks to discard:</p>

<pre class="terminal"><code>  $ git restore --patch

  diff --git a/spec/sidekiq/upload_job_spec.rb b/spec/sidekiq/upload_job_spec.rb
  index b5f1d04e..e3b6227d 100644
  --- a/spec/sidekiq/upload_job_spec.rb
  +++ b/spec/sidekiq/upload_job_spec.rb
  <span class="light-blue">@@ -7,6 +7,7 @@</span> describe '#perform' do
       context 'the application has already been uploaded' do
         let(:membership_application) { create :forsa, :uploaded }

  <span class="diff-green">+      # Here is change one of two</span>
         it 'lets us know via Appsignal' do
           allow(Appsignal).to receive(:report_error)
           job.perform(membership_application.id)

  <span class="diff-blue">(1/2) Discard this hunk from worktree [y,n,q,a,d,j,J,g,/,e,p,?]?</span>
</code></pre>

<p>Note the different phrasing of the prompt on the last line: Here we are choosing the changes we want to <em>discard</em>. Be careful, this is a destructive change, and because these are unstaged and uncommitted changes git won’t be able to help you recover the changes once they’ve been discarded!</p>

<h2 id="selectively-restoring-changes-from-another-branch-or-commit">Selectively restoring changes from another branch or commit</h2>

<p>The <code class="language-plaintext highlighter-rouge">git restore</code> command also lets you restore changes from another branch or commit by specifying the <code class="language-plaintext highlighter-rouge">--source=</code> flag. Combine this with the <code class="language-plaintext highlighter-rouge">--patch</code> flag and you can interactively choose the specific changes to restore to your current work tree:</p>

<pre class="terminal"><code>  $ git restore --source=branch-name --patch

  diff --git b/app/models/flow_definitions/neu.rb a/app/models/flow_definitions/neu.rb
  index cdabfe7a..28308244 100644
  --- b/app/models/flow_definitions/neu.rb
  +++ a/app/models/flow_definitions/neu.rb
  <span class="light-blue">@@ -42,10 +42,9 @@</span>
     step 'qualified-year' do
       radio :qualified_year do
  <span class="diff-red">-      option :'2024'
  -      option :'2023'
  -      option :'2022'
  -      option :'2021'</span>
  <span class="diff-green">+      MembershipApplication::Neu::QUALIFYING_YEARS.each do |year|
  +        option :"#{year}"
  +      end</span>

         divider

  <span class="diff-blue">(1/2) Apply this hunk to index and worktree [y,n,q,a,d,j,J,g,/,e,p,?]?</span>
</code></pre>

<p>I wil sometimes use this when I have a spike branch that I want to pull a subset of changes from to be tidied up and committed in my current branch.</p>

<h2 id="a-quick-note-on-git-restore-vs-checkout">A quick note on git restore vs checkout</h2>

<p>You may have noticed that the last two behaviours for discarding/restoring changes are also possible using <code class="language-plaintext highlighter-rouge">git checkout</code>. <a href="https://git-scm.com/docs/git-restore"><code class="language-plaintext highlighter-rouge">git restore</code></a> was <a href="https://public-inbox.org/git/xmqqy2zszuz7.fsf@gitster-ct.c.googlers.com/">introduced to Git in version 2.23</a> (alongside <a href="https://git-scm.com/docs/git-switch"><code class="language-plaintext highlighter-rouge">git switch</code></a>) in an effort to reduce the overloaded (and somewhat confusing) responsibilities placed on <code class="language-plaintext highlighter-rouge">git checkout</code>. Whilst it doesn’t look these features will be removed from <code class="language-plaintext highlighter-rouge">git checkout</code> <a href="https://github.com/git/git/blob/6a09c36371cbb902c573aee38d7cfd38f884f448/Documentation/BreakingChanges.txt">any time soon</a>, the more focused commands are easier to understand (and teach!), and arguably easier to use, at least assuming you don’t have years of muscle memory to overcome.</p>

<p>Thanks to <a href="https://ruby.social/@tom">Tom</a> and <a href="https://ruby.social/@bihi">Étienne</a> for the clarifying comments.</p>]]></content><author><name>Tekin Süleyman</name></author><category term="Git" /><category term="popular" /><summary type="html"><![CDATA[The --patch flag is handy for selectively staging changes in Git. But did you know it can also be used with other Git commands?]]></summary></entry><entry><title type="html">Appearance on the Maintainable Software podcast</title><link href="https://tekin.co.uk/2024/08/appearance-on-maintainable-podcast" rel="alternate" type="text/html" title="Appearance on the Maintainable Software podcast" /><published>2024-08-22T00:00:00+00:00</published><updated>2024-08-22T00:00:00+00:00</updated><id>https://tekin.co.uk/2024/08/appearance-on-maintainable-podcast</id><content type="html" xml:base="https://tekin.co.uk/2024/08/appearance-on-maintainable-podcast"><![CDATA[<p>I recently appeared on <a href="https://maintainable.fm/episodes/tekin-suleyman-balancing-complexity-and-team-size">the Maintainable Software podcast</a> with Robby Russell. It was fun chatting with Robby about some of the things that help keep software maintainable. <a href="https://maintainable.fm/episodes/tekin-suleyman-balancing-complexity-and-team-size">Check it out</a>!</p>]]></content><author><name>Tekin Süleyman</name></author><category term="popular" /><summary type="html"><![CDATA[Check out my recent appearance on the Maintainable Software podcast.]]></summary></entry><entry><title type="html">How to use introspection to discover what is exhausting your ActiveRecord connection pool</title><link href="https://tekin.co.uk/2023/08/introspecting-active-records-connection-pool" rel="alternate" type="text/html" title="How to use introspection to discover what is exhausting your ActiveRecord connection pool" /><published>2023-08-06T00:00:00+00:00</published><updated>2023-08-06T00:00:00+00:00</updated><id>https://tekin.co.uk/2023/08/introspecting-active-records-connection-pool</id><content type="html" xml:base="https://tekin.co.uk/2023/08/introspecting-active-records-connection-pool"><![CDATA[<p>This week I wrote about the reasons why you might need an <a href="/2023/07/active-record-connection-timeout-errors-with-puma">ActiveRecord connection pool larger than the number of configured Puma threads</a>.</p>

<p>Since then <a href="https://ruby.social/@bensheldon">Ben Sheldon</a> has pointed out that apps running embedded job workers (for example Sidekiq in <a href="https://github.com/sidekiq/sidekiq/wiki/Embedding">embedded mode</a>, GoodJob in <a href="https://github.com/bensheldon/good_job#execute-jobs-async--in-process">async mode</a> or <a href="https://github.com/brandonhilkert/sucker_punch">Sucker Punch</a>) will also be creating extra threads and therefor may need a larger thread pool.</p>

<p>In this follow-up post I’m going to describe the technique I used to uncover the source of the connection pool contention for the app I’m working on, and how you can do the same if your seeing mysterious <code class="language-plaintext highlighter-rouge">ActiveRecord::ConnectionTimeoutError</code> exceptions and don’t know where they’re coming from.</p>

<h2 id="uncovering-the-source-of-activerecord-connection-pool-contention">Uncovering the source of ActiveRecord connection pool contention</h2>

<p>These connection timeouts were a long-standing mystery in our app. Although they didn’t happen with great frequency, they were happening often enough to warrant some further digging rather than letting them become another broken window.</p>

<p>To figure out what was going on I employed some introspection on the connection pool. Whenever a thread asks for a connection from ActiveRecord, it is assigned one from the connection pool. The connection itself stores a reference to the assigned thread as its <code class="language-plaintext highlighter-rouge">owner</code>. We can inspect the assigned threads in the connection pool to learn a bit more about them:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="p">.</span><span class="nf">connection_pool</span><span class="p">.</span><span class="nf">connections</span><span class="p">.</span><span class="nf">map</span> <span class="k">do</span> <span class="o">|</span><span class="n">connection</span><span class="o">|</span>
  <span class="n">connection</span><span class="p">.</span><span class="nf">owner</span><span class="p">.</span><span class="nf">present?</span> <span class="p">?</span> <span class="n">connection</span><span class="p">.</span><span class="nf">owner</span><span class="p">.</span><span class="nf">inspect</span> <span class="p">:</span> <span class="s2">"[UNUSED]"</span>
<span class="k">end</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span>
</code></pre></div></div>

<p>You can try this in the rails console, although you’ll want to fire off a quick query first, otherwise there won’t be any connections assigned!</p>

<p>On the actual Puma web server, if connections are only assigned to Puma threads the output will look something like this:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#&lt;Thread:0x00007f0bbab3b740@puma srv tp 003 /app/.../gems/puma-6.2.2/lib/puma/thread_pool.rb:106 sleep&gt;
#&lt;Thread:0x00007f0bbab3b3d0@puma srv tp 004 /app/.../gems/puma-6.2.2/lib/puma/thread_pool.rb:106 run&gt;
#&lt;Thread:0x00007f0bbab3b8f8@puma srv tp 002 /app/.../gems/puma-6.2.2/lib/puma/thread_pool.rb:106 run&gt;
#&lt;Thread:0x00007f0bbab3bbc8@puma srv tp 001 /app/.../gems/puma-6.2.2/lib/puma/thread_pool.rb:106 sleep_forever&gt;
[UNUSED]
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">inspect</code> output tells us a bit about each thread. The key part is the path that points at the line of code where the thread was started, which in this case is inside the Puma thread pool <a href="https://github.com/puma/puma/blob/d79f59d69dd91cd1ea401ad5e9051e74b1ce0ebf/lib/puma/thread_pool.rb#L106">here</a>.</p>

<p>To narrow down the search for the mystery threads using up our connections I wired this code into our bug tracker such that we’d log this debug output whenever an <code class="language-plaintext highlighter-rouge">ActiveRecord::ConnectionTimeoutError</code> was raised:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">Bugsnag</span><span class="p">.</span><span class="nf">configure</span> <span class="k">do</span> <span class="o">|</span><span class="n">config</span><span class="o">|</span>
  <span class="n">config</span><span class="p">.</span><span class="nf">add_on_error</span><span class="p">(</span><span class="nb">proc</span> <span class="k">do</span> <span class="o">|</span><span class="n">event</span><span class="o">|</span>
    <span class="k">if</span> <span class="n">event</span><span class="p">.</span><span class="nf">errors</span><span class="p">.</span><span class="nf">first</span><span class="p">.</span><span class="nf">error_class</span> <span class="o">==</span> <span class="s2">"ActiveRecord::ConnectionTimeoutError"</span>
      <span class="n">connection_pool_info</span> <span class="o">=</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="p">.</span><span class="nf">connection_pool</span><span class="p">.</span><span class="nf">connections</span><span class="p">.</span><span class="nf">map</span> <span class="k">do</span> <span class="o">|</span><span class="n">connection</span><span class="o">|</span>
        <span class="n">connection</span><span class="p">.</span><span class="nf">owner</span><span class="p">.</span><span class="nf">present?</span> <span class="p">?</span> <span class="n">connection</span><span class="p">.</span><span class="nf">owner</span><span class="p">.</span><span class="nf">inspect</span> <span class="p">:</span> <span class="s2">"[UNUSED]"</span>
      <span class="k">end</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span>

      <span class="n">event</span><span class="p">.</span><span class="nf">add_metadata</span><span class="p">(</span><span class="ss">:app</span><span class="p">,</span> <span class="ss">:connection_pool_info</span><span class="p">,</span> <span class="n">connection_pool_info</span><span class="p">)</span>
    <span class="k">end</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<p>It didn’t take long for the culprit to materialise:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#&lt;Thread:0x00007f32eca13ad0@puma srv tp 002 /app/.../gems/puma-6.2.2/lib/puma/thread_pool.rb:106 sleep_forever&gt;
#&lt;Thread:0x00007f32f23cd6e8 /app/.../gems/actionpack-7.0.5.1/lib/action_controller/metal/live.rb:341 sleep&gt;
#&lt;Thread:0x00007f32eca12a40@puma srv tp 003 /app/.../gems/puma-6.2.2/lib/puma/thread_pool.rb:106 sleep_forever&gt;
#&lt;Thread:0x00007f32f2936288 /app/.../gems/actionpack-7.0.5.1/lib/action_controller/metal/live.rb:341 sleep&gt;
#&lt;Thread:0x00007f32eca13e40@puma srv tp 001 /app/.../gems/puma-6.2.2/lib/puma/thread_pool.rb:106 run&gt;
</code></pre></div></div>

<p>We can see that two of the connections are assigned to threads spawned by <code class="language-plaintext highlighter-rouge">ActionController::Metal::Live</code> <a href="https://github.com/rails/rails/blob/fabd0b5827a3af1f189d726fbc7669f9fbdeef5e/actionpack/lib/action_controller/metal/live.rb#L341">here</a>.</p>

<p>This was the missing piece of the puzzle. It didn’t take much tracing of code to discover that it was <code class="language-plaintext highlighter-rouge">ActiveStorage</code> proxy requests that spin up extra threads for streaming responses, and it was these threads that were putting the extra pressure on our connection pool and causing the sporadic timeout errors.</p>

<h2 id="introspection-is-good-actually">Introspection is good actually</h2>

<p>I wanted to write this post partly to serve as a pointer to others that might be seeing these connection timeouts, but also to make a broader point about the blurred lines between our code and the libraries our code depends on. Because when our app is running, it <em>all effectively becomes our code</em>. Getting comfortable diving into the source for gems you use will serve you well, and will sometimes be the only way to get to the origin of a strange bug, or unexpected behaviour.</p>

<p>One of my favourite ways to do this is using <a href="https://bundler.io/man/bundle-open.1.html"><code class="language-plaintext highlighter-rouge">bundle open</code></a>. It gets you to the literal gem code that your app will be using right there in your preferred editor, ready for exploring. You can even make changes to the gem code for a spot of <code class="language-plaintext highlighter-rouge">puts</code> debugging (although do so with care and always remember to undo your changes when you’re done!)</p>

<p>Aside: If you’re a VSCode user, you may want to set the <code class="language-plaintext highlighter-rouge">BUNDLER_EDITOR</code> environment variable to <code class="language-plaintext highlighter-rouge">"code -n"</code> to stop <code class="language-plaintext highlighter-rouge">bundle open</code> replacing your current project window.</p>]]></content><author><name>Tekin Süleyman</name></author><category term="Ruby &amp; Rails" /><summary type="html"><![CDATA[A short post on a technique you can use to see what is using your Rails app's ActiveRecord connections.]]></summary></entry><entry><title type="html">Why the advice to have a connection pool the same size as your Puma threads is (probably) wrong for you</title><link href="https://tekin.co.uk/2023/07/active-record-connection-timeout-errors-with-puma" rel="alternate" type="text/html" title="Why the advice to have a connection pool the same size as your Puma threads is (probably) wrong for you" /><published>2023-07-31T00:00:00+00:00</published><updated>2023-07-31T00:00:00+00:00</updated><id>https://tekin.co.uk/2023/07/active-record-connection-timeout-errors-with-puma</id><content type="html" xml:base="https://tekin.co.uk/2023/07/active-record-connection-timeout-errors-with-puma"><![CDATA[<p>The <a href="https://devcenter.heroku.com/articles/deploying-rails-applications-with-the-puma-web-server#database-connections">standard advice</a> goes: <a href="https://devcenter.heroku.com/articles/concurrency-and-database-connections#threaded-servers">set your Rails database’s connection pool to have as many connections as you have Puma threads</a>. The idea being that you should only need as many connections as you have concurrent threads. This advice is coming from a good place, as most of the time you will be constrained on the number of connections you have available to your database.</p>

<p>The nuance missing from that advice is that it assumes no additional threads are ever spawned during your apps operation!</p>

<p>Now although you might know your code inside and out and be 100% certain that you don’t create additional threads anywhere in your code, chances are Rails is creating additional threads without you realising it…</p>

<h2 id="an-aside-on-threads-and-activerecords-connection-pool">An aside on threads and ActiveRecord’s connection pool</h2>

<p>On a basic level, the way ActiveRecord’s connection pool works is that it assigns each thread that asks for a connection its <a href="https://github.com/rails/rails/blob/35a614c227620a62d7a2a242e375a43e7e2affc5/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb#L178-L185">own separate connection</a>, which the thread then releases once it’s finished. Using a pool like this means that many threads can be querying the database at the same time, so many more requests can be processed in parallel.</p>

<p>If your app’s Puma config sets  the maximum number of threads to 5, then you can normally expect there to be at most 5 threads asking for their own database connection, hence the advice to set the pool size to the same as size as the number of threads.</p>

<p>If however one of those threads creates another thread of its own, and that thread needs a database connection to hit the database, it will need its own connection. <strong>The spawned thread does not share the connection of the thread that spawned it</strong>.</p>

<p>So if we have threads that spawn their own threads, it’s possible to end up in a situation where there are many more threads wanting a connection then there are available connections, and you could end up seeing <code class="language-plaintext highlighter-rouge">ActiveRecord::ConnectionTimeoutError</code> exceptions being raised.</p>

<h2 id="where-your-rails-app-might-be-spinning-up-additional-threads-without-you-realising-it">Where your Rails app might be spinning up additional threads without you realising it</h2>

<p>So, back to your app. Even if you are not explicitly spawning your own threads, Rails itself could be spinning up additional threads without you realising. The main culprit here is <code class="language-plaintext highlighter-rouge">ActiveStorage</code>, or more specifically, <a href="https://edgeguides.rubyonrails.org/active_storage_overview.html#proxy-mode">ActiveStorage configured in proxy mode</a> (commonly used if you’re serving assets via a CDN).</p>

<h3 id="threads-created-by-activestorage">Threads created by ActiveStorage</h3>

<p>ActiveStorage’s two <a href="https://github.com/rails/rails/blob/main/activestorage/app/controllers/active_storage/blobs/proxy_controller.rb">proxy</a> <a href="https://github.com/rails/rails/blob/main/activestorage/app/controllers/active_storage/representations/proxy_controller.rb">controllers</a> return <em>streamed responses</em>, which (you guessed it) are <em>processed in their own threads!</em> So to handle a request for an <code class="language-plaintext highlighter-rouge">ActiveStorage</code> file via one of the proxy controllers, two threads will be called into action, both of which need their own connection from the ActiveRecord connection pool.</p>

<p>Normally (hopefully?) your app is processing requests fast enough for you to expect connections to be freed up from one of the other threads and become available for the streaming thread before a timeout occurs. And in this way, most of the time, the limited connections available in the pool can be shared between more threads than there are connections. The problems start if your app receives many successive/concurrent <code class="language-plaintext highlighter-rouge">ActiveStorage</code> proxy requests, each of which spin up an additional thread to stream a response, and they take a long time to complete their work and free up their connections; either because they’re doing expensive/slow work server-side (downloading a large file or processing an image representation for the first time), or they’re streaming the response to a slow client. At the time of writing at least, both threads hang on to their connection until the entire response is complete.</p>

<h3 id="threads-created-by-activerecords-load_async">Threads created by ActiveRecord’s load_async</h3>

<p>Another place Rails will spin up additional threads is with the new <a href="https://edgeapi.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-load_async">load_async</a> method. This was introduced in Rails 7 as a way to parallelise expensive database queries. It’s less likely that these are going to cause connection timeout errors as (again, hopefully?) the expensive queries aren’t so expensive that they hang onto connections for an excessive amount of time. That said, if you’re making heavy use of <code class="language-plaintext highlighter-rouge">load_async</code>, you could again end up in a situation where you have much higher demand for connections than there are available connections.</p>

<h2 id="so-what-should-you-do-about-all-this">So what should you do about all this?</h2>

<p>The upshot of all this is: if you’re making use of either <code class="language-plaintext highlighter-rouge">ActiveStorage</code> in proxy mode, or calling <code class="language-plaintext highlighter-rouge">load_async</code> in our app, you probably want a connection pool size that is higher than the number of configured Puma threads. The theoretical maximum number of connections you’ll need will be whatever Puma’s configured thread count is x 2, based on the assumption that each thread can potentially spawn one additional thread, but you might be able to get away with a smaller multiple depending on the performance and load characteristics of your app.</p>

<p>And if you’re close to reaching or exceeding the connection limit offered by your database, consider using a separate connection pool like <a href="http://www.pgbouncer.org">pgbouncer</a> (available as a <a href="https://github.com/heroku/heroku-buildpack-pgbouncer">Heroku buildpack</a>) to increase the number of connections you can configure.</p>

<h2 id="update-6-august-2023">Update (6 August 2023)</h2>

<p>There’s now a <a href="/2023/08/introspecting-active-records-connection-pool">follow-up post</a> outlining one of the techniques I employed to help figure out why our connection pool was being exhausted in our Rails app.</p>

<h2 id="update-19-september-2024">Update (19 September 2024)</h2>

<p>Ben Sheldon has written a great post explaining how to <a href="https://island94.org/2024/09/secret-to-rails-database-connection-pool-size">perfectly calculate your Rails database connection pool
size</a>. The TL;DR: just make it very big!</p>]]></content><author><name>Tekin Süleyman</name></author><category term="Ruby &amp; Rails" /><category term="popular" /><summary type="html"><![CDATA[Are you seeing ActiveRecord::ConnectionTimeoutErrors in your Rails app? You probably need a bigger connection pool (and here's why).]]></summary></entry><entry><title type="html">List your Git branches by recent activity</title><link href="https://tekin.co.uk/2021/11/listing-most-recent-git-branches" rel="alternate" type="text/html" title="List your Git branches by recent activity" /><published>2021-11-25T00:00:00+00:00</published><updated>2021-11-25T00:00:00+00:00</updated><id>https://tekin.co.uk/2021/11/listing-most-recent-git-branches</id><content type="html" xml:base="https://tekin.co.uk/2021/11/listing-most-recent-git-branches"><![CDATA[<p>Even if you’re diligent and regularly <a href="/2020/01/clean-up-your-git-branches-and-repositories">delete merged and stale
branches</a>
you may still find it hard to pick out a particular branch from the
alphabetically-sorted output of <code class="language-plaintext highlighter-rouge">git branch</code>. How about something
more useful, like seeing them listed based on their freshness?</p>

<p>The <code class="language-plaintext highlighter-rouge">git branch</code> command accepts a <code class="language-plaintext highlighter-rouge">--sort</code> option which we can use to
list our branches based on the last committer date:</p>

<pre class="terminal"><code>  <strong>$ git branch --sort=-committerdate</strong>
  * main
    redact-abandoned
    log-downloads
    i18n-lint
</code></pre>

<p>We can also use the <code class="language-plaintext highlighter-rouge">--format</code> option to include the exact time and
see just how fresh each branch is:</p>

<pre class="terminal"><code>  <strong>$ git branch --sort=-committerdate --format="%(committerdate)%09%(refname:short)"</strong>
  Thu Nov 25 10:29:48 2021 +0000  main
  Fri Nov 19 16:08:49 2021 +0000  redact-abandoned
  Fri Nov 19 16:04:11 2021 +0000  log-downloads
  Thu Nov 18 21:53:37 2021 +0000  i18n-lint
  Sun Jul 18 12:49:18 2021 +0100  without-routing-key-overrides
</code></pre>

<p>Or for something more friendly and easy-to-parse we can ask for a relative date:</p>

<pre class="terminal"><code>  <strong>$ git branch --sort=-committerdate --format="%(committerdate:relative)%09%(refname:short)"</strong>
  5 hours ago     main
  6 days ago      redact-abandoned
  6 days ago      log-downloads
  7 days ago      i18n-lint
  4 months ago    without-routing-key-overrides
</code></pre>

<p>That’s better! Let’s add this as a <code class="language-plaintext highlighter-rouge">git recent</code> alias to our Git config:</p>

<pre class="terminal"><code>  <strong>$ git config --global alias.recent 'branch --sort=-committerdate --format="%(committerdate:relative)%09%(refname:short)"'</strong>
</code></pre>

<p>Hat tip to <a href="https://twitter.com/tenderlove/status/1392957802163802112">Tenderlove</a>
for this particularly tasty snippet.</p>

<p>Here are some other Git related articles you might find useful:</p>

<ul>
  <li><a href="/2020/01/clean-up-your-git-branches-and-repositories">An alias for cleaning up your old/redundant Git branches</a></li>
  <li><a href="/2021/01/how-atomic-commits-make-you-a-better-coder">How focused commits make you a better coder</a></li>
  <li><a href="/2020/10/better-git-diff-output-for-ruby-python-elixir-and-more">Better Git diff output for Ruby, Python, Elixer, Go and more</a></li>
  <li><a href="/2020/06/jump-from-a-git-commit-to-the-pr-in-one-command">An alias for jumping from a commit SHA to the PR on GitHub</a></li>
  <li><a href="/2020/01/git-alias-for-amending-your-last-commit">An alias for amending your most recent commit</a></li>
</ul>]]></content><author><name>Tekin Süleyman</name></author><category term="Git" /><category term="popular" /><summary type="html"><![CDATA[Sometimes you just want to see your most recent branches. Here's a handy Git alias that will help you do just that.]]></summary></entry><entry><title type="html">How focused commits make you a better coder</title><link href="https://tekin.co.uk/2021/01/how-atomic-commits-make-you-a-better-coder" rel="alternate" type="text/html" title="How focused commits make you a better coder" /><published>2021-01-29T00:00:00+00:00</published><updated>2021-01-29T00:00:00+00:00</updated><id>https://tekin.co.uk/2021/01/how-atomic-commits-make-you-a-better-coder</id><content type="html" xml:base="https://tekin.co.uk/2021/01/how-atomic-commits-make-you-a-better-coder"><![CDATA[<p>One of the core practices I encourage in developers when joining a new team is shaping changes into small, focused, atomic commits. For folks that are used to committing code in a haphazard, laissez faire manner this is sometimes dismissed as pedantic fussiness: if the code works, it works and that’s what’s important! Well I strongly disagree. Here I want to present a few of the ways taking the time to shape small focused commits help you to be a more effective developer and ship better software.</p>

<p>First things first, let’s acknowledge the fact that doing this well does take a certain level of tooling knowledge and practice. That means getting comfortable selectively staging changes (<code class="language-plaintext highlighter-rouge">git add --patch</code>) and revising your working history as you go (<code class="language-plaintext highlighter-rouge">git rebase --interactive</code>). But once you get the hang of it, it becomes second nature and <em>not</em> doing it will start to feel a bit icky, like skipping test coverage for a new piece of code, or not brushing your teeth before bed.</p>

<h2 id="1-slowing-down">1. Slowing down</h2>

<p>As well as tooling and practice, it also take a level of discipline. That’s because to understand how a piece of work might be delivered as small focused changes, you have to slow down and think about how it could be sliced up and delivered iteratively. This is actually the first benefit in disguise! Figuring out a plan for the work that breaks it down in terms of small, iterative steps is a useful strategy for making a large or difficult problem manageable.</p>

<p>I personally do this with a checklist of tasks that I write down before starting a piece of work. I then update and adjust the list as I progress through the work and my understanding grows and changes. The tasks don’t always map one-to-one to commits, but they often do.</p>

<p><img src="/images/20210129/todo-list.jpg" alt="A text file containing a todo list of tasks" class="post-image" /></p>

<p><a href="https://twitter.com/tomstuart">@tomstuart</a> talks more about this and other strategies for breaking down large and difficult problems in his talk <a href="https://www.youtube.com/watch?v=TdBELZG0UMY">Get Off the Tightrope</a>.</p>

<h2 id="2-taking-small-steps">2. Taking small steps</h2>

<p>Whilst figuring out a series of small, digestible steps to tackle a problem can help get over the anxiety of a large or complex task, it’s not always possible to figure out the whole plan before you start; Sometimes you just need to take that first step. You might not be able to see the whole picture yet, but you see one small change that will get you a little closer. A small, focused commit is the perfect way to “bank” some progress, capturing any insight in the commit message, before continuing to chip away at the problem. Countless times I’ve found myself solving a seemingly complicated and difficult task this way, with only a rough idea of where I’m going, but edging ever closer to the solution one small commit at a time until <em>poof!</em>, the problem is solved.</p>

<h2 id="3-banking-your-progress">3. Banking your progress</h2>

<p>I love the idea of banking progress in neatly packaged focused commits. It’s a bit like reaching a save point in a video game. You feel a sense of progress. It gives you a perfect reset point if you happen to get lost (<code class="language-plaintext highlighter-rouge">git reset --hard HEAD</code>). It also allows you to unload what you’ve learnt (in the commit message) and reset your working memory, freeing up brain cycles to tackle the next part of the challenge.</p>

<h2 id="4-spotting-problems-early">4. Spotting problems early</h2>

<p>Making small, focused changes that you then wrap up in a commit also helps you get ahead of bugs and unintended side-effects. A banked commit is a perfect opportunity to trigger a build and make sure the change hasn’t broken something elsewhere, rather than waiting until all the work is finished and potentially face an avalanche of unforeseen failures that could have stemmed from any number of changes. Spotting and resolving a bug or issue is much easier when the surface area of the change is small and focused vs a 1000-line Hail Mary commit of unrelated changes. Which leads nicely into…</p>

<h2 id="5-easier-debugging">5. Easier debugging</h2>

<p>With small focused commits you unlock the full power of Git’s secret bug-smashing tool: <code class="language-plaintext highlighter-rouge">git bisect</code>. I’ve not had to use it very often, but when I have it’s felt like a super-power for finding the source of regressions and bugs. bisect will still work without small focused commits, just not quite as effectively.</p>

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">I’ve successfully taught another person `git bisect` and used it to find an obscure bug in minutes that surely would’ve taken hours/days to find otherwise. If you find yourself saying “this used to work…” in your code, use `git bisect`.</p>&mdash; Mitchell Hashimoto (@mitchellh) <a href="https://twitter.com/mitchellh/status/1352011054377697281?ref_src=twsrc%5Etfw">January 20, 2021</a></blockquote>

<p>More about git bisect over in <a href="https://git-scm.com/docs/git-bisect">the docs</a>.</p>

<h2 id="6-less-painful-merge-conflicts">6. Less painful merge conflicts</h2>

<p>This one only dawned on me recently: resolving merge conflicts becomes more straightforward when commits are small and focused. Perhaps I assumed I’d just got better at doing it (which is still partly true; the more you do it, the better you get at doing it) but on reflection I’d now attribute most of it to those small, focused commits. Because each commit is small and doing one thing, it’s more staightforward figuring out what the delta between the upstream change and the one being applied in the commit should be.</p>

<h2 id="7-writing-more-disposable-code">7. Writing more disposable code</h2>

<p>This one sounds weird, but changes that are shaped into focused atomic commits tend to be easier to revert cleanly, making code written that way easier to delete! You might need to do this whilst your still working on the feature, perhaps if you decide to change direction, or even much later in the future as the software evolves. As <a href="https://twitter.com/tef_ebooks">@tef_ebooks</a> puts it: <a href="https://programmingisterrible.com/post/139222674273/how-to-write-disposable-code-in-large-systems">Write code that is easy to delete</a>. Shaping changes into small and focused commits is one thing that will help you do that.</p>

<h2 id="8-bringing-forward-changes">8. Bringing forward changes</h2>

<p>Related to that, focused commits are also easier to pull out and use elsewhere. Maybe you’ve fixed a bug or performed a refactoring whilst still working on a larger piece of work. You can cherry-pick and ship those commits early to get the benefit now whilst also shrinking the size of your work-in-progress.</p>

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Spent a couple days working methodically through a Rails 6 upgrade, banking small, focused commits as we went. Made for calm, steady progress, with most of the commits shippable against Rails 5, shrinking the size and risk of the actual upgrade. Small, focused commits, always.</p>&mdash; Tekin Süleyman (@tekin) <a href="https://twitter.com/tekin/status/1352215564090015745?ref_src=twsrc%5Etfw">January 21, 2021</a></blockquote>

<h2 id="9-better-code-review">9. Better code review</h2>

<p>Saving one of my favourites for (almost) last, small focused commits lead to an infinitely better code review process, both for you and the reviewer. The reviewer gets the option to tackle the review in smaller commit-sized chunks, making it easier to review and less likely bugs will slip through.</p>

<p><img src="/images/20210129/pull-request-review.png" alt="A pull request review that reads: Really nice flow of commits, very easy to follow despite working in a messier area of the codebase. Everything looks good, have just left a few observations." class="post-image" /></p>

<p>For an example of what I mean, imagine trying to understand all the changes in this <a href="https://github.com/DFE-Digital/claim-additional-payments-for-teaching/pull/597">pull request</a> in one go versus reviewing the changes commit by commit, with the commit messages as guiding commentary.</p>

<p>The reviewer’s feedback can also be more targeted, and because the changes are in focused commits, chances are you’ll have a simpler time incorporating any feedback into the existing commits (for those of you that aren’t afraid of or against rewriting the history of an open PR to avoid those “PR feedback” commits that add noise and distractions to the history).</p>

<h2 id="10-a-searchable-history-for-future-code-archaeologists">10. A searchable history for future code archaeologists</h2>

<p>I won’t go into this one too much as I cover it extensively in <a href="/2019/02/a-talk-about-revision-histories">A Branch in time (a story about revision histories)</a>, but small focused commits with well-written commit messages make the software more maintainable long-term as they provide a commentary to the code’s evolution that <a href="/2020/11/patterns-for-searching-git-revision-histories">can be searched</a> by future developers to better understand the software and the way it has changed over time.</p>

<h2 id="further-exploration">Further exploration</h2>

<p>I’ll be writing more on the specific techniques that you can use to shape focused commits in the future. In the meantime if you want to explore these ideas further I’d recommend you check out the following talks:</p>

<ul>
  <li><a href="/2019/02/a-talk-about-revision-histories">A Branch in time (a story about revision histories)</a> a talk I’ve done illustrating some of these ideas</li>
  <li><a href="https://www.youtube.com/watch?v=mE8DZUfhdm4">Simplify writing code with deliberate commits</a> by <a href="https://twitter.com/joelchippindale">@joelchippindale</a></li>
  <li><a href="https://www.youtube.com/watch?v=FQ4IdcrOUz0">Getting more from Git</a> by <a href="https://twitter.com/alicebartlett">@alicebartlett</a></li>
</ul>

<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>]]></content><author><name>Tekin Süleyman</name></author><category term="Git" /><category term="popular" /><summary type="html"><![CDATA[Ten reasons shaping small and focused commits will help you be a more effective developer and ship better software.]]></summary></entry><entry><title type="html">Why Git blame sucks for understanding WTF code (and what to use instead)</title><link href="https://tekin.co.uk/2020/11/patterns-for-searching-git-revision-histories" rel="alternate" type="text/html" title="Why Git blame sucks for understanding WTF code (and what to use instead)" /><published>2020-11-13T00:00:00+00:00</published><updated>2020-11-13T00:00:00+00:00</updated><id>https://tekin.co.uk/2020/11/patterns-for-searching-git-revision-histories</id><content type="html" xml:base="https://tekin.co.uk/2020/11/patterns-for-searching-git-revision-histories"><![CDATA[<p>You’re happily working your way through a codebase when you happen upon some code that makes you stop and think: What the…!? Maybe it’s a method that’s doing something surprising. Or perhaps it’s doing something completely unsurprising, it’s just doing it in a surprising way.</p>

<p><img src="/images/20201113/math-lady.jpg" alt="" class="post-image" /></p>

<h2 id="git-blame-to-the-rescue">Git blame to the rescue?</h2>

<p>When this happens you might instinctively reach for <code class="language-plaintext highlighter-rouge">git blame</code> to help you figure out what’s going on. After all, <code class="language-plaintext highlighter-rouge">git blame</code> gives you the commit that most recently touched the line, and often that’s enough to point you in the right direction. But often it isn’t and you’re none the wiser. That’s because:</p>

<ul>
  <li><strong>git blame is too coarse</strong>: it reports against the whole line. If the most recent change isn’t related to the part of the line you’re interested, you’re out of luck.</li>
  <li><strong>git blame is too shallow</strong>: it only reports a single change; the most recent one. The story of the particular piece of code you’re interested in may have evolved over several commits.</li>
  <li><strong>git blame is too narrow</strong>: it only considers the file you are running blame against. The code you are interested in may also appear in other files, but to get the relevant commits on those you’ll need to run blame several times.</li>
</ul>

<p>If you only use <code class="language-plaintext highlighter-rouge">git blame</code> you’re limiting yourself to a one-dimensional perspective of the code you’re trying to understand. Wouldn’t it be better to view things in 3D!?</p>

<h2 id="a-more-powerful-way-to-trace-the-history-of-wtf-code">A more powerful way to trace the history of WTF code</h2>

<p>Thankfully Git has some pretty powerful search tools built right in. Let’s take a closer look at some of the tools at our disposal.</p>

<h2 id="find-all-commits-containing-a-particular-piece-code">Find all commits containing a particular piece code</h2>

<p>If <code class="language-plaintext highlighter-rouge">git blame</code> is entry-level history search, <code class="language-plaintext highlighter-rouge">git log -S</code> (also known as “the pickaxe”) is how you take things to the next level. It lets you search for all commits that contain a given string:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>git log <span class="nt">-S</span> <span class="s2">"method_name"</span>
</code></pre></div></div>

<p>This will return all the commits that contain <code class="language-plaintext highlighter-rouge">method_name</code> across the entire repository, giving you a bespoke history of a piece of code across both multiple commits and files. You can use the pickaxe to examine the entire history of a particular snippet: all the places it’s been used; how it’s usage has changed; if it’s moved file; when it was first introduced, and more.</p>

<h2 id="see-the-diffs-alongside-the-commit-messages">See the diffs alongside the commit messages</h2>

<p>If you include the <code class="language-plaintext highlighter-rouge">-p</code> option (short for <code class="language-plaintext highlighter-rouge">--patch</code>) you get the full diff alongside the commit messages, giving you even more context and making it easier to spot the relevant changes:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git log -S "method_name" -p
</code></pre></div></div>

<h2 id="find-the-commit-that-first-added-some-code">Find the commit that first added some code</h2>

<p>Say you want to find the first commit that introduced a particular class, method or snippet of code. You can use the pickaxe combined with the <code class="language-plaintext highlighter-rouge">--reverse</code> option to get the commits in reverse-chronological order so the commit where the code first appears is listed at the top:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git log -S "method_name" -p --reverse
</code></pre></div></div>

<h2 id="find-when-a-piece-of-code-was-deleted">Find when a piece of code was deleted</h2>

<p>Because the pickaxe search finds both additions and deletions, you can even use it to find code that has since been deleted:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git log -S "deleted code" -p
</code></pre></div></div>

<p>Assuming the snippet no longer exists in the codebase the first commit returned will be the one where it was removed.</p>

<h2 id="limit-the-scope-of-the-search">Limit the scope of the search</h2>

<p>Because we’re using <code class="language-plaintext highlighter-rouge">git log</code> you can also provide a path to focus the search to a given file or folder:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git log -S "some code" -p app/models/user.rb
</code></pre></div></div>

<h2 id="search-deeper-with-a-regular-expression">Search deeper with a regular expression</h2>

<p>I mostly stick to the pickaxe because I find working with regular expressions cumbersome, but if you want to perform a more advanced search using a regular expression you can do so with <code class="language-plaintext highlighter-rouge">-G</code> instead:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git log -G "REGEX HERE"
</code></pre></div></div>

<p>It’s also worth noting that searching with <code class="language-plaintext highlighter-rouge">-S</code> has a subtle limitation that <code class="language-plaintext highlighter-rouge">-G</code> does not: <code class="language-plaintext highlighter-rouge">-S</code> only shows commits that either added or removed the search string. If the string you are searching for moved within the same file but was otherwise unchanged, <code class="language-plaintext highlighter-rouge">-S</code> won’t include that commit, whereas <code class="language-plaintext highlighter-rouge">-G</code> will.</p>

<h2 id="search-the-commit-messages-themselves">Search the commit messages themselves</h2>

<p>Sometimes searching for literal code is too narrow, or the information you’re after isn’t actually in the code but in the commit messages themselves. In which case you can use <code class="language-plaintext highlighter-rouge">--grep</code> to search commit messages instead:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git log --grep "commit message search"
</code></pre></div></div>

<h2 id="in-summary">In summary</h2>

<p>Next time some code has you puzzled and you want to understand more about it, look beyond <code class="language-plaintext highlighter-rouge">git blame</code> and dig deeper into the history using the pickaxe and friends:</p>

<ul>
  <li>Find the entire history of a snippet of code with <code class="language-plaintext highlighter-rouge">git log -S</code></li>
  <li>Include <code class="language-plaintext highlighter-rouge">-p</code> to see the diff as well as the commit messages</li>
  <li>Include <code class="language-plaintext highlighter-rouge">--reverse</code> to see the commit that introduced the code listed first</li>
  <li>Scope search to specific folders or files by including a path</li>
  <li>Search with a regular expression using <code class="language-plaintext highlighter-rouge">git log -G</code></li>
  <li>Search commit messages using <code class="language-plaintext highlighter-rouge">git log --grep</code></li>
</ul>

<h2 id="epilogue">Epilogue</h2>

<p>Of course searching a codebase’s history will only be fruitful if the history itself is helpful. If the history hasn’t been constructed with atomic commits that have useful commit messages, searching it will likely be a frustrating and unhelpful experience.</p>

<p>To learn more about the how and why of putting together more useful histories, check out <a href="/2019/02/a-talk-about-revision-histories">A Branch in Time (a talk about revision histories)</a>.</p>]]></content><author><name>Tekin Süleyman</name></author><category term="Git" /><category term="popular" /><summary type="html"><![CDATA[Here's a more powerful way to look back through the history and figure out what's going on with strange and surprising code.]]></summary></entry></feed>