Ruby is object-oriented to the core, and every gem and Rails app is built from classes.
Define a class with class Name ... end and create instances with Name.new(args), which calls initialize. Instance variables (@name) hold an object's state; they are always private to the object and spring into existence (as nil) on first use.
Expose state with accessor macros, which generate methods for you:
attr_reader :namedefinesname.attr_writer :namedefinesname=.attr_accessor :namedefines both.
Inside methods, call setters with self.name = x. Without self., Ruby creates a local variable instead, a classic bug.
Visibility: methods are public by default; everything after private is callable only without an explicit receiver (from inside the object). protected allows calls from other instances of the same class.
Class methods are defined with def self.create, and class-level constants with NAME = .... Inheritance uses <: class Admin < User, and super calls the parent's version of the current method (bare super forwards all arguments).
Useful hooks: define to_s for puts and string interpolation, inspect for p, and == for value equality. Include Comparable and define <=> to get <, >, between? and clamp for free.
For simple data holders, Data.define(:x, :y) (Ruby 3.2) creates an immutable value class with keyword or positional construction and value equality.
Example
class Account
attr_reader :owner, :balance
def initialize(owner, balance = 0)
@owner = owner
@balance = balance
end
def deposit(amount)
raise ArgumentError, "amount must be positive" unless amount.positive?
@balance += amount
self
end
def to_s = "#{owner}: #{balance}"
end
acct = Account.new("Ada").deposit(50).deposit(25)
puts acct # Ada: 75
p acct.respond_to?(:balance=) # false: read-only
Point = Data.define(:x, :y)
a = Point.new(x: 1, y: 2)
p a # #<data Point x=1, y=2>
p a == Point.new(1, 2) # true: value equality
p a.with(y: 5) # #<data Point x=1, y=5>Remember
- initialize sets up @instance_variables; Name.new calls it
- attr_reader, attr_writer and attr_accessor generate accessors
- Use self.attr = inside methods to call a setter
Watch out for
- Writing balance = 10 inside a method meaning the setter; it creates a local variable. Use self.balance = 10.