Pull to refresh

Ruby: cheatsheet для изучения

Reading time 14 min
Views 59K
Это — статья-roadmap-cheatsheet для изучающих Ruby. Вместо того, чтобы рассказывать очередной how-to я постараюсь расписать все те вещи, которые, на мой взгляд, можно изложить кратко и емко, с упором на то, что программисту пришедшему с других языков и платформ может показаться не очевидным, антипоисковым и просто затратным по времени на изучение без подсказок — на остальное просто дам ссылки. Не смотря на то, что в природе существует множество туториалов/кастов/книг/чего угодно по чему угодно — именно такого формата мне самому всегда не хватает. И по сути, это те вещи, которые чаще всего рассказываю при вопросах «а как оно вообще?», «с чего начать?», «а как делается такая вот штука?», «а какой gem лучше?». Кстати, пока работал над этой статьей на Хабре появилась похожая про Python — видимо, идея витает в воздухе.

Около Ruby - слухи, интриги, расследования
Сразу оговорюсь, что текст под этим спойлером отличается от основной «объективной» части статьи, т.к. здесь изложено мое личное мнение и впечатление.

Язык программирования — это не только синтаксис, сборщик мусора, не только парадигма языка, и даже не столько его философия, в первую очередь — это коммьюнити и та кодовая база, которую это коммьюнити создало. Особенно сейчас, в эпоху OpenSource. И тут у Ruby первый жирный плюс в карму. Одна из особенностей — во всем читается прагматичная лень, начиная c опциональности скобок при вызове методов и точек с запятой в конце строки, продолжая емким и выразительным синтаксисом, и заканчивая общим ощущением от проектов — многие из них сделаны работающими из коробки, и требующих минимальных усилий, чтобы сконфигурировать их.

Многие выбирают Ruby, потому что это комфортно и приятно. Удовольствие и радость от программирования можно получить на разных языках — и в Ruby оно похоже на езду на хорошем авто, сидя за рулем которого будешь думать не столько о дороге, сколько о более важных вещах — хотя бы о маршруте и конечной цели поездки.
Также можно сравнить с игрой в Lego (и это во многом благодаря Gems). Хотя кто-то любит сварку арматуры, а кому-то достаточно картона и клея.

Есть несколько утверждений, которые иногда можно встретить о Ruby и Rails — попробую внести в них ясность.

Известно утверждение о том, что Ruby медленный. И поспорить сложно, ведь Ruby интерпретируемый язык. И что характерно, чаще всего я это слышу от тех кто пишет (исключительно) на PHP, который тоже интерпретируем, и по скорости в синтетических тестах находится примерно на том же уровне. Скорее всего, это отголоски дурной славы старых версий. По факту, мне приходилось работать и с Rails и с Symfony — на реальных приложениях Rails быстрее при одинаковом уровне оптимизации (и в обоих правильное кэширование — залог успеха). Если нужна скорость и компактность в памяти — пишите на ассемблере используйте Node.js. Но парадокс в том, что на нем как раз часто пишут рубисты — когда это действительно оправданно. И дело тут вот в чем: важна не только скорость работы, но и скорость написания приложения. И Ruby, и все то, что можно найти на github всячески помогает достичь действительно высокой производительности программиста — в том числе и в возможностях оптимизации приложения. И не нужно забывать, что и главная нагрузка, и бутылочные горлышки, зачастую — это базы данных, а веб-приложения это всего-лишь прослойка бизнес-логики. А такая прослойка неплохо масштабируется.

Есть еще своеобразный миф про высокие зарплаты Ruby-разработчиков с одной стороны, с другой — что работы на Ruby мало. Обычно сравнивают с зарплатами со средней по рынку на PHP, и количеством работы на нем же. Средний уровень зарплат на PHP — это классическая «средняя зарп температура по больнице». Если же сравнить специалистов по Yii/Symfony/Zend и Rails (можно еще добавить Django на Python) — картина выйдет совсем другая, а именно: и зарплаты и объем рынка примерно одинаковы. Действительно, хорошим программистам неплохо платят. И более того, когда берешься за проект с нуля — зачастую выбор платформы за тобой и твоей командой, а не заказчиком/начальством.

Так что, есть множество прекрасных языков и фреймворков для написания веб-приложений, и Ruby с Rails не являются серебряной пулей, которая убьет всех зайцев кроликов-оборотней сразу. Просто, на мой взгляд, если брать совокупность важных критериев, а не пытаться выбрать по одному-двум из них, RoR набирает действительно много очков. В том числе и по зрелости — это уже давно не хипстерская платформа, в которой есть только потенциал, но еще и не старичок (а примеров и тех и других уйма). И старичком, я думаю, еще долго не станет, т.к. потенциал для роста и развития еще есть и у Ruby и у Rails — но это тема для отдельной статьи.

И конечно, несмотря на направленность статьи на Rails, и популярность именно этой платформы — Ruby это далеко не только Rails.

В любом случае, я убежден, что программист не должен сидеть в своем маленьком мирке того, за что ему сегодня платят зарплату. Ruby дает в этом смысле не только +1 язык в резюме, но и, за счет своей ооп-направленности и широким возможностям метапрограммирования, опыт, который пригодится на разных языках и платформах.

Язык Ruby: история становления и перспективы развития


Ruby



Начало
Как установить Ruby на #{ os_name }?

Сами ссылки:
  • Win. По той же ссылке можно найти DevKit, которой пригодится для работы с БД и установки Native Extensions
  • Rails Installer установит сразу Ruby + DevKit, Git, Rails, Bundler и SQLite, на Windows или MacOS. Ruby, правда, 1.9.3, а установщик с 2.0 еще в alpha. (по совету Jabher)
  • *nix — ищите в своих репозиториях, или legacy

Кроме того, есть такая штука как RVM. Она позволяет установить несколько версий Ruby на одной ОС и переключаться между ними. На старте в ней потребности нет, но бывает полезна, если уже есть несколько проектов на локальной машине или на сервере — обновляться на всех разом до новой версии сразу не особо здорово. Пока — просто имейте ввиду, что она есть.
Подробно про RVM на хабре.

Тут же стоит упомянуть про среду разработки. Я приверженец продуктов JetBrains, поэтому рекомендую RubyMine, но ввиду того, что продукт коммерческий кому-то может прийтись по вкусу свободная Aptana Studio. Если же приятнее пользоваться легкими текстовыми редакторами — синтаксис Ruby поддерживается многими.

Прямо в браузере в интерактивном режиме Ruby можно попробовать на tryruby.org

Все - объект
Включая числа, строки и даже nil — они все наследуются от класса Object. Можно у чего угодно вызывать методы вроде nil?, class, methods и respond_to?:
'Hello world'.class # String
nil.class # NilClass
String.class # Class
String.anc­estors # [String, Comparable, Object, Kernel, BasicObject]; массив предков класса
nil.nil? # true
Object.new.methods # вернет методы объекта класса Object; тут же видно как создается новый объект - тоже вызовам метода
nil.respond_to?('nil?') # true
По поводу последнего: это важно для утиной типизации.

Синтаксис и пудра
Ruby сильно приправлен «синтаксическим сахаром», благодаря которому зачастую не нужно городить кашу из различных скобок, точек с запятыми и прочего. А под ним — прост и логичен, и сохраняет парадигму «все — объект».
a == b # то же, что и
a.==(b) # то есть .==() - это метод

В вызовах методов, в if можно не ставить скобки, если нет неоднозначности
nil.respond_to?('nil?') # true
nil.respond_to? 'nil?' # true
# все однозначно:
if nil.respond_to? 'nil?'
  puts 'ok'
end
# тоже
if 10.between? 1, 5
  puts 'ok'
end
# а вот так получится не тот приоритет вызовов, поэтому нужны скобки
if 10.between?(1, 50) && 20.between?(1, 50)
  puts 'ok'
end

А еще — есть символы. По сути, символы — это неизменяемые строки. Например, они часто используются как ключи в хэшах.
a = :nil? # символ
b = 'nil?' # строка
nil.respond_to? a # true
nil.respond_to? b # true
# поиграемся
a == b # false
a.to_s == b && a == b.to_sym # true; символ и строка преобразуются друг в друга

С ними связано еще немного пудры:
a = {:key1 => 'value1', :key2 => 'value2'} # создаем хэш (ассоциативный массив)
a = {key1: 'value1', key2: 'value2'} # если ключи - символы, то можно так (Ruby >= 1.9.3)
Раз уж тема зашла:
a = ['value1', 'value2'] # это массив
s = 'String'
s = "Double-quoted #{s}" # "Double-quoted String" - это, думаю, ясно

И добьем тему припудривания немного уродливым, но зато удобным способом записи:
%w[value1 value2] # ["value1", "value2"] - тот же массив, что и выше
%i[value1 value2] # [:value1, :value2] - массив символов, (Ruby >= 2.0)
s = %q(String)
s = %Q(Double-quoted #{s})
%x('ls') # команда консоли
`ls` # то же самое
%r(.*) == /.*/ # true; два способа создать регулярное выражение

Кстати, в Ruby есть многим до боли знакомая точка с запятой — пригодиться она может чтобы записать несколько выражений в одну строку
a = 10; puts a # выведет в консоль 10
if nil.respond_to? 'nil?'; puts 'ok'; end # чтобы было понятно - так тоже можно
Но однострочные if лучше писать так
puts 'ok' if nil.respond_to? 'nil?'


Вводные статьи и доки
Стоит почитать В Ruby из других языков и Ruby за двадцать минут, ну а главный друг и помощник ruby-doc.org.
Лучше сразу посмотреть методы базовых классов (во всех — какие бывают each_ и to_).
String (тут — первым делом match, sub)
Array (map, join, include?)
Hash (has_key?, has_value?, merge)
Достаточно простые примеры разобраны в Покорим Ruby вместе! Капля первая Капля вторая Капля третья
Если хочется продолжения банкета: Интересный паблик с подборкой магии на Руби.

Классы, модули и метапрограммирование
Классы в Ruby вполне очевидны, но в возможностях работы с ними кроется вся сила и прелесть Ruby.
class Foo
  def bar
    10 # любой метод возвращает значение - результат выполнения последнего выражения
  end
  def baz(a)
    bar + 20
  end
end
puts Foo.new.baz(10) # 30

Module — к нему можно относиться и как к примеси, и как к пространству имен. Внезапно? По сути — это набор классов, методов и констант, и пользоваться им можно на свое усмотрение.
module B # как пространство имен
  class Foo
    # конструктор
    def initialize(k) @k = k end # запишем методы в одну строку
    def bar; @k + 20 end
  end
end
puts B.Foo.new(3).bar # 23
puts B::Foo.new(3).bar # 23; то же самое, но читается лучше

module C # как примесь
  def bar; @k + 25 end
end
class Foo
  include C;
  def initialize(k) @k = k end
end
puts Foo.new(3).bar # 28


В Ruby классы мутабельны, и их можно патчить после их создания. И тут будьте осторожны: здесь начинаются те самые возможности, при помощи которых можно «выстрелить себе в ногу» — так что, делая что-то подобное нужно всегда отдавать отчет: зачем, что получится, кто виноват и что делать и если что — виноваты сами.
class Foo
  def bar; 20 end
end
class Foo # патчим
  def baz; bar + 2 end
end
puts Foo.new.baz # 22
# или лучше - так: сразу понятно что это патч
Foo.class_eval do
  def bazz; bar + 3 end
end
puts Foo.new.bazz # 23
# но если нет на то веской причины - используйте примеси или наследование
class Boo < Foo
  def boo; bar + 2 end
end
puts Boo.new.boo # 22
# или же - можно пропатчить только объект, а не сам класс
a = Foo.new
a.instance_eval do # патчим объект
  def booo; bar + 3 end
end
puts a.booo # 23
puts Foo.new.booo rescue puts 'error' # error; кстати, так перехватываются ошибки
puts a.respond_to? :booo # true
puts Foo.new.respond_to? :booo # false
# или - патчим объект в более легком синтаксисе
def a.booboo
  bar + 4
end
puts a.booboo # 24
А что если сделать instance_eval для класса? Конечно же, добавятся статические методы.
class Foo
  def self.bar; 10 end # обычный статический метод
end
puts Foo.bar # 10
Foo.instance_eval do
  def baz; bar + 1 end
end
puts Foo.baz # 11
Играться с этим можно вдоволь, главное помните — берегите ноги.
class Foo
  def self.add_bar # добавим статический метод, который патчит класс
	self.class_eval do
	  def bar; 'bar' end
	end
  end
end
puts Foo.new.respond_to? :bar # false
class Boo < Foo # отнаследуемся
  add_bar # пропатчим
end
puts Boo.new.bar # bar

# а теперь все то же самое, но пропатчим уже существующий класс
Foo.instance_eval do
  def add_baz
    self.class_eval do
      def baz; 'baz' end
    end	
  end
end
class Baz < Foo
  add_baz
end
puts Baz.new.baz # baz

Как раз такой подход используется на практике — по сути, получается что-то вроде примеси, в которую можно передавать параметры. Это кажется магией, если не знать, как это делается. Патчить можно и базовые классы, особенно любимы Array и String — но всегда стоит подумать трижды, прежде чем начинать их мучить: одно дело методы вроде .blank? (его добавляет Rails: что-то вроде def blank?; nil? || empty? end), другое — когда код метода специфичен для проекта, тогда логично предположить, что он относится к каким-то классам внутри проекта.

По такому принципу работает, например accessor. Что мы сделаем, чтобы добавить публичный параметр в Ruby-класс?
class Foo
  def bar # геттер
    @bar # возвращаем параметр
  end
  def bar=(val) # сеттер
    @bar = val # присваиваем значение параметру
  end
end

Представляете так писать для десятка-другого параметров? В Ruby много кода по накатанному — смертный грех: DRY. Так что, можно сделать короче.
class Foo
  attr_accessor :bar, :baz # добавим сразу парочку атрибутов
  attr_reader :boo # только геттер
  attr_writer :booo # только сеттер
end


Готовы дальше? Тогда:
Вникаем в метаклассы Ruby
Metaprogramming patterns — про monkey patching, Reuse в малом — bang!, eval
Вникаем в include и extend

Аргументы методов
Ruby 2.0 поддерживает именованные аргументы методов:
# bar обязателен
# barr имеет значение по умолчанию 0
# baz - именованный аргумент со значением по умолчанию true
def foo(bar, barr = 0, baz: true)
  baz && bar + barr
end
p foo 1 # 1
p foo 1, 2 # 3
p foo 1, baz: false # false

В предыдущих версиях добиться такого же поведения можно, разбирая атрибуты:
def foo2(bar, *args)
  args
end
p foo2 1, 2, :baz => false # [2, {:baz=>false}]

def foo3(bar, *args)
  options = args.extract_otions! # именованные аргументы вырезаются из args
  p bar
  p args
  p options
end
foo3 1, 2, 3, :baz => false
# 1
# [2, 3]
# {:baz=>false}

Начиная с Ruby 2.1 появилась возможность добавлять обязательные именованные аргументы.
def foo4(bar, baz:); bar end
foo4 1 # ошибка
foo4 1, baz: 2 # 1


Замыкания и блоки
Некоторое недоумение поначалу вызывают блоки, замыкания и класс Proc — зачем столько всего? Вкратце — на самом деле, есть только Proc.
Подробно — ссылки внизу, а сейчас — на что стоит обратить внимание.
Первыми рассмотрим блоки. Блок — это просто кусок куда, и даже не объект, просто часть синтаксиса Ruby. Блок используется чтобы передать какой-то код в метод. И уже в методе он оказывается завернут его в класс Proc.
В разделе про классы и методы они уже использовались:
Foo.instance_eval do # передаем в метод instance_eval блок с определением метода
  def baz; bar + 1 end
end

Но возьмем более базовый пример, он же «как сделать foreach в Ruby»:
[1,2,3].each do |val|
  p val # кстати, p(val) - это shortcut для puts(val.inspect)
end # выводит 1 2 3

# то же, в одну строчку 
[1,2,3].each { |val| p val } # выводит 1 2 3
[1,2,3].each_with_index { |val, i| puts val.to_s + ' ' + i.to_s } # на заметку

Если же мы хотим передавать блок в собственный метод:
def foo
  puts yield # выполняем блок
  puts yield + yield # и еще, и еще выполняем
end
foo { 2 } # 2 4
def bar(&block) # или, если поменьше пудры
  puts yield block # выполняем блок
end
bar { 3 } # 3

Стоит отметить, что блок идет всегда последним параметром, и если нужно передать несколько блоков — нужно их передавать как обычные параметры, а значит создавать Proc.new, или lambda.

Из блока всегда получается объект класса Proc, но сам блок это часть синтаксиса: мы можем его передать в метод, где он станет Proc, мы можем его передать в конструктор Proc, или использовать lambda, но нельзя просто записать блок в переменную.
proc = Proc.new { |a| a - 1 } # через конструктор
p proc.call(10) #9
p proc.class # Proc
l = lambda { |a| a + 1 } # создаем лямбду
p l.call(10) #11
p l.class # Proc
new_l = ->(a) { a + 2 } # тоже лямбда (Ruby >= 2.0)
p new_l.call(10) #12


Различия в поведении Proc созданными разными способами есть, читаем статью.
Здесь замыкания и блоки в Ruby разобраны вдоль и попрек
На Хабре про блоки, про замыкания

Стиль
По поводу как принято писать в Ruby есть целый гайд на GitHub
Вкратце, frequently used:
  • Отступы в 2 пробела
  • Названия методов строчными буквами через подчеркивание: def method_name
  • Названия классов и модулей с больших букв: class ClassName
  • Если метод возвращает true/false, название должно заканчиваться на вопрос (тот же, nil?)
  • Если есть два метода, один из которых изменяет объект, а другой возвращает новый — первый оканчивается на '!' (например, методы downcase и downcase! для строки — первый вернет новую, а второй изменит саму строку)
  • Вместо if(!value) лучше использовать алиас unless(value)
  • Блок однострочный берется в скобки {… }, а многострочный — в do… end


RubyGems - пакетный менеджер
Мир Ruby, наверное, был бы совсем другим, если бы не RubyGems.
С ними процесс дополнения проекта библиотеками выглядит очень просто:
  • выбираем что нам нужно с rubygems.org (или через ruby-toolbox.com), например, json_pure — JSON парсер на чистом Ruby (без C)
  • вводим в консоли gem install json_pure
  • а в нашем rb-файле:
    require 'json/pure' # какой именно путь должен быть стоит в доках посмотреть, обычно на github


Для удобства управления зависимостями есть bundler:
  • gem install bundler
  • bundler init
  • в появившемся Gemfile вписываем зависимости:
    source 'https://rubygems.org'
    gem 'json_pure'
    
  • bundler install
  • И в своем rb-файле
    require 'rubygems'
    require 'bundler/setup'
    

В реальных проектах список зависимостей может разрастись на несколько экранов, поэтому при переносе проекта на сервер гораздо удобнее выполнять bundle install, чем вручную смотреть что нужно и для каждого гема выполнять gem install. Ну а в Rails bundler используется из коробки.

Сами Gems обязательно рассмотрю в статье про Rails.

RubyGems — подробно
Управляем версиями с помощью Bundler
Знакомство с Gem. Часть первая Часть вторая
Пишем свой первый gem
Делаем gem для RubyGems
Создание гемов — Руководство

Ruby и C
Далеко не последняя по важности возможность RubyGems — Native Extensions. Все просто — вместе с гемом может быть скомпилирован прилагающийся код на C и вызван из кода самого гема. Благодаря этому, среди них есть достаточно шустрые парсеры, можно установить JavaScript'овый V8 в виде гема, и много других вкусностей.

А это нужно просто иметь ввиду: Inline C, может пригодится. Если коротко — в коде Ruby можно исполнять код на C, что полезно, например, при реализации численных алгоритмов.

Введение в расширения Ruby на C
Ruby и C. Часть 1, Часть 2, Часть 3.

Потоки
Простой пример с потоком:
thread = Thread.new do # создается отдельный поток
  a = 0
  1000000.times { a+= 1 } # загружаем процессор на некоторое время
  puts a # вывод результата
end
puts 'thread started' # текст выводится сразу после запуска потока
thread.join # ожидание завершения работы потока

Код выведет в консоль «thread started 100000».

Более низкоуровневые fibers (волокна) предлагают немного другой синтаксис, позволяющий управлять последовательностью исполнения, а также имеют меньшие издержки по памяти и времени инициализации, чем threads.

fiber = Fiber.new do # создание нового волокна
  Fiber.yield "fiber 1" # управление возвращается в контекст
  'fiber 2' # возвращаемое значение
end

puts fiber.resume # запуск волокна
puts 'context'
puts fiber.resume # возобновление работы волокна

Код выведет «fiber 1 context fiber 2».

Про GIL и настоящую многопоточность
Ruby поддерживает многопоточность, и различные версии интерпретатора отличаются ее реализацией. GIL в основной ветке сводит распараллеливание вычислений на нет, но использование потоков в нем имеет смысл для реализации асинхронности. Самый простой пример: мы можем слушать пользовательский интерфейс, параллельно выполняя какие-то операции. Или можем отправить запрос к БД и ждать ответа в одном потоке, отправлять email в другом, и не дожидаясь завершения отправить ответ пользователю. Но в суммарном времени исполнения выигрыша не будет, т.к. фактически в каждый момент времени будет нагружаться только одно ядро.

Чтобы обойти это ограничение можно использовать версии без GIL — jRuby или Rubinius (и при этом помнить, что потокобезопасность в них существенно ниже — для нее и нужен GIL). Другой вариант — запускать несколько отдельных копий программы, или использовать UNIX-форки (естественно, пулом таких воркеров вполне можно управлять Ruby-скриптом).

Узнав все это начинающий рубист может прийти в замешательство (желая использовать всю мощь своего 48-поточного сервера без лишней головной боли). На практике реальная многопоточность не всегда нужна (а иногда — если и нужна, то когда-нибудь потом). Кроме того, для многих задач есть соответствующие gem'ы, реализующие разные подходы, в том числе готовые HTTP-серверы (которые будут рассмотрены в статье про gems) и асинхронные фреймворки.


GIL нужен для этого:
a = 0
threads = []
10.times do # создаем 10 потоков
  threads << Thread.new do # добавляем объекты потоков в массив
    100000.times { a+= 1 } # 100 000
  end
end
threads.each(&:join) # ждем завершения всех потоков
puts a # вывод результата

Код выведет 1 000 000 в MRI, и черт знает что (от 100 000 до 1 000 000) в jRuby и Rubinius.

Используем потоки в Ruby

Зоопарк версий
MRI (Matz's Ruby Interpreter) — основная ветка Ruby от его создателя Yukihiro Matsumoto, или просто, Matz. Реализована на C. Когда просто говорят «Ruby» обычно имеют ввиду ее. Начиная с версии 1.9 объединена с проектом YARV (Yet another Ruby VM). Одна из основных ее особенностей — так называемый GIL (Global Interpreter Lock), на Хабре про нее писали (с продолжением). Сейчас актуальная версия Ruby 2.0 UPD: вышел Ruby 2.1. Чего нам ждать от Ruby 2.1?

jRuby, который написан на Java и работает в JVM, и соответственно интегрируется с кодом на Java. К сожалению, версия языка отстает от MRI (сейчас реализован Ruby 1.9.3), но тот же Rails заводится на нем с пол оборота.

Rubinius, который основан на MRI, но использует потоки операционной системы, а так же по максимуму написан на самом Ruby. По версии обычно up do date.

Скорость всех трех реализаций относительно друг друга разнится от задачи к задаче, а по потреблению памяти из троицы самый прожорливый — jRuby.

MacRuby — работает в связке с Objective-C на LLVM, заменив тем самым более ранний проект RubyCocoa. Для разработки под iOS существует форк MacRuby — RubyMotion.

IronRuby — реализация на платформе .NET. На протяжении своей жизни то забрасывается, то снова возобновляется разработка.

Opal — транслятор Ruby в JavaScript. Но не надо ждать от него чего-то выдающегося, это не jRuby для Node.js — он просто дает возможность писать ваш jQuery-код на Ruby. Или Express-код под Node.js. В общем, вариация на тему CoffeeScript.

Ruby Enterprise Edition (REE) — Ruby на стероидах. Проект завершил свое существование, т.к. новые версии и без наркотиков неплохо бегают.

Можно упомянуть MagLev — специфическая версия, может пригодится для разворачивания облачной инфраструктуры.

Также интересен проект mruby, в котором участвует Matz. Это — встраиваемый Ruby. Проект еще не закончен, но весьма многообещающе выглядит. Так что, ждем ruby на Arduino. mruby-arduino. На нем же основан MobiRuby, предназначенный для разработки мобильных приложений.

Ну и на закуску, KidsRuby, по смыслу напоминающий старичка Logo.


upd:
learnxinyminutes.com/docs/ruby — примеры кода на Руби.
Tags:
Hubs:
+49
Comments 20
Comments Comments 20

Articles