# Classes, Attributes and Initialisation — Ruby

Source: https://www.skillbyai.com/en/ruby/o-classes

> Define classes with initialize, attribute accessors and useful conversion methods.

## Defining your own objects

Define a class with `class Invoice ... end`. **`initialize`** runs when you call `Invoice.new(...)` and usually sets **instance variables** (`@number`), which are private to the object. Expose them with **`attr_reader`** (getter), **`attr_writer`** (setter) or **`attr_accessor`** (both); prefer readers and explicit methods over writers, so objects stay valid. Methods return their **last expression**. Define **`to_s`** for a human-readable string and rely on `inspect` for debugging output. **Class methods** are defined with `def self.from_csv(row)`, useful for alternative constructors. Inside a method, `self` is the current object. Methods ending in **`?`** return booleans by convention (`paid?`), and methods ending in **`!`** are "dangerous" variants (mutating or raising). Equality is customisable by defining `==`, `eql?` and `hash`, and **`<=>`** plus `include Comparable` gives you `<`, `>`, `between?` and `clamp`. Since Ruby 3.0, short methods can use **endless method** syntax: `def total = items.sum(&:price)`.

## An object's state and behaviour

Instance variables hold private state; public methods expose behaviour and selected data.

![A rounded object box with small private cells inside and a few labelled handles on its border.](assets/figures/ruby/section-3-map.svg) — Figure 3.1 — Instance variables behind public methods.

## An invoice class

Readers, predicates, a class-level constructor and an endless method.

```ruby
class Invoice
  attr_reader :number, :items

  def initialize(number, items = [])
    @number = number
    @items = items
    @paid_at = nil
  end

  def self.from_hash(data)                     # alternative constructor
    new(data.fetch(:number), data.fetch(:items, []))
  end

  def total = items.sum { |i| i[:price] * i[:qty] }   # endless method (Ruby 3.0+)

  def paid? = !@paid_at.nil?

  def mark_paid!(at = Time.now)
    raise "Invoice #{number} already paid" if paid?
    @paid_at = at
    self
  end

  def to_s = "Invoice #{number}: Rs #{format('%.2f', total)}"
end

inv = Invoice.from_hash(number: "INV-7", items: [{ price: 49.5, qty: 2 }, { price: 1200, qty: 1 }])
puts inv            # Invoice INV-7: Rs 1299.00
inv.mark_paid!
puts inv.paid?      # true
```

## Avoid attr_accessor by default

`attr_accessor` lets any code set any value, bypassing your rules. Start with `attr_reader`, and add methods such as `mark_paid!` that change state while checking invariants.

**Quiz:** What does attr_reader :number create?

- [x] A getter method number that returns @number
- [ ] A setter only
- [ ] A class variable
- [ ] A constant

*Answer:* A getter method number that returns @number. attr_reader defines a reader method for the instance variable.
