<?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: Andrey Eremin</title>
    <description>The latest articles on DEV Community by Andrey Eremin (@aeremin).</description>
    <link>https://dev.to/aeremin</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%2F781220%2Fd024fe26-84bc-4be5-9813-f688862af3cf.png</url>
      <title>DEV Community: Andrey Eremin</title>
      <link>https://dev.to/aeremin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aeremin"/>
    <language>en</language>
    <item>
      <title>Ruby: Where are we going? 2026 Edition</title>
      <dc:creator>Andrey Eremin</dc:creator>
      <pubDate>Sat, 04 Apr 2026 21:34:12 +0000</pubDate>
      <link>https://dev.to/aeremin/ruby-where-are-we-going-2026-edition-32co</link>
      <guid>https://dev.to/aeremin/ruby-where-are-we-going-2026-edition-32co</guid>
      <description>&lt;p&gt;This is more of a self-reflection than a proper blog post. As someone who has spent the last five years working primarily with statically typed languages and who teaches &lt;a href="https://www.udemy.com/course/ruby-on-types-write-robust-software-with-rbs/?referralCode=61BB9D2541B9F1C41506" rel="noopener noreferrer"&gt;RBS on Udemy&lt;/a&gt; and is writing a &lt;a href="https://andreyeremin.gumroad.com/l/zero-hallucination-ruby" rel="noopener noreferrer"&gt;book&lt;/a&gt; about static typing in the new LLM-powered era, I have always had a complicated relationship with Ruby. Every time I came back to it, the lack of types drove me crazy. The only safety net available was the test suite. Hopefully, you can layer RBS or Sorbet on top, and I strongly believe you should, but that is a topic for another day.&lt;/p&gt;

&lt;p&gt;This post is not about static typing per se — it is about where the language is going. Claims that Ruby is slow, dead, and unable to scale have been a constant background noise for years. At &lt;a href="https://survey.stackoverflow.co/2023/#most-popular-technologies-language-prof" rel="noopener noreferrer"&gt;7% popularity&lt;/a&gt; in the recent Stack Overflow survey, Ruby is certainly not at its peak, but it is very much alive. The real question is: what will the Ruby world look like in 1, 2, or 3 years from now?&lt;/p&gt;

&lt;p&gt;In 2025, &lt;a href="https://survey.stackoverflow.co/2025/technology" rel="noopener noreferrer"&gt;JavaScript&lt;/a&gt; became the most popular choice for GitHub projects. With frameworks like Angular, React, Next.js, and TypeScript covering the frontend and Node.js, Deno, or Bun covering the backend — you can build a full-stack web application without ever leaving the JavaScript ecosystem. Electron and React Native mean you can even target desktop and mobile with the same language. Throw in the fact that AI is remarkably good at generating JavaScript, and a reasonable question starts to form: do we actually need any other technologies at all?&lt;/p&gt;

&lt;p&gt;Well, we are not here because JavaScript is so perfect. The real driver behind the JavaScript monoculture is economics. Engineers are expensive, and maintaining a diverse team of specialists — backend, frontend, mobile, DevOps adds up fast, even for large organizations. An unified stack means a smaller bus factor, a shorter onboarding curve, and fewer technology silos to manage. It is a compelling argument. But that setup has a ceiling. For native apps, web-powered solutions have always lagged behind in UI performance compared to truly native technologies. That gap has existed for years, and the industry learned to live with it or simply to lower its standards. AI is what changes this equation.&lt;/p&gt;

&lt;p&gt;You can spin up a new SaaS using Next.js and Tailwind CSS in a matter of days nowadays. But if the idea gains traction, you will have to iterate and improve the product relentlessly, and this is where the drawbacks of a single-technology stack start to show. Some obstacles are manageable — they have been solved before, and the answers are out there. But others are structural and will simply cost too much to work around. In the end, your competitor who chose to ship a fully native app will just have a better product. It will feel more polished, perform more smoothly, and win users on experience alone. Historically, that meant staffing native teams, which was prohibitively expensive for most. With AI, it no longer does. You need a handful of highly experienced engineers and Claude Code or any capable AI agent, and you can cover the full stack — native or otherwise. Microsoft's recent decision to &lt;a href="https://www.techspot.com/news/111872-microsoft-plans-100-native-windows-11-apps-major.html" rel="noopener noreferrer"&gt;rewrite web-based apps in Windows 11 using native technologies&lt;/a&gt; is a public confirmation of exactly this shift.&lt;/p&gt;

&lt;p&gt;What this points to is a return to a healthy zoo of technologies — not because polyglot stacks are fashionable, but because each language was built to excel at something specific, and AI now removes the cost barrier of using the best tool for the job. Ruby, Python, Kotlin, Java, PHP — each of them has unique strengths that were sidelined in the rush toward homogeneous stacks. That era is ending.&lt;/p&gt;

&lt;p&gt;I doubt that Ruby will recapture the mass popularity it enjoyed before client-side rendering with React reshaped the web. That ship has likely sailed. But Ruby will absolutely be used more for what it has always done best: rapid development of web services, whether full stack or API only. The recent work DHH and the Rails team have put into making Rails 8+ a genuinely one-person framework is not an accident. It is a deliberate bet on exactly the kind of world I am describing.&lt;/p&gt;

&lt;p&gt;Here is where things get genuinely interesting for Ruby specifically. It turns out that Ruby is significantly &lt;a href="https://notes.ghinda.com/post/ruby-and-token-efficiency" rel="noopener noreferrer"&gt;more token-efficient&lt;/a&gt; than most other languages. This matters more than you might initially think: AI models charge per token, and the less code they have to read and generate, the faster and cheaper the process becomes. Ruby's clean, expressive syntax is not just pleasant for humans — it is economical for machines.&lt;/p&gt;

&lt;p&gt;But a revival of Ruby in the AI era will also shine a light on the language's long-standing weaknesses. The lack of types is chief among them. Programming code is not just a sequence of syntactically correct instructions — it is a sequence that makes sense, performs well, and is secure. Generating a set of functions or classes is easy for an LLM; generating code that is &lt;em&gt;correct&lt;/em&gt; is the hard part. Without type information, the model has to re-read entire files, re-run tests, and often go in circles when an error is subtle enough to have multiple possible root causes. This is precisely &lt;a href="https://github.blog/ai-and-ml/llms/why-ai-is-pushing-developers-toward-typed-languages/" rel="noopener noreferrer"&gt;why AI performs better with statically typed languages&lt;/a&gt;: LLMs can verify that their changes are consistent with the existing codebase before running a single test.&lt;/p&gt;

&lt;p&gt;Here, Ruby is further along than most people realize. Stripe and Shopify — two of the largest and most demanding Rails codebases in the world have been using static typing in production for years. They are not outliers; they are early movers. A typed Ruby codebase gives any LLM a tighter contract to reason against, reducing the number of tokens needed to understand the code and increasing the confidence that a proposed change will actually work.&lt;/p&gt;

&lt;p&gt;There is also the question of what you can build with Ruby when it comes to AI integration. Python has decades of dominance in the data science and machine learning space, along with an enormous ecosystem of libraries. Ruby does not have that history, but it has something else. Gems like &lt;a href="https://github.com/alexrudall/ruby-openai" rel="noopener noreferrer"&gt;ruby-openai&lt;/a&gt; and &lt;a href="https://github.com/crmne/ruby_llm" rel="noopener noreferrer"&gt;ruby_llm&lt;/a&gt; give developers a clean, unified API for calling any AI model, and the barrier to building AI-powered web applications with Rails is remarkably low. &lt;a href="https://oss.vicente.services/dspy.rb/" rel="noopener noreferrer"&gt;DSPy.rb&lt;/a&gt; takes this a step further by combining AI prompts with types, bringing structure and repeatability to AI interactions in a way that feels distinctly Ruby. For a language built around developer happiness and rapid development, that is a natural fit.&lt;/p&gt;

&lt;p&gt;Ruby has always pushed for developer happiness — not in the sense of accommodating every possible use case, but in the sense of making the experience of building software feel genuinely good. In the AI era, that definition is being tested. Happiness used to mean a clean DSL and a forgiving runtime. Going forward, it will also need to mean tooling that helps LLMs work confidently in the codebase, type coverage that catches problems before they reach production, and a framework that a small team or a single developer with a capable AI agent can take from idea to deployed product.&lt;/p&gt;

&lt;p&gt;Ruby is not dead. It is not slow. It does scale, and it will perform even better with the &lt;a href="https://www.tokyodev.com/articles/rubykaigi-2025-recap#zjit-building-a-next-generation-ruby-jit" rel="noopener noreferrer"&gt;upcoming switch to ZJIT&lt;/a&gt;. It will not reclaim its mid-2010s glory days — the web has changed too much for that, but it does not need to. It has a clear, defensible niche as a professional choice for building AI-powered web applications quickly and confidently. That is enough, and it is more relevant now than it has been in years.&lt;/p&gt;

&lt;p&gt;I will set a reminder to revisit this in 2-3 years and see how the picture has changed. What do you think — where is Ruby heading?&lt;/p&gt;

&lt;p&gt;Original post can be found in my &lt;a href="https://newsletters.eremin.eu/posts/ruby-where-are-we-going-2026-edition" rel="noopener noreferrer"&gt;blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>statictyping</category>
      <category>rbs</category>
      <category>ai</category>
    </item>
    <item>
      <title>T-Ruby: Adding Static Typing to Ruby Without Runtime Overhead</title>
      <dc:creator>Andrey Eremin</dc:creator>
      <pubDate>Tue, 24 Feb 2026 21:50:21 +0000</pubDate>
      <link>https://dev.to/aeremin/t-ruby-adding-static-typing-to-ruby-without-runtime-overhead-3ddf</link>
      <guid>https://dev.to/aeremin/t-ruby-adding-static-typing-to-ruby-without-runtime-overhead-3ddf</guid>
      <description>&lt;p&gt;Static typing is a formidable tool that brings immense value to codebases of all sizes. From tiny scripts to massive monoliths, the benefits are hard to ignore: you get live documentation that is always up to date, enhanced readability, and a reliable safety net that significantly boosts code reliability.&lt;/p&gt;

&lt;p&gt;Different dynamic languages have taken various paths to solve the typing puzzle. Python introduced native but optional type hints, while the JavaScript ecosystem moved toward a separate language entirely with TypeScript. Within the Ruby community, we’ve seen two distinct implementations in RBS and Sorbet, which have recently begun to converge thanks to the introduction of RBS inline and Sorbet's support for it.&lt;/p&gt;

&lt;p&gt;However, the current Ruby approach isn't without its friction. For many developers, typing still feels like a matter of personal preference rather than a core requirement. We never forget to write tests because they are the heartbeat of our CI/CD pipelines, but because type checking is often seen as "extra," it is far too easy for static checks to be ignored or forgotten entirely.&lt;/p&gt;

&lt;p&gt;There is a groundbreaking experiment currently gaining traction called &lt;a href="https://type-ruby.github.io/" rel="noopener noreferrer"&gt;&lt;strong&gt;T-Ruby&lt;/strong&gt;&lt;/a&gt;. Unlike standard runtime type-checking systems, T-Ruby compiles typed code down to pure, undecorated Ruby with zero runtime overhead. If you are looking for a shorthand description, T-Ruby is essentially TypeScript for the Ruby language. It acts as a layer on top of the language where you write RBS-inspired code that is eventually compiled into plain, standard Ruby.&lt;/p&gt;

&lt;p&gt;While some might ask why we shouldn't just use Crystal, there is a fundamental difference in philosophy. Crystal is a separate, Ruby-like language with its own ecosystem. T-Ruby, by contrast, is an extra layer specifically designed to stay close to the Ruby we already know. Much like how TypeScript eventually outputs plain JavaScript, T-Ruby ensures your final product remains pure Ruby.&lt;/p&gt;

&lt;p&gt;To demonstrate this, I’ve updated a script I originally used in 2025 to &lt;a href="https://www.eremin.eu/blog/statictyping/static-typing-the-missing-ruby-tool" rel="noopener noreferrer"&gt;showcase RBS&lt;/a&gt;. This is a simple HTTP client that fetches the latest Ruby release tag from GitHub. With T-Ruby, we can now embed types directly into the class structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require "httparty"
require "json"
require "time"

class RubyVersion
  API_URL = "https://api.github.com/repos/ruby/ruby/releases/latest"

  class Response
    attr_reader :code: Integer
    attr_reader :json: Hash&amp;lt;String, untyped&amp;gt;


    def initialize(code: Integer, json: Hash&amp;lt;String, untyped&amp;gt;): nil
      @code = code
      @json = json
    end

    def success?: Boolean
      (200..299).include?(@code)
    end
  end


  def fetch_response: Response
    http = HTTParty.get(API_URL, headers: { 'User-Agent' =&amp;gt; 'static-typing-demo' })
    Response.new(http.code.to_i, JSON.parse(http.body))
  end

  def self.fetch(printer: Proc&amp;lt;[String, String, Time], nil&amp;gt;): nil
    resp = new.fetch_response
    raise "HTTP #{resp.code}" unless (200..299).include?(resp.code)
    data = resp.json
    published = Time.parse((data['published_at'] || Time.now.utc.iso8601).to_s)
    printer.call((data['tag_name'] || data['name']).to_s, data['html_url'], published)
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By adding these types, we can strictly define the structure of our Proc and ensure the data passed through the system is valid. In standard Ruby, we would normally rely on documentation or manual guard clauses to achieve this level of certainty.&lt;/p&gt;

&lt;p&gt;Setting up the environment is straightforward: a quick &lt;code&gt;gem install t-ruby&lt;/code&gt; followed by &lt;code&gt;trc --init&lt;/code&gt; creates a configuration file. This allows you to specify your source folders for &lt;code&gt;*.trb&lt;/code&gt; files, your destination for compiled Ruby, and even post-compile commands like auto-running RSpec or Minitest.&lt;/p&gt;

&lt;p&gt;The integration with Rails is currently one of the more experimental aspects. Because Rails relies on a specific folder structure, the cleanest approach seems to be storing the entire app folder within your T-Ruby source directory. Files that don't contain T-Ruby syntax are simply copied over to the destination folder unmodified. This mirrors the &lt;code&gt;/src&lt;/code&gt; and &lt;code&gt;/dist&lt;/code&gt; workflow common in modern JavaScript frameworks like Next.js.&lt;/p&gt;




&lt;p&gt;Is the future finally here? Not quite. T-Ruby is very much in a "technical preview" phase. While the website and documentation look professional and polished, the implementation can be temperamental. During my testing, I found that even official examples could fail immediately after installation.&lt;/p&gt;

&lt;p&gt;However, the potential is undeniable. While working on a small Rails app with T-Ruby, I realized I no longer needed a separate collection of RBS files. Writing types alongside business logic feels natural, and knowing the code is "type-correct" at compile time provides a unique peace of mind. It’s a bold step forward for the Ruby ecosystem even if it isn't quite ready for your production environment just yet.&lt;/p&gt;

&lt;p&gt;Originally posted in &lt;a href="https://newsletters.eremin.eu/posts/t-ruby-adding-static-typing-to-ruby-without-runtime-overhead" rel="noopener noreferrer"&gt;my blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>rbs</category>
      <category>ruby</category>
      <category>statictyping</category>
      <category>sorbet</category>
    </item>
    <item>
      <title>Static Typing - Ruby's missing tools</title>
      <dc:creator>Andrey Eremin</dc:creator>
      <pubDate>Sun, 19 Oct 2025 16:37:08 +0000</pubDate>
      <link>https://dev.to/aeremin/static-typing-rubys-missing-tools-4n1f</link>
      <guid>https://dev.to/aeremin/static-typing-rubys-missing-tools-4n1f</guid>
      <description>&lt;p&gt;For the last 20 years, Rubyists have adopted dozens of tools and technologies that allow us to write better software, scale projects, and ship what needs to be shipped to production the way we want it. I will name just a few of them: Docker, ruby-lsp, AI, RuboCop, MiniTest, RSpec, Cucumber.&lt;/p&gt;

&lt;p&gt;The interesting fact, however, is that all these tools faced criticism when they were introduced. Some were heavily criticized, others faced a little skepticism. But the fact is, eventually, we adopted them and now it’s hard to imagine our programming life without them. We no longer argue about spaces or tabs; we just do &lt;code&gt;gem install rubocop&lt;/code&gt; and then &lt;code&gt;rubocop -a&lt;/code&gt;. We adopted these tools so that we could achieve even more. We delegated part of what we were doing to these artificial electronic helpers.&lt;/p&gt;

&lt;p&gt;Think about it. The first version (and some subsequent ones as well) of Ruby on Rails was implemented by DHH in TextMate with just syntax highlighting. No code completion, no linters, no IDEs, no AIs. I remember those days. I was using Notepad++ on Windows for PHP and Ruby development.&lt;/p&gt;

&lt;p&gt;As we see across the years, the process of adopting new tools and new ways to help us ship more, faster, and better is endless. If we cannot come up with something internally, like RuboCop, we look elsewhere and adopt things used in other ecosystems like Docker, or MiniTest (which is an adaptation of a Java library).&lt;/p&gt;

&lt;p&gt;For the last several years, I have been actively working with JVM languages like Java and Kotlin. When I first started working with them, I was quite irritated by the amount of code and things I needed to do just to run my program. Over time, however, I noticed the significant confidence I gained every time the compiler successfully built my code. It doesn’t guarantee that the code works as I expect, but it already gives me a lot - a full understanding of the data flow and all the connections between various parts of my code. My software may still crash once started, but I’m still certain that a big deal of correctness is already ensured. With a few additional tests, I can make sure the business logic works just as it should.&lt;/p&gt;

&lt;p&gt;What about Ruby? Without a single line of tests, you don’t know anything about your code. Do you pass the correct data? Does your business logic even make sense? With simple scripts, you might guess and imagine how the code works, but will you be right? What about a simple refactoring or a feature request where you have no idea what kind of input you’re dealing with? Are they simple primitives like Strings or Integers? Or maybe instances of some classes or, even worse, nested Arrays or Hashes?&lt;/p&gt;

&lt;p&gt;The only ways to check this are either to run the code and see, or to write tests. So we go with tests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tests to ensure the code accepts the correct data.&lt;/li&gt;
&lt;li&gt;Tests to ensure the business logic is correct.&lt;/li&gt;
&lt;li&gt;Tests to ensure edge cases won’t cause issues.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tests are great, don’t get me wrong. But looking at the picture I described above, it seems we rely too much on one single source of confidence. What if they fail? And this happens. What if the tests are wrong? Maybe we stubbed too much and now we’re testing stubs, not the real code. No one will save us then. It looks like it’s time to look around and see what else we can do to make things better.&lt;/p&gt;

&lt;p&gt;Here is a simple Ruby script:&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="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'json'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'time'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'httparty'&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;RubyVersion&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;
    &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:code&lt;/span&gt;
    &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:json&lt;/span&gt; 

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
      &lt;span class="vi"&gt;@code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;
      &lt;span class="vi"&gt;@json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&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;success?&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;299&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="no"&gt;API_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'https://api.github.com/repos/ruby/ruby/releases/latest'&lt;/span&gt; 

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch_response&lt;/span&gt;
    &lt;span class="n"&gt;http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;HTTParty&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;API_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;headers: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s1"&gt;'User-Agent'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'static-typing-demo'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="no"&gt;Response&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;code: &lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&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;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;printer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fetch_response&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"HTTP &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;code&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success?&lt;/span&gt;

    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;
    &lt;span class="n"&gt;published_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'published_at'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;iso8601&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;printer&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'tag_name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'html_url'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;published_at&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="no"&gt;PRINTER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;proc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;published_at&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Fetched tag=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; published_at=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;published_at&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;iso8601&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; (url=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;
  &lt;span class="kp"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; 

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="no"&gt;RubyVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;PRINTER&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vg"&gt;$PROGRAM_NAME&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kp"&gt;__FILE__&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Before we move on, let’s check what this code does. The script fetches information about the latest Ruby release via the &lt;code&gt;.fetch_response&lt;/code&gt; method. The information is returned as an instance of the &lt;code&gt;RubyVersion::Response&lt;/code&gt; class. The &lt;code&gt;.fetch_response&lt;/code&gt; method is called by our main entry point &lt;code&gt;.fetch&lt;/code&gt;, which accepts one mandatory parameter &lt;code&gt;printer&lt;/code&gt; - a Proc defined in the constant &lt;code&gt;PRINTER&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Just like we would do in real life, we’ll cover this code with the necessary tests. Here is a simple MiniTest file that covers all major aspects of our script.&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="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'minitest/autorun'&lt;/span&gt;
&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s1"&gt;'../app/fetch_ruby_latest_with_types'&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestRubyVersionFetch&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Minitest&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Test&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_fetch_response_structure&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;RubyVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch_response&lt;/span&gt;
    &lt;span class="n"&gt;assert_kind_of&lt;/span&gt; &lt;span class="no"&gt;RubyVersion&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;
    &lt;span class="n"&gt;assert_kind_of&lt;/span&gt; &lt;span class="no"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;code&lt;/span&gt;
    &lt;span class="n"&gt;assert_kind_of&lt;/span&gt; &lt;span class="no"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;test_fetch_with_printer_proc&lt;/span&gt;
    &lt;span class="n"&gt;received&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;printer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;proc&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;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;published_at&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;received&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:tag&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;
      &lt;span class="n"&gt;received&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:url&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;
      &lt;span class="n"&gt;received&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:published_at&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;published_at&lt;/span&gt;
      &lt;span class="kp"&gt;nil&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;RubyVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;printer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assert_nil&lt;/span&gt; &lt;span class="n"&gt;summary&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;received&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:tag&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;received&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:url&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;assert_kind_of&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;received&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:published_at&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;def&lt;/span&gt; &lt;span class="nf"&gt;test_fetch_without_printer&lt;/span&gt;
    &lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;RubyVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assert_nil&lt;/span&gt; &lt;span class="n"&gt;summary&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;Let’s check what we do here. With &lt;code&gt;test_fetch_response_structure&lt;/code&gt;, we ensure that &lt;code&gt;.fetch_response&lt;/code&gt; works correctly, doesn’t raise exceptions, and returns a non-empty response of the correct type. With &lt;code&gt;test_fetch_with_printer_proc&lt;/code&gt;, we cover our main business logic of accepting the Proc, calling &lt;code&gt;.fetch_response&lt;/code&gt;, and passing all the necessary data to it. Finally, with &lt;code&gt;test_fetch_without_printer&lt;/code&gt;, we test what happens when we pass &lt;code&gt;nil&lt;/code&gt;. Even though the &lt;code&gt;printer&lt;/code&gt; parameter is defined as mandatory, without any guard statement, we can still pass any value — including &lt;code&gt;nil&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now let’s add a file with type definitions.&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;module&lt;/span&gt; &lt;span class="nn"&gt;RubyVersion&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;
    &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;code: &lt;/span&gt;&lt;span class="no"&gt;Integer&lt;/span&gt;
    &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="no"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;untyped&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;code: &lt;/span&gt;&lt;span class="no"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="no"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;untyped&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;void&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;success?&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;bool&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="no"&gt;API_URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;String&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch_response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Response&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;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="no"&gt;PRINTER&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;For simplicity, I didn’t provide detailed type definitions for the &lt;code&gt;Hash&lt;/code&gt; we receive from GitHub’s endpoint. In real life, we might want to go deeper and cover that part with types as well.&lt;/p&gt;

&lt;p&gt;The syntax of RBS is very similar to Ruby, so it’s not difficult to understand what it describes. You may notice that for our &lt;code&gt;.fetch&lt;/code&gt; method, we specified what type of data it supports. This means we don’t just “allow” a &lt;code&gt;printer&lt;/code&gt; parameter to be present (though &lt;code&gt;nil&lt;/code&gt; is possible), but we describe exactly what kind of value we accept. The same applies to all other parameters and return values.&lt;/p&gt;

&lt;p&gt;Let’s break the code a bit and pass &lt;code&gt;nil&lt;/code&gt; to the &lt;code&gt;.fetch&lt;/code&gt; method like this:&lt;br&gt;
 &lt;code&gt;puts RubyVersion.fetch(nil) if $PROGRAM_NAME == __FILE__&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When we run &lt;code&gt;steep check&lt;/code&gt;, it will immediately spot the issue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; bundle exec steep check
# Type checking files:

.F

app/fetch_ruby_latest_with_types.rb:49:23: [error] Cannot pass a value of type `nil` as an argument of type `^(::String, ::String, ::Time) -&amp;gt; nil`
│   nil &amp;lt;: ^(::String, ::String, ::Time) -&amp;gt; nil
│
│ Diagnostic ID: Ruby::ArgumentTypeMismatch
│
└ puts RubyVersion.fetch(nil) if $PROGRAM_NAME == __FILE__
                         ~~~

Detected 1 problem from 1 file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It tells us:&lt;br&gt;
 &lt;code&gt;Cannot pass a value of type nil as an argument of type ^(::String, ::String, ::Time) -&amp;gt; nil&lt;/code&gt;&lt;br&gt;
 — which gives us enough information about the issue and how to fix it. Now, without even running our tests, we can ensure we’ll never pass invalid data.&lt;/p&gt;

&lt;p&gt;That also means we no longer need this test:&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;test_fetch_without_printer&lt;/span&gt;
    &lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;RubyVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assert_nil&lt;/span&gt; &lt;span class="n"&gt;summary&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Steep&lt;/code&gt;  (a static type checker) simply won’t let us pass anything but a &lt;code&gt;Proc&lt;/code&gt; of the right shape. We can go even further and simplify other tests.&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="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'minitest/autorun'&lt;/span&gt;
&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s1"&gt;'../app/fetch_ruby_latest_with_types'&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestRubyVersionMinimal&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Minitest&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Test&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_fetch_response_structure&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;RubyVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch_response&lt;/span&gt;
    &lt;span class="n"&gt;refute_nil&lt;/span&gt; &lt;span class="n"&gt;resp&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;test_fetch_with_printer_proc&lt;/span&gt;
    &lt;span class="n"&gt;received&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;printer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;proc&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;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;published_at&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;received&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:tag&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;
      &lt;span class="n"&gt;received&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:url&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;
      &lt;span class="n"&gt;received&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:published_at&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;published_at&lt;/span&gt;
      &lt;span class="kp"&gt;nil&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;RubyVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;printer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assert_nil&lt;/span&gt; &lt;span class="n"&gt;summary&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;received&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:tag&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;received&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:url&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, for the remaining cases, we only test the logic, not the types or the shape of the data we deal with.&lt;/p&gt;

&lt;p&gt;In other words, we split responsibilities between MiniTest and Steep: the first validates that the business logic works as expected, the latter ensures we always deal with what was designed.&lt;/p&gt;

&lt;p&gt;Static typing in Ruby cannot and will never replace tests. Similar to how it works in strongly typed languages, it’s a tool that brings clarity and confidence to your code.&lt;/p&gt;

&lt;p&gt;And just like you’d spend your time in, say, Java, working with RBS or Sorbet (whichever you prefer) is not an “extra” time that none of us has. It’s the same time you’d otherwise spend debugging your app manually or writing an excessive amount of tests. As we’ve seen many times before - using static typing pays off.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>statictyping</category>
      <category>rbs</category>
      <category>sorbet</category>
    </item>
    <item>
      <title>Static Typing in Ruby: It's Not a Cult, It's a Choice (You Should Probably Make)</title>
      <dc:creator>Andrey Eremin</dc:creator>
      <pubDate>Mon, 24 Mar 2025 08:53:00 +0000</pubDate>
      <link>https://dev.to/aeremin/static-typing-in-ruby-its-not-a-cult-its-a-choice-you-should-probably-make-23hk</link>
      <guid>https://dev.to/aeremin/static-typing-in-ruby-its-not-a-cult-its-a-choice-you-should-probably-make-23hk</guid>
      <description>&lt;p&gt;Alright, let's talk about static typing in Ruby, and let's get one thing straight right off the bat: it is not an all-or-nothing game!&lt;/p&gt;

&lt;p&gt;I've seen the arguments from the dynamically-typed purists. They scream, "But Ruby is about freedom! Flexibility! Programmer happiness!" And yeah, sure, that's cute. But guess what else makes programmers happy? Not spending hours debugging runtime errors that a simple type check could have caught.&lt;/p&gt;

&lt;p&gt;The dinosaurs in the room will tell you that adding static typing to Ruby is like putting a saddle on a unicorn. They'll whine about how it goes against the very nature of the language. To that, I say: &lt;strong&gt;grow up.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ruby has always been about pragmatism, about taking the best ideas from other languages and making them work for &lt;em&gt;us&lt;/em&gt;. Remember where Matz got his inspiration? He explicitly mentioned &lt;strong&gt;Perl, Smalltalk, Eiffel, Ada, and Lisp&lt;/strong&gt; as influences. Notice anything interesting there? Eiffel and Ada are statically typed! So this idea that Ruby can't be touched by the type system is utter nonsense. It's in the language's DNA, you just didn't know it.&lt;/p&gt;

&lt;p&gt;Fast forward to the modern era, and the Ruby community has given us not one, but &lt;em&gt;two&lt;/em&gt; fantastic options for bringing static typing into our beloved language. We've got &lt;strong&gt;RBS&lt;/strong&gt;, the official type signature language spearheaded by the Ruby core team, released about five years ago. Then there's &lt;strong&gt;Sorbet&lt;/strong&gt;, the brainchild of Stripe.&lt;/p&gt;

&lt;p&gt;And this is pure Ruby magic right here. We're not being forced into a single, dogmatic approach. You can pick your poison. Like the official, more declarative style of RBS? Go for it. Prefer the gradual, more integrated approach of Sorbet? Knock yourself out. Or, you know, you can keep writing code like it's 1999. 😉 &lt;/p&gt;

&lt;p&gt;Here's the thing: nobody is saying you have to go full-throttle from day one. Think of it like dipping your toes in the water. Got a gnarly, complex piece of code that's been giving you headaches? Slap some type signatures on it. Just one file. See how it feels. I've done this myself. Took a massive, legacy feature in an old app, added RBS signatures, and BAM! Instant clarity, improved reliability, and a smug sense of superiority over the bugs that used to plague us. It's like adding an extra layer of Kevlar to your test suite.&lt;/p&gt;

&lt;p&gt;The beauty of this is the control. You want to type every single method and variable? Be my guest. You only care about the critical entry points and public APIs? That's cool too. It's your codebase, your rules.&lt;/p&gt;

&lt;p&gt;Let's be crystal clear: &lt;strong&gt;static typing is not a replacement for testing.&lt;/strong&gt; If you think adding types means you can ditch your tests, you're delusional. Types give you guarantees about the shape of your data, but they don't tell you if your application actually &lt;em&gt;does&lt;/em&gt; what it's supposed to. But what they &lt;em&gt;do&lt;/em&gt; give you is a much higher degree of confidence that your code won't blow up in unexpected ways. It's like having a spellchecker for your code – it won't write the novel for you, but it'll sure catch a lot of embarrassing typos.&lt;/p&gt;

&lt;p&gt;And if the term "static typing" makes you itchy, reframe it. Think of it as &lt;strong&gt;living, breathing documentation.&lt;/strong&gt; No more outdated YARD docs.&lt;/p&gt;

&lt;p&gt;Imagine you have this classic Ruby code with YARD documentation:&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="c1"&gt;# Calculates the area of a rectangle.&lt;/span&gt;
&lt;span class="c1"&gt;# @param width [Integer] The width of the rectangle.&lt;/span&gt;
&lt;span class="c1"&gt;# @param height [Integer] The height of the rectangle.&lt;/span&gt;
&lt;span class="c1"&gt;# @return [Integer] The area of the rectangle.&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_area&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, look at the same thing with &lt;code&gt;rbs-inline&lt;/code&gt;:&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="c1"&gt;# Calculates the area of a rectangle.&lt;/span&gt;
&lt;span class="c1"&gt;#: (Integer, Integer) -&amp;gt; Integer&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_area&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See the difference? The type information is now part of the code itself. It's not just a comment that can easily become stale. Your "documentation" is now actively checked by the type checker. Tell me that's not a win!&lt;/p&gt;

&lt;p&gt;The static typing in Ruby isn't some scary monster trying to steal your dynamic joy. It's a powerful tool that can make your code more robust, more maintainable, and frankly, less prone to embarrassing runtime errors. You don't have to dive headfirst into the deep end. Just try it. Pick a small, annoying piece of code and add some types. I guarantee, once you experience the peace of mind that comes with knowing your data is shaped the way you expect, you'll wonder why you didn't do it sooner. Don't be a laggard. Give the sweet pill of static typing a try. You might just find yourself addicted.&lt;/p&gt;

</description>
      <category>statictyping</category>
      <category>rbs</category>
      <category>ruby</category>
      <category>sorbet</category>
    </item>
    <item>
      <title>Why Static Typing is Essential for Efficient Ruby and Rails Development</title>
      <dc:creator>Andrey Eremin</dc:creator>
      <pubDate>Wed, 12 Mar 2025 19:59:52 +0000</pubDate>
      <link>https://dev.to/aeremin/why-static-typing-is-essential-for-efficient-ruby-and-rails-development-3n0d</link>
      <guid>https://dev.to/aeremin/why-static-typing-is-essential-for-efficient-ruby-and-rails-development-3n0d</guid>
      <description>&lt;p&gt;For years, Ruby has been celebrated for its flexibility and developer-friendly syntax. It allows rapid prototyping, but this freedom comes at a cost—maintainability and debugging can become expensive as projects scale.&lt;/p&gt;

&lt;p&gt;Many developers think of static typing as an unnecessary hurdle, slowing them down rather than making their work more efficient. But just like automated tests, static typing (whether with RBS or Sorbet) is an investment that pays off significantly. Not only does it help you write software faster after an initial learning curve, but it also acts as free documentation, making your code easier to maintain and reducing overall costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Trade-Off: Initial Effort vs. Long-Term Efficiency
&lt;/h2&gt;

&lt;p&gt;It’s true—introducing static typing in Ruby comes with an upfront cost. You have to learn how to use RBS or Sorbet, and annotating an existing codebase takes effort. But here’s the catch: this investment quickly pays for itself.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fewer runtime errors&lt;/strong&gt;, meaning less time wasted on debugging.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clearer, self-documenting code&lt;/strong&gt;, making it easier for new developers to onboard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safer refactoring&lt;/strong&gt;, allows you to make changes with confidence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your team spends hours tracking down bugs caused by unexpected method arguments or &lt;code&gt;nil&lt;/code&gt; values, static typing will significantly reduce that time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Type Signatures as Free Documentation
&lt;/h2&gt;

&lt;p&gt;How often have you opened a Ruby codebase and struggled to understand what a method expects as input and what it returns? In dynamically typed languages, you usually rely on guessing, reading tests, or digging through documentation (if it exists).&lt;/p&gt;

&lt;p&gt;With static typing, the code itself provides these answers. Instead of writing a vague comment 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="c1"&gt;# Processes an order and returns confirmation&lt;/span&gt;
&lt;span class="c1"&gt;# @param order [Order] - The order to process&lt;/span&gt;
&lt;span class="c1"&gt;# @return [String] - Confirmation message&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can define it with RBS or Sorbet, ensuring that this information stays up-to-date and accurate:&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;sig&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;order: &lt;/span&gt;&lt;span class="no"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;returns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;# Sorbet example, but can be rbs-inline as well&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# method logic&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, every developer reading this method instantly knows what to expect—without relying on possibly outdated comments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Catching Bugs Before They Happen
&lt;/h2&gt;

&lt;p&gt;Ruby's flexibility allows for quick iterations, but it also makes it easy to introduce subtle bugs. Simple mistakes like passing the wrong argument type can go unnoticed until they break something in production.&lt;/p&gt;

&lt;p&gt;Consider this common mistake:&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;add_numbers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;add_numbers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"10"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Runtime error!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you had a type checker in place, this issue would be caught instantly:&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="c1"&gt;# @rbs (a Integer, b Integer) -&amp;gt; Integer&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_numbers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Static typing prevents these kinds of runtime surprises, saving you debugging time and frustration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making Refactoring Safe and Predictable
&lt;/h2&gt;

&lt;p&gt;If you’ve ever worked on a large Rails project, you know how scary refactoring can be. Changing a method signature can silently break multiple parts of your application without warning.&lt;/p&gt;

&lt;p&gt;With static typing, your editor and type checker will immediately highlight every place where a change is needed. No more crossing your fingers and hoping that test coverage will catch everything—static typing makes refactoring a predictable and stress-free process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Better Developer Experience with IDE Support
&lt;/h2&gt;

&lt;p&gt;One of the underrated benefits of static typing is improved IDE support. When your code is typed, editors like VS Code or RubyMine can provide more accurate auto-completions, better navigation, and intelligent suggestions.&lt;/p&gt;

&lt;p&gt;This makes working with large codebases much smoother because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can quickly jump to method definitions without guessing what arguments they accept.&lt;/li&gt;
&lt;li&gt;You get instant feedback when calling methods with incorrect parameters.&lt;/li&gt;
&lt;li&gt;You spend less time looking up class definitions and more time writing actual code.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Static Typing Doesn’t Replace Tests—It Complements Them
&lt;/h2&gt;

&lt;p&gt;Some developers argue that good test coverage is enough to catch type-related errors. While tests are crucial, they don’t fully replace the benefits of static typing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tests check expected behavior; types prevent unexpected mistakes.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Static typing works everywhere&lt;/strong&gt;, whereas tests only cover the cases you remember to write.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refactoring is safer with types&lt;/strong&gt;, as they enforce correct method usage automatically.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of static typing as an extra safety net that catches errors before they even reach your tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Business Case: Reducing Costs with Static Typing
&lt;/h2&gt;

&lt;p&gt;From a business perspective, every hour spent debugging, reading undocumented code, or manually checking method arguments is money wasted. Static typing directly reduces these inefficiencies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fewer bugs in production&lt;/strong&gt; → Less firefighting and support costs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Faster onboarding&lt;/strong&gt; → New engineers understand the codebase quicker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More confident deployments&lt;/strong&gt; → Features can be shipped faster without introducing instability.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Static typing isn’t just a developer convenience for teams working on long—term projects; it’s a cost-saving measure.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Introduce Static Typing in Your Rails Project
&lt;/h2&gt;

&lt;p&gt;If you’re new to static typing in Ruby, don’t worry—you don’t have to type everything at once. Here’s a gradual approach:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start with new code&lt;/strong&gt;: Use Sorbet or RBS in strict mode for new methods and classes while keeping existing code in gradual mode.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Focus on critical business logic&lt;/strong&gt;: Annotate core domain models and service objects first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avoid unnecessary complexity&lt;/strong&gt;: Keep type definitions practical and don’t overcomplicate them.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By introducing types incrementally, you can improve your codebase without disrupting your workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  -
&lt;/h2&gt;

&lt;p&gt;Static typing isn’t about making Ruby less fun—it’s about making development more efficient. By preventing common bugs, improving readability, and enabling safer refactoring, it helps developers focus on writing features instead of fixing errors.&lt;/p&gt;

&lt;p&gt;If you’re serious about building maintainable Rails applications that scale without headaches, static typing is a powerful tool you shouldn’t ignore.&lt;/p&gt;




&lt;p&gt;Need help implementing static typing in your project? I specialize in building efficient, maintainable Ruby applications—let’s talk!&lt;/p&gt;

</description>
      <category>rbs</category>
      <category>sorbet</category>
      <category>ruby</category>
      <category>rails</category>
    </item>
    <item>
      <title>The State of Static Typing in Ruby in 2025</title>
      <dc:creator>Andrey Eremin</dc:creator>
      <pubDate>Sun, 19 Jan 2025 12:58:46 +0000</pubDate>
      <link>https://dev.to/aeremin/the-state-of-static-typing-in-ruby-in-2025-3o4b</link>
      <guid>https://dev.to/aeremin/the-state-of-static-typing-in-ruby-in-2025-3o4b</guid>
      <description>&lt;p&gt;Ruby has long been praised for its flexibility and expressiveness, making it a favorite among developers who value rapid prototyping and ease of use. However, this dynamic nature has also brought challenges, particularly in maintaining large codebases where bugs and inconsistencies can slip through undetected. Over the years, the Ruby community has recognized the need for tools to bring more predictability and safety to Ruby code without sacrificing its dynamic essence. This is how static typing came into play.&lt;/p&gt;

&lt;p&gt;Stripe was the first to introduce type-checking with its Sorbet gem. Later, the Ruby team officially picked up this topic as well. Since that time, there have been two available solutions for static typing. Both are great, and both come with their pros and cons.&lt;/p&gt;

&lt;p&gt;Today, I am not going to discuss Sorbet, a great tool used by many companies, including Shopify. Instead, I will provide an overview of the official Ruby solution as of 2025.&lt;/p&gt;

&lt;p&gt;RBS, or Ruby Signature, is at the heart of Ruby’s static typing system. It lets developers define type signatures for their Ruby code without altering its runtime behavior. By providing a formal way to describe the structure of Ruby code, RBS helps developers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Catch type errors early in the development process.&lt;/li&gt;
&lt;li&gt;Improve documentation with clear type annotations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Over the years, RBS has seen numerous enhancements, including better support for complex data structures, generics, and more comprehensive integration with Ruby’s core libraries. This evolution has made it an indispensable tool for developers seeking to bring more rigor to their Ruby projects.&lt;/p&gt;

&lt;p&gt;While RBS provides the foundation for static typing, one requires a tool to do the actual type checking - Steep. Developed as a companion tool to RBS, Steep analyzes Ruby code against its type signatures to identify potential type mismatches and other issues.&lt;/p&gt;

&lt;p&gt;Steep allows Ruby developers to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gradually adopt static typing in existing codebases.&lt;/li&gt;
&lt;li&gt;Ensure consistency between type signatures and actual code.&lt;/li&gt;
&lt;li&gt;Catch subtle bugs that might otherwise go unnoticed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Its integration with CI/CD pipelines has made it easy for teams to enforce type safety standards, contributing to the overall reliability and maintainability of their codebases.&lt;/p&gt;

&lt;p&gt;To sum up, we need 2 components to ensure the proper static type checking in Ruby: &lt;code&gt;RBS&lt;/code&gt; and &lt;code&gt;Steep&lt;/code&gt;. Those are enough for you to start, though one may want to get the benefits of modern IDEs like Visual Studio Code (with some additional extensions) or RubyMine (which supports RBS out of the box).&lt;/p&gt;

&lt;p&gt;Personally, I have been working with Ruby for more than a decade and have always been missing something that would describe the code better than my test suites. RBS can finally give me that. It helps not just check that, let's say, the &lt;code&gt;age&lt;/code&gt; parameter passed to a method must be an &lt;code&gt;Integer&lt;/code&gt;, it also serves as perfect documentation, which is always up to date.&lt;/p&gt;

&lt;p&gt;Getting into RBS and type-checking requires one to change one's mindset. From "Let's write some dirty quick script first and iterate later if possible" to "Let's write it once and it will work forever". Not literally, but quite closer to it. The feeling of this is similar to adopting TDD or Rubocop (when it just came to the market). You have to think a little bit differently and accept that you will proceed slowly, especially at the beginning.&lt;/p&gt;

&lt;p&gt;Once you get familiar with RBS' syntax, the ecosystem, with all the principles around it, you will be surprised how fast you will go. It is such a great feeling of confidence with your code. Something that you cannot get by covering every bit of your code with tests. Add IDE support here and you will never want to switch back.&lt;/p&gt;

&lt;p&gt;Enough of words, let's briefly set things up and check how it works. As I mentioned, we need 2 gems &lt;code&gt;rbs&lt;/code&gt; and &lt;code&gt;steep&lt;/code&gt;. So let's install them with &lt;code&gt;gem install rbs steep&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Once you have done this, we need to initialize Steep with &lt;code&gt;steep init&lt;/code&gt;. This command will generate a &lt;code&gt;Steepfile&lt;/code&gt; - a configuration file that will tell Steep how to check your code. We do not need much of it not, but let's open it and replace the content with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;target :app do
  signature "sig"

  check "func.rb"
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will tell Steep to check just our ruby script and get signatures from the &lt;code&gt;sig&lt;/code&gt; folder. We do not need anything else for this example. Let's add the missing parts. First of all, we need a new folder - &lt;code&gt;sig&lt;/code&gt;, where we will store the signatures for our code.&lt;/p&gt;

&lt;p&gt;Next, I will create 2 files, the first one is &lt;code&gt;func.rb&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def factorial(n)
  return 1 if n &amp;lt;= 1
  n * factorial(n - 1)
end

puts factorial(1)
puts factorial("1")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then the corresponding signature file for it in &lt;code&gt;sig/func.rbs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Object
  def factorial: (Integer n) -&amp;gt; Integer
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, when I run &lt;code&gt;steep check&lt;/code&gt; I will get the output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Type checking files:

.F

func.rb:8:15: [error] Cannot pass a value of type `::String` as an argument of type `::Integer`
│   ::String &amp;lt;: ::Integer
│     ::Object &amp;lt;: ::Integer
│       ::BasicObject &amp;lt;: ::Integer
│
│ Diagnostic ID: Ruby::ArgumentTypeMismatch
│
└ puts factorial("1")
                 ~~~
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which tells us that we pass the value of the wrong type to our function. Even without running any single test, Steep allows us to prevent lots of errors and make the software more robust.&lt;/p&gt;

&lt;p&gt;Over time, you will get a habit of writing signature files first and implementing them in Ruby code later. Similar to how you would do it in vanilla TDD when you first write the test and then the corresponding logic. &lt;strong&gt;You still have to work on test coverage, but with RBS, you get an extra safety layer.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To sum up, the adoption of static typing in Ruby has brought numerous benefits to developers and organizations alike:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Improved Code Quality:&lt;/strong&gt; Type annotations act as a form of documentation, making code easier to understand and maintain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Early Bug Detection:&lt;/strong&gt; Static type checking catches errors at compile time, reducing the likelihood of runtime issues.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enhanced Tooling Support:&lt;/strong&gt; IDEs and editors now offer better features like intelligent autocomplete and error highlighting, powered by RBS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easier Collaboration:&lt;/strong&gt; In team environments, type annotations provide a shared understanding of data structures and interfaces.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability:&lt;/strong&gt; Static typing adds predictability to large projects, making them easier to scale and refactor.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There are many other features, that I am not going to cover much in the blog post. Right now, RBS and Co give you the full coverage of Ruby's standard library right out of the box plus a bunch of popular gems including Ruby on Rails. There are ways of automatically generating signatures for your code (which does not work super precisely yet, but it is a good starting point for working with legacy software) and even writing the signatures in line with your code.&lt;/p&gt;

&lt;p&gt;So far, I have described only positive or easy things about static typing in Ruby. Is this really that easy and straightforward, after all? The simplest answer would be yes, but © there are, of course, some nuances that one should consider.&lt;/p&gt;

&lt;p&gt;Apart from the mindset change, what I already mentioned, the dev. experience is not yet super polished enough. Today, you get two gems that let you write signatures for your code and ensure that everything is correct by doing type-checking. You will also get IDE support (whether it is RubyMine or VS Code). However, as all those parts are being actively developed, which means there may be cases when something stops working for a while (until it's fixed). &lt;/p&gt;

&lt;p&gt;If you check the web for similar blog posts but from previous years, you will see how significantly the whole ecosystem around static type checking has evolved. It is now not just a bunch of experiments but a rather stable production-ready set of tools, yet with some small risks to keep in mind.&lt;/p&gt;

&lt;p&gt;The ecosystem around static typing is rapidly evolving in Ruby. Whether it is RBS or Sorbet, you can get all the necessary features and documentation right away. The wait is over. The switch seems hard but only from the first look. Once you accept the mindset change, the rest will be just a matter of time.&lt;/p&gt;

&lt;p&gt;Personally, for me, that was a game changer for the projects I work on. It gives me so much confidence in how the code works. I started adopting static typing when it was just released back in 2020 and had to figure out many solutions on my own or wait until someone else found the proper way. &lt;strong&gt;You, however, now may take a shortcut&lt;/strong&gt; and &lt;strong&gt;learn everything you need around RBS in just a matter of several hours&lt;/strong&gt; with my &lt;a href="https://www.udemy.com/course/ruby-on-types-write-robust-software-with-rbs/" rel="noopener noreferrer"&gt;video course&lt;/a&gt;. &lt;/p&gt;




&lt;p&gt;There's a growing need to raise awareness about static typing in Ruby—a powerful tool still waiting to be embraced by Ruby developers worldwide. That's why I publish a monthly newsletter covering RBS and Sorbet. &lt;a href="https://static-ruby.eremin.eu/" rel="noopener noreferrer"&gt;Jump in!&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rbs</category>
      <category>sorbet</category>
      <category>statictyping</category>
    </item>
    <item>
      <title>Try them all</title>
      <dc:creator>Andrey Eremin</dc:creator>
      <pubDate>Sat, 25 Jun 2022 19:45:16 +0000</pubDate>
      <link>https://dev.to/aeremin/try-them-all-48g3</link>
      <guid>https://dev.to/aeremin/try-them-all-48g3</guid>
      <description>&lt;p&gt;There are so many things we, programmers, want to try out, experience and play around with every day. It is so tempting to start a new project with new technology just because we believe it is cool. Does it really bring much value? How to get a hands-on experience in a rather short time frame without putting your work in danger?&lt;/p&gt;

&lt;p&gt;There is actually, a solution. Reserve some time for yourself and pick the task. Make sure, the task is valuable, small and achievable. In one word — just use &lt;a href="https://en.wikipedia.org/wiki/SMART_criteria" rel="noopener noreferrer"&gt;SMART&lt;/a&gt;. That’s basically it. This way you definitely will get the desired result and spend a fixed amount of time without any risks.&lt;/p&gt;

&lt;p&gt;At &lt;a href="https://tech.xing.com/" rel="noopener noreferrer"&gt;XING&lt;/a&gt;  every engineer is able to dedicate 2 weeks per year to work on his own idea. We call such time — Hackweeks. I used that opportunity recently to try out 4 programming languages that I wanted to put my hands on for quite some time. I came up with the idea of a small console app and spend some time each day implementing that software using a particular programming language. By the end of the week, I had 4 repositories with the same app but written in Go, Dart, Elixir and Rust. I liked Go, but this is another story 😉&lt;/p&gt;

</description>
      <category>programming</category>
      <category>go</category>
      <category>elixir</category>
      <category>rust</category>
    </item>
    <item>
      <title>Migrate from PostgreSQL to MySQL using Slick (Scala)</title>
      <dc:creator>Andrey Eremin</dc:creator>
      <pubDate>Fri, 03 Jun 2022 07:35:49 +0000</pubDate>
      <link>https://dev.to/aeremin/migrate-from-postgresql-to-mysql-using-slick-scala-23ce</link>
      <guid>https://dev.to/aeremin/migrate-from-postgresql-to-mysql-using-slick-scala-23ce</guid>
      <description>&lt;p&gt;For my rather long engineering career, I did several migrations from one database to another. Usually, I dealt with cases like switching from SQL- to non-SQL or vice versa. This time I had an interesting case of migrating from Postgres to MySQL.&lt;/p&gt;

&lt;p&gt;For a rather small database (of 14 GBs of size) where we do not use anything special from what PostgreSQL can provide, I did not expect to face any kind of issues. Sure, some SQL queries in Scala code must be adapted to follow the MySQL syntax, but other than that it was expected to run smoothly.&lt;/p&gt;

&lt;p&gt;Even though, making the code producing correct data for MySQL was rather easy, I faced some difficulties with adopting it for be compatible with existing data and especially &lt;strong&gt;timestamps&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I was surprised to learn that timestamps produced by Slick in MySQL and PostreSQL have different format. In any case, we use &lt;a href="https://en.wikipedia.org/wiki/ISO_8601" rel="noopener noreferrer"&gt;ISO 8601&lt;/a&gt; format to store timestamps for both &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/datetime.html" rel="noopener noreferrer"&gt;MySQL&lt;/a&gt; and &lt;a href="https://www.postgresql.org/docs/current/datatype-datetime.html" rel="noopener noreferrer"&gt;PostreSQL&lt;/a&gt;. So, store such data in the default format like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2022-03-11 21:59:00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means that after the migration, the MySQL compatible code must read and produce the same data, however that did not happen. Instead, the code produced this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2022-03-11T21:59:00Z
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After checking the &lt;a href="https://books.underscore.io/essential-slick/essential-slick-3.html#date-and-time-methods" rel="noopener noreferrer"&gt;official Slick documentation&lt;/a&gt; regarding working with 'Date and Time' we can see, that Slick  saves data as &lt;code&gt;TIMESTAMP&lt;/code&gt; for PostgreSQL and as &lt;code&gt;TEXT&lt;/code&gt; for MySQL. In the end that should not be the issue. &lt;a href="https://books.underscore.io/essential-slick/essential-slick-3.html#custom-column-mappings" rel="noopener noreferrer"&gt;Custom column mapping&lt;/a&gt; should help us convert the data from one type to another. Unfortunately that did not work out. Basically, it did not make any changes.&lt;/p&gt;

&lt;p&gt;The answer and actually, the final solution, I found in the &lt;a href="https://scala-slick.org/doc/3.3.3/upgrade.html#support-for-java.time-columns" rel="noopener noreferrer"&gt;migration guide from 3.2 to 3.3&lt;/a&gt; that states that custom mappind does not work for dates and suggests to adjust the &lt;code&gt;Profile&lt;/code&gt; class directly. That was my way to go - create a new &lt;code&gt;Profile&lt;/code&gt; class based on &lt;code&gt;slick.jdbc.MySQLProfile&lt;/code&gt; and copy the logic regarding &lt;code&gt;instantType&lt;/code&gt; from &lt;code&gt;PostgreSQLProfile&lt;/code&gt;. This way I managed to be absolutely compatible with timestamps produced by MySQL version of Slick. The rest data types worked well out of the box.&lt;/p&gt;

&lt;p&gt;Cheers.&lt;/p&gt;

</description>
      <category>scala</category>
      <category>mysql</category>
      <category>slick</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Jupyter Notebooks for Ruby developers</title>
      <dc:creator>Andrey Eremin</dc:creator>
      <pubDate>Fri, 07 Jan 2022 10:07:25 +0000</pubDate>
      <link>https://dev.to/aeremin/jupyter-notebooks-for-ruby-developers-35gf</link>
      <guid>https://dev.to/aeremin/jupyter-notebooks-for-ruby-developers-35gf</guid>
      <description>&lt;p&gt;What I always enjoyed while learning a new programming language was that even if in the end I cannot stick with it, I at least can enrich my daily working life with something that this another language is good for.&lt;/p&gt;

&lt;p&gt;That happened to me 10 years ago when I discovered Ruby and especially Ruby On Rails. Back that time, I was a PHP developer and was amazed by the “magic” that came from Rails. Before finally switch to Ruby I constantly tried to implement my PHP code in Rails way. Even though it looked sometimes weird, it worked pretty well and did the job.&lt;/p&gt;

&lt;p&gt;The same thing happened to me this year when I step on my path of learning AI development and did some progress in designing neural networks using Python. And of course, I discovered Jupyter notebooks – one of the most popular tools for Data Scientists.&lt;/p&gt;

&lt;p&gt;The Jupyter Notebook is an open-source web application that allows you to create and share documents that contain live code, equations, visualizations and narrative text. Uses include: data cleaning and transformation, numerical simulation, statistical modelling, data visualization, machine learning, and much more.&lt;/p&gt;

&lt;p&gt;It was just a matter of time until I thought about using it with Ruby and you know what? I did not regret it. Let me tell you a couple of features that will probably change some of your daily routines as a ruby programmer in a better way.&lt;/p&gt;

&lt;p&gt;Jupyter is a web app written in Python, so the very first step we need to do is to get your things dirty with &lt;a href="https://docs.conda.io/en/latest/miniconda.html" rel="noopener noreferrer"&gt;Conda&lt;/a&gt; and pip. I do not even mention that you must have a fresh Python version on your machine. Next thing would be to &lt;a href="https://jupyter.org/install.html" rel="noopener noreferrer"&gt;install actual Jupyter&lt;/a&gt;. Finally, to be ready for some action, you will need to install &lt;a href="https://github.com/SciRuby/iruby" rel="noopener noreferrer"&gt;iRuby&lt;/a&gt; which allows you to use Ruby within Jupyter. That’s it. Now you can just start the session with this command jupyter notebook. &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%2F2rxpwoak8j010kyatf1x.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%2F2rxpwoak8j010kyatf1x.png" alt="You can create various Notebooks for various languages" width="740" height="243"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s it. Now you can create notebooks and execute Ruby code right from them. And here comes the main question — what are the benefits?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The basics of Jupyter&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let’s start with some basics first before diving into the benefits that Jupyter may bring to you. Each notebook may have many cells. That can be code or markdown. You can execute the code directly in the cells and the result will be stored within the notebook file. This is super handy, as you can host this notebook on Github (it supports them) or just send it to somebody and once it’s opened even without execution one may see the previous results.&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%2Fj92yfbogn2el98uxuxev.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%2Fj92yfbogn2el98uxuxev.png" alt="Notebooks can be explored in Github with all the previously computed results" width="740" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Because Jupyter supports many kernels (Scala, PHP, Python, Ruby, JavaScript and others), it supports plugins as well – you can include Google Maps, use linters and debuggers and many more. Finally, notebooks can be exported into different formats like pdf or html or even converted into presentations and &lt;a href="https://voila.readthedocs.io/en/stable/install.html" rel="noopener noreferrer"&gt;standalone web applications&lt;/a&gt;. &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%2Fdb69i6ngutqvpzz4zkyp.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%2Fdb69i6ngutqvpzz4zkyp.png" alt="You can combine markdown cells with code blocks in Jupyter Notebooks" width="740" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use cases&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Alright – alright. We got it, and still – why do we need this? From the description, we know that Jupyter is good and mainly used in Data Science world. In other words, it is fit for retrieving, preparing and processing the data, designing and training the models. But that does not stop here. One may store documentation for a library or write a book that contains code blocks that one may execute right away by hitting ⏯ button. That are just a few examples.&lt;/p&gt;

&lt;p&gt;We all like Gists, do we? They are quite handy and allow us to store some piece of code or complete scripts and share it with our colleagues or the whole world. However, the biggest issue I usually face with Gists is that you need to do certain things before you can even execute that code. Let’s start from the output — say we found a Gist with some snippet of how to call an endpoint and process its result. What is the response of that code? Do we get Array or instance of some class or simply nil ? We will be lucky if the author added some comment describing this part, but quite often this is not the case. With Jupyter, the results of the previous execution are stored within the notebook itself, so we can immediately see what this code does and what it returns. In the end, if you still want to keep on using Gists you can convert a notebook back to a simple gist using &lt;a href="https://jupyter-contrib-nbextensions.readthedocs.io/en/latest/nbextensions/gist_it/readme.html" rel="noopener noreferrer"&gt;the plugin&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Some of the other handy examples can be found &lt;a href="https://nbviewer.jupyter.org/github/SciRuby/sciruby-notebooks/tree/master/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;My main use case of using Jupyter is playing around with the code or making some documentation. Quite often I have to try out different external endpoints. However, this is not just about doing a REST call, but also playing around with the response. So traditionally without Jupyter, I would use curl, or (most likely) IRB. The problems that happen regularly are that once you are done with the investigation, you have a history full of different steps you did – some of them were successful, some of them consist of misprints and now you have to recall the right order so that you can copy the final script to someplace in order to find it later like gist, for example. With Jupyter you do this automatically. Each time you execute code you already make it clean, you make it work. The only thing you will have to do in the end is to clean up some no used cells and maybe add comments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Things may not be that good&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Jupyter was built with Data Science and Machine Learning in mind. Even though it was extended and adopted by other languages, it is still a web app written in Python with Data Science in mind. Jupyter works fine with Ruby unless you need to do something especially complicated, something that is not usual, something that does not fit the basic usage like including a gem and executing some code. For example – including the whole Rails project so that you can do some ActiveRecord queries or accessing some other classes can be a tricky task. Or what about executing the whole RSpec test suite from inside Jupyter e.g. programmatically? The problem is not that this is not possible, but because nobody did that before, you will not be even able to google for it. In this case, you will have to dig into the source code or documentation to find a solution. To say frankly, this is not that bad in the end, but this will take quite some time to get used to.&lt;/p&gt;

&lt;p&gt;On top of that, think about how many Rubyists have Python installed along with their favourite language. That does not really matter unless they decide to modify and run the code from your notebook for which they will have to install Jupyter, Python and all the dependencies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not give up&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Jupyter is a great tool that works with many programming languages. It fits many needs and use cases and works pretty nicely with Ruby and may enhance your daily working life. However, that does not come for free. Ruby usage with Jupyter is not a popular thing, so in some cases, especially when you try to do something completely special you will be on your own with that. Even a search engine will not help you as there will be simply nobody out there in the world who did that before you.&lt;/p&gt;

&lt;p&gt;On the other hand, if you find your own path and adopt the Jupyter for your needs it will definitely simplify a bit of your daily programming life just like it happened with me.&lt;/p&gt;

</description>
      <category>jupyter</category>
      <category>ruby</category>
      <category>rails</category>
      <category>notebook</category>
    </item>
  </channel>
</rss>
