<?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: Dominik</title>
    <description>The latest articles on DEV Community by Dominik (@dominik_alberski).</description>
    <link>https://dev.to/dominik_alberski</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F239160%2F3d203814-7fa6-4691-9941-3437b2b0bd63.jpeg</url>
      <title>DEV Community: Dominik</title>
      <link>https://dev.to/dominik_alberski</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dominik_alberski"/>
    <language>en</language>
    <item>
      <title>I asked Gemma for Japanese homework. It wrote about eating a friend.</title>
      <dc:creator>Dominik</dc:creator>
      <pubDate>Sun, 18 Oct 2026 17:19:27 +0000</pubDate>
      <link>https://dev.to/dominik_alberski/i-asked-gemma-for-japanese-homework-it-wrote-about-eating-a-friend-1972</link>
      <guid>https://dev.to/dominik_alberski/i-asked-gemma-for-japanese-homework-it-wrote-about-eating-a-friend-1972</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week2-2026-10-12"&gt;Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;JaExC&lt;/strong&gt; (Japanese Exercise Creator) is a Ruby gem that writes kanji-reading exercises with a self-hosted open-weight model, and then refuses to trust it.&lt;/p&gt;

&lt;p&gt;My partner teaches Japanese. She asked for example sentences using specific kanji, which inspired a larger teaching project. That project predates this challenge. JaExC is a new standalone gem I built for the challenge to generate exercises.&lt;/p&gt;

&lt;p&gt;The potential customers are Japanese teachers who repeatedly prepare exercises for students with different kanji knowledge. A teaching app built around JaExC could charge for reducing that preparation work. I have not tested willingness to pay yet.&lt;/p&gt;

&lt;p&gt;A request might be: "Use these allowed kanji, practise 食 飲 行 見 来, and give me ten N5 sentences." JaExC returns candidates with kana readings and English and Polish translations. Here is one recorded example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. 毎朝水を飲みます。
   reading: まいあさみずをのみます。
   en: I drink water every morning.
   pl: Piję wodę każdego ranka.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The caller supplies the target and allowed-kanji lists. Ruby checks the actual Japanese text, not the model's declaration of what it used. The JLPT level guides generation; it is not a validated difficulty rating.&lt;/p&gt;

&lt;p&gt;JaExC generates sentences or dictionary words without requiring Rails or a database. A teaching app can use the library to prepare candidates for review.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://asciinema.org/a/aXq65KlZEa8knIB7" rel="noopener noreferrer"&gt;https://asciinema.org/a/aXq65KlZEa8knIB7&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The recording shows correction attempts followed by a result with reading warnings. The CLI uses the same public Ruby API as a consuming application.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/jaexc-demo &lt;span class="nt"&gt;--type&lt;/span&gt; kanji_reading &lt;span class="nt"&gt;--target&lt;/span&gt; 食,飲,行,見,来 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--allowed&lt;/span&gt; 食,飲,行,見,来,茶,水,友,毎,日,人,何,今,朝 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--level&lt;/span&gt; N5 &lt;span class="nt"&gt;--count&lt;/span&gt; 10 &lt;span class="nt"&gt;--locales&lt;/span&gt; en,pl &lt;span class="nt"&gt;--corrections&lt;/span&gt; 2 &lt;span class="nt"&gt;--check-readings&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In Ruby, the same request looks like this. Here, &lt;code&gt;provider&lt;/code&gt; is the configured Ollama adapter and &lt;code&gt;analyzer&lt;/code&gt; is the optional Sudachi adapter described in the README.&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="n"&gt;creator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;JaExC&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Creator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;provider: &lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;analyzer: &lt;/span&gt;&lt;span class="n"&gt;analyzer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;corrections: &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;creator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;exercise_type: :kanji_reading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;target_kanji: &lt;/span&gt;&lt;span class="sx"&gt;%w[食 飲 行 見 来]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;allowed_kanji: &lt;/span&gt;&lt;span class="sx"&gt;%w[食 飲 行 見 来 茶 水 友 毎 日 人 何 今 朝]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;level: :N5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;count: &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;translation_locales: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:en&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:pl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exercises&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attempts&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warnings&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;attempts&lt;/code&gt; counts model calls, including corrections. &lt;code&gt;warnings&lt;/code&gt; contains reading differences; omit &lt;code&gt;analyzer:&lt;/code&gt; to skip the reading check.&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/DominikAlberski" rel="noopener noreferrer"&gt;
        DominikAlberski
      &lt;/a&gt; / &lt;a href="https://github.com/DominikAlberski/JaExC" rel="noopener noreferrer"&gt;
        JaExC
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;JaExC — Japanese Exercise Creator&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;JaExC is a Ruby gem that creates Japanese kanji-reading exercises with a local open-weight model
It runs inside your application through Ruby method calls
It does not need Rails, a database, or an HTTP service.&lt;/p&gt;
&lt;p&gt;JaExC validates the structure and the characters of each exercise.
JaExC does not prove that the Japanese is natural or that the translations are correct.
A teacher must review the exercises before use.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Exercise types&lt;/h2&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;:kanji_reading&lt;/code&gt; generates sentences.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;:kanji_words&lt;/code&gt; generates dictionary words.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each exercise contains Japanese text, its kana reading, translations, and the target kanji in the text.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Setup&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Requirements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ruby 3.2 or later.&lt;/li&gt;
&lt;li&gt;An &lt;a href="https://ollama.com" rel="nofollow noopener noreferrer"&gt;Ollama&lt;/a&gt; server with an open-weight model.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Add the gem from git:&lt;/p&gt;
&lt;div class="highlight highlight-source-ruby notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-en"&gt;gem&lt;/span&gt; &lt;span class="pl-s"&gt;"jaexc"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-pds"&gt;git&lt;/span&gt;: &lt;span class="pl-s"&gt;"https://github.com/DominikAlberski/JaExC"&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Pull a model on the Ollama server:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;ollama pull &lt;span class="pl-k"&gt;&amp;lt;&lt;/span&gt;model&lt;span class="pl-k"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Optional, for the reading check: install the &lt;a href="https://github.com/WorksApplications/sudachi.rs" rel="noopener noreferrer"&gt;Sudachi&lt;/a&gt; CLI
and a Sudachi system dictionary
The repository…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/DominikAlberski/JaExC" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;MIT License. The README includes setup instructions, the public API and a summary of the recorded correction results.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;I chose local inference to run generation on my own hardware, with electricity costs and no per-token API charges. Using an open model was also part of the challenge.&lt;/p&gt;

&lt;p&gt;I tried a dense Qwen 27B model first. It exceeded my GPU's 16 GB of VRAM. With thinking enabled, it took 3 minutes 21 seconds to generate three sentences. I switched to &lt;code&gt;gemma4:26b-a4b-it-qat&lt;/code&gt;, a mixture-of-experts model with about 4B active parameters per token. I also preferred Gemma's output in the comparison samples.&lt;/p&gt;

&lt;p&gt;After I disabled thinking, Gemma generated ten sentences in 7–14 seconds on that machine. The requests and thinking settings differed, so these timings do not isolate the effect of model architecture. That final Gemma setup made local generation practical for this project.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;JaExC::Creator&lt;/code&gt; talks to the model through &lt;a href="https://rubyllm.com" rel="noopener noreferrer"&gt;RubyLLM&lt;/a&gt; with a JSON schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;request validation → prompt → local model → JSON parse → rule validation
        → (corrections) → optional reading check → Result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  When thinking ate the answer
&lt;/h3&gt;

&lt;p&gt;Some early calls failed with "not valid JSON". One returned an empty answer. Replaying the requests against Ollama and inspecting the raw responses showed reasoning in a separate field. In the failed calls, the prompt and generated reasoning exhausted the 16,384-token context before the model could complete the JSON.&lt;/p&gt;

&lt;p&gt;In the replay investigation, I made ten calls per setting, split equally between Ollama's native and OpenAI-compatible endpoints. With thinking on, seven responses were empty and one contained truncated JSON. With thinking off, all ten returned complete JSON. Calls took about seven seconds with thinking off, compared with 120–136 seconds with thinking on: roughly 20 times longer.&lt;/p&gt;

&lt;p&gt;The adapter now disables thinking by default and distinguishes truncated output from malformed JSON.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check the text, then ask for a correction
&lt;/h3&gt;

&lt;p&gt;One early batch contained sentences like &lt;code&gt;私 は 水 を 飲 む&lt;/code&gt;, with spaces throughout the Japanese text. The validator accepted it at the time. I added a whitespace rule; the next spaced batch was rejected. I also updated the prompt to forbid spaces.&lt;/p&gt;

&lt;p&gt;Ruby checks the kanji in the actual &lt;code&gt;jp&lt;/code&gt; text against the allowed list and verifies the declared targets. It also rejects duplicates and readings containing kanji, whitespace, Latin letters or digits. The model doesn't get to mark its own answer as correct.&lt;/p&gt;

&lt;p&gt;Prompt reminders had mixed results in the small runs I recorded. I added an optional correction loop. It sends the original instructions, the previous answer and the specific rules it violated back to the model. With &lt;code&gt;corrections: 2&lt;/code&gt;, JaExC allows an initial attempt and up to two corrections, validating the entire batch each time.&lt;/p&gt;

&lt;p&gt;Without corrections, the same request passed structural validation in three of six trials. With &lt;code&gt;corrections: 2&lt;/code&gt;, all six passed in a later series. Five needed corrections. The average was 2.0 model calls per request, with total times from 8 to 25 seconds. These are small samples, and some passing batches still had reading warnings.&lt;/p&gt;

&lt;p&gt;Only exercise-rule violations trigger corrections. Invalid JSON, truncated output, provider failures and reading warnings do not. Automatic transport retries remain disabled.&lt;/p&gt;

&lt;h3&gt;
  
  
  A dictionary can disagree too
&lt;/h3&gt;

&lt;p&gt;The optional &lt;a href="https://github.com/WorksApplications/sudachi.rs" rel="noopener noreferrer"&gt;Sudachi&lt;/a&gt; analyzer gives a second reading to compare with the model's. It caught errors that passed the structural rules:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Sentence&lt;/th&gt;
&lt;th&gt;Model reading&lt;/th&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;友が来ます&lt;/td&gt;
&lt;td&gt;とも&lt;strong&gt;と&lt;/strong&gt;きます&lt;/td&gt;
&lt;td&gt;が replaced with と&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;人が来ます&lt;/td&gt;
&lt;td&gt;ひとがき&lt;strong&gt;まま&lt;/strong&gt;す&lt;/td&gt;
&lt;td&gt;typo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;毎日は人が来る&lt;/td&gt;
&lt;td&gt;…ひと&lt;strong&gt;り&lt;/strong&gt;がくる&lt;/td&gt;
&lt;td&gt;wrong word&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;友と茶を飲みます&lt;/td&gt;
&lt;td&gt;ともと&lt;strong&gt;お&lt;/strong&gt;ちゃを…&lt;/td&gt;
&lt;td&gt;added お&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In the recorded sentence 何を食べますか, Sudachi returned &lt;em&gt;nan-o&lt;/em&gt; for 何を; the expected reading was &lt;em&gt;nani-o&lt;/em&gt;. A reading difference therefore produces a warning for the teacher. It does not reject the exercise or trigger a correction.&lt;/p&gt;

&lt;h3&gt;
  
  
  What still needs a teacher
&lt;/h3&gt;

&lt;p&gt;One sentence passed the checks: 友だちを食べに来ます. Its translation said "A friend is coming to eat." The Japanese says someone is coming &lt;em&gt;to eat the friend&lt;/em&gt;. Another passing sentence asked 水を食べますか, "Do you eat water?" That's nonsense, unless you are an alien visiting Earth.&lt;/p&gt;

&lt;p&gt;JaExC enforces the implemented structural rules and can flag reading differences. It does not certify meaning, naturalness or translation quality.&lt;/p&gt;

&lt;p&gt;I used AI assistance for development and for editing this post. I made the architecture and interface decisions; an AI coding agent implemented the agreed components with test-first development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;Running my own Ollama server let me inspect raw responses and their reasoning fields when an answer came back empty. That exposed the context-exhaustion problem and gave me evidence for changing the adapter.&lt;/p&gt;

&lt;p&gt;In the demonstrated configuration, generation and the optional dictionary check run on my own hardware. JaExC does not log prompts or answers. The prompt and validation code are open for other developers to inspect. The provider adapter lets me change the configured model without rewriting the exercise API.&lt;/p&gt;

&lt;p&gt;For this challenge, I built an exercise generator with a working demo and a correction loop. It also produced some memorable bad Japanese. Teacher review remains part of using it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best Use of Gemma.&lt;/strong&gt; The demonstrated setup uses Gemma 4 26B-A4B through Ollama for practical local generation on my machine with one 16 GB GPU. I also preferred its word variety: Gemma included 本 and 日曜日, while Qwen returned four 日本 compounds in the comparison sample.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>ruby</category>
      <category>ai</category>
    </item>
    <item>
      <title>Forest Radar: planning a walk with Polish forest data and local AI</title>
      <dc:creator>Dominik</dc:creator>
      <pubDate>Sat, 10 Oct 2026 21:12:10 +0000</pubDate>
      <link>https://dev.to/dominik_alberski/forest-radar-planning-a-walk-with-polish-forest-data-and-local-ai-5e4o</link>
      <guid>https://dev.to/dominik_alberski/forest-radar-planning-a-walk-with-polish-forest-data-and-local-ai-5e4o</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week1-2026-10-05"&gt;Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;My idea for the Touch Grass challenge was a small application that helps you choose a nearby forest for a walk. Give it a location and a radius, then get information about the forests nearby and suggestions about what you might find there. Save the result or print it, and take it outside.&lt;/p&gt;

&lt;p&gt;I called it Forest Radar, or Leśny Radar in the Polish interface.&lt;/p&gt;

&lt;p&gt;The original idea included mushrooms, berries, animal tracks, and questions like whether you can bring a dog. Those questions sound simple until you start looking at the available data. Forest inventory data can tell you about the trees and habitat. It cannot tell you that a mushroom is there today, or that a particular forest is safe to enter.&lt;/p&gt;

&lt;p&gt;The current application accepts coordinates and a radius between 1 and 20 kilometres. You can also ask the browser to fill in your location. It produces a separate Polish description for each forest range returned by the search, with a Google Maps link and the same safety footer on every card.&lt;/p&gt;

&lt;p&gt;A forest range is an administrative area, not a complete walking route. This version does not provide turn-by-turn navigation, verify dog-access rules, or guarantee a suitable entrance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;The application runs locally. The example in the screenshot uses latitude 52, longitude 21, and a search radius of 5 kilometres. The first card is for Uwieliny. It has an AI-generated forest description, a Maps link, a print button, and a mushroom-safety warning with a link to look for expert help.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2pr0w5d4mgccqlswigrb.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2pr0w5d4mgccqlswigrb.png" alt=" " width="800" height="1081"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Screenshot: the Uwieliny card in Leśny Radar. The description is labelled as AI-generated and potentially inaccurate; the safety footer is fixed application text.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A search at this location produces cards for Uwieliny, Bogatki, Dobiesz, Chojnów, Góra Kalwaria, and Sękocin. One run took about 97 seconds to fetch the data and generate all six descriptions. It is slow enough that I would want to prepare the cards before leaving, rather than wait for them on the trail.&lt;/p&gt;

&lt;p&gt;The app can generate the cards, but the descriptions still contain errors. They need checking before anyone relies on them to plan a walk.&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;The project is written in Ruby with Sinatra and is available under the MIT licence:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/DominikAlberski/forest_radar" rel="noopener noreferrer"&gt;Forest Radar on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I chose Sinatra because the application needs one page, a JSON endpoint, and a printable result. Rails would add more structure than I need here, while writing the HTTP and view layers myself would take time away from the application.&lt;/p&gt;

&lt;p&gt;The interface uses i18n. The Polish translations are complete; the English translation keys exist, but their values are still empty. The README includes instructions for running the app and its tests.&lt;/p&gt;
&lt;h2&gt;
  
  
  How I built it
&lt;/h2&gt;

&lt;p&gt;The main data source is &lt;a href="https://www.bdl.lasy.gov.pl/" rel="noopener noreferrer"&gt;Bank Danych o Lasach&lt;/a&gt;, the Polish Forest Data Bank. The application queries its OGC API Features service for forest administration and inventory data near the requested location.&lt;/p&gt;

&lt;p&gt;The useful fields include tree species, habitat type, stand age, and inventory year. A stand is a unit in the forest inventory. The application groups the relevant stands by forest range and summarizes their attributes before sending them to the model. Each forest gets its own description.&lt;/p&gt;

&lt;p&gt;For Uwieliny, the query returned 431 stands within the search radius, with an inventory year of 2026. Here is part of the species summary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SO (pine):       216 stands
DB (oak):        79 stands
Missing species: 53 stands
BRZ (birch):     39 stands
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are counts of stands grouped by their recorded species code. They are not hectares or percentages, and they do not count individual trees.&lt;/p&gt;

&lt;p&gt;I keep this raw data off the user-facing card because it is hard to interpret, but include it in the model's prompt.&lt;/p&gt;

&lt;p&gt;I chose &lt;a href="https://ollama.com/SpeakLeash" rel="noopener noreferrer"&gt;Bielik&lt;/a&gt; because it is a Polish model and I wanted the forest descriptions in Polish. I run it through Ollama on a separate machine on my local network, using &lt;code&gt;SpeakLeash/bielik-11b-v2.3-instruct:Q4_K_M&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I originally asked Bielik for a structured response with specific keywords and separate sections. It consistently left out the keywords or sections I requested. The forbidden-word checks described below also kept rejecting its answers, so I stopped requiring that format and now display the response as it comes back, with a disclaimer.&lt;/p&gt;

&lt;p&gt;The prompt asks, in Polish, what the BDL data can tell us about the forest's area, accessibility for walkers, and possible animals, plants, and mushrooms. The application supplies numbered data lines, but it does not check whether the generated description accurately reflects them.&lt;/p&gt;

&lt;p&gt;The backend also queries OpenStreetMap through Overpass and includes the retrieved trail information in each forest's prompt. The input distinguishes four cases: a mapped trail crosses the forest, the search found no crossing trail, the data was unavailable, or the forest was not checked. The prompt explains that missing data does not mean there are no trails on the ground. It also explains that the Google Maps link identifies the forest location rather than a verified walking route.&lt;/p&gt;

&lt;p&gt;I added this trail information after reviewing the earlier output shown here. Responses remain unvalidated, so the extra context does not guarantee a correct description.&lt;/p&gt;

&lt;h3&gt;
  
  
  The mushroom rule I changed
&lt;/h3&gt;

&lt;p&gt;I wanted the application to suggest possible mushroom species for a habitat. I did not want it to decide whether a mushroom is edible or poisonous.&lt;/p&gt;

&lt;p&gt;My first approach was strict validation: reject responses containing forbidden words about edibility. Bielik kept using those words when listing mushrooms, and the validator kept rejecting the answers. I removed the word check from the running version.&lt;/p&gt;

&lt;p&gt;The screenshot shows the description mentioning "grzyby jadalne i trujące" (edible and poisonous mushrooms). In a later run, before I added the trail information, the model went further and listed named mushrooms as edible. The original validation would have rejected that output. The current application does not.&lt;/p&gt;

&lt;p&gt;I opted for a disclaimer and the same mushroom-safety footer on every forest card. The description is labelled as AI-generated and potentially wrong. The footer warns against collecting unfamiliar mushrooms and points readers toward a sanitary station, where a qualified expert can check them. It includes a Google Maps search link to help find that service.&lt;/p&gt;

&lt;p&gt;This is a trade-off, not a replacement safety guarantee. A disclaimer does not prevent unsafe output. Forest Radar is not a mushroom-identification or edibility tool, and its generated text is not a reason to eat something found in the forest.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the model got wrong
&lt;/h3&gt;

&lt;p&gt;Before I added the trail information to the prompt, one Uwieliny description included this phrase:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;sosna (216 ha) i świerk (79 ha)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That means "pine (216 hectares) and spruce (79 hectares)." The source summary actually had 216 stands recorded with the pine code and 79 with the oak code. The model changed both the units and the species associated with the second value.&lt;/p&gt;

&lt;p&gt;It also called the forest accessible to walkers without current access evidence. These are errors in the generated description, not facts established by BDL. Animals, berries, and mushrooms mentioned in the text are not verified sightings either.&lt;/p&gt;

&lt;p&gt;I have not yet taken a card outside and compared it with what I found in the forest.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why I removed the entry-ban check
&lt;/h3&gt;

&lt;p&gt;Checking whether a forest was closed was part of the original plan. During development, the entry-ban service returned HTTP 400 errors, so the application could not determine whether a ban was in place. I removed the check rather than keep a feature that always reported an unknown result.&lt;/p&gt;

&lt;p&gt;That means Forest Radar does not establish current entry-ban status. The model can still write an accessibility claim, as the demo shows, but that claim is not a substitute for checking current notices from the relevant forest district.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does open innovation matter?
&lt;/h2&gt;

&lt;p&gt;Public forest data makes this project possible. It gives the application a source for local forest information instead of asking a model to invent it from a place name. It also lets me compare the generated description with the input and identify mistakes such as the stand-count error.&lt;/p&gt;

&lt;p&gt;An open-weight model served through Ollama gives me control over inference. I can choose the model host and change the model without replacing the rest of the application. Running it on hardware I control avoids a proprietary model API and its per-request charges, although the hardware and electricity still have a cost.&lt;/p&gt;

&lt;p&gt;The application still needs internet access. BDL and Overpass receive queries containing a bounding box around the user's location. The model host receives summarized forest data, trail information, and distances, rather than the raw input coordinates. Google Maps receives location information when a user opens one of the links.&lt;/p&gt;

&lt;p&gt;For this project, the benefit is control over the model service and access to the underlying data. Neither makes the generated text automatically correct.&lt;/p&gt;

&lt;p&gt;Forest Radar is a working local prototype for exploring nearby forest options. I want the screen to be the preparation for a walk, not the activity itself. Before relying on the descriptions as a guide, I still need to improve their accuracy and test the cards outside.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>I wanted Claude to delegate to a local model. That became MAF.</title>
      <dc:creator>Dominik</dc:creator>
      <pubDate>Sat, 10 Oct 2026 12:44:29 +0000</pubDate>
      <link>https://dev.to/dominik_alberski/i-wanted-claude-to-delegate-to-a-local-model-that-became-maf-1e2a</link>
      <guid>https://dev.to/dominik_alberski/i-wanted-claude-to-delegate-to-a-local-model-that-became-maf-1e2a</guid>
      <description>&lt;p&gt;I started building MAF because I wanted to make AI more reliable and spend less on it.&lt;/p&gt;

&lt;p&gt;My idea was simple: use a paid claude based model for complex reasoning, planning, and judgment, then use a local model for implementation work. Keep the expensive model involved where it helps and use the local model for work it can handle.&lt;/p&gt;

&lt;p&gt;At the time, I couldn't find an option that fit my workflow easily. My experiments with local models were also disappointing. Simple, well-explained tasks worked, but tool calls or longer agentic workflows would overflow the context window.&lt;/p&gt;

&lt;p&gt;I started using Claude Code, but I still wanted to use a local model through Ollama and OpenCode for some side tasks.&lt;/p&gt;

&lt;p&gt;I tried several approaches: first subagents, then skill bridges that told Claude Code how to delegate simple tasks to OpenCode. Automatic communication between the two harnesses—the tools running the agents—was unreliable or used too many Claude tokens.&lt;/p&gt;

&lt;p&gt;I was making progress, though. I worked out a process where Claude Code wrote a task file with instructions for OpenCode to implement. I still had to point OpenCode to those files myself.&lt;/p&gt;

&lt;p&gt;The shared-board idea came from a conversation at EuRuKo with Maciej Mensfeld. He described his own agentic workflow and how he isolated agents with &lt;a href="https://github.com/coipond/coi" rel="noopener noreferrer"&gt;Coi (Code on Incus)&lt;/a&gt;, a tool that runs coding agents inside isolated Incus system containers. He also mentioned agents coordinating through a Kanban-like task service.&lt;/p&gt;

&lt;p&gt;What stuck with me was the idea of agents using a task board to communicate. I wanted something similar, but local: no external task service and no web requests just to coordinate work. The agents could still use cloud models, but their board and messages would live in the project.&lt;/p&gt;

&lt;p&gt;That gave me a way to move beyond manually passing task files between agents. It became Multi_Agent_Flow (MAF).&lt;/p&gt;

&lt;h2&gt;
  
  
  A shared board instead of another agent runtime
&lt;/h2&gt;

&lt;p&gt;MAF is the glue that lets different coding-agent tools coordinate work on one project. It does not replace Claude Code, OpenCode, Codex, or Hermes. Those tools still run the agents, using the model access you configure for each one.&lt;/p&gt;

&lt;p&gt;You can use Claude Code with your subscription and OpenCode with a local model, or choose another combination of harnesses and models. MAF does not require the whole team to use the same provider. It comes with predefined roles and workflow examples, but you can define your own.&lt;/p&gt;

&lt;p&gt;MAF gives the agents a shared way to coordinate work on one repository:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A project-local Taskwarrior board records goals, tasks, and claims.&lt;/li&gt;
&lt;li&gt;File inboxes carry messages between roles and workers.&lt;/li&gt;
&lt;li&gt;Git worktrees give workers separate checkouts.&lt;/li&gt;
&lt;li&gt;Resource locks let workers serialize access to something they share, such as a local model host.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The common interface is the &lt;code&gt;coord&lt;/code&gt; CLI. An agent does not need a vendor SDK to read the board or send a message. It needs to run a command.&lt;/p&gt;

&lt;p&gt;There is an optional shared-memory layer through graphify and an Obsidian vault. Obsidian makes it easier for you to browse the agents' knowledge.&lt;/p&gt;

&lt;p&gt;There is also a web dashboard, which you can start by running &lt;code&gt;.maf/bin/dashboard&lt;/code&gt; from the project root. It shows worker status, the task board, and coordination activity. It also includes analysis tools that suggest ways to reduce token usage.&lt;/p&gt;

&lt;p&gt;The part I care about most is choosing tools by role. I can keep a cloud model on planning and review while routing scoped implementation work to another harness. MAF provides the coordination; it does not guarantee that a local model will do a task correctly or cheaply.&lt;/p&gt;

&lt;h2&gt;
  
  
  Roles are responsibilities, not model names
&lt;/h2&gt;

&lt;p&gt;An architect decomposes a goal into tasks. Developers implement them. A tester checks behavior. A reviewer examines the result.&lt;/p&gt;

&lt;p&gt;For example, imagine adding CSV export to an app. The architect defines the task and its acceptance criteria. A developer claims it and implements it in a separate worktree. A tester checks the behavior, and a reviewer examines the diff. If something is missing, they send feedback through the inbox instead of relying on me to copy messages between terminals.&lt;/p&gt;

&lt;p&gt;A project manager can sit between me and the architect: I describe the outcome, and the project manager creates the goal and reports back. The lead roles do not claim implementation tasks.&lt;/p&gt;

&lt;p&gt;A harness is the tool running a role. A worker is a running instance of that role. More than one worker can serve the same role.&lt;/p&gt;

&lt;p&gt;This distinction matters because I do not want my project structure to depend on which model I happen to use this month.&lt;/p&gt;

&lt;p&gt;For example, this configures a mixed team:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;maf add claude:architect opencode:backend-developer claude:reviewer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That command installs the flow and generates the role files. It does not start the agents.&lt;/p&gt;

&lt;p&gt;OpenCode is the harness here, not the model. To use it for local implementation, I configure it to use a model served by Ollama.&lt;/p&gt;

&lt;p&gt;You aren't limited to the predefined roles. You could define a writer, editor, researcher, or proofreader and assign each one to whichever harness and model fits best. &lt;code&gt;maf role add NAME&lt;/code&gt; creates a stub in &lt;code&gt;.maf/roles.yml&lt;/code&gt;; you fill in its responsibilities and permissions. You can also write your own workflow in &lt;code&gt;.maf/workflow.md&lt;/code&gt; for the architect to follow.&lt;/p&gt;

&lt;h2&gt;
  
  
  MAF stays out of the project's tracked files
&lt;/h2&gt;

&lt;p&gt;The tool keeps its main installation in &lt;code&gt;.maf/&lt;/code&gt;. It uses &lt;code&gt;.git/info/exclude&lt;/code&gt; to keep those files out of the clone's normal Git status. The knowledge graph lives separately in &lt;code&gt;graphify-out/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;MAF also installs Git hooks and harness-specific files. Those are real changes to the local environment, even though the tool keeps its setup out of the project's tracked source files. A clean Git status does not mean that nothing changed on disk.&lt;/p&gt;

&lt;p&gt;The agents' code and project documentation still go through your normal Git workflow; MAF's local setup stays outside it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Different ways to run MAF
&lt;/h2&gt;

&lt;p&gt;At first, I ran all my harnesses interactively. That let me see what each agent was doing in its own terminal window and micromanage the process when I needed to.&lt;/p&gt;

&lt;p&gt;Now I talk to a single agent in the project manager role. The rest of the team runs in &lt;code&gt;--dispatch&lt;/code&gt; mode, in terminals I start myself. The dispatcher waits for tasks or messages and invokes the agent when there is work. You can also ask the project manager to arrange a team with the architect, within the worker limits and allowed harnesses you configure.&lt;/p&gt;

&lt;p&gt;One important caveat: dispatch mode bypasses approval prompts for Claude Code, Codex, and Hermes; OpenCode uses the permissions in its role file. Separate worktrees help keep changes apart, but they are not a security sandbox. Only use unattended workers with access you are comfortable granting them.&lt;/p&gt;

&lt;p&gt;Try different approaches to see what works for you. I started with interactive sessions because I wanted to watch the coordination before leaving more of it unattended.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it on something small
&lt;/h2&gt;

&lt;p&gt;You don't need to start with the whole team. Begin with an architect and one developer, watch how they coordinate, and add other roles when you need them.&lt;/p&gt;

&lt;p&gt;You need Ruby 3.0 or later, Git, and Taskwarrior for the coordination layer. For actual agent work, you also need an installed coding-agent harness with the model access it requires. Graphify is optional but recommended for shared knowledge.&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="nb"&gt;install &lt;/span&gt;maf
maf version
maf guide
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can then open a coding agent in your project and ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Run &lt;code&gt;maf guide&lt;/code&gt;. Read the output and help me set it up.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;MAF is &lt;a href="https://github.com/DominikAlberski/multi_agent_flow" rel="noopener noreferrer"&gt;open source on GitHub&lt;/a&gt; and &lt;a href="https://rubygems.org/gems/maf" rel="noopener noreferrer"&gt;published on RubyGems&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can ask your agent about MAF as you go. It can guide you through adding custom roles or workflows rather than making you learn every command first.&lt;/p&gt;

&lt;p&gt;I'd like feedback from people whose workflows differ from mine. If you try MAF, tell me which tools you used and where setup or coordination became confusing. If you encounter a bug, please open an issue with a reproducible example.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>ruby</category>
      <category>opensource</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I had Ruby running on a 2009 iPhone. Then I needed something to run.</title>
      <dc:creator>Dominik</dc:creator>
      <pubDate>Sun, 04 Oct 2026 22:23:12 +0000</pubDate>
      <link>https://dev.to/dominik_alberski/i-had-ruby-running-on-a-2009-iphone-then-i-needed-something-to-run-471o</link>
      <guid>https://dev.to/dominik_alberski/i-had-ruby-running-on-a-2009-iphone-then-i-needed-something-to-run-471o</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01"&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I had Ruby 4.0.7 running on a 2009 iPhone 3GS. I just didn't know what to run on it.&lt;/p&gt;

&lt;p&gt;Then this challenge gave me an idea: turn the phone into a home network dashboard connected to a local AI model.&lt;/p&gt;

&lt;p&gt;I built it for my best friend: myself. It do not solves any problems but it let me learned some new concepts.&lt;/p&gt;

&lt;p&gt;The Ruby port was already part of a larger side project. The dashboard is the new project I built for this challenge. I wanted something to run on the phone and something to write about here.&lt;/p&gt;

&lt;p&gt;The phone shows devices on my home network. I can type commands or ask questions about the latest device scan. A model on my PC answers the questions. The phone runs the Ruby dashboard; the PC runs the model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;iPhone 3GS (Ruby daemon, Safari)
      └── HTTP LAN ──&amp;gt; Mac (Ruby agent: scan / ping / wake / chat)
                            └── HTTP LAN ──&amp;gt; PC (Ollama: Qwen3.8-27B-Ridge)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three machines to ask which devices are online. This is what happens when I choose my own weekend projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;The interface is one HTML page served on the phone by Ruby's &lt;code&gt;TCPServer&lt;/code&gt;. Safari displays the page. An ordinary HTML form sends questions. A refresh link updates the display. There is no JavaScript.&lt;/p&gt;

&lt;p&gt;The page lists registered and discovered devices, their online status, and the model's availability. The latest reply appears above the question box.&lt;/p&gt;

&lt;p&gt;During verification over USB SSH, Ruby on the phone returned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ruby 4.0.7 (2026-09-29 revision fdc00de497) +PRISM [armv7-darwin10]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An HTTP request from the phone to its own dashboard returned 13 device entries. Eleven entries were online and two were offline. A second request from the phone reached the Mac agent successfully. The agent reported the local model as reachable.&lt;/p&gt;

&lt;p&gt;Those two offline entries need a qualification: &lt;code&gt;pc&lt;/code&gt; and &lt;code&gt;printer&lt;/code&gt; still had placeholder records in my configuration. The result proves the dashboard receives data. It does not prove that my actual PC and a real printer were offline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;The dashboard source is available on &lt;a href="https://github.com/DominikAlberski/dashboard_project" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;agent/&lt;/code&gt; runs on the Mac. It scans the network, handles commands, and sends questions to the model.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;client/&lt;/code&gt; runs on the phone. It serves the dashboard and talks to the agent.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;config/&lt;/code&gt; holds device records and agent settings.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;deploy/&lt;/code&gt; installs the phone's dashboard service.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The agent and client use Ruby's standard library, with no runtime gems. The tests use Minitest.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;The model is &lt;a href="https://huggingface.co/empero-ai/Qwen3.8-27B-Ridge-GGUF" rel="noopener noreferrer"&gt;Qwen3.8-27B-Ridge&lt;/a&gt;, served locally by &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; on my PC. The Mac agent reaches Ollama over HTTP on the LAN.&lt;/p&gt;

&lt;p&gt;The jailbroken phone runs iOS 6.1.6 and my cross-compiled CRuby build. Getting Ruby onto the phone needed changes to CRuby for compatibility with the old iOS SDK. That work belongs to the larger project. This weekend's task was to give Ruby an application.&lt;/p&gt;

&lt;p&gt;I chose a web page for the interface because the phone already has Safari and an onscreen keyboard. Ruby serves the page, and a launchd service starts the dashboard. I had already used this approach for a light-meter application on the same phone.&lt;/p&gt;

&lt;p&gt;The most useful bug appeared after the connections worked. The agent scanned the network. The model answered questions. But the model knew nothing about the scan.&lt;/p&gt;

&lt;p&gt;I asked what devices were on my network. The reply began:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I can’t see your network. On your computer: run &lt;code&gt;arp -a&lt;/code&gt; …&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fair answer. I had connected the model without sending the information the model needed.&lt;/p&gt;

&lt;p&gt;The fix was to attach the latest device snapshot as context:&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;system_prompt&lt;/span&gt;
  &lt;span class="s2"&gt;"You are a LAN overseer. Answer from the device list below only. "&lt;/span&gt; &lt;span class="p"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"Keep the answer short. Devices: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;device_summary&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the fix, the recorded answer began with “10 devices online” and listed addresses from the snapshot. The model now had information about my network instead of only my question.&lt;/p&gt;

&lt;p&gt;Commands such as &lt;code&gt;ping&lt;/code&gt;, &lt;code&gt;scan&lt;/code&gt;, &lt;code&gt;wake&lt;/code&gt;, and &lt;code&gt;ports&lt;/code&gt; go through Ruby command handlers. Free-text questions go to the model. The model explains the supplied device status; it does not choose or execute network commands.&lt;/p&gt;

&lt;p&gt;The prompt does not guarantee accurate answers. One later reply suggested that the placeholder printer might have responded on a printer-related port. The supplied context contained no evidence for that explanation.&lt;/p&gt;

&lt;p&gt;That is a useful limit to show alongside the working demo. Providing context helps, but the configuration and the answers still need checking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;Open source made both parts possible: adapting Ruby for the phone and running a model on hardware I control.&lt;/p&gt;

&lt;p&gt;The phone's Ruby build has no OpenSSL extension. The dashboard uses plain HTTP inside my home LAN. The Mac agent has no authentication, so this prototype belongs on a trusted home network.&lt;/p&gt;

&lt;p&gt;Local inference gives this project three useful properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Local data.&lt;/strong&gt; The application sends device status and questions to my own PC.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No per-token API fees.&lt;/strong&gt; I use existing hardware, although running that hardware still uses electricity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A replaceable model.&lt;/strong&gt; I can install another compatible model in Ollama and change &lt;code&gt;model_name&lt;/code&gt; in &lt;code&gt;config/agent.yml&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The phone does not need to run the model itself. A small Ruby client is enough to use the model through the Mac agent.&lt;/p&gt;

&lt;p&gt;That is enough for this weekend. I started with Ruby running on an old phone and no application in mind. Now the phone has a dashboard, and I have a project to write about.&lt;/p&gt;

&lt;p&gt;Eventually, I want to share the wider Ruby-on-3GS story at a Ruby user group, perhaps as my first lightning talk. This dashboard gives me one working example to show.&lt;/p&gt;

&lt;p&gt;My best friend seems pleased with the result. He is already thinking about what else to run on the phone.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>hf26challenge</category>
      <category>ruby</category>
    </item>
    <item>
      <title>Reading The Pragmatic Programmer in the age of AI</title>
      <dc:creator>Dominik</dc:creator>
      <pubDate>Tue, 07 Jul 2026 13:51:39 +0000</pubDate>
      <link>https://dev.to/dominik_alberski/reading-the-pragmatic-programmer-in-the-age-of-ai-3jnf</link>
      <guid>https://dev.to/dominik_alberski/reading-the-pragmatic-programmer-in-the-age-of-ai-3jnf</guid>
      <description>&lt;p&gt;I bought &lt;em&gt;The Pragmatic Programmer&lt;/em&gt; a few years ago but never finished it. As part of my current effort to sharpen my fundamentals, I decided to finally read it from cover to cover.&lt;/p&gt;

&lt;p&gt;I expected it to feel slightly dated. The first edition was written in 1999. The second edition came out in 2019, just before generative AI reshaped how many of us work. Instead I found the opposite: most of the principles still hold. What changed isn't their validity — it's the level at which they apply.&lt;/p&gt;

&lt;p&gt;The first thing that stopped me was one sentence in chapter 2. The authors note, almost in passing, that there are still no compilers capable of generating software from a concise description of requirements. Reading that struck a strange tone in my mind. We still don't have the compiler they were imagining, but we do have LLMs and AI agents that occupy a surprisingly similar role — turning a few paragraphs of natural language into working software. A later chapter reinforces the feeling: the authors argue that programming can't be reduced to a mechanical process, or CASE tools would have replaced programmers long ago. History spent twenty years appearing to prove them right. Then it stopped being so obvious.&lt;/p&gt;

&lt;p&gt;That became the question I read the rest of the book with: if implementation is finally being automated, which of these ideas become &lt;em&gt;more&lt;/em&gt; important, and which become less?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Knowledge Portfolio, revisited
&lt;/h2&gt;

&lt;p&gt;Take the &lt;strong&gt;Knowledge Portfolio&lt;/strong&gt;. On the surface, AI seems to reduce the need to learn deeply. Why spend months mastering something an assistant can explain or generate in seconds?&lt;/p&gt;

&lt;p&gt;My strongest evidence to the contrary came from a React migration. I'm primarily a backend developer with little frontend experience. With AI's help I produced a UI that matched the mockups and met every functional requirement. To me, the code looked fine. The senior frontend engineers reviewing it disagreed — it ignored several established patterns in the codebase, and I didn't have enough experience to recognize the problems myself.&lt;/p&gt;

&lt;p&gt;Later, on smaller and more clearly defined tasks, I noticed something. AI followed project conventions much more reliably when I could extract and state those conventions explicitly. The better I understood the rules of the system, the more useful the AI became. The lesson wasn't that AI replaced expertise. It was that expertise determined whether I could recognize good output when I saw it.&lt;/p&gt;

&lt;p&gt;So learning and gaining expertise is still essential. Otherwise how will you notice mistakes in AI thinking? For me, the valuable investment is in mental models — functional programming, distributed systems, architecture, security, AI systems themselves. The goal is no longer accumulating syntax knowledge across many languages but understanding how software behaves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Orthogonality, DRY, and the cost of unclear thinking
&lt;/h2&gt;

&lt;p&gt;The idea that gained the most weight for me was &lt;strong&gt;orthogonality&lt;/strong&gt;. Clear boundaries between modules and responsibilities have always been good practice; in the AI era they're closer to a prerequisite. AI increasingly writes the implementation while the engineer defines interfaces, constraints, and responsibilities — and the quality of those boundaries determines how effectively AI can contribute. Vague definitions produce tangled, duplicated, hard-to-verify output. Clear ones produce something you can actually review and trust.&lt;/p&gt;

&lt;p&gt;This sharpens &lt;strong&gt;DRY&lt;/strong&gt;, too. The principle was always about duplicated &lt;em&gt;knowledge&lt;/em&gt;, not duplicated code — but AI scatters business logic across services and modules so reliably when left unsupervised that the distinction becomes impossible to ignore.&lt;/p&gt;

&lt;p&gt;There's one idea I initially thought was new, then reconsidered. I'd written in my notes that AI makes poor thinking &lt;em&gt;more expensive&lt;/em&gt; — a vague prompt burns tokens, time, and iterations. True, but engineers have always paid for unclear thinking. The currency used to be debugging and rework; now it's tokens and review cycles. The cost isn't new. What's new is how fast and how visibly it arrives. AI acts less like a new source of risk and more like a mirror: it doesn't create bad habits, it reflects them back faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Blackboard pattern, reframed
&lt;/h2&gt;

&lt;p&gt;Master your tools, invest in your environment — still valid. But increasingly the tool is operated by an &lt;em&gt;agent&lt;/em&gt;, not typed directly by a developer. Which makes plain text more central, not less: it's the medium through which intent reaches both humans and machines, in prompts, configuration files, and instructions. The skill that used to be "master your tools" is becoming "design workflows that you and an AI can both operate in effectively."&lt;/p&gt;

&lt;p&gt;That reframing shows up most clearly in the &lt;strong&gt;Blackboard pattern&lt;/strong&gt; — one of the book's more academic-feeling ideas, where independent specialists post partial solutions to a shared space until a solution emerges. It suddenly resembles modern multi-agent systems, where specialized agents collaborate through a shared space rather than a shared conversation.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi5ct4683vgwq4ycnbeqz.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi5ct4683vgwq4ycnbeqz.png" alt=" " width="800" height="353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The reframe I keep coming back to is that what agents need to share isn't conversational context, which gets expensive fast, but structured knowledge: goals, decisions, findings, tasks.&lt;/p&gt;

&lt;p&gt;Shared knowledge rather than shared context.&lt;/p&gt;

&lt;p&gt;The more I experiment with agentic systems, the more I believe preserving useful knowledge matters more than preserving every step of the conversation that produced it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing: don't let AI do the thinking part
&lt;/h2&gt;

&lt;p&gt;One place that caught my interest was testing. The authors frame tests as valuable less for catching bugs than for forcing you to reason about your code before you write it — and that matches what TDD taught me: deciding how to test something is part of designing it.&lt;/p&gt;

&lt;p&gt;Which is why I'm cautious about handing testing entirely to an AI. Ask an agent to "add test coverage" and it will — but if it does the thinking too, you skip the part that was actually useful. You never ask what the code should guarantee, where it might break, or what "correct" means here. The tests exist, but the design reasoning they usually prompt didn't happen. So in practice I let the AI write the tests while I stay responsible for what to test — writing assertions is mechanical; deciding what to verify is where the thinking is.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lizard brain, and where instinct comes from
&lt;/h2&gt;

&lt;p&gt;That worry generalizes. The authors tell you to trust your "lizard brain" — the instinct you develop from years of writing, debugging, and refactoring code yourself. That instinct is &lt;em&gt;earned&lt;/em&gt;, and it's earned specifically by doing the work. So what happens when AI does more and more of that work? Can you build the same intuition reviewing generated code that you'd build writing it yourself? Or will the next generation of developers grow a different instinct — one tuned to architecture, decomposition, and judging AI output rather than to implementation detail?&lt;/p&gt;

&lt;p&gt;I genuinely enjoy working with AI. It automates the repetitive parts and lets me explore ideas faster than before. But every problem it solves for me is a problem I don't solve myself — and solving problems is how experience accumulates. There's a balance to strike between using AI as a productivity tool and protecting the learning that produces judgment in the first place.&lt;/p&gt;

&lt;p&gt;That, in the end, is what rereading &lt;em&gt;The Pragmatic Programmer&lt;/em&gt; left me with. Software engineering is becoming less about writing code and more about making decisions — architecture, domain modeling, contracts, boundaries. You can't reason well about AI-generated code if you don't understand software engineering yourself. Knowledge hasn't become less important; it's shifted from producing code to evaluating and guiding it.&lt;/p&gt;

&lt;p&gt;The book didn't change my mind about AI. It sharpened it. The implementation was never the hardest part of the job. If AI keeps automating the mechanics, the ability to think clearly about software may become the most valuable skill a developer can have — and I'm genuinely unsure how we'll keep teaching it once the machine is doing the reps.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>ai</category>
      <category>tdd</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Rails Integration Testing with Fixtures: A Practical Approach for Complex Data Sets</title>
      <dc:creator>Dominik</dc:creator>
      <pubDate>Tue, 07 May 2024 07:47:48 +0000</pubDate>
      <link>https://dev.to/dominik_alberski/rails-integration-testing-with-fixtures-a-practical-approach-for-complex-data-sets-mce</link>
      <guid>https://dev.to/dominik_alberski/rails-integration-testing-with-fixtures-a-practical-approach-for-complex-data-sets-mce</guid>
      <description>&lt;h4&gt;
  
  
  The Challenge of Realistic Test Data
&lt;/h4&gt;

&lt;p&gt;I recently faced a unique challenge in writing integration specs for a Rails application. The goal was to simulate real-world complexity within our test environment, involving interactions across 13 models like orders, line items, and products. The catch? I needed to generate around 10,000 interrelated records, reflecting the kind of volume and complexity we see in production. Typically, I'd reach for the &lt;code&gt;factory_bot&lt;/code&gt; gem for test data generation, but due to the sheer volume and the need for persistent data, this method proved too slow. My ideal solution needed to enable the insertion of a large volume of data into the database efficiently, mirroring production data characteristics as closely as possible.&lt;/p&gt;

&lt;h4&gt;
  
  
  Solution: Tapping into Production DB and Fixtures
&lt;/h4&gt;

&lt;p&gt;After exploring several avenues, I discovered that leveraging Rails' default fixtures, combined with our staging database (which mirrors the last three months of production records), provided an unexpectedly effective solution. Here’s how I transformed real production data into a manageable, anonymized test suite:&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;fixture_creator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;anonymized&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;root&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/spec/fixtures/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;table_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.yml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"w+"&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;fixture_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;in_batches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;of: &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;fixture_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;fixture_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
          &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;anonymized&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt; &lt;span class="n"&gt;fixture_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_yaml&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;h4&gt;
  
  
  Exploring the Code
&lt;/h4&gt;

&lt;p&gt;In this snippet, &lt;code&gt;fixture_creator&lt;/code&gt; function does the heavy lifting:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Batch Processing&lt;/strong&gt;: By processing the data in batches (&lt;code&gt;model.in_batches(of: 100)&lt;/code&gt;), we efficiently handle large volumes of data without overwhelming memory usage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unique Keys for Fixtures&lt;/strong&gt;: We create unique keys for each record (&lt;code&gt;"#{record.id}_#{fixture_data.size + 1}"&lt;/code&gt;), ensuring each entry in our fixtures file is distinct and easily identifiable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anonymization&lt;/strong&gt;: The &lt;code&gt;anonymized&lt;/code&gt; parameter is a hash that overrides sensitive attributes with dummy values, crucial for maintaining data privacy.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This approach significantly speeds up the process compared to individually creating records via &lt;code&gt;factory_bot&lt;/code&gt;. By directly exporting data from a staging environment (a subset of our production data), we ensure our tests are running against data that closely reflects real user interactions, both in volume and complexity.&lt;/p&gt;

&lt;h4&gt;
  
  
  Anonymization and Security
&lt;/h4&gt;

&lt;p&gt;One key aspect of using production data for testing is ensuring all sensitive information is thoroughly anonymized. In my case, the &lt;code&gt;anonymized&lt;/code&gt; hash includes mappings of original attribute names to anonymized values, applied to each record. This step is crucial not just for security and privacy, but also for complying with legal obligations like GDPR.&lt;/p&gt;

&lt;h4&gt;
  
  
  Conclusion
&lt;/h4&gt;

&lt;p&gt;This method of generating fixtures from production data presents a robust way to create realistic, high-volume test environments for Rails applications. It's particularly useful when the test scenarios are complex, and the realism of data interrelations is crucial. While this approach worked well in my case, it's important to tailor the solution to the specific needs and constraints of your project and always prioritize data security in any testing strategy.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>testing</category>
      <category>fixtures</category>
    </item>
    <item>
      <title>Memoization in Modern Ruby: How a Shape-Based Approach to Instance Variables Lookup Impacts Performance.</title>
      <dc:creator>Dominik</dc:creator>
      <pubDate>Thu, 02 Nov 2023 11:32:39 +0000</pubDate>
      <link>https://dev.to/dominik_alberski/memoization-in-modern-ruby-using-a-shape-based-approach-to-instance-variables-lookup-2lok</link>
      <guid>https://dev.to/dominik_alberski/memoization-in-modern-ruby-using-a-shape-based-approach-to-instance-variables-lookup-2lok</guid>
      <description>&lt;p&gt;Memoization is a classic technique in Ruby programming, greatly assisting in boosting performance. With Ruby's ongoing evolution, particularly towards a shape-based method in instance variable lookups, thoughtfully implementing memoization can be beneficial. Inspired by Jean Boussier's insights on RailsAtScale.com, this post highlights some suggested best practices.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why Consider Memoization?
&lt;/h4&gt;

&lt;p&gt;Memoization helps cache results of resource-heavy function calls, aiming to speed up subsequent executions. Ruby’s newer shape-based mechanism for instance variable lookups suggests organizing objects based on their instance variables. This approach influences performance and memory usage, but it's important to note that using memoization indiscriminately could lead to a range of object shapes, which might inadvertently affect performance. Understanding these subtleties can be helpful for developers looking to craft efficient and thoughtful Ruby code.&lt;/p&gt;

&lt;h4&gt;
  
  
  Suggested Best Practices
&lt;/h4&gt;

&lt;p&gt;Aligning with Ruby's shape-based approach and optimizing memoization strategies, developers might consider the following approaches:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Mindful Use of Memoized Variables&lt;/strong&gt;: It’s generally a good idea to keep memoized variables to a minimum. Ideally, aiming for one or two per class helps. If you find yourself needing more, initializing these variables to &lt;code&gt;nil&lt;/code&gt; in the constructor could be a proposed better approach:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyClass&lt;/span&gt; 
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;     
        &lt;span class="vi"&gt;@expensive_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;   
    &lt;span class="k"&gt;end&lt;/span&gt;    

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;expensive_value&lt;/span&gt;     
        &lt;span class="vi"&gt;@expensive_value&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="n"&gt;compute_expensive_value&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;ol&gt;
&lt;li&gt;
&lt;strong&gt;Handling Falsy Values with Tokens&lt;/strong&gt;: When dealing with memoized values that might return falsy results, using a token could be a neat way to differentiate between an uninitialized state and a value that's legitimately falsy:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyClass&lt;/span&gt;   
    &lt;span class="no"&gt;NOT_SET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;   
    &lt;span class="n"&gt;private_constant&lt;/span&gt; &lt;span class="ss"&gt;:NOT_SET&lt;/span&gt;    
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;     
        &lt;span class="vi"&gt;@expensive_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;NOT_SET&lt;/span&gt;   
    &lt;span class="k"&gt;end&lt;/span&gt;    

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;expensive_value&lt;/span&gt;     
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="vi"&gt;@expensive_value&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="vi"&gt;@expensive_value&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;NOT_SET&lt;/span&gt;     &lt;span class="vi"&gt;@expensive_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;compute_expensive_value&lt;/span&gt;   
        &lt;span class="k"&gt;end&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;h4&gt;
  
  
  Wrapping Up
&lt;/h4&gt;

&lt;p&gt;Adjusting to Ruby's shape-based approach for variable lookups, we might consider refining our memoization tactics to help maintain efficient performance and thoughtful memory usage. For those interested in exploring this topic further, Jean Boussier provides a deeper dive in his article at &lt;a href="https://railsatscale.com/2023-10-24-memoization-pattern-and-object-shapes/" rel="noopener noreferrer"&gt;RailsAtScale&lt;/a&gt;. It's a thought-provoking read for anyone interested in evolving their Ruby coding practices in harmony with the latest language developments.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This summary reflects ideas from Jean Boussier's article published on October 24, 2023, on RailsAtScale.com. To broaden your understanding, the original article is well worth a read.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
