Published on

What Ruby taught me about flexibility in Software Engineering

Authors
  • avatar
    Name
    Eyji Koike Cuff
    Twitter

🧠 This is part of my series inspired by Seven Languages in Seven Weeks (Tate, 2010). I’m exploring each language not to master it, but to learn how different paradigms shape how we think as software engineers. These are the takeaways I’d share with another backend dev over coffee.


🧵 Ruby in a Nutshell

Ruby is expressive, opinionated, and built for human-friendly syntax — developer speed and flexibility are at stage here. Everything’s an object. Blocks are first-class. And the standard library feels like it wants to do the thinking for you. It’s meant to create Domain Specifc Languages (DSL) and scripts.

Appreciate the following lines of code:

File.foreach(file_name).with_index do |line, idx|
    if line.include?(search_string) 
        puts "The line #{idx} includes #{search_string}"
    end
end

How cool is the fact that you can literally ask each line if it includes something?

Coming from Python with strict typing, Ruby felt like coding with silk gloves — powerful, but a bit slippery.


🧠 Takeaway #1: Duck Typing vs. Type Awareness

Ruby and Python both embrace dynamic typing, but they diverge in how much they lean on structure.

Ruby is unapologetically flexible — no type hints, no static checks, no complaints. It trusts that if you send a message to an object, it knows how to handle it — or it’ll throw a clear error when it can’t.

if it quacks like a duck, then it's a duck

Meanwhile in my Python world:
I use Pydantic for data validation and Pyright for static type checking. My models are explicit. My tooling catches mistakes early. It’s a kind of defensive confidence that scales well in real-world services and essential for growing codebases.

What hit me:

Ruby feels like a jazz musician — improvising, expressive, and fluid. My Python is more like chamber music — precise, coordinated, and rule-driven.

What I’m rethinking:
Structure adds clarity, especially in growing systems. But maybe I’ve leaned too far into it — Ruby reminded me that some trust and flexibility can make code faster to write and more fun to read. I will steal Tate's (Tate, 2010) comparison with Mary Poppins:

Mary Poppins made the household more efficient by making it fun and coaxing every last bit of passion from her charges. Ruby does the same thing and with more syntactic sugar than a spoonful.

All that syntactic sugar — the things that make code more readable — goes directly into the software engineer’s bloodstream.


🧠 Takeaway #2: Metaprogramming as a Language Feature

Ruby’s metaprogramming is not a side trick — it’s built into the DNA.

You can define methods at runtime, open up classes, and intercept missing methods with method_missing. And somehow, it still feels readable.

You can literally create a class to create methods and properties inside another class:

module ActAsCsv
    def self.included(base)
        base.extend ClassMethods
    end

    module ClassMethods
        def act_as_csv
            include InstanceMethods
        end
    end

    module InstanceMethods
        def read
            @csv_contents = []
            filename = self.class.to_s.downcase + '.txt'
            file = File.new(filename)
            @headers = file.gets.chomp.split(',')

            file.each do |row|
                values = row.chomp.split(',')
                @csv_contents << CsvRow.new(@headers, values)
            end
        end

        attr_accessor :headers, :csv_contents
        def initialize
            read
        end

        def each(&block)
            @csv_contents.each(&block)
        end
    end

    class CsvRow
        def initialize(headers, values)
        @data = Hash[headers.zip(values)]
        end

        def method_missing(name, *args)
        @data[name.to_s]
        end

        def respond_to_missing?(name, include_private = false)
        @data.key?(name.to_s) || super
        end

        def to_s
        @data.inspect
        end
    end

end
   
class RubyCsv
    include ActAsCsv
    act_as_csv
end

m = RubyCsv.new
m.each {|row| puts row.one}

Compared to Python:
Python can do metaprogramming with decorators, __getattr__, and metaclasses — but it’s not as encouraged. In Ruby, it’s almost expected in idiomatic code, especially in DSL-heavy frameworks like Rails.

What I’m rethinking:

Could some of my rigid abstractions in Python be replaced with smarter, more flexible object behavior?


🔁 From Ruby Back to Python

Reading bout Ruby challenged me to be less defensive with my design.

  • 🧼 I appreciate how Ruby handles optional structure with elegance.
  • 🔍 I’m more intentional about where strict types help — and where they overcomplicate.
  • 🧰 I’m curious to explore small, metaprogramming - inspired tools where Python makes sense.
  • ⌨️ I know Ruby and Rails can be a tool where low time-to-market is key

⚖️ Tradeoffs: Flexibility vs. Safety

Ruby’s dynamic nature makes it incredibly expressive — but it also means you won’t catch certain bugs until runtime. For quick scripts or developer-friendly DSLs, this is a win. But in large, evolving codebases, that same flexibility can lead to uncertainty and false assumptions. And let’s not forget: all that sugar adds a questionable performance penalty, plus the fact that methods can change behavior at any time.

In contrast: Python’s ecosystem has grown to embrace stronger type safety through tools like Pydantic and Pyright — and it’s changed the way I write code. I get clearer contracts, better editor support, and earlier bug detection.

But this comes with overhead:

  • You write more boilerplate
  • The feedback loop tightens, but creativity can sometimes feel boxed in
  • You optimize for maintainability, not always velocity or engineer experience

So where’s the line?

Ruby reminded me that some looseness — when used intentionally — can improve developer experience. But in production systems with long lifecycles, I still lean toward Python's newer type — heavy practices.

Both approaches have a cost. Ruby bets on developer intuition. Python (with static types) bets on structure. Knowing when to trade one for the other? That’s the real move.


💬 Final Thought

Ruby reminded me that flexibility isn't the enemy of clarity — it can enhance it, when used with discipline.

Next up: Io, the tiny prototype-based language that made my brain hurt — in a good way.


Want more like this? Subscribe here to get new posts straight to your inbox.

References

Tate, B. A. (2010). Seven Languages in Seven Weeks: A Pragmatic Guide to Learning Programming Languages. The Pragmatic Bookshelf. https://pragprog.com/titles/btlang/seven-languages-in-seven-weeks/