ШПАРГАЛКА

Ruby

Шпаргалка по Ruby: переменные, строки и интерполяция, символы, массивы, хеши, диапазоны, условия, итераторы, методы, классы, модули, исключения и идиомы.

Ruby — выразительный язык с динамической типизацией, где «всё является объектом». Эта шпаргалка собирает базовый синтаксис, типы, коллекции, классы и идиомы Ruby с рабочими примерами и результатами в комментариях # =>.

Переменные и типы

Переменные в Ruby не объявляются заранее и не имеют фиксированного типа. Регистр первой буквы определяет область видимости.

name = "Аня"      # локальная переменная
age = 25          # Integer
pi = 3.14         # Float
active = true     # TrueClass

p name.class      # => String
p age.class       # => Integer
p pi.class        # => Float
p nil.class       # => NilClass

Префиксы переменных: @var — переменная экземпляра, @@var — переменная класса, $var — глобальная, CONST — константа (с заглавной буквы).

PI = 3.14159      # константа
$global = "всем"  # глобальная переменная
x = 5
y = x             # копия значения
puts x + y        # => 10

Строки

Строки в двойных кавычках поддерживают интерполяцию #{} и escape-последовательности. В одинарных кавычках интерполяции нет.

name = "Мир"
puts "Привет, #{name}!"   # => Привет, Мир!
puts 'Привет, #{name}!'   # => Привет, #{name}!

# Интерполяция любого выражения
puts "2 + 2 = #{2 + 2}"   # => 2 + 2 = 4

Полезные методы строк

s = "Ruby"
p s.length        # => 4
p s.upcase        # => "RUBY"
p s.downcase      # => "ruby"
p s.reverse       # => "ybuR"
p s.include?("ub")# => true
p s * 3           # => "RubyRubyRuby"
p "  тест  ".strip# => "тест"
p "a,b,c".split(",")   # => ["a", "b", "c"]
p ["a","b"].join("-")  # => "a-b"
p "hello".chars   # => ["h", "e", "l", "l", "o"]
p "Ruby"[0]       # => "R"
p "Ruby"[0..1]    # => "Ru"

Символы (:sym)

Символ — это неизменяемый именованный идентификатор. Одинаковые символы — это один и тот же объект в памяти, поэтому они эффективны как ключи хешей и имена.

status = :active
p status          # => :active
p status.class    # => Symbol

# Один и тот же объект
p :foo.object_id == :foo.object_id   # => true
# А строки — разные объекты
p "foo".object_id == "foo".object_id # => false

p :hello.to_s     # => "hello"
p "world".to_sym  # => :world

Числа

Ruby различает Integer и Float. Целочисленное деление возвращает целое; чтобы получить дробь — используйте число с точкой.

p 7 / 2           # => 3   (целочисленное деление)
p 7.0 / 2         # => 3.5
p 7 % 2           # => 1   (остаток)
p 2 ** 10         # => 1024 (степень)

p 10.even?        # => true
p 7.odd?          # => true
p (-5).abs        # => 5
p 3.14.round      # => 3
p 3.14.round(1)   # => 3.1
p 7.to_f          # => 7.0
p "42".to_i       # => 42
p 255.to_s(16)    # => "ff" (в шестнадцатеричной)

Массивы и их методы

Массив — упорядоченная коллекция произвольных объектов, индексация с нуля. Отрицательные индексы отсчитываются с конца.

arr = [1, 2, 3, 4, 5]
p arr[0]          # => 1
p arr[-1]         # => 5
p arr[1..3]       # => [2, 3, 4]
p arr.first       # => 1
p arr.last        # => 5
p arr.length      # => 5

Изменение и обход

a = [1, 2, 3]
a.push(4)         # a => [1, 2, 3, 4]
a << 5            # a => [1, 2, 3, 4, 5]
p a.pop           # => 5,  a => [1, 2, 3, 4]
p a.shift         # => 1,  a => [2, 3, 4]
a.unshift(0)      # a => [0, 2, 3, 4]

p [3, 1, 2].sort         # => [1, 2, 3]
p [1, 2, 2, 3].uniq      # => [1, 2, 3]
p [1, 2, 3].reverse      # => [3, 2, 1]
p [1, 2, 3].include?(2)  # => true
p [1, 2, 3].sum          # => 6
p [1, 2, 3, 4].min       # => 1
p [1, 2, 3, 4].max       # => 4

Хеши

Хеш — это набор пар «ключ — значение». Ключами часто служат символы. Есть два синтаксиса записи.

# Современный синтаксис с символами-ключами
user = { name: "Аня", age: 25 }
p user[:name]     # => "Аня"
p user[:age]      # => 25

# Классический синтаксис (=>) для любых ключей
prices = { "хлеб" => 50, "молоко" => 80 }
p prices["хлеб"]  # => 50

user[:city] = "Москва"   # добавление ключа
p user.keys       # => [:name, :age, :city]
p user.values     # => ["Аня", 25, "Москва"]
p user.key?(:age) # => true
p user.fetch(:name)      # => "Аня"
p user[:unknown]  # => nil

Обход хеша

scores = { math: 5, history: 4 }
scores.each do |subject, mark|
  puts "#{subject}: #{mark}"
end
# => math: 5
# => history: 4

Диапазоны (Range)

Диапазон описывает интервал значений. Две точки .. включают конец, три точки ... исключают его.

p (1..5).to_a     # => [1, 2, 3, 4, 5]
p (1...5).to_a    # => [1, 2, 3, 4]
p ('a'..'e').to_a # => ["a", "b", "c", "d", "e"]

p (1..10).include?(7)  # => true
p (1..100).sum         # => 5050

# Диапазоны удобны в case и циклах
(1..3).each { |i| print i }   # => 123

Условия (if / unless / case)

В Ruby «ложными» считаются только false и nil; всё остальное (включая 0 и "") — истина.

age = 20

if age >= 18
  puts "взрослый"
elsif age >= 14
  puts "подросток"
else
  puts "ребёнок"
end
# => взрослый

# Постфиксная форма
puts "ok" if age > 0          # => ok
puts "мало" unless age > 100  # => мало

case / when

grade = 4
result = case grade
         when 5 then "отлично"
         when 4 then "хорошо"
         when 3 then "удовлетворительно"
         else "неуд"
         end
p result          # => "хорошо"

# case с диапазонами
score = 87
case score
when 90..100 then puts "A"
when 80..89  then puts "B"
else puts "C"
end               # => B

Циклы и итераторы

В Ruby редко пишут классические циклы — чаще используют итераторы с блоками: each, map, select, times, upto.

# each — обход без возврата нового массива
[1, 2, 3].each { |x| print x }   # => 123

# map — преобразование в новый массив
p [1, 2, 3].map { |x| x * 2 }    # => [2, 4, 6]

# select — фильтрация
p [1, 2, 3, 4].select { |x| x.even? }  # => [2, 4]
# reject — обратная фильтрация
p [1, 2, 3, 4].reject { |x| x.even? }  # => [1, 3]

# reduce — свёртка
p [1, 2, 3, 4].reduce(0) { |s, x| s + x }  # => 10

times, upto, классические циклы

3.times { |i| print i }     # => 012
1.upto(3) { |i| print i }   # => 123
3.downto(1) { |i| print i } # => 321

i = 0
while i < 3
  print i
  i += 1
end                         # => 012

# each_with_index
%w[a b c].each_with_index do |val, idx|
  puts "#{idx}: #{val}"
end
# => 0: a
# => 1: b
# => 2: c

Методы

Методы определяются через def. Возвращается значение последнего выражения — return необязателен.

def greet(name)
  "Привет, #{name}!"
end
p greet("Аня")    # => "Привет, Аня!"

Аргументы по умолчанию и именованные

def power(base, exp = 2)
  base ** exp
end
p power(3)        # => 9
p power(2, 3)     # => 8

# Именованные аргументы
def connect(host:, port: 80)
  "#{host}:#{port}"
end
p connect(host: "site.ru")             # => "site.ru:80"
p connect(host: "site.ru", port: 443)  # => "site.ru:443"

# Произвольное число аргументов
def total(*nums)
  nums.sum
end
p total(1, 2, 3, 4)   # => 10

Блоки, yield и &block

Блок — это кусок кода, передаваемый методу. Внутри метода его вызывают через yield или принимают явно как &block.

def twice
  yield
  yield
end
twice { puts "хей" }
# => хей
# => хей

# yield с аргументом
def with_value
  yield 42
end
with_value { |v| puts "значение #{v}" }  # => значение 42

# block_given? — проверка наличия блока
def maybe
  return "нет блока" unless block_given?
  yield
end
p maybe                # => "нет блока"

# Явный параметр-блок &block
def run(&block)
  block.call(10)
end
p run { |x| x + 5 }    # => 15

Классы и объекты

Класс описывает шаблон объекта. Метод initialize — это конструктор, вызываемый при .new.

class Animal
  attr_accessor :name   # геттер + сеттер
  attr_reader :legs     # только геттер

  def initialize(name, legs = 4)
    @name = name
    @legs = legs
  end

  def describe
    "#{@name} ходит на #{@legs} лапах"
  end
end

cat = Animal.new("Кот")
p cat.name              # => "Кот"
cat.name = "Барсик"     # сеттер от attr_accessor
p cat.describe          # => "Барсик ходит на 4 лапах"
p cat.legs              # => 4

Наследование

class Dog < Animal
  def initialize(name)
    super(name, 4)      # вызов конструктора предка
  end

  def speak
    "#{@name}: Гав!"
  end
end

d = Dog.new("Рекс")
p d.speak               # => "Рекс: Гав!"
p d.describe            # => "Рекс ходит на 4 лапах" (унаследован)
p d.is_a?(Animal)       # => true

Модули и mixin

Модуль — набор методов для подмешивания в классы через include. Так Ruby реализует множественное «наследование» поведения.

module Greetable
  def hello
    "Привет, я #{name}"
  end
end

class Person
  include Greetable
  attr_reader :name
  def initialize(name)
    @name = name
  end
end

p Person.new("Лена").hello   # => "Привет, я Лена"

Символы vs строки

Главное отличие: строки изменяемы и каждая — новый объект; символы неизменяемы и существуют в единственном экземпляре. Символы используют для ключей хешей, имён методов и состояний.

СвойствоСтрока "foo"Символ :foo
Изменяемостьизменяеманеизменяем
Объект в памятиновый каждый разодин и тот же
Типичное применениетекст, ввод-выводключи, имена, флаги
# Как ключи хеша символы предпочтительнее
config = { host: "localhost", port: 3000 }
p config[:port]   # => 3000

# Преобразования между ними
p "name".to_sym   # => :name
p :name.to_s      # => "name"

Обработка исключений

Блок begin/rescue/ensure ловит ошибки. rescue обрабатывает исключение, ensure выполняется всегда, raise бросает ошибку.

begin
  result = 10 / 0
rescue ZeroDivisionError => e
  puts "Ошибка: #{e.message}"   # => Ошибка: divided by 0
ensure
  puts "Готово"                 # => Готово (выполнится в любом случае)
end

raise и несколько типов

def divide(a, b)
  raise ArgumentError, "ноль!" if b == 0
  a / b
end

begin
  divide(10, 0)
rescue ArgumentError => e
  puts e.message      # => ноль!
end

# Несколько типов ошибок
begin
  Integer("abc")
rescue TypeError, ArgumentError => e
  puts "не число"     # => не число
end

Идиомы Ruby

Короткие выразительные конструкции, которые делают код «руби-стайл».

Тернарный оператор

age = 20
status = age >= 18 ? "взрослый" : "ребёнок"
p status          # => "взрослый"

||= и &&= (присваивание по условию)

# ||= присваивает, только если переменная nil или false
x = nil
x ||= 10
p x               # => 10
x ||= 99
p x               # => 10 (уже задано — не меняется)

# &&= присваивает, только если значение «истинно»
y = 5
y &&= y * 2
p y               # => 10
z = nil
z &&= 100
p z               # => nil (осталось nil)

Безопасная навигация &.

Оператор &. вызывает метод, только если объект не nil, иначе возвращает nil вместо ошибки.

user = nil
p user&.name      # => nil (без ошибки NoMethodError)

name = "Аня"
p name&.upcase    # => "АНЯ"

# Цепочка вызовов безопасна
profile = nil
p profile&.address&.city   # => nil

Прочие удобные идиомы

# Множественное присваивание
a, b = 1, 2
a, b = b, a       # обмен значениями
p [a, b]          # => [2, 1]

# Значение по умолчанию через fetch
h = { x: 1 }
p h.fetch(:y, 0)  # => 0

# Превращение в булево через !!
p !!nil           # => false
p !!"текст"       # => true

# Однострочный цикл с диапазоном
p (1..5).map { |n| n * n }   # => [1, 4, 9, 16, 25]
Поддержать проект