LEARN X · ЗА 15 МИН

Ruby

Весь Ruby на одной странице: вывод, числа, строки, массивы, хеши, методы, блоки, классы, модули и идиомы — в плотно закомментированном коде.

Ruby — динамический, полностью объектно-ориентированный язык с лаконичным синтаксисом, где почти всё является выражением и возвращает значение. Ниже — весь язык в одном плотно закомментированном файле: читай комментарии # и результаты # => ....

Вывод и комментарии

# Это однострочный комментарий

=begin
А это многострочный
комментарий между =begin и =end
=end

puts "Привет"   # puts печатает строку и добавляет перевод строки
print "без "    # print печатает БЕЗ перевода строки
print "переноса\n"
p "строка"        # p выводит "отладочно" (с кавычками) и возвращает объект
# p "строка"     # => "строка"   (видно кавычки)
puts 42          # числа печатаются как есть  # => 42

Переменные и числа

Типизация динамическая: тип определяется значением, объявлять не нужно.

x = 10           # целое (Integer)
y = 3.14         # дробное (Float)
name = "Ruby"    # строка (String)
flag = true      # логическое (true / false)
nothing = nil    # "ничего" (аналог null)

puts 7 + 2       # => 9
puts 7 - 2       # => 5
puts 7 * 2       # => 14
puts 7 / 2       # => 3   (целочисленное деление)
puts 7 % 2       # => 1   (остаток)
puts 7.0 / 2     # => 3.5 (хотя бы один Float — дробный результат)
puts 2 ** 10     # => 1024 (возведение в степень)

puts 10.class    # => Integer (у всего есть класс)
puts "5".to_i    # => 5   (строка -> число)
puts 5.to_s      # => "5" (число -> строка)

Строки

a = "мир"
puts "Привет, #{a}!"        # интерполяция #{} => Привет, мир!
puts 'без #{a}'             # одинарные кавычки НЕ интерполируют => без #{a}

puts "abc".upcase          # => ABC
puts "ABC".downcase        # => abc
puts "  trim  ".strip      # => "trim" (убрать пробелы по краям)
puts "ruby".length         # => 4
puts "ruby".reverse        # => ybur
puts "ruby".include?("ub") # => true
puts "a,b,c".split(",").inspect  # => ["a", "b", "c"]
puts "ха" * 3              # => хахаха (повтор строки)
puts "раз" + " " + "два"   # => раз два (конкатенация)

# Символы — неизменяемые "имена", легче строк, удобны как ключи
status = :active           # это Symbol, а не String
puts status                # => active
puts status.class          # => Symbol

Условия

Почти всё — выражение и возвращает значение. Ложны только false и nil; 0 и пустая строка — истинны.

age = 18

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

# unless — это "если НЕ"
puts "гость" unless age >= 18   # выполнится, только если условие ложно

# постфиксная форма — условие в конце строки
puts "взрослый" if age >= 18

# case / when — выбор по значению
grade = 4
result = case grade
         when 5 then "отлично"
         when 4 then "хорошо"
         when 3 then "удовлетворительно"
         else "неуд"
         end
puts result                       # => хорошо

# тернарный оператор
puts age >= 18 ? "можно" : "нельзя"  # => можно

Массивы

arr = [1, 2, 3, 4, 5]
puts arr[0]                # => 1   (первый элемент)
puts arr[-1]               # => 5   (последний элемент)
puts arr[1..3].inspect     # => [2, 3, 4] (срез по диапазону)

arr.push(6)                # добавить в конец
arr << 7                    # тот же push, оператор-лопата
puts arr.inspect           # => [1, 2, 3, 4, 5, 6, 7]
puts arr.first             # => 1
puts arr.last              # => 7
puts arr.length            # => 7
puts arr.sum               # => 28
puts [3, 1, 2].sort.inspect # => [1, 2, 3]

# Итерация и преобразования
[1, 2, 3].each { |n| print n }        # => 123 (просто перебор)
puts
puts [1, 2, 3].map { |n| n * 10 }.inspect    # => [10, 20, 30] (преобразовать каждый)
puts [1, 2, 3, 4].select { |n| n.even? }.inspect  # => [2, 4] (отфильтровать)
puts [1, 2, 3, 4].reject { |n| n.even? }.inspect  # => [1, 3] (выбросить подходящие)
puts [1, 2, 3, 4].reduce(0) { |sum, n| sum + n }  # => 10 (свернуть в одно)

Хеши

# Хеш — набор пар ключ => значение
person = { "name" => "Аня", "age" => 25 }
puts person["name"]        # => Аня

# Чаще ключами делают символы — короткий синтаксис key:
user = { name: "Боб", role: :admin, age: 30 }
puts user[:name]           # => Боб
puts user[:role]           # => admin

user[:email] = "[email protected]"  # добавить пару
puts user.keys.inspect     # => [:name, :role, :age, :email]
puts user.values.inspect   # => ["Боб", :admin, 30, "[email protected]"]
puts user.key?(:name)      # => true
puts user.fetch(:age)      # => 30 (как [], но кидает ошибку, если ключа нет)

# Перебор хеша
user.each { |key, value| puts "#{key} = #{value}" }
# => name = Боб
# => role = admin  ...

Диапазоны и циклы

(1..5).each { |i| print i }    # => 12345  (.. включает конец)
puts
(1...5).each { |i| print i }   # => 1234   (... НЕ включает конец)
puts
puts (1..5).to_a.inspect       # => [1, 2, 3, 4, 5]

3.times { |i| print i }        # => 012  (повторить N раз)
puts
1.upto(3) { |i| print i }      # => 123  (от и до включительно)
puts
3.downto(1) { |i| print i }    # => 321  (в обратную сторону)
puts

# while — цикл с условием
i = 0
while i < 3
  print i
  i += 1
end                            # => 012
puts

# for тоже есть, но в Ruby редок — обычно используют each/times
for n in [10, 20] do print n end  # => 1020
puts

Методы

# Метод объявляется через def ... end
def greet(name)
  "Привет, #{name}!"   # последнее выражение — это и есть результат (return необязателен)
end
puts greet("Ruby")     # => Привет, Ruby!

# Аргумент по умолчанию
def power(base, exp = 2)
  base ** exp
end
puts power(5)          # => 25  (exp по умолчанию = 2)
puts power(5, 3)       # => 125

# Явный return (например, для раннего выхода)
def sign(n)
  return "ноль" if n.zero?
  n > 0 ? "плюс" : "минус"
end
puts sign(-7)          # => минус

# splat * — произвольное число аргументов собирается в массив
def total(*numbers)
  numbers.sum
end
puts total(1, 2, 3, 4) # => 10

# Метод с ? обычно возвращает true/false, с ! — меняет объект на месте
puts 4.even?           # => true

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

Блок — кусок кода, передаваемый методу. Это сердце Ruby.

# Две формы блоков: {} для коротких и do..end для многострочных
[1, 2, 3].each { |n| puts n }          # фигурные скобки
[1, 2, 3].each do |n|                   # do..end — то же самое
  puts n * 2
end

# yield вызывает переданный блок изнутри метода
def twice
  yield        # выполнить блок
  yield        # ещё раз
end
twice { puts "ку" }   # => ку (дважды)

# Можно передавать значение в блок
def with_tax(price)
  yield(price * 1.2)
end
with_tax(100) { |total| puts "Итого: #{total}" }  # => Итого: 120.0

# block_given? — проверить, передан ли блок
def maybe
  return "нет блока" unless block_given?
  yield
end
puts maybe                       # => нет блока

# &block — захватить блок в именованный параметр (объект Proc)
def run(&block)
  block.call
end
run { puts "запущено" }          # => запущено

Классы и ООП

class Dog
  # attr_accessor создаёт геттер И сеттер для @name
  attr_accessor :name
  attr_reader :breed              # только чтение

  # initialize — конструктор, вызывается при Dog.new
  def initialize(name, breed)
    @name = name                  # @ — переменная экземпляра (живёт в объекте)
    @breed = breed
  end

  # обычный метод экземпляра
  def bark
    "#{@name} говорит: Гав!"
  end
end

rex = Dog.new("Рекс", "овчарка")  # создать объект
puts rex.bark                     # => Рекс говорит: Гав!
puts rex.name                     # => Рекс  (геттер)
rex.name = "Шарик"                # сеттер (благодаря attr_accessor)
puts rex.name                     # => Шарик
puts rex.breed                    # => овчарка (только чтение)
puts rex.is_a?(Dog)               # => true

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

class Animal
  def initialize(name)
    @name = name
  end

  def speak
    "#{@name} издаёт звук"
  end
end

# Cat наследует от Animal через <
class Cat < Animal
  def speak
    # super вызывает одноимённый метод родителя
    super + ", а именно: Мяу"
  end
end

puts Cat.new("Барсик").speak      # => Барсик издаёт звук, а именно: Мяу

# Модуль — набор методов для подмешивания (mixin), не создаёт объектов
module Greetable
  def hello
    "Привет, я #{@name}"
  end
end

class Person
  include Greetable               # подмешать методы модуля в класс
  def initialize(name)
    @name = name
  end
end

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

Исключения

begin
  result = 10 / 0               # вызовет ZeroDivisionError
rescue ZeroDivisionError => e   # перехватить конкретную ошибку
  puts "Ошибка: #{e.message}"   # => Ошибка: divided by 0
rescue => e                     # перехватить любую прочую ошибку
  puts "Что-то пошло не так"
ensure
  puts "ensure выполнится в любом случае"  # всегда, как finally
end

# raise — выбросить своё исключение
def withdraw(amount)
  raise ArgumentError, "сумма должна быть > 0" if amount <= 0
  amount
end

begin
  withdraw(-5)
rescue ArgumentError => e
  puts e.message                # => сумма должна быть > 0
end

Идиомы Ruby

# ||= присвоить, только если переменная nil или false ("мемоизация")
@cache ||= []
name = nil
name ||= "по умолчанию"
puts name                       # => по умолчанию

# &. — безопасный вызов: если слева nil, вернёт nil, а не упадёт с ошибкой
user = nil
puts user&.upcase.inspect       # => nil   (без &. была бы ошибка)
puts "Боб"&.upcase              # => БОБ

# &:method — короткая запись блока { |x| x.method }
puts ["a", "bb", "c"].map(&:upcase).inspect   # => ["A", "BB", "C"]
puts [1, 2, 3].map(&:to_s).inspect            # => ["1", "2", "3"]

# В Ruby ВСЁ — объект, даже числа и nil, у всего есть методы
puts 5.times.to_a.inspect       # => [0, 1, 2, 3, 4]
puts nil.to_s.inspect           # => ""   (даже у nil есть методы)
puts (-7).abs                   # => 7
puts 1.is_a?(Object)            # => true (число — тоже объект)

# Множественное присваивание
a, b = 1, 2
a, b = b, a                     # обмен значениями без временной переменной
puts "#{a} #{b}"                # => 2 1
Поддержать проект