<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title></title>
    <description>Rodrigo Panachi&apos;s tech blog</description>
    <link>https://rpanachi.com/</link>
    <atom:link href="https://rpanachi.com/feed.xml" rel="self" type="application/rss+xml" />
    <pubDate>Wed, 24 Jun 2026 20:21:20 +0000</pubDate>
    <lastBuildDate>Wed, 24 Jun 2026 20:21:20 +0000</lastBuildDate>
    <generator>Jekyll v4.4.1</generator>
    
      <item>
        <title>The Last Layer Standing</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;“It is unsinkable. God himself could not sink this ship.”&lt;br /&gt;
— Cal Hockley, &lt;em&gt;Titanic&lt;/em&gt; (1997)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After more than two decades in software, I’m probably closer to the end of my career than the beginning.&lt;/p&gt;

&lt;p&gt;Lately I keep wondering what software will even look like by then. AI has already changed everything, and it’s only getting started. So what comes after?&lt;/p&gt;

&lt;p&gt;When I want to imagine the future, I look at my past. I’ve been doing this long enough to have watched the industry tear itself down and rebuild from scratch more than once. The details change every time. The pattern doesn’t. Every era is certain its dominant platform is unsinkable, and every era has been wrong.&lt;/p&gt;

&lt;h2 id=&quot;the-industry-never-sits-still&quot;&gt;The industry never sits still&lt;/h2&gt;

&lt;p&gt;When I started writing code, the internet didn’t exist yet, not for most of us, anyway. The industry ran on desktop software: office suites, productivity tools, business systems, ERPs, CRMs. You shipped on floppies and CDs, and a “deploy” meant someone installing it by hand.&lt;/p&gt;

&lt;p&gt;And then came the internet. Everything changed. The client-server model, a new programming paradigm, a new kind of usability, an entire category of problems nobody had solved yet. But the same internet that broke everything also connected us: suddenly we could share ideas, read each other’s articles, steal each other’s tricks, and ask strangers for help. We learned faster because we learned together.&lt;/p&gt;

&lt;p&gt;Then infrastructure became the bottleneck. Running software meant buying racks of expensive hardware and hiring a dedicated team to babysit it. It was slow, it was costly, and it didn’t scale.&lt;/p&gt;

&lt;p&gt;And then came AWS. Everything changed again. Almost overnight, owning hardware stopped being an advantage and started to look like a liability. A small team could suddenly rent world-class infrastructure for pocket change, and a whole class of scalability problems vanished behind a single click.&lt;/p&gt;

&lt;p&gt;And then came the SaaS economy. Why build authentication, payments, search, or messaging yourself when you could rent each one and wire them together in an afternoon? Whole categories of software that used to take a team a year shrank to a few lines of integration and a monthly invoice. Building started to look less like engineering from scratch and more like assembling parts someone else maintained.&lt;/p&gt;

&lt;p&gt;Notice the rhythm. Every decade or so, something arrives that resets the board. Each time, the skills that felt essential become optional, and the ones that felt optional become survival.&lt;/p&gt;

&lt;h2 id=&quot;and-then-came-ai&quot;&gt;And then came AI&lt;/h2&gt;

&lt;p&gt;Before, learning to do something new took weeks. You picked up a new language or framework, read a mountain of documentation, fumbled through examples, broke things, fixed them, and two or three weeks later you had that fancy integration working. Today I describe it in a paragraph and have a working draft in thirty minutes.&lt;/p&gt;

&lt;p&gt;That speed-up isn’t a one-off. It shows up everywhere:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A CRUD app that used to need a team of five (backend, frontend, database, a little DevOps, someone to glue it all together) can now be scaffolded by one person in an afternoon.&lt;/li&gt;
  &lt;li&gt;Boilerplate that took a day writes itself in seconds: models, migrations, endpoints, forms, validation, tests.&lt;/li&gt;
  &lt;li&gt;The documentation safari (twelve browser tabs and a pilgrimage to Stack Overflow) collapses into a single question.&lt;/li&gt;
  &lt;li&gt;Translating a service from one language to another, generating tests for legacy code, drafting the first version of a CI/CD pipeline: all of it, faster and cheaper than ever.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The economics are simply different now. Work that justified a team and a quarter can be done by one experienced person in a sprint.&lt;/p&gt;

&lt;h2 id=&quot;software-is-becoming-a-commodity&quot;&gt;Software is becoming a commodity&lt;/h2&gt;

&lt;p&gt;Follow the trend to its end and the conclusion is uncomfortable but hard to dodge: the production of code can be almost entirely automated. Not assisted — automated. Be precise about what that means, though: it’s the &lt;em&gt;writing&lt;/em&gt; of code that gets automated, not the deciding of &lt;em&gt;what&lt;/em&gt; to write. Each wave so far abstracted away a layer we used to sweat over, and the last layer standing is the code itself.&lt;/p&gt;

&lt;p&gt;We’re already partway there. A non-technical person can ship a real feature just by describing what they want, but only because a developer built the machine that makes it possible: the architecture, the infrastructure, the security model, the guardrails. Someone defined the rules of the game so others could play it without knowing them. Soon even that layer gets abstracted away. The same way AWS turned hardware into something you rent by the minute, building software becomes a feature of a platform: you describe the rules, and it writes, tests, and runs the code underneath. If that sounds far-fetched, remember that “rent a supercomputer with a credit card” sounded far-fetched too.&lt;/p&gt;

&lt;p&gt;And the early versions already exist. Cursor, Replit, Lovable, v0, Claude Code: these are crude, first-generation sketches of that platform, and they’re improving faster than anything I’ve watched in twenty years. They already scaffold a working app, wire up a database, and fix their own errors from a plain-English prompt; the things they still get wrong are shrinking release by release, not decade by decade. Each iteration eats a little more of the work that used to require someone who knew the syntax by heart. Extrapolate that curve (not to magic, just to “considerably better than today”) and a platform that writes, tests, and runs its own code stops reading like science fiction and starts reading like the next obvious thing to rent.&lt;/p&gt;

&lt;p&gt;And no company gets to sit this one out. Every one of those waves left bodies on the path: outfits that owned their era and were too comfortable, too slow, or too afraid to follow it into the next. Borland owned developer tools and dissolved into a footnote; Kodak invented the digital camera and then buried it. The rule never changes: when the ground moves, you move with it or you become history. This wave won’t spare anyone who stands still either.&lt;/p&gt;

&lt;h2 id=&quot;so-are-developers-history&quot;&gt;So, are developers history?&lt;/h2&gt;

&lt;p&gt;That’s the companies. But what about us, the people who actually write the software? If building really can be automated, is the developer, as we know the role, about to become history?&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;code-typist&lt;/em&gt; version of the job is. The person whose value was mostly turning tickets into syntax is in for a hard decade. That part of the work is exactly what the machine is best at.&lt;/p&gt;

&lt;p&gt;But “developer” was never really about typing. The new developer is the person who uses creativity, judgment, and tools (AI included) to create value and solve real problems. Does that person need to be deeply technical? Well… &lt;em&gt;it depends&lt;/em&gt; (says the senior developer).&lt;/p&gt;

&lt;p&gt;Because here’s the thing the hype skips: there is an ocean of legacy out there. Gigantic codebases, decades old, quietly running the world’s banks, hospitals, logistics, and governments. Nobody is going to throw all of that away and migrate to some shiny describe-it-and-it-appears platform. Some of them &lt;em&gt;can’t&lt;/em&gt;. When the software &lt;em&gt;is&lt;/em&gt; the business, you don’t get to start over.&lt;/p&gt;

&lt;p&gt;Those systems need people who actually understand them: experienced, technical, stubborn people who can read a mess, hold the whole thing in their head, and make the call.&lt;/p&gt;

&lt;p&gt;So no, skilled people don’t disappear. The seats reserved for genuinely capable professionals are never empty, because businesses still need humans to run them. What disappears is the comfortable middle: the role that was only ever about producing code.&lt;/p&gt;

&lt;p&gt;There’s a catch I won’t gloss over, though. That comfortable middle was also the &lt;em&gt;path in&lt;/em&gt;: the rung where juniors used to climb from turning tickets into syntax up to making real calls. Automate the bottom of the ladder and you have to ask how anyone is supposed to reach the top of it. I don’t have a clean answer. The honest version is that the way in gets narrower and steeper: the people who make it will be the ones who jump straight to judgment instead of spending years earning it one ticket at a time. Pretending the senior seats stay full without asking who gets to fill them would be dishonest.&lt;/p&gt;

&lt;h2 id=&quot;what-ai-still-cant-do&quot;&gt;What AI still can’t do&lt;/h2&gt;

&lt;p&gt;It’s worth being concrete about why those seats stay filled. For all its speed, AI still can’t do the things the job actually rewards.&lt;/p&gt;

&lt;p&gt;It doesn’t know &lt;em&gt;what&lt;/em&gt; to build. Hand it a vague, contradictory, half-formed idea (which is what real requirements always are) and it can’t tell you which parts matter, which are wrong, and which the stakeholder forgot to mention. That’s still a human problem.&lt;/p&gt;

&lt;p&gt;It doesn’t understand &lt;em&gt;why&lt;/em&gt;. It has no model of your business, your customers, your constraints, or the three years of decisions that explain why the code looks the way it does. It optimizes the question you asked, not the one you should have asked.&lt;/p&gt;

&lt;p&gt;It drowns in real legacy. A clean greenfield CRUD is one thing. A fifteen-year-old codebase full of implicit rules, undocumented quirks, and load-bearing hacks is another. AI will confidently “fix” something and quietly break three things you didn’t know were connected.&lt;/p&gt;

&lt;p&gt;It produces plausible-but-wrong code. The output always &lt;em&gt;looks&lt;/em&gt; right. Telling the difference between code that works and code that merely compiles still takes someone who actually understands the system. That someone is you.&lt;/p&gt;

&lt;p&gt;And it doesn’t carry responsibility. When the deploy takes down production at 2 a.m., the AI doesn’t get paged. It doesn’t make trade-offs it can be held to, doesn’t navigate the politics of a reorg, doesn’t decide what risk is acceptable. Accountability has no API.&lt;/p&gt;

&lt;p&gt;This is the gap. And it’s exactly where the value moved.&lt;/p&gt;

&lt;h2 id=&quot;the-last-layer&quot;&gt;The last layer&lt;/h2&gt;

&lt;p&gt;Strip away the specifics, and the second half comes down to four things I try not to forget, and what each one means for what I’m actually going to do over the next ten or fifteen years.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI is a tool.&lt;/strong&gt; A genuinely powerful one, but a tool, in the same lineage as the compiler, the cloud, and the framework. It’s the next chapter, not the last word. So I won’t fight the tide. I’ll surf it. I intend to use AI harder than anyone around me, mastering it the way I once mastered the compiler and the cloud, until it’s an extension of my hands and I’m the one shipping what used to take five.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tools exist so that someone can make something.&lt;/strong&gt; Nobody remembers the hammer; they remember the house. The value lived in what you built and the problem you solved, never in the instrument. So I’ll stay glued to the outcome, not the code: closer to the business, the customer, and the money, the context no model can infer from a ticket. Whoever understands the &lt;em&gt;problem&lt;/em&gt; best is the last to be automated away.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every tool has to be mastered.&lt;/strong&gt; The developers who thrived through each shift weren’t the ones with the most to protect; they were the ones who learned the new thing fastest. The ones who refused, who insisted the old way was the only &lt;em&gt;real&lt;/em&gt; way, became the bodies on the path. So I’ll move up the stack deliberately, putting my hours where AI is weakest: architecture, system design, the trade-offs and the calls that carry consequences. Deciding &lt;em&gt;what&lt;/em&gt; to build and &lt;em&gt;why&lt;/em&gt; is the job now; the &lt;em&gt;how&lt;/em&gt; is becoming a prompt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read the signals and adapt.&lt;/strong&gt; Watch the environment. Notice what’s becoming a commodity and move before the floor you’re standing on disappears. Clean greenfield is the first thing to go, so I’ll go the other way: toward the gnarly legacy, the mission-critical, the can’t-just-restart-it systems where someone has to understand the whole machine and answer for it when it breaks. Relevance isn’t a position you hold; it’s something you keep earning.&lt;/p&gt;

&lt;p&gt;None of this is guaranteed. Nothing is, anymore. But a plan beats hoping the tornado skips your house. “God himself could not sink this ship,” they said, and every unsinkable ship belongs to someone who stopped watching the water. Borland was unsinkable once. So was Kodak.&lt;/p&gt;

&lt;p&gt;I’ve watched this industry demolish and rebuild itself several times now, and the lesson is always the same. The technology that arrives isn’t what kills you. Standing still is. Each wave abstracts away another layer, and one day it will come for the code itself, but the last layer standing was never the code. It’s the person who knows what to build, why it matters, and who answers for it when it breaks. That layer is you.&lt;/p&gt;

&lt;p&gt;I’ve had to reinvent how I work every decade. Here we go again.&lt;/p&gt;
</description>
        <pubDate>Wed, 24 Jun 2026 00:00:00 +0000</pubDate>
        <link>https://rpanachi.com/the-last-layer-standing</link>
        <guid isPermaLink="true">https://rpanachi.com/the-last-layer-standing</guid>
        
        
        <category>ai</category>
        
        <category>software engineering</category>
        
        <category>career</category>
        
        <category>thoughts</category>
        
      </item>
    
      <item>
        <title>How to not destroy your production database</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;“Data is a precious thing and will last longer than the systems themselves.”&lt;br /&gt;
— Tim Berners-Lee&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;tldr&quot;&gt;TL;DR&lt;/h2&gt;

&lt;p&gt;Keep data changes out of migration files. Make every data script idempotent, batched, and dry-run by default — and never trust an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ALTER&lt;/code&gt; you haven’t read. A bad deploy can be reverted; an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UPDATE&lt;/code&gt; without a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WHERE&lt;/code&gt; clause cannot.&lt;/p&gt;

&lt;p&gt;Over the years I’ve collected a fair share of scars — what worked, what didn’t, and the mistakes that were entirely avoidable. What follows are the practices that came out of them: the kind that keep you from being paged at 3 a.m. or frantically restoring a database backup while production is on fire.&lt;/p&gt;

&lt;p&gt;The rules below are stack-agnostic. The examples use plain SQL (MySQL/PostgreSQL flavors noted where they differ) and pseudocode — adapt the syntax to your framework, keep the rules.&lt;/p&gt;

&lt;h2 id=&quot;keep-migrations-schema-only&quot;&gt;Keep migrations schema-only&lt;/h2&gt;

&lt;p&gt;Migration files exist to evolve the schema. &lt;strong&gt;They must not contain &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;INSERT&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UPDATE&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DELETE&lt;/code&gt;, backfills or seeding of reference rows.&lt;/strong&gt; This is the anti-pattern:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;-- migration: add_status_to_orders&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orders&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ADD&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;COLUMN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;VARCHAR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;-- BAD: data change smuggled into a schema migration&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orders&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;SET&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;pending&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Looks harmless, works fine in development, and fails in production in several independent ways:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Your migration may not run where you think it does.&lt;/strong&gt; Branching platforms (PlanetScale, for example) apply schema changes on an isolated copy and merge only the schema back — data written by the migration is silently lost.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;It couples a slow operation to a fast one.&lt;/strong&gt; A backfill over millions of rows can take hours, hold locks, stall replication, or get killed by the pipeline’s timeout halfway through.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;A failure mid-data-step corrupts your migration state.&lt;/strong&gt; MySQL implicitly commits around DDL, so the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ALTER&lt;/code&gt; can’t be rolled back when the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UPDATE&lt;/code&gt; dies. PostgreSQL’s DDL is transactional — but only if your migration tool actually wraps the migration in one, and a long backfill should never live inside one transaction anyway.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;It can’t be batched, throttled, resumed or dry-run.&lt;/strong&gt; All the safety tooling from the next two sections is unavailable inside a migration file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The correct shape is a three-step pattern you’ll see again in this post: &lt;strong&gt;add nullable → backfill via script → tighten&lt;/strong&gt;.&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;-- migration 1: schema only&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orders&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ADD&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;COLUMN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;VARCHAR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;-- data change: separate idempotent script (next section)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;-- migration 2, days later, after the backfill is verified:&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orders&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;MODIFY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;COLUMN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;VARCHAR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;pending&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;-- MySQL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The column &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DEFAULT&lt;/code&gt; handles &lt;em&gt;new&lt;/em&gt; rows from the moment the schema lands; the backfill handles &lt;em&gt;existing&lt;/em&gt; rows; the tightening migration runs only when both are true.&lt;/p&gt;

&lt;p&gt;And no, lookup tables (countries, roles, currencies) are not an exception. Seeders handle fresh environments; a one-time idempotent script introduces them into existing ones. Both are versioned code; neither lives inside a migration.&lt;/p&gt;

&lt;h2 id=&quot;make-data-changes-idempotent-rerunnable-scripts&quot;&gt;Make data changes idempotent, rerunnable scripts&lt;/h2&gt;

&lt;p&gt;Any change to data — backfill, correction, deletion — is a standalone command in your framework’s task runner, versioned in the repo and code-reviewed like everything else. Two things, no exceptions:&lt;/p&gt;

&lt;h3 id=&quot;it-must-be-safe-to-run-twice&quot;&gt;It must be safe to run twice&lt;/h3&gt;

&lt;p&gt;Retries, partial failures and overlapping executions happen. Pick an idempotency mechanism and test the re-run: execute it twice in staging; the second run must change zero rows.&lt;/p&gt;

&lt;p&gt;Naturally idempotent predicates — the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WHERE&lt;/code&gt; selects only un-migrated rows, so each run processes the remainder:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orders&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;SET&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;pending&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Upserts for inserting reference rows (requires a unique key on the natural identifier):&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;-- PostgreSQL&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;roles&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;auditor&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Auditor&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CONFLICT&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DO&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;SET&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EXCLUDED&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;-- MySQL 8.0.19+&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;roles&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;`key`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;auditor&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Auditor&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DUPLICATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;An execution ledger for scripts whose effect can’t be detected from the data itself: a tiny table recording which named scripts already ran; the script checks it and exits early.&lt;/p&gt;

&lt;h3 id=&quot;it-must-be-batched&quot;&gt;It must be batched&lt;/h3&gt;

&lt;p&gt;A single &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UPDATE&lt;/code&gt; over millions of rows holds locks for the full duration, bloats the undo log and stalls replicas. Process in batches over an indexed key:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;BATCH = 1000
last_id = checkpoint.load() or 0

loop:
    ids = sql(&quot;SELECT id FROM orders
               WHERE id &amp;gt; :last_id AND status IS NULL
               ORDER BY id LIMIT :batch&quot;, last_id, BATCH)
    if ids is empty: break

    within transaction:
        sql(&quot;UPDATE orders SET status = &apos;pending&apos;
             WHERE id IN (:ids)
               AND status IS NULL&quot;, ids)   # repeat the predicate on the write!

    last_id = max(ids)
    checkpoint.save(last_id)               # crash here → resume, not restart
    progress.log(last_id, rows=len(ids))
    sleep(throttle)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Why each piece matters:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Repeat the predicate on the write.&lt;/strong&gt; Live traffic may have set a row to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&apos;paid&apos;&lt;/code&gt; between your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SELECT&lt;/code&gt; and your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UPDATE&lt;/code&gt; — without &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AND status IS NULL&lt;/code&gt; on the write, you clobber it back.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Keyset pagination, never &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OFFSET&lt;/code&gt;.&lt;/strong&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OFFSET&lt;/code&gt; re-scans skipped rows on every batch and skips or repeats rows when the data shifts underneath it — which is exactly what a backfill does.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;One transaction per batch.&lt;/strong&gt; A multi-hour transaction is an outage waiting for a deadlock.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Checkpoint, throttle, log progress.&lt;/strong&gt; A crash at row 4,000,000 resumes instead of restarting; replicas keep up; “is it stuck?” becomes a glance instead of a guess.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Run it manually, per environment, after the schema migration has landed — on purpose, watching it run. Never as a hidden deploy side effect.&lt;/p&gt;

&lt;h2 id=&quot;default-to-dry-run&quot;&gt;Default to dry-run&lt;/h2&gt;

&lt;p&gt;Every script that deletes or bulk-modifies data supports a dry-run mode — and dry-run is the default. Mutation requires an explicit opt-in:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;purge_abandoned_carts [--execute] [--max-affected=N]

  affected = count rows matching the predicate
  print(&quot;would delete {affected} rows; sample:&quot;)
  print(first 10 matching rows)

  if not --execute:
      print(&quot;DRY RUN — re-run with --execute to apply&quot;); exit

  if affected &amp;gt; --max-affected (default 50k):
      abort(&quot;affected rows exceed safety ceiling&quot;)

  ... batched deletion as in the previous section ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Safe by default.&lt;/strong&gt; The flag enables danger (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--execute&lt;/code&gt;), never disables it. Forgetting a flag should always land you on the safe side.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Counts AND a sample.&lt;/strong&gt; The count tells you the magnitude; ten concrete rows tell you whether the predicate selects what you &lt;em&gt;believe&lt;/em&gt; it selects. Most data incidents are a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WHERE&lt;/code&gt; clause that meant something else.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;A sanity ceiling.&lt;/strong&gt; If you expect ~10k rows and the script computes 4 million, that’s a bug in the predicate, not a bigger job.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the part everybody skips: a dry run predicts the change, but only a restore path lets you undo it. Confirm a snapshot covers the affected tables — and know the restore &lt;em&gt;time&lt;/em&gt;; a backup you can’t restore within tolerance isn’t a safety net.&lt;/p&gt;

&lt;p&gt;For targeted operations: archive the rows first.&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_archive_2026_06_cart_purge&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;carts&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;abandoned&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;updated_at&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cutoff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That one table makes the operation reversible in minutes instead of via a full restore. Drop it after an agreed retention period.&lt;/p&gt;

&lt;p&gt;Protip: save the dry-run output (count + sample) in the PR or ticket &lt;em&gt;before&lt;/em&gt; executing. Write the expectation down first.&lt;/p&gt;

&lt;h2 id=&quot;dont-alter-columns-in-place&quot;&gt;Don’t ALTER columns in place&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Never change a column’s type in place.&lt;/strong&gt; Add a new column, migrate onto it, and once everything has stabilized — every instance writing and reading the new one — drop the old. The safe operations are the additive ones: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ADD COLUMN&lt;/code&gt; (nullable) is cheap, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DROP COLUMN&lt;/code&gt; is destructive but safe &lt;em&gt;once nothing reads the column&lt;/em&gt;, which is exactly why it ships last and on its own. The moves in between — a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MODIFY&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RENAME&lt;/code&gt; on the column your whole fleet and every replica are reading right now — look like one-liners, but their blast radius is the entire table and every running instance at once.&lt;/p&gt;

&lt;h3 id=&quot;change-columns-additively-not-in-place&quot;&gt;Change columns additively, not in place&lt;/h3&gt;

&lt;p&gt;The “safe rename” is a sequence, not a statement — and the same sequence covers any column change (retype, widen, split, merge), not just renames:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;   (renaming customer_code → customer_ref)
1. migration:  ADD COLUMN customer_ref (nullable)
2. code:       write to BOTH columns
3. script:     backfill customer_ref from customer_code   (batched, idempotent)
4. code:       read from customer_ref, stop writing customer_code
5. migration:  DROP COLUMN customer_code   (days later, after metrics
                                            confirm nothing reads it)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A direct &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RENAME COLUMN&lt;/code&gt; is atomic for the database but not for your fleet: during a rolling deploy, old instances are still querying the old name the instant the rename lands, and they start erroring. Routing through a new column keeps both names valid until every instance has moved over.&lt;/p&gt;

&lt;p&gt;The principle under the whole sequence: for the entire migration period the schema lives in two shapes at once, so your application code has to work against &lt;strong&gt;both&lt;/strong&gt; — the old column and the new — until every instance has rolled over. That compatibility window is exactly what steps 2–4 buy you; skip it and the change only survives if the database and the whole fleet flip in the same instant, which during a rolling deploy they never do.&lt;/p&gt;

&lt;p&gt;Narrowing a type is the sharpest reason to work this way: a direct &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ALTER&lt;/code&gt; to a smaller width &lt;strong&gt;silently truncates&lt;/strong&gt; every overflowing value under a non-strict MySQL — data loss with a green checkmark. Migrate through a new column and the overflow surfaces in the backfill, where &lt;em&gt;you&lt;/em&gt; decide what happens to it instead of the engine deciding for you.&lt;/p&gt;

&lt;h3 id=&quot;what-an-in-place-alter-quietly-takes-from-you&quot;&gt;What an in-place ALTER quietly takes from you&lt;/h3&gt;

&lt;p&gt;The additive rule isn’t dogma — it’s because of how much an in-place change silently destroys. Many engines treat a column modification as a &lt;em&gt;replacement&lt;/em&gt;, not a patch:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;-- existing: status VARCHAR(20) NOT NULL DEFAULT &apos;pending&apos;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;-- BAD: &quot;just widen it&quot; — MySQL silently drops NOT NULL and the DEFAULT&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orders&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;MODIFY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;COLUMN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;VARCHAR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;-- to survive it, you must restate every attribute, by hand, every time&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orders&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;MODIFY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;COLUMN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;VARCHAR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;pending&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And that’s the easy trap. For the in-place changes you genuinely can’t avoid — the tightenings below — the same rule holds: &lt;strong&gt;spell out the full definition and read the generated SQL before it ships.&lt;/strong&gt; Miss one attribute and it’s silent data loss; the additive path never asks you to remember.&lt;/p&gt;

&lt;p&gt;PostgreSQL’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ALTER COLUMN ... TYPE&lt;/code&gt; preserves nullability and defaults, but ORM “change column” helpers often regenerate the full definition from whatever you declared — same trap, different layer.&lt;/p&gt;

&lt;p&gt;For string columns, “complete” includes character set and collation: MySQL resets omitted ones to the table defaults, and a collation change alters which strings compare equal — silently breaking unique indexes and lookups.&lt;/p&gt;

&lt;p&gt;Datetime type changes deserve their own paragraph of fear. Converting &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DATETIME&lt;/code&gt; ↔ &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TIMESTAMP&lt;/code&gt; in MySQL, or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;timestamp&lt;/code&gt; → &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;timestamptz&lt;/code&gt; in PostgreSQL, rewrites every stored value through the session time zone unless you pin it. A uniform N-hour shift looks plausible row by row and errors nowhere.&lt;/p&gt;

&lt;p&gt;And on a large table the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ALTER&lt;/code&gt; itself can be the outage: the same statement is instant on one engine version and an hour-long lock on another. Know the locking behavior of every &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ALTER&lt;/code&gt; you write, reach for online schema-change tooling (gh-ost, pt-online-schema-change) on hot tables, and rehearse against production-sized data — a migration’s profile on a 10k-row staging table tells you nothing about 200M rows.&lt;/p&gt;

&lt;h3 id=&quot;if-you-must-tighten-gate-it-first&quot;&gt;If you must tighten, gate it first&lt;/h3&gt;

&lt;p&gt;Tightening is the in-place change you can’t always route around: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NOT NULL&lt;/code&gt; and unique constraints land &lt;em&gt;after&lt;/em&gt; the backfill, and each gets a pre-flight violation query before it ships.&lt;/p&gt;

&lt;p&gt;Unique constraints: duplicates fail the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ALTER&lt;/code&gt; at deploy time — but NULLs slip right past it. In MySQL and (by default) PostgreSQL a unique index permits &lt;em&gt;any number&lt;/em&gt; of NULL rows, and the usual &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GROUP BY ... HAVING COUNT(*) &amp;gt; 1&lt;/code&gt; dup-check is no help here: it collapses every NULL into a single group and &lt;em&gt;reports&lt;/em&gt; it as a false duplicate. Verify zero remaining NULLs separately, then pair the index with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NOT NULL&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;name-your-indexes-and-constraints&quot;&gt;Name your indexes and constraints&lt;/h3&gt;

&lt;p&gt;Index naming isn’t an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ALTER&lt;/code&gt; problem, but it’s the same lesson — don’t let the tool decide for you:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INDEX&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orders_customer_created_idx&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orders&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;customer_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Tool-generated names drift between environments (breaking the “drop the index” migration later), hit identifier length limits, and make incident work painful. A convention like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{table}_{columns}_{type}&lt;/code&gt; costs nothing and pays forever.&lt;/p&gt;

&lt;h2 id=&quot;dont-use-referential-integrity&quot;&gt;Don’t use referential integrity&lt;/h2&gt;

&lt;p&gt;Heresy first, justification after: &lt;strong&gt;keep foreign keys out of your database.&lt;/strong&gt; Declare the relationship in your code, index the column, and enforce integrity where you can actually reason about it — not in a constraint that fires on every write and fights you on every migration.&lt;/p&gt;

&lt;p&gt;This is the one rule here people will argue about, so let me make the case.&lt;/p&gt;

&lt;h3 id=&quot;why-foreign-keys-cost-more-than-theyre-worth&quot;&gt;Why foreign keys cost more than they’re worth&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;They lock the parent on every child write.&lt;/strong&gt; InnoDB takes a shared lock on the referenced row whenever you insert or update a child, so a hot parent — the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;users&lt;/code&gt; row of your busiest account, a popular &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;product&lt;/code&gt; — turns into a contention point and a deadlock factory, all to enforce a check that says nothing about &lt;em&gt;your&lt;/em&gt; business rules.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON DELETE CASCADE&lt;/code&gt; is the exact thing the rest of this post warns against.&lt;/strong&gt; It’s an unbounded, unbatched, un-throttled, un-dry-runnable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DELETE&lt;/code&gt; fired as a side effect of another &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DELETE&lt;/code&gt;. A single &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DELETE FROM users WHERE id = 42&lt;/code&gt; can drag a million rows across ten tables down with it, holding locks the whole way — and you find out from the graphs. Everything in “Default to dry-run” becomes impossible the moment the database does the deleting for you.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;They break online schema changes.&lt;/strong&gt; gh-ost refuses to touch a table that has foreign keys; pt-online-schema-change supports them only through fragile, risky workarounds. The tooling you most need on large tables (previous section) is exactly what FKs disable.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;They don’t survive scale or service boundaries.&lt;/strong&gt; You can’t enforce a foreign key across shards, across a partition boundary, or across two services that each own their own database. The day &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;orders&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;customers&lt;/code&gt; live apart, the guarantee you leaned on is gone — better to have never depended on it.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;They turn routine data work into a fight.&lt;/strong&gt; Backfills, archival, soft deletes, reparenting, importing legacy rows in the “wrong” order — all of it has to be choreographed around a constraint that rejects every intermediate state, even a temporary and intentional one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What FKs actually buy you — “a child never points at a missing parent” — is real, but it’s a &lt;em&gt;guarantee about a single moment&lt;/em&gt;, paid for on every write forever. You can get the same correctness far more cheaply.&lt;/p&gt;

&lt;h3 id=&quot;enforce-integrity-where-you-can-reason-about-it&quot;&gt;Enforce integrity where you can reason about it&lt;/h3&gt;

&lt;p&gt;Validate the relationship in the application, on write, alongside every other business rule — because the useful conditions live there anyway: the parent must exist &lt;em&gt;and&lt;/em&gt; belong to the right tenant, be in the right state, not be archived. A foreign key can’t express any of that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Index the referencing column yourself.&lt;/strong&gt; A FK in PostgreSQL does &lt;em&gt;not&lt;/em&gt; auto-create an index on the child column (MySQL/InnoDB does); either way, add it explicitly — you want it for joins and lookups whether or not a constraint is attached.&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INDEX&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orders_customer_id_idx&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orders&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;customer_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A bug can still slip an orphan through. Fine — you detect it, instead of locking your tables on every write to prevent it. The audit is a cheap anti-join you run on a schedule:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;-- orphaned orders: child points at a parent that isn&apos;t there&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orders&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;LEFT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customers&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;customer_id&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;customer_id&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Alert on a non-zero count and you learn about drift in minutes — with the offending row IDs in hand, not a constraint violation blocking an unrelated deploy at 3 a.m.&lt;/p&gt;

&lt;h3 id=&quot;handle-deletes-on-purpose&quot;&gt;Handle deletes on purpose&lt;/h3&gt;

&lt;p&gt;Without &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CASCADE&lt;/code&gt;, deleting a parent becomes an explicit, reviewable operation — which is the entire point. Pick the policy per relationship:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Cascade in application code&lt;/strong&gt;, child tables first, using the batched, dry-run script from earlier. Now the cascade is countable, throttled, resumable and reversible.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Nullify&lt;/strong&gt; the reference (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SET customer_id = NULL&lt;/code&gt;) when the child outlives the parent — an anonymized order, a retained event log.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Block&lt;/strong&gt; the delete with a pre-flight check (“does this customer still have orders?”) when children should keep the parent alive — the same intent as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RESTRICT&lt;/code&gt;, but with a real error message and your own timing.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Soft-delete&lt;/strong&gt; the parent and let a background job reconcile children, when an inline hard delete is too expensive to do on the request path.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every one of these you can dry-run, batch, log and resume. A foreign key gives you exactly one option — &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CASCADE&lt;/code&gt; — and it’s the one that violates every other rule in this post.&lt;/p&gt;

&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping up&lt;/h2&gt;

&lt;p&gt;Before shipping anything that touches schema or data:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;migrations: DDL only&lt;/li&gt;
  &lt;li&gt;data scripts: idempotent, batched, re-run tested&lt;/li&gt;
  &lt;li&gt;dry-run by default, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--execute&lt;/code&gt; explicit&lt;/li&gt;
  &lt;li&gt;sanity ceiling on affected rows&lt;/li&gt;
  &lt;li&gt;restore path before any destructive run&lt;/li&gt;
  &lt;li&gt;change columns additively (add → migrate → drop), never &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ALTER&lt;/code&gt;/&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RENAME&lt;/code&gt; in place&lt;/li&gt;
  &lt;li&gt;if you must &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ALTER&lt;/code&gt;, restate the full definition and name your indexes&lt;/li&gt;
  &lt;li&gt;tighten only after a verified backfill&lt;/li&gt;
  &lt;li&gt;no database-level foreign keys; enforce in code, index the column, audit for orphans&lt;/li&gt;
  &lt;li&gt;destructive steps ship separately&lt;/li&gt;
  &lt;li&gt;know your lock profile; rehearse on production-sized data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Schema is code — it can be reverted. &lt;strong&gt;Data is forever.&lt;/strong&gt; Respect that, and you’ll never have to explain why the orders table has 4 million rows with status &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&apos;pending&apos;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;COMMIT;&lt;/p&gt;
</description>
        <pubDate>Fri, 12 Jun 2026 00:00:00 +0000</pubDate>
        <link>https://rpanachi.com/how-to-not-destroy-your-production-database</link>
        <guid isPermaLink="true">https://rpanachi.com/how-to-not-destroy-your-production-database</guid>
        
        
        <category>database</category>
        
        <category>sql</category>
        
        <category>migrations</category>
        
        <category>best practices</category>
        
      </item>
    
      <item>
        <title>How Dark Souls Made Me a Better Software Engineer</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;“Today is victory over yourself of yesterday.”&lt;br /&gt;
— Miyamoto Musashi, The Book of Five Rings&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Dark Souls is not a game for the masses. It’s known for its brutal difficulty, its near-total lack of guidance, and a story buried so deep in item descriptions and environmental detail that most players never piece it together.&lt;/p&gt;

&lt;p&gt;I first played it when it launched, and I hated it. I spent hours trying to understand how to level up my character and make it stronger, and found only death, again and again. Every path I took ended the same way: killed by an enemy, or falling into a trap I never saw coming.&lt;/p&gt;

&lt;p&gt;The game never told me where to go. No tips, no shortcuts, no markers. I’d find an item with no idea where to use it. I’d meet an NPC with no way to know if they were friend or foe. Even the weakest enemy could kill me if it caught me off guard, or if I let myself get surrounded.&lt;/p&gt;

&lt;p&gt;After enough suffering, I quit. But the feeling of defeat followed me for years. What had I done wrong? Was the game just bad? Was it punishing for the sake of being punishing, designed only to make players angry?&lt;/p&gt;

&lt;p&gt;It took me years to find the answer. I’d grown up a little, both as a person and as an engineer, and I decided to give it another try — this time with a more mature, more patient, more deliberate approach. And almost immediately I understood what had gone wrong the first time.&lt;/p&gt;

&lt;h2 id=&quot;i-was-playing-it-wrong&quot;&gt;I Was Playing It Wrong&lt;/h2&gt;

&lt;p&gt;Dark Souls is not a game that holds your hand and hands you free doses of dopamine to keep you hooked. It doesn’t show you where to go. Instead, it lays every path in front of you and lets you choose, and then expects you to learn — the hard way — that the path you picked wasn’t the one you were ready for yet.&lt;/p&gt;

&lt;p&gt;It teaches through its mechanics, not through tutorials:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;You can’t be good at everything at once.&lt;/strong&gt; Instead of leveling every attribute, you learn to focus on one or two, and to pick the weapons and armor that scale with them. That’s how the game teaches you to specialize.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Spend your progress with parsimony.&lt;/strong&gt; After killing a few enemies, it’s smarter to rest at a bonfire, level up, review your build, and move on. Push your luck instead, and you risk losing all your souls to a single trap or an invader.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Keep one eye on everything at once:&lt;/strong&gt; your stamina, your weight load, your build, and the strengths and weaknesses of your own character.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Don’t rush a new enemy.&lt;/strong&gt; It’s probably a trap, and most of the time it is. Retreat, think through your strategy, then advance one hit at a time. Be greedy, and you die.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first time around, I had done the exact opposite of every one of these. I spread my levels across every attribute and mastered none. I burned my souls the instant I had them instead of banking my progress. I sprinted into every new room with my eyes on the loot, and walked face-first into every ambush. I wasn’t fighting the game. I was fighting the way I’d decided to play it.&lt;/p&gt;

&lt;p&gt;The difficulty wasn’t cruelty. It was a teacher. I just hadn’t been listening.&lt;/p&gt;

&lt;h2 id=&quot;you-are-your-own-enemy&quot;&gt;You Are Your Own Enemy&lt;/h2&gt;

&lt;p&gt;The hardest enemy in Dark Souls is the player. Smash buttons, rush forward, fight without strategy or restraint, and you will die — a lot.&lt;/p&gt;

&lt;p&gt;The game becomes easy the moment you understand this. You learn to control your anxiety, study your enemies, move with patience, and make progress one deliberate step at a time.&lt;/p&gt;

&lt;p&gt;Died for the tenth time to the same boss? Don’t give up. Persist, but change something each attempt. Study the boss’s behavior, learn its patterns, find the openings between its attacks. And when you finally win, the real reward isn’t in the game. It’s the knowledge that you kept control and discipline when it would have been easier to rage and flail.&lt;/p&gt;

&lt;p&gt;I know that moment intimately. Ornstein and Smough killed me so many times I was sure the fight was simply unfair, controller halfway to the wall. The shift came when I stopped playing angry. Instead of charging in to get it over with, I’d put the controller down, breathe, and actually watch what they did before each swing. The fight never got easier. I did. The same attacks I’d been dying to became a rhythm I could finally read.&lt;/p&gt;

&lt;p&gt;You didn’t beat the boss. You beat your own internal enemy.&lt;/p&gt;

&lt;h2 id=&quot;the-software-engineering-connection&quot;&gt;The Software Engineering Connection&lt;/h2&gt;

&lt;p&gt;Software engineering, it turns out, demands almost the same set of skills — and most of them are not technical.&lt;/p&gt;

&lt;p&gt;You have to resist the urge to reach for the hyped new framework just because it’s there. You have to study the problem before attacking it, and come up with the best strategy to beat it. You have to read the situation, find the openings, and pick the right weapon — the right tool — for each occasion instead of swinging the same one at everything.&lt;/p&gt;

&lt;p&gt;Re-read that list of lessons with an engineer’s eye, and it’s almost the same list:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Specialize before you generalize.&lt;/strong&gt; You can’t master every language, framework, and platform at once, just like you can’t max out every attribute. Go deep on one or two things first, and let the rest transfer from there.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Make progress in small, safe steps.&lt;/strong&gt; The bonfire is a commit. Do a focused piece of work, consolidate it, verify your build still stands, then move on. Try to do everything in one heroic push and you’ll lose hours to a single bad bug.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Watch your own capacity.&lt;/strong&gt; Stamina and weight load are your bandwidth and your tech debt. Overload yourself and you’ll be too sluggish to react when something goes wrong.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Don’t charge into unfamiliar territory.&lt;/strong&gt; A new codebase, a new system, an unfamiliar problem — treat it like a new enemy. Observe, understand the patterns, then move one careful step at a time.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Persist on the hard problems, but change your approach.&lt;/strong&gt; Banging your head against the same bug the same way is just button-mashing. Step back, study the behavior, and form a new hypothesis on each attempt.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Read the lore before you touch anything.&lt;/strong&gt; Dark Souls hides its story in item descriptions and the shape of its world; a legacy codebase hides its story in commits, comments, and tribal knowledge. Nobody hands you the plot. You reconstruct it, piece by piece, before you act.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Not every newcomer is on your side.&lt;/strong&gt; In Dark Souls you meet characters with no way to know whether they’ll help you or knife you in the back. Every new dependency, library, or “quick” integration is that NPC. Vet it before you trust it with your build.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the most important thing I took from the game and applied to my work was this:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;“If it’s too hard, you’re doing it wrong.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This doesn’t contradict the difficulty being a teacher — it completes it. The difficulty is always real. The question is whether you’re facing a wall you must learn to climb, or standing at the wrong wall entirely: under-leveled, wrong area, wrong weapon. In Dark Souls, grinding harder against the wrong wall just feeds it your souls. Telling the two apart — climb, or walk away and come back stronger — is the whole skill.&lt;/p&gt;

&lt;p&gt;Learning to stay calm — and keeping enough emotional intelligence to recognize those moments where the difficulty is a signal, not a wall — was the turning point in my career. When something feels impossibly hard, it’s rarely because the problem is impossible. It’s because I’m approaching it wrong: wrong tool, wrong abstraction, wrong order, or simply rushing. The difficulty is feedback. Listen to it.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;You won’t prosper by rushing, or by trying to do everything at once without thinking through your strategy. Success — in the game and in the work — comes after you learn to master and dominate your own flaws: impatience, ego, the craving for instant gratification, and the impulse to flail when things get hard.&lt;/p&gt;

&lt;p&gt;Dark Souls didn’t make me a better engineer by teaching me anything about software. It made me a better engineer by teaching me about myself. The enemies, the bosses, the traps were never the real challenge. I was. And once I learned to beat that enemy — with patience, discipline, and the willingness to step back and rethink instead of pushing harder — everything else got easier.&lt;/p&gt;

&lt;p&gt;Praise the sun.&lt;/p&gt;
</description>
        <pubDate>Tue, 02 Jun 2026 00:00:00 +0000</pubDate>
        <link>https://rpanachi.com/how-dark-souls-made-me-a-better-software-engineer</link>
        <guid isPermaLink="true">https://rpanachi.com/how-dark-souls-made-me-a-better-software-engineer</guid>
        
        
        <category>software engineering</category>
        
        <category>thoughts</category>
        
        <category>games</category>
        
      </item>
    
      <item>
        <title>How Agile made the software industry worse</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;“Individuals and interactions over processes and tools.”&lt;br /&gt;
– The Agile Manifesto, 2001 (RIP)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let me be blunt: what we call “Agile” today killed the software industry. Not the real Agile - that died years ago, suffocated by consultants, certification programs, and managers who confused act[…]&lt;/p&gt;

&lt;p&gt;What we have now is a bureaucratic frankenstein wearing Agile’s clothes, shambling through sprint ceremonies and demanding story points while the original principles rot in meeting room graveyards[…]&lt;/p&gt;

&lt;h2 id=&quot;the-great-agile-scam&quot;&gt;The Great Agile Scam&lt;/h2&gt;

&lt;p&gt;Somewhere between 2001 and today, we took four simple principles and turned them into an industrial complex of frameworks, tools, and processes. The irony is so thick you could sprint through it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you need tools and processes to be “Agile”, you aren’t Agile.&lt;/strong&gt; You’re just doing waterfall in two-week chunks with better marketing. The manifesto literally starts with “Individuals and inte[…]&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you need contracts and closed scope to manage your projects, you aren’t using Agile.&lt;/strong&gt; You’re using traditional project management with daily standups. Real Agile thrives on ambiguity and emb[…]&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you don’t trust your team, you aren’t using Agile.&lt;/strong&gt; You’re using surveillance with colorful sticky notes. All those ceremonies, metrics, and reporting requirements? They’re trust-filling mec[…]&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you only ship “perfect software”, you aren’t using Agile.&lt;/strong&gt; You’re using traditional development with shorter iterations. The whole point is to ship early, ship often, and learn from real use[…]&lt;/p&gt;

&lt;h2 id=&quot;the-ceremony-of-control&quot;&gt;The Ceremony of Control&lt;/h2&gt;

&lt;p&gt;Modern “Agile” is a collection of countermeasures masquerading as methodology. Every ceremony exists because someone, somewhere, didn’t trust someone else:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Daily standups&lt;/strong&gt; exist because managers don’t trust developers to communicate&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Sprint planning&lt;/strong&gt; exists because stakeholders don’t trust teams to prioritize correctly&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Sprint reviews&lt;/strong&gt; exist because business doesn’t trust development to build the right thing&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Retrospectives&lt;/strong&gt; exist because organizations don’t trust teams to improve naturally&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is Agile. It’s all about control, predictability, and filling the gaps left by poor management and broken trust.&lt;/p&gt;

&lt;p&gt;Real Agile isn’t Scrum. It isn’t daily meetings. It isn’t planning poker or story points or sprint reviews. These are all bureaucratic barnacles that attached themselves to the Agile ship until it[…]&lt;/p&gt;

&lt;h2 id=&quot;what-real-agile-actually-looks-like&quot;&gt;What Real Agile Actually Looks Like&lt;/h2&gt;

&lt;p&gt;Real Agile is about &lt;strong&gt;outcomes&lt;/strong&gt;, not output. It’s about &lt;strong&gt;adaptable chaos&lt;/strong&gt;, not predictable processes. It’s about &lt;strong&gt;communication&lt;/strong&gt;, not documentation. It’s about &lt;strong&gt;trust&lt;/strong&gt;, not surveillance. It[…]&lt;/p&gt;

&lt;p&gt;Here’s how you actually do it:&lt;/p&gt;

&lt;h3 id=&quot;focus-on-outcomes-let-the-team-organize-the-workflow&quot;&gt;Focus on outcomes, let the team organize the workflow&lt;/h3&gt;

&lt;p&gt;Stop measuring velocity and start measuring impact. Your team should organize around problems to solve, not tasks to complete. If they need to reorganize every week based on what they learned, gre[…]&lt;/p&gt;

&lt;h3 id=&quot;make-sure-everyone-understands-and-commits-to-the-whys&quot;&gt;Make sure everyone understands and commits to the WHYs&lt;/h3&gt;

&lt;p&gt;Don’t just tell people what to build - tell them why it matters. When your team understands the business context and user problems, they make better decisions than any process ever could.&lt;/p&gt;

&lt;h3 id=&quot;communicate-as-much-as-possible-adopt-extreme-visibility&quot;&gt;Communicate as much as possible; adopt extreme visibility&lt;/h3&gt;

&lt;p&gt;Not through status reports - through actual conversation. Make everything visible: progress, blockers, decisions, trade-offs. Use whatever tools work, but prioritize human communication over tool […]&lt;/p&gt;

&lt;h3 id=&quot;decide-what-can-be-decided-leave-future-decisions-for-later&quot;&gt;Decide what can be decided; leave future decisions for later&lt;/h3&gt;

&lt;p&gt;Stop trying to plan everything upfront. Make the decisions you can make now with the information you have now. When you have more information, make more decisions. Uncertainty isn’t a bug - it’s a[…]&lt;/p&gt;

&lt;h3 id=&quot;collaboration-is-key-theres-no-my-job-scope&quot;&gt;Collaboration is key. There’s no “my job scope”&lt;/h3&gt;

&lt;p&gt;The team works together on whatever needs to be done to achieve the goals. If the backend developer needs to write CSS, they write CSS. If the designer needs to update documentation, they update d[…]&lt;/p&gt;

&lt;h3 id=&quot;a-shared-doc-or-canvas-should-be-enough-to-register-the-entire-work-scope&quot;&gt;A shared doc or canvas should be enough to “register” the entire work scope&lt;/h3&gt;

&lt;p&gt;You don’t need project management software. You need shared understanding. Whether it’s a Figma board, a Miro canvas, or a Google Doc, the tool doesn’t matter - the clarity does.&lt;/p&gt;

&lt;h2 id=&quot;just-drop-the-label&quot;&gt;Just Drop the Label&lt;/h2&gt;

&lt;p&gt;If you can’t accept this - if you need your frameworks and processes and ceremonies - just drop the Agile label. Call it “Interactive Process Management” or “Structured Development” or whatever yo[…]&lt;/p&gt;

&lt;p&gt;The real Agile died when we turned principles into processes, values into ceremonies, and trust into tools. What we have now is just mediocre project management with better branding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The software industry doesn’t need more Agile.&lt;/strong&gt; It needs fewer meetings, more trust, clearer communication, and teams that are actually empowered to solve problems instead of following processe[…]&lt;/p&gt;

&lt;p&gt;Until we get back to the original principles - until we value individuals over processes, working software over documentation, customer collaboration over contracts, and responding to change over […]&lt;/p&gt;

&lt;p&gt;The manifesto is only 68 words long. Maybe it’s time we actually &lt;a href=&quot;https://agilemanifesto.org&quot;&gt;read it&lt;/a&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;Protip: If your “Agile transformation” requires hiring consultants, buying tools, and training everyone on new processes, you’ve already lost.&lt;/em&gt;&lt;/p&gt;

&lt;hr /&gt;
</description>
        <pubDate>Wed, 22 Apr 2026 00:00:00 +0000</pubDate>
        <link>https://rpanachi.com/how-agile-killed-the-software-industry</link>
        <guid isPermaLink="true">https://rpanachi.com/how-agile-killed-the-software-industry</guid>
        
        
        <category>agile</category>
        
        <category>software engineering</category>
        
        <category>thoughts</category>
        
      </item>
    
      <item>
        <title>What 6 months of PHP/Laravel taught me as a Ruby/Rails developer</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;“Good programmers use their brains, but good guidelines save us having to think out every case.”&lt;br /&gt;
— Francis Glassborow&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After many years working with &lt;strong&gt;Ruby on Rails&lt;/strong&gt;, I’ve spent the last &lt;strong&gt;six months working full time with PHP and Laravel&lt;/strong&gt;.
What surprised me the most was not learning a new syntax, but &lt;strong&gt;how little friction I felt switching stacks&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Not because the languages are the same (they aren’t) but because &lt;strong&gt;Rails and Laravel share a very similar philosophy&lt;/strong&gt;: convention over configuration, developer happiness, and code that reads like intention.&lt;/p&gt;

&lt;p&gt;Below are the main similarities and differences I noticed from a very practical, day-to-day perspective.&lt;/p&gt;

&lt;h2 id=&quot;eloquent-models-feel-like-activerecord-and-thats-a-good-thing&quot;&gt;Eloquent Models feel like ActiveRecord (and that’s a good thing)&lt;/h2&gt;

&lt;p&gt;If you’ve used Rails’ ActiveRecord for years, Eloquent will feel instantly familiar.&lt;/p&gt;

&lt;h3 id=&quot;model-definition&quot;&gt;Model definition&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Rails (ActiveRecord):&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Post&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;belongs_to&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;has_many&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:comments&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;scope&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:published&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;published: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Laravel (Eloquent):&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Post&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Model&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$fillable&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;title&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;body&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;published&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;belongsTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;comments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;hasMany&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Comment&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scopePublished&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$query&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;published&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;querying-feels-almost-identical&quot;&gt;Querying feels almost identical&lt;/h3&gt;

&lt;p&gt;This is where Rails muscle memory really kicks in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rails:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;published&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;user_id: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;created_at: :desc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;limit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Laravel:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nc&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;published&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;user_id&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;orderBy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;created_at&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;desc&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;limit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Chaining, scopes, lazy evaluation - the flow and intent are basically the same.&lt;/p&gt;

&lt;h2 id=&quot;collections-api-ruby-vibes-everywhere&quot;&gt;Collections API: Ruby vibes everywhere&lt;/h2&gt;

&lt;p&gt;Laravel’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Collection&lt;/code&gt; API is one of its strongest features — and clearly inspired by Ruby’s Enumerable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ruby:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;users&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;active?&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sort&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Laravel:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$users&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$u&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$u&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;active&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$u&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$u&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Once you stop thinking in raw arrays and start thinking in &lt;strong&gt;collections&lt;/strong&gt;, PHP becomes much more expressive.&lt;/p&gt;

&lt;h2 id=&quot;hash-vs-array-different-names-same-idea&quot;&gt;Hash vs Array: different names, same idea&lt;/h2&gt;

&lt;p&gt;This is one of the first mental adjustments when moving from Ruby to PHP.&lt;/p&gt;

&lt;p&gt;In Ruby, &lt;strong&gt;Hash&lt;/strong&gt; is the natural structure for key-value data.
In PHP, &lt;strong&gt;arrays do everything&lt;/strong&gt; - lists, maps, hashes - which feels odd at first but works well in practice.&lt;/p&gt;

&lt;h3 id=&quot;defining-key-value-data&quot;&gt;Defining key-value data&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Ruby (Hash):&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;User&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;email: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;user@example.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;active: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;PHP (array as hash):&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;&apos;name&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;User&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;&apos;email&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;user@example.com&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;&apos;active&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;accessing-elements&quot;&gt;Accessing elements&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Ruby:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;PHP:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;email&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;email&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The null-coalescing operator makes safe access very straightforward.&lt;/p&gt;

&lt;h3 id=&quot;adding-or-updating-values&quot;&gt;Adding or updating values&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Ruby:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:role&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;admin&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;merge!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;last_login: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;PHP:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;role&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;admin&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;$user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;array_merge&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;&apos;last_login&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With Laravel Collections:&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;collect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;merge&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;last_login&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()])&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;toArray&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;transforming-data&quot;&gt;Transforming data&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Ruby:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;prices&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;apple: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;banana: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;prices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;transform_values&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;PHP (Collection):&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$prices&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;collect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;apple&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;banana&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$v&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;toArray&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;nested-access&quot;&gt;Nested access&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Ruby:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:profile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:address&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:city&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;PHP (Laravel helper):&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nf&quot;&gt;data_get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;profile.address.city&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Different syntax, same intent.&lt;/p&gt;

&lt;h2 id=&quot;request-validation-is-as-simple-as-rails-strong-params&quot;&gt;Request validation is as simple as Rails strong params&lt;/h2&gt;

&lt;p&gt;Validation in Laravel feels very close to Rails - simple, readable, and explicit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rails:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;permit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;validates&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;presence: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;uniqueness: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Laravel:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;validate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;&apos;name&apos;&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;required|string&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;&apos;email&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;required|email|unique:users&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In both frameworks, validation lives close to where data enters the system.&lt;/p&gt;

&lt;h2 id=&quot;routes-familiar-dsl-different-syntax&quot;&gt;Routes: familiar DSL, different syntax&lt;/h2&gt;

&lt;p&gt;Routing is another area where the philosophies strongly align.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rails:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;routes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;draw&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;resources&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:posts&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/health&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;to: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;health#check&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Laravel:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nc&quot;&gt;Route&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;posts&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PostController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;Route&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;/health&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;HealthController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;check&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;RESTful by default, expressive, and easy to reason about.&lt;/p&gt;

&lt;h2 id=&quot;artisan-feels-like-rails-generators-on-steroids&quot;&gt;Artisan feels like Rails generators on steroids&lt;/h2&gt;

&lt;p&gt;If you like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rails generate&lt;/code&gt;, you’ll feel at home with Artisan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rails:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;rails generate model Post title:string body:text
rails routes
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Laravel:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;php artisan make:model Post &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt;
php artisan make:controller PostController &lt;span class=&quot;nt&quot;&gt;--resource&lt;/span&gt;
php artisan route:list
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Artisan goes beyond generators:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;queue workers&lt;/li&gt;
  &lt;li&gt;cache management&lt;/li&gt;
  &lt;li&gt;app optimization&lt;/li&gt;
  &lt;li&gt;custom CLI commands&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s a very strong part of the Laravel ecosystem.&lt;/p&gt;

&lt;h2 id=&quot;php-is-simple-explicit-and-predictable&quot;&gt;PHP is simple, explicit, and predictable&lt;/h2&gt;

&lt;p&gt;Modern PHP is very different from what many of us remember.&lt;/p&gt;

&lt;h3 id=&quot;less-magic-more-clarity&quot;&gt;Less magic, more clarity&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Ruby:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_by_email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;test@test.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;PHP:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;email&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;test@test.com&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;No hidden meta-programming - what you see is what runs.&lt;/p&gt;

&lt;h3 id=&quot;interfaces-and-typing-help-a-lot&quot;&gt;Interfaces and typing help a lot&lt;/h3&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PaymentGateway&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;charge&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$amount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Clear contracts reduce ambiguity, especially in larger teams.&lt;/p&gt;

&lt;h2 id=&quot;the-not-so-great-parts&quot;&gt;The not-so-great parts&lt;/h2&gt;

&lt;p&gt;No stack is perfect.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Laravel caching can be annoying and inconsistent across environments&lt;/li&gt;
  &lt;li&gt;Finding libraries for newer or niche services can be harder than in Ruby&lt;/li&gt;
  &lt;li&gt;PHP still requires more boilerplate in some areas&lt;/li&gt;
  &lt;li&gt;The ecosystem is more fragmented (versions, extensions, hosting)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are deal breakers - just trade-offs.&lt;/p&gt;

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

&lt;p&gt;After six months working daily with Laravel, one thing is very clear to me:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PHP is not dead. Not even close.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Laravel delivers:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;a modern developer experience&lt;/li&gt;
  &lt;li&gt;a familiar MVC philosophy&lt;/li&gt;
  &lt;li&gt;excellent productivity&lt;/li&gt;
  &lt;li&gt;and a surprisingly smooth transition for Rails developers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rails still shines in elegance and ecosystem maturity, but Laravel proves that &lt;strong&gt;PHP has evolved - a lot - and in the right direction&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At the end of the day, good software is less about the language and more about:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;clear code&lt;/li&gt;
  &lt;li&gt;good tests&lt;/li&gt;
  &lt;li&gt;solid architecture&lt;/li&gt;
  &lt;li&gt;and teams that know what they’re doing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And in that sense, &lt;strong&gt;both Rails and Laravel get the job done - really well&lt;/strong&gt;.&lt;/p&gt;
</description>
        <pubDate>Mon, 05 Jan 2026 00:00:00 +0000</pubDate>
        <link>https://rpanachi.com/what-6-months-of-laravel-taught-me-as-a-rails-developer</link>
        <guid isPermaLink="true">https://rpanachi.com/what-6-months-of-laravel-taught-me-as-a-rails-developer</guid>
        
        
        <category>php</category>
        
        <category>laravel</category>
        
        <category>ruby</category>
        
        <category>rails</category>
        
        <category>career</category>
        
        <category>thoughts</category>
        
      </item>
    
      <item>
        <title>My thoughts about AI tools, vibe coding, and some predictions about the future of programming</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;“AI tools are great if you know what you’re doing.”&lt;br /&gt;
— A tired developer&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;magical-tools-arent-new&quot;&gt;“Magical tools” aren’t new&lt;/h2&gt;

&lt;p&gt;I started programming about 25 years ago. Self-taught. A lot of trial and error. When I was lucky, I had access to books. When I wasn’t, I explored every corner of the &lt;em&gt;Help&lt;/em&gt; menu of Delphi. I learned by breaking things, fixing them, and slowly understanding how everything fit together. The learning curve was high.&lt;/p&gt;

&lt;p&gt;At that time, the “best” tools were the ones that promised productivity through visual components: drag-and-drop forms, buttons, grids, data sources, and magically “connected” events. The pitch was always the same: &lt;em&gt;“You don’t need to write code. Just drag components and connect them.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Delphi, Visual Basic, PowerBuilder, and similar tools built entire empires on that promise.&lt;/p&gt;

&lt;p&gt;Old-school developers hated it. They said it wasn’t real programming. That it was cheating. That people using those tools didn’t really understand what was happening underneath.&lt;/p&gt;

&lt;p&gt;The market didn’t care.&lt;/p&gt;

&lt;p&gt;Companies shipped software faster. Businesses made money. And those tools dominated for years. Eventually, they shaped the industry, influenced modern frameworks, and became the foundation of what we now call “rapid application development”.&lt;/p&gt;

&lt;p&gt;AI tools feel different — but they really aren’t. They are just the next iteration of “magical tools”.&lt;/p&gt;

&lt;h2 id=&quot;ai-tools-are-great-if-you-know-what-you-are-doing&quot;&gt;AI tools are great if you know what you are doing&lt;/h2&gt;

&lt;p&gt;If you just ask an AI to &lt;em&gt;solve a problem for you&lt;/em&gt;, you’re doing it wrong.&lt;/p&gt;

&lt;p&gt;AI is not a replacement for thinking. It’s an &lt;strong&gt;advanced code generator&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The quality of the output is directly proportional to the quality of your prompt — which, in practice, means the quality of your understanding of the problem, the codebase, and the architecture.&lt;/p&gt;

&lt;p&gt;A bad prompt looks like this:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“Add a button that lists people on this page.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the best case scenario, the AI will generate something that works. In the worst case, it will dump a pile of spaghetti code that ignores conventions, architecture, separation of concerns, permissions, tests, and long-term maintainability.&lt;/p&gt;

&lt;p&gt;A &lt;em&gt;good&lt;/em&gt; prompt looks like this:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“Add a button that lists people on this page.&lt;br /&gt;
Use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MyButton&lt;/code&gt; component and centralize all fetching/mutation logic into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;api.ts&lt;/code&gt; file.&lt;br /&gt;
Get the list of people from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ContactsService&lt;/code&gt; class, but keep all filtering logic inside &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PeopleController&lt;/code&gt;.&lt;br /&gt;
Make sure to verify user permissions to create a new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Person&lt;/code&gt; using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;User.canExecute(&quot;action&quot;)&lt;/code&gt; method.&lt;br /&gt;
Write an integration test covering this usage scenario.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now you are in control.&lt;/p&gt;

&lt;p&gt;You already know &lt;em&gt;what&lt;/em&gt; needs to be done. You know &lt;em&gt;where&lt;/em&gt; the logic belongs. You know &lt;em&gt;how&lt;/em&gt; your system should evolve. You’re just delegating the mechanical part — writing code — to the AI.&lt;/p&gt;

&lt;p&gt;That’s not laziness. That’s leverage.&lt;/p&gt;

&lt;h2 id=&quot;youll-become-dependent-and-forget-how-to-code&quot;&gt;“You’ll become dependent and forget how to code”&lt;/h2&gt;

&lt;p&gt;This argument keeps coming back. And yes — it’s partially true.&lt;/p&gt;

&lt;p&gt;When you don’t have to worry about syntax, language quirks, or framework boilerplate, you naturally forget some details. You might not remember the exact syntax of a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;for&lt;/code&gt; loop in Go, or how Ruby blocks behave in edge cases.&lt;/p&gt;

&lt;p&gt;But here’s the uncomfortable truth: &lt;strong&gt;languages are commodities now&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It really doesn’t matter that much if your backend is written in Go, JavaScript, Ruby, or Python. What matters is that you understand:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;logic&lt;/li&gt;
  &lt;li&gt;data flow&lt;/li&gt;
  &lt;li&gt;architecture&lt;/li&gt;
  &lt;li&gt;patterns&lt;/li&gt;
  &lt;li&gt;trade-offs&lt;/li&gt;
  &lt;li&gt;constraints&lt;/li&gt;
  &lt;li&gt;failure modes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have that, the AI can handle the syntax.&lt;/p&gt;

&lt;p&gt;We already went through this transition before. We stopped memorizing assembly when high-level languages appeared. We stopped writing raw SQL everywhere when ORMs became popular. We stopped managing servers by hand when cloud platforms emerged.&lt;/p&gt;

&lt;p&gt;This is just the next step.&lt;/p&gt;

&lt;h2 id=&quot;junior-developers-will-disappear&quot;&gt;Junior developers will disappear&lt;/h2&gt;

&lt;p&gt;AI tools are the new junior developers.&lt;/p&gt;

&lt;p&gt;A mid/senior developer, using AI effectively, can be dramatically more productive than a senior leading and coordinating a team of juniors. There’s no onboarding, no context switching, no waiting for pull requests, no repeated explanations of architecture decisions.&lt;/p&gt;

&lt;p&gt;More importantly:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;AI doesn’t forget conventions&lt;/li&gt;
  &lt;li&gt;AI can instantly generate alternatives&lt;/li&gt;
  &lt;li&gt;AI can refactor relentlessly&lt;/li&gt;
  &lt;li&gt;AI can write tests, docs, and examples on demand&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For companies, the cost/benefit equation changes completely. Instead of hiring multiple juniors and hoping they grow, a smaller team of experienced developers with AI assistance can deliver faster and with more consistency.&lt;/p&gt;

&lt;p&gt;That doesn’t mean &lt;em&gt;people&lt;/em&gt; disappear — it means the traditional entry point to the industry will change radically.&lt;/p&gt;

&lt;h2 id=&quot;generalists-will-rule&quot;&gt;Generalists will rule&lt;/h2&gt;

&lt;p&gt;For the vast majority of companies, you no longer need deep specialists in frontend, backend, and database just to build and run a product.&lt;/p&gt;

&lt;p&gt;A single developer, armed with AI tools, can:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;design APIs&lt;/li&gt;
  &lt;li&gt;build frontend interfaces&lt;/li&gt;
  &lt;li&gt;model databases, write migrations&lt;/li&gt;
  &lt;li&gt;configure infrastructure&lt;/li&gt;
  &lt;li&gt;handle authentication and security practices&lt;/li&gt;
  &lt;li&gt;set up CI/CD&lt;/li&gt;
  &lt;li&gt;deploy and monitor applications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not science fiction. It’s already happening.&lt;/p&gt;

&lt;p&gt;Of course, I’m talking about &lt;strong&gt;common software&lt;/strong&gt; — the kind that runs most businesses. Not rocket science. Not medical devices. Not highly specialized domains.&lt;/p&gt;

&lt;p&gt;If your company operates in a niche area that requires deep domain knowledge, specialists will still be essential — not because of coding itself, but because of the &lt;em&gt;knowledge&lt;/em&gt; required to make correct decisions.&lt;/p&gt;

&lt;p&gt;But for most cases, generalists who understand the whole system will be far more valuable than narrow specialists.&lt;/p&gt;

&lt;h2 id=&quot;software-development-will-still-need-people&quot;&gt;Software development will still need people&lt;/h2&gt;

&lt;p&gt;Despite all the hype, software development won’t disappear.&lt;/p&gt;

&lt;p&gt;What &lt;em&gt;will&lt;/em&gt; change is the role of the developer.&lt;/p&gt;

&lt;p&gt;Developers will increasingly become the bridge between business intent and technical execution. AI lowers the learning curve, but it doesn’t remove the complexity. Someone still needs to:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;understand what the business actually wants&lt;/li&gt;
  &lt;li&gt;translate vague ideas into concrete systems&lt;/li&gt;
  &lt;li&gt;make trade-offs&lt;/li&gt;
  &lt;li&gt;define boundaries&lt;/li&gt;
  &lt;li&gt;ensure quality&lt;/li&gt;
  &lt;li&gt;manage risk&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The “new programmers” will orchestrate execution. They’ll use AI, vibe coding, text-to-code, diagrams, prompts, and automation to transform business needs into working software — faster than ever before.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Every generation of developers believes their tools are the last ones that require “real skill”. And every generation is wrong.&lt;/p&gt;

&lt;p&gt;We’ve always built abstractions to move faster. AI is just the most powerful abstraction we’ve seen so far.&lt;/p&gt;

&lt;p&gt;The core skill has never been typing code. It has always been &lt;strong&gt;thinking clearly&lt;/strong&gt;, &lt;strong&gt;designing systems&lt;/strong&gt;, and &lt;strong&gt;solving real problems&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;AI doesn’t replace that. It amplifies it.&lt;/p&gt;

&lt;p&gt;If you don’t know what you’re doing, AI will happily help you create a mess — faster than ever.&lt;br /&gt;
If you &lt;em&gt;do&lt;/em&gt; know what you’re doing, AI becomes a force multiplier.&lt;/p&gt;

&lt;p&gt;And that’s why I truly believe this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI tools are great if you know what you’re doing.&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;personal-notes&quot;&gt;Personal notes&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;This text was partially written using AI. The content is mine, thoughts, examples, topics, and logical structure. I just asked IA to transform my notes into a blog post format. This is why I kept the “AI dash” on it.&lt;/li&gt;
  &lt;li&gt;If you’re a junior developer, don’t focus on just writing code; you should learn the concepts, software patterns, best practices, and soft skills. Achievements and deeds have more weight on your profile than technical skills purely.&lt;/li&gt;
&lt;/ol&gt;
</description>
        <pubDate>Thu, 18 Dec 2025 00:00:00 +0000</pubDate>
        <link>https://rpanachi.com/thoughts-about-ai-tools-vibe-coding-predicitions-about-future-of-programming</link>
        <guid isPermaLink="true">https://rpanachi.com/thoughts-about-ai-tools-vibe-coding-predicitions-about-future-of-programming</guid>
        
        
        <category>ai</category>
        
        <category>vibe coding</category>
        
        <category>thoughts</category>
        
      </item>
    
      <item>
        <title>Useful Ruby methods and tips that you might not know (or remember)</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;“Code is like humor. When you have to explain it, it’s bad.”&lt;br /&gt;
— Cory House&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;enumerable-methods&quot;&gt;Enumerable Methods&lt;/h2&gt;

&lt;h3 id=&quot;enumerabletally&quot;&gt;Enumerable#tally&lt;/h3&gt;

&lt;p&gt;The tally method groups and counts elements in an enumerable. It’s great for frequency analysis.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;apple&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;banana&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;apple&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;orange&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;tally&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;apple&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;banana&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;orange&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;enumerableinject&quot;&gt;Enumerable#inject&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inject&lt;/code&gt; accumulates a result by applying a block to each element. It’s useful for summing, multiplying, and more.&lt;/p&gt;

&lt;p&gt;Using with an accumulator:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;inject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Or passing a method proc as argument:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;inject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;enumerablechunk&quot;&gt;Enumerable#chunk&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chunk&lt;/code&gt; groups consecutive elements based on a block condition.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;chunk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:even?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_a&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]],&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]],&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]],&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;enumerableeach_with_object&quot;&gt;Enumerable#each_with_object&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;each_with_object&lt;/code&gt; iterates and builds an object like a hash or array.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;irb&amp;gt; [&quot;apple&quot;, &quot;banana&quot;].each_with_object({}) { |fruit, hash| hash[fruit] = fruit.length }
=&amp;gt; {&quot;apple&quot;=&amp;gt;5, &quot;banana&quot;=&amp;gt;6}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;enumerableslice_when&quot;&gt;Enumerable#slice_when&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slice_when&lt;/code&gt; splits an enumerable when the block returns true.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;slice_when&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_a&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;enumerableflat_map&quot;&gt;Enumerable#flat_map&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;flat_map&lt;/code&gt; maps and flattens the result in one step.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;flat_map&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;hash-methods&quot;&gt;Hash Methods&lt;/h2&gt;

&lt;h3 id=&quot;hashdig&quot;&gt;Hash#dig&lt;/h3&gt;

&lt;p&gt;Extracts the nested value specified by the sequence of key objects.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;foo: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;bar: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;baz: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}}}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:foo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:bar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:baz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:invalid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;hashinvert&quot;&gt;Hash#invert&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;invert&lt;/code&gt; swaps keys and values in a hash.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;a: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;b: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;invert&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;hashtransform_keys&quot;&gt;Hash#transform_keys&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transform_keys&lt;/code&gt; lets you change hash keys with a block.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;a: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;b: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;transform_keys&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;upcase&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;A&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;B&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;hashtransform_values&quot;&gt;Hash#transform_values&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transform_values&lt;/code&gt; creates a new hash with the values transformed by the given block, while keeping the keys unchanged.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;a: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;b: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;transform_values&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;a: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;b: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;array-methods&quot;&gt;Array Methods&lt;/h2&gt;

&lt;h3 id=&quot;arrayrotate&quot;&gt;Array#rotate&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rotate&lt;/code&gt; shifts elements to the left by default, or by a given count.&lt;/p&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;rotate&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;arraycombination&quot;&gt;Array#combination&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;combination&lt;/code&gt; generates all combinations of a given length.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;combination&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_a&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;arraypermutation&quot;&gt;Array#permutation&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;permutation&lt;/code&gt; returns all possible permutations of a given length.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;permutation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_a&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;object-methods&quot;&gt;Object Methods&lt;/h2&gt;

&lt;h3 id=&quot;objectmethodmethod_namesource_location&quot;&gt;Object#method(:method_name).source_location&lt;/h3&gt;

&lt;p&gt;Find where a &lt;a href=&quot;https://apidock.com/ruby/Method/source_location&quot;&gt;method is defined&lt;/a&gt; in your source code.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;g&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Greeter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:say_it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;source_location&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/Users/home/app/models/greeter.rb&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;objectmethodmethod_namearity&quot;&gt;Object#method(:method_name).arity&lt;/h3&gt;

&lt;p&gt;Check the &lt;a href=&quot;https://apidock.com/ruby/Method/arity&quot;&gt;number of arguments&lt;/a&gt; a method accepts.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;foo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Hello&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;arity&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;objectmethodsfalse&quot;&gt;Object#methods(false)&lt;/h3&gt;

&lt;p&gt;List only &lt;a href=&quot;https://apidock.com/ruby/Object/methods&quot;&gt;methods defined directly on the object&lt;/a&gt;, excluding inherited methods.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;obj&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;greet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Hello!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;methods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:greet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;kernel-methods&quot;&gt;Kernel Methods&lt;/h2&gt;

&lt;h3 id=&quot;callee&quot;&gt;callee&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;callee&lt;/strong&gt; returns the current method’s name.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;example&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;   &lt;span class=&quot;n&quot;&gt;__callee__&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;example&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:example&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;rescueretry&quot;&gt;rescue/retry&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;retry&lt;/code&gt; restarts the block after a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rescue&lt;/code&gt; clause.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attempts&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;begin&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;   &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Error&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attempts&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;   &lt;span class=&quot;k&quot;&gt;retry&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;begin--end&quot;&gt;BEGIN / END&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BEGIN&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;END&lt;/code&gt; define blocks executed before and after the script runs.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;BEGIN&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Starting...&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;END&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Ending...&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Hello!&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;activesupport-methods&quot;&gt;ActiveSupport Methods&lt;/h2&gt;

&lt;h3 id=&quot;enumerablecompact_blank&quot;&gt;Enumerable#compact_blank&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compact_blank&lt;/code&gt; removes blank elements from an enumerable.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Hello&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;compact_blank&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Hello&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;arrayindex_by&quot;&gt;Array#index_by&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;index_by&lt;/code&gt; creates a hash where keys are determined by the block.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;apple&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;banana&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;pear&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;index_by&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fruit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fruit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;a&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;apple&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;b&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;banana&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;p&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;pear&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;miscellaneous&quot;&gt;Miscellaneous&lt;/h2&gt;

&lt;h3 id=&quot;right-assignment&quot;&gt;Right Assignment&lt;/h3&gt;

&lt;p&gt;Assign results in reverse order for cleaner syntax.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;active: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;all&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;#&amp;lt;Contact:&amp;gt;,...]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id=&quot;destructuring-block-arguments&quot;&gt;Destructuring Block Arguments&lt;/h3&gt;

&lt;p&gt;Pattern matching in blocks lets you destructure array elements.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;last&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;   &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;First: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;, Last: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;last&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;First&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Last&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;First&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Last&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;calling-procs&quot;&gt;Calling Procs&lt;/h3&gt;

&lt;p&gt;You could call procs in different ways:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;my_proc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#&amp;lt;Proc:0x007fb1ebe9c1a0@(irb):1 (lambda)&amp;gt;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;my_proc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;hello&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;hello&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;my_proc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;hello&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;hello&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;my_proc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;hello&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;hello&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;command-line-ruby-tips&quot;&gt;Command line Ruby tips&lt;/h2&gt;

&lt;p&gt;Given the following files in the current directory:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-l&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;-rw-r--r--&lt;/span&gt;@ 1 user  group    36 28 Mai 17:57 file1.txt
&lt;span class=&quot;nt&quot;&gt;-rw-r--r--&lt;/span&gt;@ 1 user  group     8 28 Mai 17:57 file2.txt
&lt;span class=&quot;nt&quot;&gt;-rw-r--r--&lt;/span&gt;@ 1 user  group   613 28 Mai 17:58 file3.txt
&lt;span class=&quot;nt&quot;&gt;-rw-r--r--&lt;/span&gt;@ 1 user  group  3143 28 Mai 18:48 photo.jpg
&lt;span class=&quot;nt&quot;&gt;-rw-r--r--&lt;/span&gt;@ 1 user  group  1004 28 Mai 18:47 report.ofx
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;using--n-and--e-to-process-lines&quot;&gt;Using -n and -e to Process Lines&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-n&lt;/code&gt; wraps your code in a loop that reads each line from input. Combine with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-e&lt;/code&gt; for quick one-liners.&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt; | ruby &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;puts $_ if $_.match(/\.txt/&apos;&lt;/span&gt;
file1.txt
file2.txt
file3.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;using--p-to-modify-lines-in-place&quot;&gt;Using -p to Modify Lines in Place&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-p&lt;/code&gt; is like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-n&lt;/code&gt; but automatically prints each line. Great for filters and transformations.&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt; | ruby &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;$_ = $_.capitalize&apos;&lt;/span&gt;
File1.txt
File2.txt
File3.txt
Photo.jpg
Report.ofx
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;using-stdin&quot;&gt;Using STDIN&lt;/h3&gt;

&lt;p&gt;The command below returns the total size of all files returned by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ls -l&lt;/code&gt; command:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-l&lt;/span&gt; | ruby &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;puts STDIN.to_a.map { |l| l.split[4].to_i }.sum&apos;&lt;/span&gt;
4804
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Thu, 29 May 2025 00:00:00 +0000</pubDate>
        <link>https://rpanachi.com/useful-ruby-methods-and-tips-that-you-might-not-know-or-remember</link>
        <guid isPermaLink="true">https://rpanachi.com/useful-ruby-methods-and-tips-that-you-might-not-know-or-remember</guid>
        
        
        <category>ruby</category>
        
        <category>ruby on rails</category>
        
      </item>
    
      <item>
        <title>I&apos;ve been writing software for the last 25 years. Here are a few more things I&apos;ve learned so far (part 2)</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;The code you write makes you a programmer. The code you delete makes you a good one. The code you don’t have to write makes you a great one.&lt;br /&gt;
–  Unknown&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is part 2 of the previous post: &lt;a href=&quot;/after-25-years-writing-software-here-some-things-learned-so-far&quot;&gt;I’ve been writing software for the last 25 years. Here some things I learned so far&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;but-first-some-context&quot;&gt;But first, some context&lt;/h2&gt;

&lt;p&gt;I’m a self-taught generalist (aka full-stack) software engineer experienced mainly in web applications, having a strong focus on pragmatism and problem solving. Over the last 25 years, I’ve worked in small startups, mid-sized companies, and large enterprises—mostly in Linux environments, using open-source languages and tools. Along the way, I’ve also had some experience leading and managing teams.&lt;/p&gt;

&lt;p&gt;The lessons below come from years of seeing what works and what doesn’t. Some are the result of success, others from hitting the wall—hard.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;building-and-designing-software&quot;&gt;Building and Designing Software&lt;/h2&gt;

&lt;h3 id=&quot;automate-your-workflow&quot;&gt;Automate your workflow&lt;/h3&gt;
&lt;p&gt;Automate everything you can—project setup, configuring local environments, creating test data with scripts, etc. If you do it more than once, automate it. Your future self (and teammates) will thank you.&lt;/p&gt;

&lt;h3 id=&quot;applications-should-run-locally&quot;&gt;Applications should run locally&lt;/h3&gt;
&lt;p&gt;If your app can’t run on a dev machine, you’re doing it wrong. Make local setup simple—native or Docker, doesn’t matter. Devs need full control to reproduce problems, debug, and test… locally. Include scripts to generate sample data, simulate API calls, and preload images or documents. These tools are part of the product too.&lt;/p&gt;

&lt;h3 id=&quot;design-your-code-to-fail&quot;&gt;Design your code to fail&lt;/h3&gt;
&lt;p&gt;Don’t trust inputs. Add logs where it matters. Write checkers, guard clauses, notifications for edge case scenarios. Expect things to break—and make it easy to recover.&lt;/p&gt;

&lt;h3 id=&quot;track-requests-and-responses&quot;&gt;Track requests and responses&lt;/h3&gt;
&lt;p&gt;Log everything used in third-party integrations—requests, responses, and payloads. You &lt;em&gt;will&lt;/em&gt; need it for debugging and audits.&lt;/p&gt;

&lt;h3 id=&quot;dont-blindly-trust-your-data&quot;&gt;Don’t blindly trust your data&lt;/h3&gt;
&lt;p&gt;Users send broken data. Bugs introduce garbage into database. Validate everything—on input, on output, on storage. Your system is only as good as the data flowing through it.&lt;/p&gt;

&lt;h3 id=&quot;dont-ignore-idempotency&quot;&gt;Don’t ignore idempotency&lt;/h3&gt;
&lt;p&gt;If something fails, you should be able to retry it without causing damage. Idempotency is your friend. Don’t ship production code without it.&lt;/p&gt;

&lt;h3 id=&quot;be-explicit&quot;&gt;Be explicit&lt;/h3&gt;
&lt;p&gt;Write greppable code. Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;mobile&quot;&lt;/code&gt; instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3&lt;/code&gt; to describe enums. Name things clearly: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;has_balance&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;requested_amount&lt;/code&gt;, etc. Never assume something is “obvious”. It never is.
More on this here: &lt;a href=&quot;https://morizbuesing.com/blog/greppability-code-metric/&quot;&gt;Greppability: A code metric&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;testing--debugging&quot;&gt;Testing &amp;gt; Debugging&lt;/h3&gt;
&lt;p&gt;Relying only on debugging tools could lead you to chase problems after they happen. Testing (especially TDD) helps you move in small, safe steps. You define expectations first, then work towards meeting them. It’s like climbing a mountain one secure foothold at a time.&lt;/p&gt;

&lt;h3 id=&quot;avoid-unnecessary-frameworks&quot;&gt;Avoid unnecessary frameworks&lt;/h3&gt;
&lt;p&gt;The web is just HTTP—receive a request, send a response. That hasn’t changed in decades. You don’t need heavy frameworks to handle 90% of what you build. Stick with boring, battle-tested tools.&lt;/p&gt;

&lt;h3 id=&quot;monoliths-arent-always-bad-microservices-arent-always-good&quot;&gt;Monoliths aren’t always bad. Microservices aren’t always good.&lt;/h3&gt;
&lt;p&gt;Don’t split your system into pieces unless you have a &lt;em&gt;really&lt;/em&gt; good reason. Scale only when you must. Premature complexity is the real bottleneck.&lt;/p&gt;

&lt;h3 id=&quot;worse-is-better&quot;&gt;Worse is better&lt;/h3&gt;
&lt;p&gt;Keep it simple. Forget hexagonal architecture and all the over-engineered “best practices” that solve problems you don’t have. &lt;a href=&quot;https://www.dreamsongs.com/WorseIsBetter.html&quot;&gt;Worse is better&lt;/a&gt;—and often faster, clearer, and easier to maintain.&lt;/p&gt;

&lt;h3 id=&quot;dont-try-to-make-it-perfect-make-it-adaptable&quot;&gt;Don’t try to make it perfect. Make it adaptable.&lt;/h3&gt;
&lt;p&gt;There’s no silver bullet. Instead of aiming for the perfect solution, aim for something adjustable, configurable, flexible, and easy to change when needed. Prioritize adaptability over perfection.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;shipping-and-maintaining-software&quot;&gt;Shipping and Maintaining Software&lt;/h2&gt;

&lt;h3 id=&quot;you-wont-get-it-right-the-first-time&quot;&gt;You won’t get it right the first time&lt;/h3&gt;
&lt;p&gt;No matter the architecture, design, or framework, your first attempt won’t be perfect. You’ll need to iterate to make it truly work. So don’t overthink or over-plan. Ship something functional as soon as possible and let feedback guide you.&lt;/p&gt;

&lt;h3 id=&quot;monitor-and-observe-your-software&quot;&gt;Monitor and observe your software&lt;/h3&gt;
&lt;p&gt;Know something’s broken before your users do. Instrument your systems. Set alerts. Pay attention to the data.&lt;/p&gt;

&lt;h3 id=&quot;always-have-a-plan-b&quot;&gt;Always have a Plan B&lt;/h3&gt;
&lt;p&gt;Have redundancy. Keep backups. Make sure someone else can step in if needed. Single points of failure—whether in infrastructure or people—are liabilities.&lt;/p&gt;

&lt;h3 id=&quot;care-about-the-ux&quot;&gt;Care about the UX&lt;/h3&gt;
&lt;p&gt;Interfaces aren’t just forms anymore. Users talk to systems with voice and text (Alexa, ChatGPT, etc.). If your signup form has 12 fields, they’ll probably quit halfway through.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;working-with-teams-and-people&quot;&gt;Working with Teams and People&lt;/h2&gt;

&lt;h3 id=&quot;estimates-are-bad-manage-expectations-instead&quot;&gt;Estimates are bad. Manage expectations instead.&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://world.hey.com/dhh/software-estimates-have-never-worked-and-never-will-a41a9c71&quot;&gt;Software estimates have never worked—and never will&lt;/a&gt;. Communicate uncertainty. Share tradeoffs. Set expectations early and often.&lt;/p&gt;

&lt;h3 id=&quot;extreme-visibility&quot;&gt;Extreme visibility&lt;/h3&gt;
&lt;p&gt;Keep your teammates, managers, and stakeholders aware of what you’re doing, your progress, and what’s blocking you. It builds trust and reduces surprises.&lt;/p&gt;

&lt;h3 id=&quot;dont-micromanage&quot;&gt;Don’t micromanage&lt;/h3&gt;
&lt;p&gt;If you don’t trust your team, why are they on the team? Focus on outcomes: what’s the impact on the product, team, or company? What’s blocking them? How can you help? Let people figure things out—it’s how they grow.&lt;/p&gt;

&lt;h3 id=&quot;communicate-decisions&quot;&gt;Communicate decisions&lt;/h3&gt;
&lt;p&gt;Why this framework instead of that one? Why this database, this design, this pattern? Write it down. Share it. It prevents repeat mistakes and helps others understand the “why.”&lt;/p&gt;

&lt;h3 id=&quot;share-what-you-know&quot;&gt;Share what you know&lt;/h3&gt;
&lt;p&gt;Sharing accelerates your career more than anything else. Teach. Present. Write. Share what you learn, what you build, and what you discover. The more you share, the more you grow.&lt;/p&gt;

&lt;h3 id=&quot;dont-postpone-obvious-decisions&quot;&gt;Don’t postpone obvious decisions&lt;/h3&gt;
&lt;p&gt;You’re going to make a lot of decisions. Some are big, others are trivial. Don’t waste time overthinking the obvious ones. Make the call, communicate it, and spend your energy where it really matters.&lt;/p&gt;

&lt;h3 id=&quot;reject-dogma&quot;&gt;Reject dogma&lt;/h3&gt;
&lt;p&gt;Take principles like the ones in this post as guidance—not gospel. Don’t follow advice blindly just because someone said it’s best practice. Think. Understand the context. Apply what makes sense.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping up&lt;/h2&gt;

&lt;p&gt;After all these years, one thing is clear: writing software isn’t just about code. It’s about people, communication, trade-offs, and learning to live with imperfection. You won’t get everything right. You’ll ship things that break. You’ll rethink decisions. That’s normal. That’s healthy.&lt;/p&gt;

&lt;p&gt;What matters is staying curious, being honest about what works (and what doesn’t), and helping those around you grow. The lessons in this post aren’t universal truths—they’re just what I’ve picked up from the trenches. Take what’s useful, challenge what isn’t, and keep moving forward.&lt;/p&gt;

&lt;p&gt;And above all: keep it simple, keep it working, and keep learning.&lt;/p&gt;

&lt;p&gt;Enjoy o/&lt;/p&gt;
</description>
        <pubDate>Mon, 14 Apr 2025 00:00:00 +0000</pubDate>
        <link>https://rpanachi.com/after-25-years-writing-software-here-are-a-few-more-things-ive-learned-so-far-part2</link>
        <guid isPermaLink="true">https://rpanachi.com/after-25-years-writing-software-here-are-a-few-more-things-ive-learned-so-far-part2</guid>
        
        
        <category>software engineering</category>
        
        <category>career</category>
        
      </item>
    
      <item>
        <title>How to backup your photos from Google and fix the Exif metadata</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;Any sufficiently advanced incompetence is indistinguishable from malice.&lt;br /&gt;
– Grey’s Law&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Downloading your whole backup from Google Photos should be easy, right? Well, not really. Google’s Takeout service makes it way more annoying than it should be. Not only do you have to wait for about 3 days, manually download a bunch of ZIP files from a page with the worst usability I deal it in the last decade, and discover that the files didn’t come with the info metadata on it (which keeps the correct photo date, time, location, etc), but stored separately in JSON files with inconsistent naming.&lt;/p&gt;

&lt;p&gt;So, sharing my frustration here and how I dealt with it to clean up Google’s mess and get my photos back in order.&lt;/p&gt;

&lt;h2 id=&quot;step-1-getting-the-backup-from-google-takeout&quot;&gt;Step 1: Getting the backup from Google Takeout&lt;/h2&gt;

&lt;p&gt;First, I requested my export from &lt;a href=&quot;https://takeout.google.com/&quot;&gt;Google Takeout&lt;/a&gt;. This took three days to process, since I had about 150GB of photos.&lt;/p&gt;

&lt;p&gt;Once it was ready, I had to manually download 72 ZIP files, one by one, because Google apparently hates bulk downloads. Super fun.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/google-takeout.png&quot; alt=&quot;Google Takeout&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;step-2-extracting-everything&quot;&gt;Step 2: Extracting everything&lt;/h2&gt;

&lt;p&gt;After finally downloading all the files, I extracted them into a single directory with this command:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;for zip in downloaded/*.zip; do unzip $zip -d extracted/; done
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This dumped all the files into a structure like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;extracted/
  Takeout/
    Google Fotos/
      Photos From 2024/
        image1.jpg
        image1.jpg.supplemental-metadata.json
      Photos From 2025/
        image1.jpg
        image1.jpg.supplemental-m.json
        image1.jpg.supplemental-metadata(1).json
        image1(1).jpg
        image2.jpg
        image2.jpg.sup-meta.json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As you can see, consistency isn’t Google’s strong point. I need the metadata filenames to follow the same pattern because it will be used in the next step.&lt;/p&gt;

&lt;h2 id=&quot;step-3-fixing-the-metadata-filenames&quot;&gt;Step 3: Fixing the metadata filenames&lt;/h2&gt;

&lt;p&gt;The correct format of metadata files should be:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;image.jpg.supplemental-metadata.json&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But I found things like:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;image.jpg.sup-meta.json&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;image(1).jpg.supplemental-meta.json&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To clean this up, I wrote a Ruby script that:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Finds all metadata files with weird names.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Renames them to match the expected format.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;If an image doesn’t have a metadata file, it tries to guess the date from the filename (e.g., IMG_20240515.jpg → May 15, 2024).&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s the Ruby script I used:&lt;/p&gt;

&lt;script src=&quot;https://gist.github.com/rpanachi/aa8a18bf090b580d6c1c2d4e9c6f51c6.js&quot;&gt;&lt;/script&gt;

&lt;p&gt;To run it, simply execute on the terminal:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ruby fix_metadata.rb extracted/Takeout/Google\ Fotos/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The output will be like:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ ruby fix_metadata.rb ./extracted/Takeout/Google\ Photos/
Total files found on ./extracted/Takeout/Google Photos/**/*: 555
Total photos from YYYY dirs found: 539
Total supported photos formats found: 131
Total metadata files found: 408

Process finalized with 3 errors:
[1/3] Unable to infer metadata for ./extracted/Takeout/Google Photos/Photos from 2013/CameraZOOM-01611c5b98.jpg
[2/3] Unable to infer metadata for ./extracted/Takeout/Google Photos/Photos from 2013/CameraZOOM-20131224200750052.jpg
[3/3] Metadata file: .//extracted/Takeout/Google Photos/Photos from 2014/IMG_1181.JPG.supplemental-metadata(1).json not exist for image: ./extracted/Takeout/Google Photos/Photos from 2014/IMG_1181(1).JPG

Process finalized with 22 fixes:
[1/22] 5142914356_01611c5b98_o.jpg.supplemental-metad.json moved to 5142914356_01611c5b98_o.jpg.supplemental-metadata.json
[2/22] 1-3922-1990-20121024123511.jpg.supplemental-me.json moved to 1-3922-1990-
...
[22/22] IMG_1182.JPG.supplemental-metadata(1).json moved to IMG_1182(1).JPG.supplemental-metadata.json

Metadata not found for 21 files:
[1/21] ./extracted/Takeout/Google Photos/Photos from 2013/CameraZOOM-20131224200623261.jpg
[2/21] ./extracted/Takeout/Google Photos/Photos from 2013/CameraZOOM-20131224200750052.jpg
...
[21/21] ./extracted/Takeout/Google Photos/Photos from 2025/IMG_5854.HEIC
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;step-4-applying-the-metadata-to-files&quot;&gt;Step 4: Applying the metadata to files&lt;/h2&gt;

&lt;p&gt;Once the filenames were fixed, I used &lt;a href=&quot;https://exiftool.org/&quot;&gt;exiftool&lt;/a&gt; to write the correct metadata into the photo files:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;exiftool -r -d %s -tagsfromfile &quot;%d/%F.supplemental-metadata.json&quot; \
  &quot;-GPSAltitude&amp;lt;GeoDataAltitude&quot; &quot;-GPSLatitude&amp;lt;GeoDataLatitude&quot; \
  &quot;-GPSLatitudeRef&amp;lt;GeoDataLatitude&quot; &quot;-GPSLongitude&amp;lt;GeoDataLongitude&quot; \
  &quot;-GPSLongitudeRef&amp;lt;GeoDataLongitude&quot; &quot;-Keywords&amp;lt;Tags&quot; &quot;-Subject&amp;lt;Tags&quot; \
  &quot;-Caption-Abstract&amp;lt;Description&quot; &quot;-ImageDescription&amp;lt;Description&quot; \
  &quot;-DateTimeOriginal&amp;lt;PhotoTakenTimeTimestamp&quot; \
  -ext &quot;*&quot; -overwrite_original -progress --ext json -ifd0:all= \
  extracted/Takeout/Google\ Fotos/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(just change the last argument to the directory where you extract the photos)&lt;/p&gt;

&lt;p&gt;This does a bunch of things:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Reads the correct timestamp from the JSON file.&lt;/li&gt;
  &lt;li&gt;Writes it into the photo’s EXIF data.&lt;/li&gt;
  &lt;li&gt;Restores GPS location and descriptions.&lt;/li&gt;
  &lt;li&gt;Fixes bad tags in one go.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;

&lt;p&gt;Now that all the metadata is fixed, I can import everything back into iCloud Photos (my case) or any other storage service without messing up the timeline.&lt;/p&gt;

&lt;p&gt;Let me know if you have any other tricks or improvements!&lt;/p&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;&quot;&gt;https://legault.me/post/correctly-migrate-away-from-google-photos-to-icloud&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;&quot;&gt;https://www.photools.com/community/index.php?topic=13589.0&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
        <pubDate>Sun, 16 Mar 2025 00:00:00 +0000</pubDate>
        <link>https://rpanachi.com/how-to-takeout-from-google-photos-and-fix-metadata-exif-info</link>
        <guid isPermaLink="true">https://rpanachi.com/how-to-takeout-from-google-photos-and-fix-metadata-exif-info</guid>
        
        
        <category>tutorial</category>
        
        <category>google takeout</category>
        
        <category>hacking</category>
        
        <category>ruby</category>
        
        <category>exiftool</category>
        
      </item>
    
      <item>
        <title>Monitoring my swimming pool temperature with a cheap BLE sensor and ESPHome</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;One accurate measurement is worth a thousand expert opinions.&lt;br /&gt;
– Grace Hopper&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Maintaining the right temperature for my swimming pool is crucial for enjoying a good swim. Instead of frequently checking a thermometer or relying on a mobile app, I wanted to get real-time temperature updates remotely. Here’s how I achieved this using an affordable BLE sensor and ESPHome.&lt;/p&gt;

&lt;h2 id=&quot;ble-sensor&quot;&gt;BLE Sensor&lt;/h2&gt;

&lt;p&gt;I found a suitable &lt;a href=&quot;https://s.click.aliexpress.com/e/_mNXCHZQ&quot;&gt;BLE sensor on AliExpress&lt;/a&gt; that fit my requirements perfectly:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Power efficiency: the sensor runs on two AAA batteries, lasting about six months.&lt;/li&gt;
  &lt;li&gt;Precision: it has good precision with an error of just 1° C approximately.&lt;/li&gt;
  &lt;li&gt;Data collecting: it works well with its dedicated mobile app, but doesn’t integrate with third-party apps.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/Hb00c297cf3e249018102d7f62cedd77bE.png&quot; alt=&quot;BLE Pool Sensor&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Despite its limitations, the sensor was perfect for my project. However, I needed a way to access the temperature data in real-time without using the mobile app or being near the pool.&lt;/p&gt;

&lt;h2 id=&quot;esphome&quot;&gt;ESPHome&lt;/h2&gt;

&lt;p&gt;To bridge this gap, I used an ESP32-WROOM-32U, which I also &lt;a href=&quot;https://s.click.aliexpress.com/e/_msnaRdA&quot;&gt;bought from AliExpress&lt;/a&gt;. The external antenna on this model ensures a reliable connection over a longer range.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/H718a967ecf3b48429b23ade45b4a9543n.png&quot; alt=&quot;ESP-WROOM-32U&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The ESP32 will work just as a BLE &amp;lt;&amp;gt; Wi-Fi bridge, connecting to the sensor on each hour, reading the temperature and sending the data to Home Assistant sensors.&lt;/p&gt;

&lt;p&gt;Here’s how I set it up on ESPHome config:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;esphome&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;esphome-pool-monitor&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;friendly_name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Pool Monitor&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;esp32&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;board&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;esp32dev&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;framework&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;arduino&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Enable logging&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Enable Home Assistant API&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;api&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;encryption&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;wmIdKCoFredactedNGeGZHeQI=&quot;&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;ota&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;wifi&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;ssid&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;!secret&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;wifi_ssid&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;!secret&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;wifi_password&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;platform&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;homeassistant&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;homeassistant_time&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;on_time&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;seconds&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;minutes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;30&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;switch.turn_on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ibs_p01b_switch&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;5s&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;while&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;condition&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;switch.is_on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ibs_p01b_switch&lt;/span&gt;
                  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;binary_sensor.is_off&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ibs_p01b_connected&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;logger.log&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Trying&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;IBS-P01/B&quot;&lt;/span&gt;
                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;esp32_ble_tracker.stop_scan&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;5s&lt;/span&gt;
                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;esp32_ble_tracker.start_scan&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;1min&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;esp32_ble_tracker&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;scan_parameters&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; 
    &lt;span class=&quot;na&quot;&gt;active&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;continuous&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;1min&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;on_ble_advertise&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;mac_address&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;XX:XX:XX:XX:XX:XX&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;lambda&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|-&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;ESP_LOGD(&quot;ble_adv&quot;, &quot;IBS-P01/B device found&quot;);&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;button&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;platform&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;template&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;BLE&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Start&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Scan&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;on_press&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;esp32_ble_tracker.start_scan&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;platform&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;template&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;BLE&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Stop&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Scan&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;on_press&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;esp32_ble_tracker.stop_scan&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;

  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;platform&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;restart&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;ESP32&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Restart&quot;&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;binary_sensor&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;platform&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;status&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;ESP32&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Status&quot;&lt;/span&gt;

&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;platform&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;template&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ibs_p01b_connected&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;icon&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;mdi:bluetooth-connect&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;IBS-P01/B&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Connected&quot;&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;ble_client&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; 
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;mac_address&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;XX:XX:XX:XX:XX:XX&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ibs_p01b&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;on_connect&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;wait_until&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;   
            &lt;span class=&quot;na&quot;&gt;lambda&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|-&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;esphome::ble_client::BLEClient* client = id(ibs_p01b);&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;esphome::ble_client::BLECharacteristic* chr = client-&amp;gt;get_characteristic(0xFFF0, 0xFFF2);&lt;/span&gt;

              &lt;span class=&quot;s&quot;&gt;return chr != nullptr;              &lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;lambda&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|-&lt;/span&gt; 
            &lt;span class=&quot;s&quot;&gt;ESP_LOGD(&quot;ble_client_lambda&quot;, &quot;Connected to IBS-P01/B&quot;);&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;id(ibs_p01b_connected).publish_state(true);&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;on_disconnect&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;lambda&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|-&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;ESP_LOGD(&quot;ble_client&quot;, &quot;Disconnected from IBS-P01/B&quot;);&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;id(ibs_p01b_connected).publish_state(false);&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;switch&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;platform&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ble_client&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ibs_p01b_switch&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ble_client_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ibs_p01b&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;IBS-P01/B&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Enabled&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;restore_mode&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ALWAYS_OFF&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;sensor&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;platform&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;uptime&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;ESP32&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Uptime&quot;&lt;/span&gt;

  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;platform&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;template&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;device_class&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;timestamp&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;IBS-P01/B&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Last&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Measurement&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;ibs_p01b_last_measurement&quot;&lt;/span&gt;

  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;platform&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;template&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;IBS-P01/B&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Temperature&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ibs_p01b_temperature&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;unit_of_measurement&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;°C&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;accuracy_decimals&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;state_class&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;measurement&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;device_class&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;temperature&lt;/span&gt;

  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;platform&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ble_client&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ibs_p01b_sensor&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;characteristic&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ble_client_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ibs_p01b&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;service_uuid&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;fff0&apos;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;characteristic_uuid&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;fff2&apos;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;notify&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;internal&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;update_interval&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;30s&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;lambda&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|-&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;if (x.size() == 0) return NAN;&lt;/span&gt;

      &lt;span class=&quot;s&quot;&gt;// https://community.home-assistant.io/t/hex-string-to-dec-value/458945/2&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;auto temp = (float)((int16_t)(x[1]&amp;lt;&amp;lt; 8) + x[0])/100;&lt;/span&gt;

      &lt;span class=&quot;s&quot;&gt;if (temp != NAN) {&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;id(ibs_p01b_temperature).publish_state(temp);&lt;/span&gt;

        &lt;span class=&quot;s&quot;&gt;// https://community.home-assistant.io/t/publish-timestamp-into-text-sensor/122531/11&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;id(ibs_p01b_last_measurement).publish_state(id(homeassistant_time).now().timestamp);&lt;/span&gt;

        &lt;span class=&quot;s&quot;&gt;id(ibs_p01b_switch).turn_off();&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;s&quot;&gt;return 0.0;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Notes:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;For obvious reasons, I redacted the mac address of my device.&lt;/li&gt;
  &lt;li&gt;You can find the mac address and inspect the characteristics for your sensor with apps like &lt;a href=&quot;https://apps.apple.com/us/app/bluetooth-inspector/id1509085044&quot;&gt;BT Inspector&lt;/a&gt; or
&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.punchthrough.lightblueexplorer&amp;amp;hl=en&quot;&gt;LightBlue&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IBS-P01/B&lt;/code&gt; is the name/model of my sensor. You can use other devices for this
purpose since they have bluetooth support.&lt;/li&gt;
  &lt;li&gt;I’ll not cover the details for setting up ESPHome and devices, but you can
watch a &lt;a href=&quot;https://www.youtube.com/watch?v=iufph4dF3YU&quot;&gt;detailed tutorial from Everything Smart Home Youtube
channel&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After uploading the firmware to ESP32 and with a little luck, you will see something like that on the logs:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[16:24:04][D][switch:012]: &apos;IBS-P01/B Enabled&apos; Turning ON.
[16:24:04][D][switch:055]: &apos;IBS-P01/B Enabled&apos;: Sending state ON
[16:24:05][D][button:010]: &apos;BLE Start Scan&apos; Pressed.
[16:24:05][D][esp32_ble_tracker:266]: Starting scan...
[16:24:11][D][ble_adv:069]: IBS-P01/B device found
[16:24:11][D][esp32_ble_client:110]: [0] [XX:XX:XX:XX:XX:XX] Found device
[16:24:11][D][esp32_ble_tracker:665]: Found device XX:XX:XX:XX:XX:XX RSSI=-82
[16:24:11][D][esp32_ble_tracker:686]:   Address Type: PUBLIC
[16:24:11][D][esp32_ble_tracker:215]: Pausing scan to make connection...
[16:24:11][D][esp32_ble_tracker:303]: End of scan.
[16:24:11][I][esp32_ble_client:067]: [0] [XX:XX:XX:XX:XX:XX] 0x00 Attempting BLE connection
[16:24:24][I][ble_sensor:031]: [ibs_p01b_sensor] Connected successfully!
[16:24:26][I][esp32_ble_client:227]: [0] [XX:XX:XX:XX:XX:XX] Connected
[16:24:26][D][ble_client_lambda:115]: Connected to IBS-P01/B
[16:24:26][D][binary_sensor:036]: &apos;IBS-P01/B Connected&apos;: Sending state ON
[16:24:32][D][sensor:094]: &apos;IBS-P01/B Temperature&apos;: Sending state 22.77000 °C with 1 decimals of accuracy
[16:24:32][D][sensor:094]: &apos;IBS-P01/B Last Measurement&apos;: Sending state 1722626688.00000  with 1 decimals of accuracy
[16:24:32][D][sensor:094]: &apos;ibs_p01b_sensor&apos;: Sending state 0.00000  with 0 decimals of accuracy
[16:25:02][D][sensor:094]: &apos;IBS-P01/B Temperature&apos;: Sending state 22.77000 °C with 1 decimals of accuracy
[16:25:02][D][sensor:094]: &apos;IBS-P01/B Last Measurement&apos;: Sending state 1722626688.00000  with 1 decimals of accuracy
[16:25:02][D][sensor:094]: &apos;ibs_p01b_sensor&apos;: Sending state 0.00000  with 0 decimals of accuracy
[16:25:43][D][ble_client:121]: Disconnected from IBS-P01/B
[16:25:43][D][binary_sensor:036]: &apos;IBS-P01/B Connected&apos;: Sending state OFF
[16:25:43][W][ble_sensor:037]: [ibs_p01b_sensor] Disconnected!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And, of course, the data in Home Assistant device:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/yXf3chgQ.png&quot; alt=&quot;Home Assistant sensor&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;

&lt;p&gt;It’s working but it’s not finished. I need to keep the ESP32 turned on and near the pool, running continuously.&lt;/p&gt;

&lt;p&gt;I used a standard USB 5V DC adapter plugged into a wall outlet near the pool. To protect the hardware from the elements, I 3D printed a box to accommodate both the ESP32 and the DC adapter:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/esp32-pool-monitor.jpg&quot; alt=&quot;ESP32 BLE Pool Monitor&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If you want to print the same box for your project, &lt;a href=&quot;https://www.printables.com/model/962422-esp32-pool-monitor-box&quot;&gt;download the model from my Printables profile&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This setup has been running flawlessly for the past 10 months, providing me with accurate and (almost) real-time water temperature, making pool maintenance much easier. From here, I can create automations to turn the heater on, send a message on family group when the temperature is ideal, and much more.&lt;/p&gt;

&lt;p&gt;Enjoy o/&lt;/p&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.home-assistant.io/integrations/esphome&quot;&gt;https://www.home-assistant.io/integrations/esphome&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://esphome.io/guides/getting_started_hassio.html&quot;&gt;https://esphome.io/guides/getting_started_hassio.html&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://esphome.io/components/ble_client.html&quot;&gt;https://esphome.io/components/ble_client.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Tue, 03 Sep 2024 00:00:00 +0000</pubDate>
        <link>https://rpanachi.com/monitoring-swimming-pool-temperature-cheap-sensor-esphome</link>
        <guid isPermaLink="true">https://rpanachi.com/monitoring-swimming-pool-temperature-cheap-sensor-esphome</guid>
        
        
        <category>home assistant</category>
        
        <category>home automation</category>
        
        <category>esphome</category>
        
        <category>3dprinting</category>
        
        <category>diy</category>
        
      </item>
    
  </channel>
</rss>
