<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Waruna</title>
    <description>The latest articles on DEV Community by Waruna (@waruna).</description>
    <link>https://dev.to/waruna</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1270744%2Fcfe828a4-218d-4e5b-a509-88300444878a.png</url>
      <title>DEV Community: Waruna</title>
      <link>https://dev.to/waruna</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/waruna"/>
    <language>en</language>
    <item>
      <title>I Turned Claude Code Into a Dev Team With One File</title>
      <dc:creator>Waruna</dc:creator>
      <pubDate>Wed, 08 Apr 2026 15:05:02 +0000</pubDate>
      <link>https://dev.to/waruna/i-turned-claude-code-into-a-dev-team-with-one-file-29i6</link>
      <guid>https://dev.to/waruna/i-turned-claude-code-into-a-dev-team-with-one-file-29i6</guid>
      <description>&lt;p&gt;Most people drop a few rules into their &lt;code&gt;CLAUDE.md&lt;/code&gt; and call it a day. I built an entire engineering workflow — a Tech Lead, specialist agents, a senior code reviewer, and a validation pipeline — all orchestrated from a single global file sitting in &lt;code&gt;~/.claude/CLAUDE.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's how I got there, and why it changed how I work with Claude Code.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: Claude Code Is a Brilliant Intern
&lt;/h2&gt;

&lt;p&gt;Out of the box, Claude Code is smart but unsupervised. It'll jump straight into implementing something without asking clarifying questions. It'll say "done" without compiling. It'll delete a file it shouldn't have touched. It'll assume what you meant instead of asking what you meant.&lt;/p&gt;

&lt;p&gt;Sound familiar?&lt;/p&gt;

&lt;p&gt;These aren't Claude Code problems — they're process problems. And they're the exact same problems you'd have with a junior developer who has no engineering lead, no code review, and no CI pipeline.&lt;/p&gt;

&lt;p&gt;So I gave Claude Code all three.&lt;/p&gt;




&lt;h2&gt;
  
  
  Global vs Project: The Right Separation
&lt;/h2&gt;

&lt;p&gt;Before I walk through the file, one important design decision: I keep two layers of &lt;code&gt;CLAUDE.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Global&lt;/strong&gt; (&lt;code&gt;~/.claude/CLAUDE.md&lt;/code&gt;) defines &lt;em&gt;how&lt;/em&gt; work gets done — the process, the roles, the rules. It applies to everything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Project-level&lt;/strong&gt; (&lt;code&gt;project-root/CLAUDE.md&lt;/code&gt;) defines &lt;em&gt;what&lt;/em&gt; we're working with — the tech stack, naming conventions, file structure, dependencies. It's specific to each codebase.&lt;/p&gt;

&lt;p&gt;This separation means my workflow stays consistent whether I'm working on a SwiftUI app, a Rails backend, or a Next.js frontend. The process doesn't change. Only the context does.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Pipeline
&lt;/h2&gt;

&lt;p&gt;Here's the core idea: every task flows through a pipeline, just like it would on a real engineering team.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Task
    → Tech Lead Orchestrator (analyze, route, spec)
        → Specialist Agent (implement)
            → Senior Code Reviewer (review, loop until clean)
                → Validation (build, compile, test — prove it works)
                    → Delivery
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No shortcuts on quality. Every path goes through review and validation. Let me break down each piece.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Tech Lead Orchestrator
&lt;/h2&gt;

&lt;p&gt;Every task starts here. No agent works independently without a brief from the Tech Lead. This is the single most important rule in the file, and it solves the "Claude just started coding before understanding the task" problem.&lt;/p&gt;

&lt;p&gt;The Tech Lead's job:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read the project's CLAUDE.md&lt;/strong&gt; before doing anything (context loading)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analyze the task&lt;/strong&gt; and determine complexity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clarify ambiguities&lt;/strong&gt; with the user before assigning work&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Route appropriately&lt;/strong&gt; — trivial tasks get a fast track, complex ones get a full spec&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That context loading step is subtle but critical. Without it, Claude will carry assumptions from a previous task into a new one. Explicitly telling it to re-read the project file at the start of every task prevents drift.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Fast-Track vs Full Path
&lt;/h2&gt;

&lt;p&gt;Not every task needs the same ceremony. A typo fix shouldn't go through the same process as a new feature.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fast-track&lt;/strong&gt; is for trivial changes — single-file fixes, formatting, config tweaks, simple renames. The Tech Lead assigns directly to a specialist with a lightweight brief instead of a full spec.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full path&lt;/strong&gt; is for everything else. The Tech Lead writes a detailed spec with acceptance criteria, relevant file paths, and clear deliverables.&lt;/p&gt;

&lt;p&gt;Here's the key: &lt;strong&gt;fast-track skips the detailed spec, not the review.&lt;/strong&gt; Both paths converge at the same Senior Code Reviewer and Validation gates. Nothing ships unreviewed, regardless of how simple it seems.&lt;/p&gt;

&lt;p&gt;This was a deliberate choice. I've been bitten too many times by "trivial" changes that introduced regressions. Even a one-liner deserves a second pair of eyes.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. The Senior Code Reviewer
&lt;/h2&gt;

&lt;p&gt;This is where quality gets enforced. Every change, from every path, passes through the Senior Code Reviewer before it reaches the user.&lt;/p&gt;

&lt;p&gt;The reviewer checks against:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Project standards and the project-level &lt;code&gt;CLAUDE.md&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Bugs, edge cases, and security issues&lt;/li&gt;
&lt;li&gt;A severity classification: &lt;strong&gt;critical&lt;/strong&gt; (blocker), &lt;strong&gt;warning&lt;/strong&gt; (should fix), &lt;strong&gt;nit&lt;/strong&gt; (optional)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If there are concerns, the work goes back to the specialist with specific feedback. This loops until the reviewer passes with no concerns. No critical issues means the code moves to validation.&lt;/p&gt;

&lt;p&gt;The review loop is what turns Claude Code from "generate and hope" into "generate and verify." It's the difference between code that looks right and code that is right.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Validation: Prove It Works
&lt;/h2&gt;

&lt;p&gt;This is the piece most &lt;code&gt;CLAUDE.md&lt;/code&gt; files miss entirely. Claude will happily tell you "the implementation is complete" without ever running the code. That's not good enough.&lt;/p&gt;

&lt;p&gt;After review passes, the code must be validated using the appropriate method for the project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Swift/iOS&lt;/strong&gt; → &lt;code&gt;xcodebuild&lt;/code&gt;, run in iOS Simulator&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Android&lt;/strong&gt; → Gradle build, run in Android Emulator&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web (JS/TS)&lt;/strong&gt; → &lt;code&gt;npm run build&lt;/code&gt; or &lt;code&gt;yarn build&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web (Rails)&lt;/strong&gt; → &lt;code&gt;bundle exec rails test&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browser UI&lt;/strong&gt; → Playwright end-to-end tests&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CLI tools&lt;/strong&gt; → Run the command, verify output&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Libraries&lt;/strong&gt; → Run the test suite (pytest, rspec, jest, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If validation fails, it loops back to the specialist with the actual errors. No task is considered done until it has been proven working.&lt;/p&gt;

&lt;p&gt;This single addition — "compile it, build it, run it" — eliminated an entire class of bugs I used to catch manually after delivery.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. The Safety Rails
&lt;/h2&gt;

&lt;p&gt;Beyond the pipeline, I have a few rules that apply globally across all agents.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Golden Rule
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;NEVER ASSUME. ALWAYS ASK.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If requirements are unclear, ambiguous, or incomplete: stop work, list specific questions, wait for clarification. This applies to every agent at every stage.&lt;/p&gt;

&lt;p&gt;It sounds obvious, but without this rule, Claude will fill in the blanks with assumptions — and those assumptions are often wrong. Especially in agentic loops where one bad assumption compounds through the entire pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Destructive Actions
&lt;/h3&gt;

&lt;p&gt;Certain operations require explicit user confirmation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deleting files or directories&lt;/li&gt;
&lt;li&gt;Renaming files that are imported elsewhere&lt;/li&gt;
&lt;li&gt;Large-scale refactors across multiple files&lt;/li&gt;
&lt;li&gt;Removing or replacing dependencies&lt;/li&gt;
&lt;li&gt;Dropping database tables or destructive migrations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I learned this the hard way. Claude is fast. Sometimes too fast. A confirmation gate on irreversible actions gives you a chance to catch mistakes before they happen.&lt;/p&gt;

&lt;h3&gt;
  
  
  Iteration Limit
&lt;/h3&gt;

&lt;p&gt;Maximum 3 revision cycles across review and validation combined. If the issue isn't resolved after 3 loops, the Tech Lead escalates to the user with a summary of what's blocking progress. This prevents infinite loops where Claude keeps trying and failing to fix the same issue.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Standardized Communication
&lt;/h2&gt;

&lt;p&gt;When you have multiple agents handing off work to each other, the format of those handoffs matters. I standardized it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tech Lead → Specialist&lt;/strong&gt;: Task description, acceptance criteria as a checklist, relevant file paths.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Specialist → Reviewer&lt;/strong&gt;: Summary of changes, files modified, trade-offs or decisions made.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reviewer → Tech Lead&lt;/strong&gt;: Critical issues (blockers), warnings (should fix), nits (optional). Clear pass/fail.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't glamorous, but it prevents a lot of wasted cycles. When the reviewer gets a change with no context about what was intended, the review quality drops. When the specialist gets vague feedback like "needs improvement," they guess — and usually guess wrong.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Meta Moment
&lt;/h2&gt;

&lt;p&gt;Here's the part that made me smile: I built this file &lt;em&gt;with&lt;/em&gt; Claude. I started with my original version, asked Claude to critique it, iterated on the suggestions, and refined the result through conversation.&lt;/p&gt;

&lt;p&gt;Claude reviewed its own operating instructions, found gaps, and helped fix them. If that's not eating your own dog food, I don't know what is.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Changed in Practice
&lt;/h2&gt;

&lt;p&gt;Since adopting this workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fewer "it's done" surprises.&lt;/strong&gt; The validation step catches compilation errors and test failures before I ever see the output.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clearer communication.&lt;/strong&gt; When Claude asks me a clarifying question instead of assuming, it saves 10 minutes of undoing wrong assumptions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistent quality across projects.&lt;/strong&gt; Whether I'm in a Swift codebase or a Rails app, the process is the same. Only the project-level context changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Less babysitting.&lt;/strong&gt; The review loop catches issues I would have caught manually in a second pass. Now I spend that time on higher-level decisions.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;The full file lives in &lt;code&gt;~/.claude/CLAUDE.md&lt;/code&gt;. That's the global config — it applies to every project you work on with Claude Code.&lt;/p&gt;

&lt;p&gt;For project-specific rules (tech stack, naming conventions, file structure), create a &lt;code&gt;CLAUDE.md&lt;/code&gt; in your project root. The global file handles the &lt;em&gt;how&lt;/em&gt;, the project file handles the &lt;em&gt;what&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Start with the pieces that solve your biggest pain points. If Claude keeps assuming instead of asking, add the Golden Rule. If you're tired of "done" without proof, add the Validation step. If changes keep shipping with bugs, add the Review loop.&lt;/p&gt;

&lt;p&gt;You don't need the whole pipeline on day one. But once you have it, you won't go back to working without it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you're using Claude Code and want to see the complete CLAUDE.md file, drop a comment — happy to share the full thing.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>programming</category>
      <category>vibecoding</category>
    </item>
    <item>
      <title>I built a real-time F1 race replay and live timing app — here's how</title>
      <dc:creator>Waruna</dc:creator>
      <pubDate>Thu, 26 Feb 2026 11:15:30 +0000</pubDate>
      <link>https://dev.to/waruna/i-built-a-real-time-f1-race-replay-and-live-timing-app-heres-how-4ph</link>
      <guid>https://dev.to/waruna/i-built-a-real-time-f1-race-replay-and-live-timing-app-heres-how-4ph</guid>
      <description>&lt;p&gt;I'm a massive F1 fan. And like most F1 fans, I've always been frustrated by the TV coverage — you only ever see what the director wants you to see. What's happening with the midfield battle? Where exactly is everyone on track right now?&lt;/p&gt;

&lt;p&gt;So I built Race Telemetry.&lt;/p&gt;

&lt;p&gt;It's a web app that lets you replay any F1 race — all 20 cars, moving around the actual circuit in real time, exactly as it happened. Think of it like one of those scale electric race sets, but for real F1 data. You can also follow live car positions during a Grand Prix as it's happening.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What it does&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Race replay — pick any race from 2024 or 2025, hit play, and watch every car move around the real circuit layout&lt;/li&gt;
&lt;li&gt;Live timing — during a live race, track where every car actually is on track (not just the standings)&lt;/li&gt;
&lt;li&gt;Analytics — dig into lap data, position changes, and strategy&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;The tech stack&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Frontend&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Next.js 15 (App Router) + React 19 + TypeScript&lt;/li&gt;
&lt;li&gt;Tailwind CSS v4 + Framer Motion for animations&lt;/li&gt;
&lt;li&gt;Canvas for the track map (much better than SVG for 20 moving dots with GPS data)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Backend&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;FastAPI (Python) with async httpx&lt;/li&gt;
&lt;li&gt;File-based JSON cache to avoid hammering the API&lt;/li&gt;
&lt;li&gt;OpenF1 API for all race data (real GPS coordinates, lap times, positions)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Data&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The hardest part was figuring out car positions. The OpenF1 API gives you GPS coordinates for each car, but interpolating that into smooth, accurate replay required some work. I ended up using cumulative lap time to compute positions rather than relying on the position endpoint alone, with a fallback for edge cases.&lt;/p&gt;

&lt;p&gt;Another tricky bit — OAuth2 token handling with OpenF1. It returns expires_in as a string, not an integer. Took me longer than I'd like to admit to figure that out.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;The track map&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One of the things I'm most happy with is the track map. It uses actual GPS circuit outlines from the OpenF1 Location API — not a simplified oval or SVG approximation. Every circuit is rendered from real coordinate data, which means the Monaco hairpin looks like the Monaco hairpin.&lt;/p&gt;

&lt;p&gt;Rendering is done on Canvas rather than SVG or DOM elements. With 20 cars updating every fraction of a second, Canvas is significantly faster and the animation stays smooth.&lt;/p&gt;




&lt;p&gt;What's next&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;iOS mobile app&lt;/li&gt;
&lt;li&gt;Deeper analytics (tire strategy, gap charts, sector times)&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Try it&lt;/p&gt;

&lt;p&gt;It's live now at &lt;a href="https://www.racetelemetry.com" rel="noopener noreferrer"&gt;https://www.racetelemetry.com&lt;/a&gt; — no signup, just pick a race and go.&lt;/p&gt;

&lt;p&gt;Would love feedback from other devs and F1 fans. What would you want to see next?&lt;/p&gt;

</description>
      <category>data</category>
      <category>showdev</category>
      <category>sideprojects</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Ruby on Rails - Calculating pricing based user's purchasing power parity</title>
      <dc:creator>Waruna</dc:creator>
      <pubDate>Fri, 10 Jan 2025 18:12:29 +0000</pubDate>
      <link>https://dev.to/waruna/ruby-on-rails-calculating-pricing-based-users-purchasing-power-parity-4bc8</link>
      <guid>https://dev.to/waruna/ruby-on-rails-calculating-pricing-based-users-purchasing-power-parity-4bc8</guid>
      <description>&lt;p&gt;Recently, I published a simple ruby gem for calculating pricing for digital products based on the user's purchasing power parity. &lt;/p&gt;

&lt;p&gt;Not everyone can afford the default pricing of the high-income world. A cup of Coffee in India or Argentina costs less than in the USA or Singapore. Therefore, I wanted to apply the same principle to a digital product I'm building. I couldn't find an easy way to calculate this with Ruby. Looking around, I got inspired by an NPM package called purchasing-power-parity npm and created a similar library using Ruby.&lt;/p&gt;

&lt;p&gt;If you would like to take a look at the source code or would like to contribute here in the link to Github repo.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/warunacds/geo_ppp" rel="noopener noreferrer"&gt;https://github.com/warunacds/geo_ppp&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Library is pretty simple to use. Add this line to your application's Gemfile like any other library&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gem &lt;span class="s1"&gt;'geo_ppp'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and run bundle&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;now you can call it anywhere you like and it would look like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
  &lt;span class="n"&gt;original_price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;79.99&lt;/span&gt;

  &lt;span class="k"&gt;begin&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;GeoPPPFetcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;
     &lt;span class="n"&gt;discounted_price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;original_price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'pppConversionFactor'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
     &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;original_price: &lt;/span&gt;&lt;span class="n"&gt;original_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;discounted_price: &lt;/span&gt;&lt;span class="n"&gt;discounted_price&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
     &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;success: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;StandardError&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;success: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;error: &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;status: :unprocessable_entity&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That pretty much it for now. If you like this project or find it useful don't forget to give a star on github. &lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
    </item>
    <item>
      <title>Monkey patching on Ruby</title>
      <dc:creator>Waruna</dc:creator>
      <pubDate>Fri, 04 Oct 2024 18:33:15 +0000</pubDate>
      <link>https://dev.to/waruna/monkey-patching-on-ruby-349k</link>
      <guid>https://dev.to/waruna/monkey-patching-on-ruby-349k</guid>
      <description>&lt;p&gt;Paperclip used to be a popular accessible file attachment library for ActiveRecord. However, with the introduction of ActiveStorage, its use case was replaced. It is now deprecated and does not support Ruby 3.0.&lt;/p&gt;

&lt;p&gt;Recently, I had to work on an old Ruby on Rails app, migrating it from Ruby 2.7 to Ruby 3.0. However, moving all the paperclip logic to ActiveStorage was impossible, given the time and budget constraints.&lt;/p&gt;

&lt;p&gt;But I was getting the following error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;undefined method escape' for URI:Module

module Paperclip
class UrlGenerator
  def escape_url(url)
    else
      URI.escape(url).gsub(escape_regex){|m| "%#{m.ord.to_s(16).upcase}" }
     end
   end
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After researching, I found out this error happens because URI.escape was deprecated in Ruby 2.7 and removed in Ruby 3.0. This is where monkey patching comes in.&lt;br&gt;
In Ruby, a Monkey Patch is a dynamic modification to a class, which means adding new or overwriting existing methods at runtime. Ruby provides this ability to give coders more flexibility. &lt;/p&gt;

&lt;p&gt;To fix this, we can rewrite the escape_url method to use an alternative way to escape URLs, such as URI::DEFAULT_PARSER.escape or CGI.escape.&lt;/p&gt;

&lt;p&gt;So, how would you do this? First, create a monkey patch file in the config/initializers directory. So it will be loaded when the project runs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# config/initializers/paperclip_monkeypatch.rb

module Paperclip
  class UrlGenerator
    # Monkey patch to replace the deprecated URI.escape with URI::DEFAULT_PARSER.escape
    def escape_url(url)
      if url.respond_to?(:escape)
        url.escape
      else
        # Use URI::DEFAULT_PARSER.escape to handle the escaping
        URI::DEFAULT_PARSER.escape(url).gsub(escape_regex) { |m| "%#{m.ord.to_s(16).upcase}" }
      end
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;URI::DEFAULT_PARSER.escape: This is the recommended alternative to URI.escape, which provides similar functionality for escaping URLs.&lt;/p&gt;

&lt;p&gt;Restart Rails server to apply the change.&lt;/p&gt;

&lt;p&gt;This should resolve the undefined method 'escape' error in Paperclip. I want to highlight that this is not a recommended method. It is only a quick solution, a bandage, so to speak. I'm planning to move this project to active storage as soon as possible, but this might come in handy for your hobby project or as a quick fix.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>paperclip</category>
      <category>monkeypatch</category>
    </item>
    <item>
      <title>Generating dynamic layouts in Vue using recursive components</title>
      <dc:creator>Waruna</dc:creator>
      <pubDate>Fri, 04 Oct 2024 17:58:33 +0000</pubDate>
      <link>https://dev.to/waruna/generating-dynamic-layouts-in-vue-using-recursive-components-4ci7</link>
      <guid>https://dev.to/waruna/generating-dynamic-layouts-in-vue-using-recursive-components-4ci7</guid>
      <description>&lt;p&gt;Inspiration for this article comes from a recent implementation I did to build dynamic layouts on Vue based front end.&lt;/p&gt;

&lt;p&gt;Let's assume your API endpoint returns a YAML layout like below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
row:
  - col: 12
    row:
      - col: 12
        component_id: 1
  - col: 12
    row:
      - col: 6
        component_id: 2
      - col: 6
        row:
          - col: 12
            component_id: 3
          - col: 12
            component_id: 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We would expect a layout like the below from this YAML expression.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frg1a3bvkh1lqty6hzj6v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frg1a3bvkh1lqty6hzj6v.png" alt=" " width="800" height="195"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, it make this easy to work with we will parse this YAML structure and generate JSON object. I used yaml package.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install yaml

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

&lt;/div&gt;



&lt;p&gt;Then, we could import it and run the parser.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import YAML from 'yaml'
const jsonObject = YAML.parse(yaml_struct)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make this work, we need to create a recursive Vue template and a component that can call itself recursively when encountering nested rows. This way, the structure will dynamically handle deeply nested layouts like the one I provided.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;v-row&amp;gt;
        &amp;lt;template v-for="col in row"&amp;gt;
            &amp;lt;v-col :cols="col.col"&amp;gt;
                &amp;lt;template v-if="col.component_id"&amp;gt;
                    &amp;lt;ComponentWantToRender :col="col"&amp;gt;&amp;lt;/ComponentWantToRender&amp;gt;
                &amp;lt;/template&amp;gt;

                &amp;lt;template v-if="col.row"&amp;gt;
                    &amp;lt;RecursiveRow :row="col.row"&amp;gt;&amp;lt;/RecursiveRow&amp;gt;
                &amp;lt;/template&amp;gt;
            &amp;lt;/v-col&amp;gt;
        &amp;lt;/template&amp;gt;
    &amp;lt;/v-row&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script setup&amp;gt;
import { defineProps } from 'vue';
import RecursiveRow from './RecursiveRow.vue';
defineProps({
    row: Array,
});
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we can use this RecursiveRow component inside your parent component to handle the top-level layout.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;RecursiveRow :row="jsonObject"&amp;gt;&amp;lt;/RecursiveRow&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script setup&amp;gt;
import RecursiveRow from './RecursiveRow.vue';

defineProps({
  jsonObject: Array,
});
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can find full working version of this here: &lt;a href="https://github.com/warunacds/vue-dynamic-layout-generation" rel="noopener noreferrer"&gt;Github Link&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's it. If you have any questions, please leave a comment below.&lt;/p&gt;

</description>
      <category>vue</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
