# Duck Typing, Open Classes and Refinements — Ruby

Source: https://www.geekswithgeeks.com/en/ruby/x-dynamic

> Use duck typing and understand monkey patching and refinements.

## Behaviour over types

Ruby relies on **duck typing**: what matters is whether an object **responds to** the methods you call, not its class. A method that calls `io.write(text)` works with a file, a `StringIO` or a network socket. Check capabilities with `respond_to?(:write)` when needed, rather than `is_a?`. Ruby classes are **open**: you can reopen any class, even core ones like `String` or `Integer`, and add or change methods. This **monkey patching** is powerful, and Rails' ActiveSupport adds conveniences such as `3.days.ago` this way, but global patches to core classes can conflict between libraries and surprise other developers. **Refinements** (`refine String do ... end` inside a module, activated with `using`) scope such changes to a file or module, making them much safer. Prefer adding methods to your own classes, wrapping objects in your own types, or helper modules; reach for refinements only when extending a core or third-party class really makes the calling code clearer.

## Duck typing

Any object that responds to the needed method works, whatever its class.

![Three different shaped objects each with the same small speaker icon, all feeding into one receiving box.](assets/figures/ruby/section-5-map.svg) — Figure 5.1 — Different classes satisfying the same interface by behaviour.

## Duck typing and a scoped refinement

Any writable object works; the String extension is visible only where refined.

```ruby
require "stringio"

def export_csv(rows, io)
  rows.each { |r| io.write(r.join(",") + "\n") }   # anything with #write works
  io
end

buffer = export_csv([["o1", 1200], ["o2", 300]], StringIO.new)
puts buffer.string
File.open("orders.csv", "w") { |f| export_csv([["o1", 1200]], f) }

module RupeeFormatting
  refine Integer do
    def to_rupees = format("Rs %.2f", self / 100.0)
  end
end

module Receipts
  using RupeeFormatting                     # refinement active only inside this module

  def self.line(item, paise) = "#{item}: #{paise.to_rupees}"
end

puts Receipts.line("Notebook", 12_000)    # Notebook: Rs 120.00
# 12_000.to_rupees                         # NoMethodError here: refinement not active
```

## Patch core classes only with care

A global patch to `String` or `Hash` affects every gem in the process. If two libraries patch the same method differently, the result depends on load order. Refinements or your own wrapper classes avoid this.

**Quiz:** What does duck typing mean in Ruby?

- [x] Code depends on what methods an object responds to, not on its class
- [ ] Objects must inherit from Duck
- [ ] All variables must declare types
- [ ] Only classes can be passed to methods

*Answer:* Code depends on what methods an object responds to, not on its class. If it responds to the right methods, it can be used.
