Архив метки: Ruby

Главная / Ruby
9 Постов

Приведу диаграмму самых ценных технологий в резюме программиста.

Самые оплачиваемые языки программирования

Первое место занимает Ruby on Rails.

Так что, самый оплачиваемый язык это Ruby.

Также в этом списке я бы отметил JavaScript. JS отстает от Ruby почти на $20,000 годовой зарплаты, но тем не менее он в топе наиболее ценных веб-технологий.

JS в комбинации с Ruby дает отличный эффект.


Диаграмма приведена для США.

Классы

Для начала мы поговорим о классах. Мы рассмотрим:

  • Классы
  • Как создаются объекты
  • Как получить доступ к данным в них

Обзор объектно-ориентированного программирования

  • Определите вещи, с которыми ваша программа имеет дело
  • Этими вещами (их «чертежами») являются классы
    • Классы содержат методы (поведение)
  • Объекты являются экземплярами этих вещей
  • Объекты содержат переменные экземпляров (состояние)

Переменные экземпляров

В Ruby, переменные экземпляров начинаются на @. К примеру, @name.

Интересно то, что в Ruby переменные экземпляра не объявляются. Они как бы просто оживают, когда вызваны впервые.

Они доступны для всех методов экземпляра класса.

Создание объекта

Способ создания объектов в Ruby — вызов метода new в классе, фактически это вызывает метод initialize.

Метод initialize в Ruby — это специальный метод, который действует как конструктор, как мы увидим в примере ниже.

Состояние объекта должно быть инициализировано внутри метода initialize, то есть конструктора.


class Person
  def initialize (name, age) # "Конструктор"
    @name = name
    @age = age
  end
  def get_info
    @additional_info = "Интересно"
    "Имя: #{@name}, возраст: #{@age}"
  end
end

person1 = Person.new("Джо", 14)
p person1.instance_variables # [:@name, :@age]
puts person1.get_info # => Имя: Джо, возраст: 14
p person1.instance_variables # [:@name, :@age, :@additional_info]

Доступ к данным

Переменные экземпляров являются приватными, они не могут быть доступны вне класса.

Методы имеют публичный доступ по умолчанию.

Для доступа к переменным экземпляров нужно определить методы «getter» / «setter».


class Person 
  def initialize (name, age) # Конструктор
    @name = name 
    @age = age 
  end 
  def name # name getter
    @name 
  end 
  def name= (new_name) # name setter
    @name = new_name 
  end 
end 

person1 = Person.new("Джо", 14) 
puts person1.name # Джо
person1.name = "Майк" 
puts person1.name # Майк
# puts person1.age # undefined method `age' for #<Person:

Часто логика getter/setter довольно проста: получить существующее значение / установить новое значение.

Но должен быть путь еще проще, чем фактическое определение методов getter/setter.

Так что можно использовать синтаксис attr_*:

  • attr_accessor — getter и setter
  • attr_reader — только getter
  • attr_writer — только setter

class Person 
  attr_accessor :name, :age # геттеры и сеттеры для name и age
end 

person1 = Person.new 
p person1.name # => nil 
person1.name = "Майк" 
person1.age = 15 
puts person1.name # => Майк 
puts person1.age # => 15 
person1.age = "пятнадцать" 
puts person1.age # => пятнадцать

В этом примере есть две небольшие проблемы:

  • При создании, Person находится в неинициализированном состоянии (без name или age)
  • Скорее всего, мы хотели бы контролировать максимальный установленный возраст

Чтобы решить это, нужно использовать конструктор и более разумный setter для age.

Но для начала мы должны рассмотреть self.

self

Внутри метода экземпляра, self относится к самому объекту.

Обычно, использование self для вызова других методов того же экземпляра является чуждым.

Но иногда вызов self является обязательным, к примеру когда он может означать назначение локально переменной.

Вне определения метода экземпляра, self относится к самому классу.


class Person 
  attr_reader :age 
  attr_accessor :name 

  def initialize (name, ageVar) # Конструктор
    @name = name 
    self.age = ageVar # вызываем age= метод
    puts age 
  end 
  def age= (new_age) # более разумный setter для age
    @age = new_age unless new_age > 120
  end 
end 

person1 = Person.new("Ким", 13) # => 13 
puts "Мой возраст #{person1.age}" # => Мой возраст 13 
person1.age = 130 # Попытка изменить возраст
puts person1.age # => 13 (setter не дал провести изменение)

Далее мы поговорим о методах/переменных и наследовании классов.

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

Мы рассмотрим:

  • Оператор ||
  • Методы классов и операторы классов
  • Наследование классов

Оператор ||

Оператор || оценивает левую сторону.

  • Если true — возвращает ее
  • В другом случае, возвращает правую сторону
  • @x = @x || 5 в первый раз возвратит 5, в следующий — @x

Краткая форма:

  • @x ||= 5 — то же, что и сверху

var = var || что-то

Давайте посмотрим чем это может быть полезно.


class Person 
  attr_reader :age 
  attr_accessor :name 
  
  def initialize (name, age) # Конструктор
    @name = name 
    self.age = age # вызывает age= метод
  end 
  def age= (new_age) 
    @age ||= 5 # устанавливаем 5 только для первого раза
    @age = new_age unless new_age > 120 
  end 
end 
person1 = Person.new("Ким", 130) 
puts person1.age # => 5 (по умолчанию) 
person1.age = 10 # изменяем на 10 
puts person1.age # => 10 
person1.age = 200 # Пытаемся изменить на 200
puts person1.age # => 10 (всё еще)

Методы и переменные классов

Давайте поговорим методах классов и переменных классов.

Они вызываются на классе, а не экземпляре класса. Так что если вы знакомы с концептом метода static в Java, это его эквивалент.

Self вне определения метода относится к объекту Class.

Как мы увидим, есть еще два способа определить методы класса в Ruby, кроме self вне определения метода.

Переменные класса начинаются на @@.

Единичный символ @ дал бы вам переменную экземпляра, как уже упоминалось, а двойной символ даст переменную класса.


class MathFunctions 
  def self.double(var) # 1. Используем self 
    times_called; var * 2; 
  end 
  class << self # 2. Используем << self 
    def times_called 
      @@times_called ||= 0; @@times_called += 1 
    end 
  end 
end 
def MathFunctions.triple(var) # 3. Вне класса
  times_called; var * 3 
end

# Без создания экземпляра!
puts MathFunctions.double 5 # => 10 
puts MathFunctions.triple(3) # => 9 
puts MathFunctions.times_called # => 3 

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

Каждый класс неявно наследуется от Object. Сам Object наследуется от BasicObject.

Множественного наследования нет, вместо этого используются примеси (mixins).

Давайте взглянем на небольшой пример.


class Dog # неявно наследует Object
  def to_s
    "Собака"
  end
  def bark
    "лает громко"
  end
end
class SmallDog < Dog # означает наследование, наследует класс Dog
  def bark # Перезаписываем
    "лает тихо"
  end
end

dog = Dog.new # (кстати, new это метод класса)
small_dog = SmallDog.new
puts "#{dog}1 #{dog.bark}" # => Собака1 лает громко
puts "#{small_dog}2 #{small_dog.bark}" # => Собака2 лает тихо

Модули

Теперь рассмотрим модули.

  • Модули
    • Как пространства имен
    • Как примеси
  • Использование встроенных в Ruby модулей, особенно Enumerable
  • require_relative

В общем-то, модули являются контейнерами для классов, методов и констант. Или других модулей.

Они походи на Классы, но не могут быть инстанцированы.
Class наследует Module и добавляет new.

Модули полезны для двух целей: пространства имен и примеси.

Модуль как пространство имен


module Sports
  class Match # Класс match как часть модуля Sports
    attr_accessor :score
  end
end

module Patterns
  class Match # Класс match как часть модуля Patterns
    attr_accessor :complete
  end
end

# Итак, мы имеем два разных Match

match1 = Sports::Match.new # заметьте использование оператора ::
match1.score = 40; puts match1.score # => 40

match2 = Patterns::Match.new
match2.complete = true; puts match2.complete # => true

Модуль как примеси

Другой способ использования модулей — это примеси.

В ОО-программировании есть интерфейсы. Они могут определять что класс «может» делать.

Примеси идут немного дальше и позволяют разделять (примешивать) готовый код между несколькими классами.

Кроме того, вы можете включить встроенные модули как Enumerable, которые могут сделать трудную работу за вас.


module SayMyName
  attr_accessor :name
  def print_name
    puts "Имя: #{@name}"
  end
end

class Person
  include SayMyName # позволяет включить функциональность этого модуля
end
class Company
  include SayMyName
end

person = Person.new
person.name = "Джо"
person.print_name # => Имя: Джо
company = Company.new
company.name = "Google & Microsoft LLC"
company.print_name # => Имя: Google & Microsoft LLC

Модуль Enumerable

Что действительно интересно, вы можете включить встроенные модули из самого Ruby. Когда мы говорили о массивах, мы затрагивали, что они имеют такие методы как map, select, reject, detect и т.д.

Дело в том, что массивы включают модуль Enumerable и это то что дает им такую функциональность.

Вы можете включить его в свой собственный класс.

Все что вам для этого нужно — обеспечить реализацию метода each. Как только вы это сделаете, вся функциональность Enumerable будет доступна!

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


# имя файла - player.rb

class Player

  attr_reader :name, :age, :skill_level

  def initialize (name, age, skill_level)
    @name = name
    @age = age
    @skill_level = skill_level
  end

  def to_s
    "<#{name}: #{skill_level}(SL), #{age}(возраст)>"
  end

end


# team.rb

class Team
  include Enumerable # Множество функций

  attr_accessor :name, :players
  def initialize (name)
    @name = name
    @players = []
  end
  def add_players (*players) # splat
    @players += players
  end
  def to_s
    "#{@name} team: #{@players.join(", ")}"
  end
  def each
    @players.each { |player| yield player }
  end
end


require_relative 'player' # require_relative позволяет включать другие .rb файлы
require_relative 'team'

player1 = Player.new("Боб", 13, 5); player2 = Player.new("Джим", 15, 4.5)
player3 = Player.new("Майк", 21, 5) ; player4 = Player.new("Джо", 14, 5)
player5 = Player.new("Скотт", 16, 3)

red_team = Team.new("Красная команда")
red_team.add_players(player1, player2, player3, player4, player5) # (splat)

# выбирает только игроков от 14 до 20 and отклоняет всех ниже уровня SL 4.5
elig_players = red_team.select {|player| (14..20) === player.age }
                       .reject {|player| player.skill_level < 4.5}
puts elig_players # => <Джим: 4.5(SL), 15(возраст)>
		  # => <Джо: 5(SL), 14(возраст)>

Области

Теперь рассмотрим области.

  • Области переменных
  • Области констант
  • Как область работает с блоками

Методы и классы начинают новую область для переменных.

Внешние переменные не имеют отношения к внутренней области.

Всегда можно использовать метод local_variables, чтобы посмотреть какие переменные входят (а какие — нет) в текущую область.


v1 = "outside" # вне области

class MyClass 
  def my_method
  	# p v1 Выдается исключение - нет такой переменной
    v1 = "inside" # внутри
    p v1
    p local_variables 
  end 
end 

p v1 # => outside
obj = MyClass.new 
obj.my_method # => inside 
              # => [:v1] 
p local_variables # => [:v1, :obj]
p self # => main

Область: константы

  1. Константа — это любая ссылка, которая начинается с заглавной буквы, включая классы и модули.
  2. Правила области константы отличаются от правил области переменных.
  3. Внутренняя область может использовать константы, определенные вне этой области, и также может перезаписывать внешние константы.
    • При этом внешнее значение остается неизменным за ее пределами

Давайте взглянем на пример.


module Test
  PI = 3.14
  class Test2
    def what_is_pi
      puts PI
    end
  end
end
Test::Test2.new.what_is_pi # => 3.14

module MyModule
  MyConstant = 'Внешняя константа'
  class MyClass
    puts MyConstant # => Внешняя константа
    MyConstant = 'Внутренняя константа'
    puts MyConstant # => Внутренняя константа
  end

  # остается неизменной вне области
  puts MyConstant # => Внешняя константа
end

Область: блоки

Блоки, в отличие от методов, наследуют внешнюю область.

Блоки закрыты. Они запоминают контекст в котором они были определены и используют этот контекст при каждом вызове.

Посмотрим на пример для блоков.


class BankAccount
  attr_accessor :id, :amount
  def initialize(id, amount)
    @id = id
    @amount = amount
  end
end

acct1 = BankAccount.new(123, 200) # id, сумма
acct2 = BankAccount.new(321, 100)
acct3 = BankAccount.new(421, -100)
accts = [acct1, acct2, acct3]

total_sum = 0 # объявляем переменную
accts.each do |eachAcct| # считаем общую сумму в блоке
  total_sum += eachAcct.amount
end

# та же область
puts total_sum # => 200

Блок – локальная область

Даже хотя блоки разделяют внешнюю область, переменная созданная внутри блока доступна только этому блоку.

Параметры блока всегда локальны для него, даже если они имеют то же имя, что и переменные внешней области.

Вы можете явно указывать локальные для блока переменные после двоеточия в списке параметров блока.


arr = [5, 4, 1] 
cur_number = 10 
arr.each do |cur_number| 
  some_var = 10 # Не доступна вне блока
  print cur_number.to_s + " " # => 5 4 1 
end 
puts # отдает пустую строку
puts cur_number # => 10 

adjustment = 5 
arr.each do |cur_number;adjustment| 
  adjustment = 10 
  print "#{cur_number + adjustment} " # => 15 14 11 
end 
puts 
puts adjustment # => 5 (Не затронута блоком)

Контроль доступа

Теперь рассмотрим:

  • Три уровня контроля доступа
  • Управление доступом
  • Насколько приватен приватный доступ?

При проектировании класса, важно подумать о том как много из него будет раскрыто миру.

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

Всего Ruby предоставляет три метода контроля доступа: public, protected и private.

Инкапсуляция

В примере ниже детали рассчета рейтинга сохраняются внутри класса.


class Car
  def initialize(speed, comfort)
    @rating = speed * comfort
  end

  # Нельзя задать rating извне
  def rating
    @rating
  end
end

puts Car.new(4, 5).rating # => 20

Назначение контроля доступа

В Ruby есть два пути назначения контроля доступа:

  1. Указать public, protected или private
    • Всё до следующего ключевого слова управления доступом будет этого уровня доступа
  2. Определить методы обычным методом и затем указать public, protected или private, и перечислить через запятую методы под этими уровнями, используя символы методов

По умолчанию (если вы не указываете ни один из уровней), методы являются public.

Итак, взглянем на пример такого назначения.


class MyAlgorithm
  private
    def test1
      "Приватный"
    end
  protected
    def test2
      "Защищенный"
    end
  public
    def public_again
      "Публичный"
    end
end

# Второй способ:

class Another
  def test1
    "Приватный, как объявлено ниже"
  end
  private :test1
end

Что означают публичный, защищенный и приватный уровни?

public методы — нет установленного управления доступом. Кто угодно может вызывать эти методы.

protected методы — могут быть вызваны объектами определяющего класса или его подклассами.

private методы — не могут быть вызваны с явным получателем.
Исключение: установка атрибута может быть вызвана с явным получателем.

Приватный доступ


class Person
  def initialize(age)
  	self.age = age # Валидно - исключение
  	puts my_age
  	# puts self.my_age # Не валидно
  	                   # Нельзя использовать self на любом другом получателе
  end

  private 
    def my_age
    	@age
    end
    def age=(age)
      @age = age
    end
end

Person.new(24) # => 24

Строки

Для начала поговорим о строках. Мы рассмотрим:

  • Различные виды строк, поддерживаемые Ruby
  • Многие методы, поддерживаемые String API
  • Символы

Строки с одной кавычкой очень буквальны. Они позволяют экранирование ' с помощью /, и показывают всё остальное практически как есть.

Строки с двойными кавычками имеют специальные символы, которые они могут интерпретировать: /n, /t и другие.
Другая интересная функция, которую они имеют, это интерполяцию строк.

Строки / интерполяция


single_quoted = 'ice cream \n followed by it\'s a party!'
double_quoted = "ice cream \n followed by it\'s a party!" 

puts single_quoted # => ice cream \n followed by it's a party!
puts double_quoted # => ice cream 
                   # =>   followed by it's a party! 

def multiply (one, two) 
  "#{one} умножить на #{two} равняется #{one * two}" # интерполяция доступна только для строк с двойными кавычками
end 
puts multiply(7, 3) 
# => 7 умножить на 3 равняется 21

Еще о строках:

  • Строковые методы, заканчивающиеся на !, изменяют существующую строку
    • Большинство других просто возвращают новую строку
  • Можно использовать %Q{длинная мультистрочная строка}
    • Ведет себя так же как и строка с двойными кавычками

my_name = " тим" 
puts my_name.lstrip.capitalize # => Тим
# методы без ! выводят измененную копию строки
p my_name # => " тим" 

my_name.lstrip! # убирает первый пробел
# метод с ! на самом деле изменяет строку

my_name[0] = 'К' # заменяет первый символ
puts my_name # => Ким 

cur_weather = %Q{Сегодня жарко
			     Захватите зонтики...} 

cur_weather.lines do |line| 
  line.sub! 'жарко', 'дождливо' # заменяет "жарко" на "дождливо"
  puts "#{line.strip}"
end 
# => Сегодня дождливо
# => Захватите зонтики...

API строк

https://ruby-doc.org/core-2.4.1/String.html

Здесь есть много хороших примеров.

strings пример

strings пример 2

Символы

Другой тип строк, который многих удивляет, — это идея символа.

Итак, что такое символ? Символ — это двоеточие, за которым следует некоторая строка.

К примеру, :foo-. Символы — это просто оптимизированные строки.

У них гораздо меньше методов, чем у (обычных) строк, и они действительно служат другой цели. Это постоянные имена, которые вам не нужно объявлять.

Символы гарантированно будут уникальными и неизменными. Они могут быть конвертированы в Строку с помощью to_s, или со Строки в Символ с помощью to_sym.

Символ может являться репрезентацией имени метода.

Так, к примеру, на любом объекте в Ruby вы можете просто спросить, какие у меня методы? И он предоставит вам все методы, поддерживаемые этим объектом.

irb методы

В этом случае мы делаем чуть больше, мы ищем методы со словом case.

Интересно, что мы видим обычную версию метода и версию с !, которая изменяет строку безвозвратно.

Массивы

Далее мы обсудим массивы (множества):

  • Как они создаются
  • Как изменять массивы
  • Доступ к элементам внутри массива

Вы могли познакомиться с массивами в других языках программирования, и Ruby также отлично поддерживает их, как мы увидим.

Массивы:

  • Коллекция ссылок на объекты
  • Индексируется с использованием оператора []
  • Может индексироваться с отрицательными числами или диапазонами
  • В одном массиве разрешается иметь различные типы данных
  • Можно использовать %w{str1 str2} для создания массива строк

het_arr = [1, "два", :три] # различные типы
puts het_arr[1] # => два (индексы массива начинаются с 0)

arr_words = %w{ какой же сегодня хороший день! }
puts arr_words[-2] # => хороший
puts "#{arr_words.first} - #{arr_words.last}" # => какой - день! 
p arr_words[-3, 2] # => ["сегодня", "хороший"] (вернуться на 3 элемента назад и взять 2 из них) 

# (Диапазон, который мы рассмотрим позже...)
p arr_words[2..4] # => ["сегодня", "хороший", "день!"] 

# Сделать строку из элементов массива, разделенных ','
puts arr_words.join(',') # => какой,же,сегодня,хороший,день!

Изменение массивов:

  • Добавление: push или <<
  • Удаление: pop или shift
  • Задать конкретный элемент: метод []=

Другие функции:

  • Показать случайный элемент(ы) с помощью sample
  • Сортировать или реверсировать массив с помощью sort! и reverse!

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

И аналогично строкам у вас есть либо сортировка без восклицательного знака, либо сортировка с ним. Как вы догадались, способ без ! возвращает отсортированную копию массива, а способ с ! изменяет сам массив.


# Вам нужен стек? Без проблем
stack = []; stack << "один"; stack.push ("два")
puts stack.pop # => два

# Вам нужна очередь? Тоже есть
queue = []; queue.push "один"; queue.push "два"
puts queue.shift # => один

a = [5,3,4,2].sort!.reverse! 
p a # => [5,4,3,2] (фактически изменяет массив) 
p a.sample(2) # => 2 случайных элемента

# Что будет, если добавить 33 как шестой (седьмой, считая с 0) элемент?
# Массив автоматически расширится и добавит nil вместо пустых элементов
a[6] = 33
p a # => [5, 4, 3, 2, nil, nil, 33]

Другие полезные методы:

  • each — перебирает массив
  • select — фильтрует массив, выбирая
  • reject — фильтрует массив, отклоняя
  • map — изменяет каждый элемент в массиве

API массивов.
Много других полезных методов можно найти, посмотрев API массивов: https://ruby-doc.org/core-2.4.1/Array.html

Давайте посмотрим некоторые примеры методов, которые я упомянул выше.


a = [1, 3, 4, 7, 8, 10]
a.each { |num| print num } # => 1347810 (нет новой строки)
puts # => (с новой строки)

new_arr = a.select { |num| num > 4 }
p new_arr # => [7, 8, 10]
new_arr = a.select { |num| num < 10 }
           .reject{ |num| num.even? }
p new_arr # => [1, 3, 7]

# Умножить каждый элемент на 3, создав новый массив
new_arr = a.map {|x| x * 3}
p new_arr # => [3, 9, 12, 21, 24, 30]

В итоге.
Таким образом, API массивов очень гибкий и очень мощный. Я просто показал несколько методов, но есть еще многие, которые могут быть полезными. И есть много способов обработки элементов.

Далее мы собираемся рассмотреть диапазоны.

Диапазоны

Поговорим о диапазонах и о том как они полезны в Ruby.

Итак, диапазоны — это по сути тип данных в Ruby, который используется для выражения естественных последовательностей.

  • 1..20, 'a' .. 'z'

Есть два правила, которые следует иметь в виду. Если используется две точки между началом и концом, то это означает, что всё включено.

К примеру, в 1..10 включается 1, включается 10 и, очевидно, всё что между ними.

Если используется три точки, к примеру 1...10, то последняя цифра исключается. То есть, в этом примере будет включено 1, включено 1-9, но 10 исключено.

Можно легко это запомнить как "чем больше точек у вас есть, тем меньше включено в конце". Хотя это слегка нелогично, но таким образом это легко запомнить.

Что такого особенного в диапазонах? Разве мы не можем сделать это с помощью массивов?

В первую очередь, их очень легко создать, вам просто нужна первая и последняя часть последовательности.

Кроме того:

  • Диапазоны очень эффективны
  • Могут быть конвертированы в массив с помощью to_a
  • Используются для условий и интервалов

Скажем, у вас есть последовательность из чисел от одного до миллиона, вы не собираетесь хранить миллион элементов вашего массива в памяти. Вы просто храните первое число и последнее число, что здорово.

Если вам нужно больше методов для этого диапазона, вы всегда можете преобразовать его в массив, вызвав метод to_a.

И еще одна причина по которой используются диапазоны, наверное самая частая, это условия и интервалы, которые мы рассмотрим в примере ниже.


some_range = 1..3
puts some_range.max # => 3
puts some_range.include? 2 # => true

puts (1...10) === 5.3 # => true
puts ('a'...'r') === "r" # => false (конец исключается)

p ('k'..'z').to_a.sample(2) # => ["k", "w"]
# или другой случайный массив с двумя буквами из диапазона

age = 55
case age
  when 0..12 then puts "Еще ребенок"
  when 13..99 then puts "Молод в душе!"
  else puts "Вы становитесь старше..."
end
# => Молод в душе!

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

Хэши

Теперь поговорим о хэшах.

Как они используются, зачем они используются, и есть ли у них схожести с блоками.

Хэши:

  • Индексированные коллекции ссылок на объекты
  • Создаются с помощью {} или Hash.new
  • Также известны как ассоциативные массивы
  • Индекс (ключ) может быть чем угодно
    • А не только целым числом, как в случае с массивами

Также:

  • Доступны с помощью оператора []
  • Значения задаются с помощью:
    • => (создание)
    • [] (после создания)

Давайте взглянем на пример.


editor_props = { "font" => "Arial", "size" => 12, "color" => "red"}

# Выше указан не блок, это хэш
puts editor_props.length # => 3
puts editor_props["font"] # => Arial

editor_props["background"] = "Blue"
editor_props.each_pair do |key, value|
  puts "Ключ: #{key}, значение: #{value}"
end
# => Ключ: font, value: Arial
# => Ключ: size, value: 12
# => Ключ: color, value: red
# => Ключ: background, value: Blue

Что будет, если вы попытаетесь получить доступ к значению в хэше, для которого нет записи? В этом случае вернется nil.

Если хэш был создан с помощью Hash.new(0) (0 в качестве примера), то будет возвращен 0.


API хэшей.
API хэшей тоже полезно освоить: https://ruby-doc.org/core-2.4.1/Hash.html

К примеру, используя синтаксис Hash.new со значением 0, мы хотим рассчитать частоту слов. Скажем, у нас есть предложение с какими-то словами, каждое из которых повторяется дважды.


word_frequency = Hash.new(0)

sentence = "Chicka chicka boom boom"
sentence.split.each do |word|
  word_frequency[word.downcase] += 1
end

p word_frequency # => {"chicka" => 2, "boom" => 2}

Еще о хэшах

В текущей версии Ruby, порядок записей в хэшах сохраняется.

При использовании символов в качестве ключей, можно использовать синтаксис symbol:.

Если хэш является последним аргументом метода, {} являются опциональными.

Давайте взглянем на всё это в примере.


family_tree_19 = {самый_старший: "Джим", старший: "Джо", младший: "Джек"} # хэш
family_tree_19[:самый_младший] = "Джереми"
p family_tree_19
# => {:самый_старший=>"Джим", :старший=>"Джо", :младший=>"Джек“, :самый_младший => “Джереми”}
# порядок поддерживается


def adjust_colors (props = {foreground: "красный", background: "белый"})
  puts "Передний план: #{props[:foreground]}" if props[:foreground]
  puts "Фон: #{props[:background]}" if props[:background]
end
adjust_colors # => Передний план: красный
              # => Фон: белый
adjust_colors ({ :foreground => "зеленый" }) # => Передний план: зеленый
adjust_colors background: "желтый" # => Фон: желтый
adjust_colors :background => "фиолетовый" # => Фон: фиолетовый

Путаница между хэшами и блоками

Это происходит не слишком часто, но иногда начинающие разработчики допускают ошибку.


# Скажем, у вас есть хэш
a_hash = { :one => "one" }

# Затем вы выводите его
puts a_hash # => {:one=>"one"}

# Если вы попытаетесь сделать это за один шаг - вы получите SyntaxError
# puts { :one => "one" }

# Ruby путается и думает, что {} является блоком!!!

# Чтобы обойти это, вы можете использовать скобки
puts ({ :one => "one" }) # => {:one=>"one"}

# Или вообще отбросить {}... 
puts one: "one" # => {:one=>"one"}

В итоге.
Хэши являются индексированными коллекциями. Их использование очень похоже на использование массивов.

Хотя это не является частым, хэши могут быть спутаны с блоками, так что следует быть осторожным с этим.

В этом посте мы обсудим:

  • Чтение и запись в файлы
  • Исключения
  • Чтение значений из переменных среды

Чтение из файла

Итак, у вас есть файл с именем test.txt, и вы хотите написать программу на Ruby, которая читает что-то из этого файла.

тестовый файл test.txt

У вас есть класс File, и у вас есть метод foreach, содержащий в качестве параметра файл, который вы хотите использовать. Затем идет блок, в котором представлены действия.


File.foreach ( 'test.txt' ) do |line|
  puts line
  p line
  p line.chomp # отсекает символ новой строки
  p line.split # массив слов строки
end

чтение файла

Чтение неизвестного файла

Что будет, если файл, из которого вы пытаетесь читать, не существует? Предсказуемо, вы получите исключение.


File.foreach( 'do_not_exist.txt' do |line|
  puts line.chomp
end

Руби попытается прочитать файл, он не найдет его, и выдаст, что do_not_exist.txt не существует, и всё.

И это на самом деле несколько неудачный результат работы вашей программы, потому что она прекращает свое выполнение.

Чтобы обойти это, вы можете обработать исключение. В этом случае вы контролируете, что с этим делать.


begin

  File.foreach ( 'do_not_exist.txt' ) do |line|
    puts line.chomp
  end

rescue Exception => e
  puts e.message
  puts "Давайте притворимся, что этого не было"
end

Результат будет таким:
обработка исключений

Альтернатива исключениям

В качестве альтернативы можно проверять, существует ли файл.


if File.exist? 'test.txt'

  File.foreach( 'test.txt' ) do |line|
    puts line.chomp
  end

end

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

Запись в файл

Как насчет записи в файл? Для этого Ruby тоже использует структуру блока.


File.open("test1.txt", "w") do |file|
  file.puts "Первая строка"
  file.puts "Вторая строка"
end

Файл будет автоматически закрыт после выполнения блока.

Переменные среды

Переменные среды специфичны для операционной системы, но как только вы установили их, вы можете легко прочесть их.

К примеру, установив у себя переменную BEST_LANG, я могу прочесть ее:


puts ENV["BEST_LANG"] # => Ruby

В этом посте мы рассмотрим блоки в Ruby.

  • Блоки
  • Как они используются
  • Как включить их в свои собственные методы

Некоторые люди отбрасываются блоки в Ruby, потому что в других языках нет блоков. Но просто подумайте о них как о кусках кода, который выполняется. Вы передаете их между фигурными скобками ({}) или ключевыми словами do и end, и они передаются методам в качестве последнего параметра.

Конвенция:

  • Используйте {}, когда содержимое блока является одной строкой
  • Используйте do и end, когда содержимое блока занимает несколько строк

Блоки часто используются в качестве итераторов. Также они могут принимать аргументы.


1.times { puts "Привет, мир!" }
# => "Привет, мир!"

2.times do |index|
  if index > 0
    puts index
  end
end
# => 1

2.times { |index| puts index if index > 0 }
# => 1

Программирование с блоками

Есть два пути, чтобы настроить блок в вашем собственном методе.

Неявный:

  • Используйте block_given?, чтобы посмотреть был ли задан блок
  • Используйте yield, чтобы «вызвать» блок

Явный:

  • Используйте & перед последним «параметром»
  • Используйте метод call, чтобы вызвать блок

Неявный

Вы должны проверить задан ли блок, в другом случае будет выдано исключение.


def two_times_implicit
  return "Нет блока" unless block_given?
  yield
  yield
end

puts two_times_implicit { print "Привет " } # => "Привет Привет"
puts two_times_implicit # => Нет блока

Явный

Явный способ очень похож, но вы явным образом заявляете, что этот метод может принимать блок.

Здесь вы должны проверить является ли блок nil?.


def two_times_explicit (&i_am_a_block)
  return "Нет блока" if i_am_a_block.nil?
  i_am_a_block.call
  i_am_a_block.call
end

puts two_times_explicit # => Нет блока
two_times_explicit { puts "Привет" } # => Привет
                                     # => Привет

Эти два способа очень похожи, и это зависит от вкуса, какой вы хотите использовать.

В итоге

Таким образом, блоки — это просто код, который вы передаете в методы. И еще раз, может уйти некоторое время чтобы привыкнуть к ним, потому что они не очень распостранены в других языках.

И если вы хотите включить блоки в свои методы, вы можете сделать это неявно, вроде притворяясь что блок не существует, и отдавая его если он существует, или вы можете явно указать его в своем методе и затем вызвать его, убедившись что он не является nil.

В этом посте мы рассмотрим:

  • Функции/методы
    • Определения
    • Как их называть?
    • Что и как они возвращают?
    • Аргументы по умолчанию
  • Как сделать методы более выразительными
  • Что такое «splat»

Функции и методы

Технически, функция определяется за пределами класса, а метод определяется внутри класса.

В Ruby, каждый метод/функция имеет как минимум один класс, к которому он пренадлежит.

  • Не всегда записан внутри класса

Таким образом, каждая функция на самом деле является методом в Ruby.

Методы

Методы могут иметь скобки, но они полностью опциональны как при определении метода, так и при вызове метода.

Скобки используются для ясности, хотя это и не необходимо.


def simple
  puts "без скобок"
end

def simple1()
  puts "со скобками"
end

simple() # => без скобок
simple # => без скобок
simple1 # => со скобками

В этом примере мы определили метод simple без скобок, но мы его можем вызвать как со скобками, так и без, — и это полностью разрешено. То же самое относится и к методу simple1.

Возврат

  • Нет необходимости объявлять тип параметров
  • Может возвращать всё, что вы хотите
  • Ключевое слово return опционально (возвращается последняя выполненная строка)

def add(one, two)
 one + two
end

def divide(one, two)
  return "Я так не думаю" if two == 0
  one / two
end

puts add(2,2) # => 4
puts divide(2,0) # => Я так не думаю
puts divide(12, 4) # => 3

Выразительные имена методов

Имена методов могут оканчиваться на:

  • ? — основанные на чем-то методы (как правило, возвращает логическое значение)
  • ! — может иметь опасные побочные эффекты

def can_divide_by?(number)
  return false if number.zero?
  true
end

puts can_divide_by? 3 # => true
puts can_divide_by? 0 # => false

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

Методы могут иметь аргументы по умолчанию.

  • Если внесено значение — используется данное значение
  • В другом случае — используется значение по умолчанию

def factorial (n)
  n ==? 1 : n * factorial(n - 1)
end

def factorial_with_default (n = 5)
  n ==0? 1 : n * factorial_with_default(n - 1)
end

puts factorial 5 # => 120
puts factorial_with_default # => 120
puts factorial_with_default(3) # => 6

Splat

Splat — это интересная концепция.

* идет перед параметром в определении метода

  • Может применяться даже к параметру посередине, а не только последнему

Он почти похож на указатель, как указатель в C, но это не так. Это похоже на var args, переменные аргументы в Java.

Итак, у вас есть много параметров которые вы хотите передать, но вы не знаете сколько, или вам неважно сколько. Вы хотите, чтобы все параметры обрабатывались как один параметр, и все эти параметры становятся одним большим массивом внутри метода.

И интересная часть заключается в том, что он может относиться к среднему параметру, а не только к последнему параметру метода.


def max(one_param, *numbers, another)
  numbers.max
end

puts max("что-то", 6, 31, -5, "еще") # => 31

В этом посте мы обсудим управление потоком данных в Ruby.

  • Управление потоком данных:
    • if / elsif / else
    • case
    • until / unless?
    • while / for
  • Что означает true и false.
  • Что такое ===.

Итак, if, elsif — это, вероятно, структуры, с которыми многие из нас знакомы из других языков программирования.

Отличие в том, что в Ruby нет скобок или фигурных скобок в этом случае, и вы используете end для закрытия блока управления потоком.
Так что если это просто оператор if или оператор if / else, у вас всегда будет end в конце.


a = 5 # объявляем переменную

if a == 3
puts "a равняется 3"
elsif a == 5
puts "a равняется 5"
else
puts "a не равняется ни 3, ни 5"
end

# => a равняется 5

Также в Ruby есть интересное ключевое слово unless. Unless это в когда что-то не совпадает с чем-то другим («если только не»).

Итак, в этом случае a равняется 5. Мы пишем в коде «если только a не равняется 6, делай то что ты должен делать», в этом случае вывод текста.


a = 5 # объявляем переменную

unless a == 6
puts "a не равняется 6"
end

# => a не равняется 6

Кроме того, есть цикл while. В этом примере у нас переменная, которой присваивается значение 10. И пока a больше 9, продолжается выполнение содержимого цикла.


a = 10

while a > 9
  puts a
  a -= 1
  # то же что и a = a - 1
end

# => 10

В этом случае вы вычитаете 1 из a, каждый раз, проходя через цикл, так что он будет выполняться только один раз, потому что второй раз a будет не больше девяти. Таким образом, единственный вывод из этого цикла будет равен 10.

Затем идет еще один специфический оператор Ruby, который является противоположностью while, это until.

В этом примере даем значение 9. До тех пор пока a станет больше или равно 10, продолжай выводить «9». Так что цикл выполнится только один раз, потому что в следующем цикле значение будет равняться 10, поэтому он не будет выполняться.


a = 9

until a >= 10
  puts a
  a += 1
end

# => 9

Поток управления данными: измененная форма

  • if, unless, while, until — в одной строке с выражением

# if в одной строке

a = 5
b = 0

puts "В одну строку" if a == 5 and b == 0

# => В одну строку

Читается почти как естественный английский. То же самое относится и к while:


# while в одной строке

times_2 = 2
times_2 *= 2 while times_2 < 100
puts times_2

# => ?

Можете догадаться какой ответ?

Если вы ответили 128, вы правы.

Что является true и что является false в Ruby?

  • Объекты false и nil являются false
  • Всё остальное является true!

puts "0 является true" if 0 # => 0 является true
puts "false является true?" if "false" # => false является true?
puts "не может быть - false равняется false" if false # => НИЧЕГО
puts "пустая строка является true" if "" # => пустая строка является true
puts "nil является true?" if "nil" # => nil является true?
puts "не может быть - nil является false" if nil # => НИЧЕГО

Тройной знак равенства

  • Тройной знак равенства ===
  • «Равно» по-своему
    • Иногда речь идет о не совсем равном

Чаще всего вы будете видеть стандартный ==, который использует большинство объектов. Но также есть интересная реализация в виде тройного знака равенства.

Тройной знак равенства в большинстве случаев просто делегируется двойному знаку равенства, поэтому он похож на его расширенную версию.


if /lan/ === "language"
  puts "Тройной знак равенства"
end
# => Тройной знак равенства

if "language" === "language"
  puts "тоже работает"
end
# => тоже работает

if Integer === 21
  puts "21 является целым числом"
end
# => 21 является целым числом

Здесь «lan» является частью строки «language», равны ли они на самом деле? Конечно нет. Но было бы здорово сказать, что это регулярное выражение соответствует строке, и это то, что делает тройной знак равенства.

Тройной знак равенства также работает если вы сравните строку саму с собой. В этом случае сработал бы и ==.

В третьем случае значение «21» выглядит как целое число, поэтому === позволяет сравнить и это.

Выражения Case

  • Две «особенности»:
    1. Похоже на серию выражений «if»
    2. Указывайте цель после case и условие после каждого when
  • === называется оператором сравнения case, потому что он используется именно в этом случае
  • Отсутствие «логики падения» (fall-through logic)

Заметка: если вы привыкли к стилю C, в котором есть логика падения в выражениях case, здесь это не так. В Ruby выполняется только один случай, который фактически соответствует, и никакие другие.


age = 21
case # первая особенность
  when age >= 21
    puts "Вы можете купить напиток"
  when 1 == 0
    puts "Написано пьяным программистом"
  else
    puts "Состояние по умолчанию"
end
# => Вы можете купить напиток


name = 'Fisher'
case name # вторая особенность
  when /fish/i then puts "Есть что-то рыбное"
  when 'Смит' then puts "Ваше имя Смит"
end

# => Есть что-то рыбное

Во втором примере у нас есть цель (name) и после каждого when у нас идет условие, которое сравнивается с этой целью.
Для сравнения здесь используется тройной знак равенства, то есть каждое условие сравнивает себя с целью при помощи этого свойства.

Циклы

Циклы существуют в Ruby, но они не используются часто.


for i in 0..2 # диапазон
puts i

# => 0
# => 1
# => 2

Циклы редко используются в Ruby, так как предпочтение отдается each и times.

В этом посте мы обсудим такие вещи как:

  • Краткая история Ruby
  • «Учить еще один язык программирования? Зачем?»
  • Основные принципы и конвенции Ruby

История Ruby

  1. Руби был изобретен Юкихиро «Matz» Мацумото
  2. Первая версия (1.0) была выпущена в 1996 году в Японии
  3. Язык был популяризован фреймворком RoR в 2005 году

Ruby динамичен.

Ruby также объектно ориентирован, и почти все в Ruby — это объект.

Еще один язык программирования?

Ruby — элегантный, выразительный и декларативный. Это три слова, которые я бы использовал, чтобы описать его как язык.

На него повлияли такие языки как Perl, Smalltalk, Eiffel и Lisp.

И, как однажды сказал сам Matz, «Ruby был разработан, чтобы сделать программистов счастливыми». («Ruby was designed to make programmers happy».)

Основы Ruby

В Ruby рекомендуется ставить два пробела для каждого вложенного уровня.
Это не требуется, как в Python, но это рекомендуется.

Вы используете # для комментариев. Комментарии должны использоваться в умеренных количествах, потому что сам язык довольно выразителен.

puts — стандартный метод в Руби для вывода строк в консоль (сокращение от «put string»)

p — выводит внутреннее представление объекта.

Скажем, у вас есть файл test.rb. Чтобы запустить файл из командной строки, нужно ввести в терминале «ruby test.rb».

Конвенции

Переменные пишутся в нижнем регистре или как snake_case если имеют несколько слов.

Константы пишутся капсом ALL_CAPS или с первой заглавной FirstCap.

Классы и модули также принято записывать с первой заглавной: CamelCase.

Отбросьте двоеточия. Не ставьте двоеточия в конце строки.
Хотя иногда двоеточия можно ставить между двумя утверждениями, это не рекомендуется, лучше разбивать их на разные строки.


a = 3 # двоеточие не нужно
a = 2; b = 3 # иногда используется

IRB — интерактивный Ruby. IRB это консольный интерпретатор Ruby, который идет сразу с установкой языка и дает вам поэксперементировать.
Вызывается командой irb в консоли.

Итак, мы рассмотрели установку рабочего окружения, теперь я расскажу о самом языке Ruby.

Мы рассмотрим такие основы как:

  • Синтаксис
  • Комментарии
  • Переменные
  • Функции

Затем поговорим о коллекциях:

  • Массивы
  • Списки
  • Хэши

Поговорим об объектно-ориентированной стороне программирования:

  • Классы
  • Наследование
  • Модули
  • Примеси (mixins)

Многие люди думают о Ruby как о скриптовом языке, но это совсем не так. Ruby на самом деле очень объектно-ориентированный язык.

После этого мы собираемся перейти к тестированию. Юнит-тестирование является важной частью любого языка. Ruby не является исключением.

Ruby сам по себе является очень увлекательным языком.

Многие люди после перехода с других языков (к примеру, Java) поражаются тем, как много задач может быть выполнено так легко или намного проще в Ruby.