class Класс
extend Модуль1
include Модуль2
CustomErrorKlass = Class.new(StandardError) # вложенные классы
КОНСТАНТА = 20
attr_reader :переменная
def self.some_method
end
def initialize
end
def some_method
end
protected
private
end
При присваивании переменной значения в переменную не помещается указанное значение, а с переменной связывается объект (другими словами хранит не объект, а ссылку на него).
a = [1, 2]
b = a
a << 3
b # содержит [1, 2, 3]
Присваивание значений нескольким переменным
a, b = 1, 2
Присвоение значения если значение еще не присвоено (memoization)
переменная ||= значение
или, в случае когда создание значения по умолчанию требует нескольких строк кода
переменная ||= begin
код возвращающий значение
end
Если необходимо сделать memoization для значений по умолчанию nil или false (приведенный выше способ в таких случаях работать не будет) можно использовать способ memoization через singleton
class Класс
переменная = метод
def метод
def self.метод
@переменная
end
@переменная = значение
end
end
В коде выше при первом вызове метода (присвоение значение переменной) создается синглтон метод объекта Класса, с тем же именем что и вызванный метод, после чего переменной экземпляра Класса присваивается значение по умолчанию.
Если, для получения значения переменной метод вызывается не в первый раз, то будет вызваться уже синглтон метод (он переопределил старый метод при его первом вызове), который просто возвращает, установленное в первый раз значение переменной класса.
Присвоение значения если значение присвоено
переменная &&= значение
Изменение значения если значение присвоено
переменная &&= значение.метод
Присваивание значений, возвращаемых методом, сразу нескольких переменных
переменная1, переменная2 = метод
Динамическая типизация - тип переменной не определяется до помещения в нее значения (другими словами у переменных нет типа, тип есть только у конкретных значений - объектов в случае Ruby).
В Ruby используется вид динамической типизации - Duck Typing.
Duck Typing это когда тип объекта определяется по тому что он умеет делать (имеющиеся у него методы).
Duck typing не заменяет Type Safety.
Type Safety реализуется проверкой данных на границы системы, например проверка параметров метода.
Для проверки данных в ruby можно использовать гем dry-validation.
Числовые типы данных (все числа наследуют от класса Numeric)
Для создания числа BigDecimal
require ‘bigdecimal’
Bigdecimal.new(‘0.1’)
или
0.1E1
Еще есть
%q(
строка
)
или для строк с интерпретацией кода (#{код})
%Q(строка)
или в нотации HEREDOC (встроенные документ):
переменная = <<-SQL # вместо SQL может быть другая строка
SELECT *
FROM users
ORDER BY name
SQL
Где “-“ перед первым SQL позволяет разместить закрывающее слово не в самом начале строки.
Что бы убрать пробелы в начале каждой строки можно использовать метод #strip_heredoc из ActiveSupport
переменная = <<-SQL.strip_heredoc
SELECT *
или метод ruby #squish
переменная = <<-SQL.squish
SELECT *
С версии 2.3 вместо #strip_heredoc можно указать перед закрывающей строкой ~
переменная = <<~SQL # вместо SQL может быть другая строка
SELECT *
FROM users
ORDER BY name
SQL
где ~ убирает все пробелы в отступах левее закрывающего символа SQL (в примере сверху выделены красным, ~ можно не указывать)
Для добавления строк лучше использовать « а не + :
переменная << 'строка'
переменная.sub(‘подстрока’, ‘подстрока’)
Замена каждого символа соответствующему в первом аргументе, на соответствующий ему символ во втором аргументе шаблон подстрока/символ
переменная.tr(‘символы’, ‘символы’)
Короткие массивы из строк лучше определять таким способом
массив = %w(строка1 строка2 строка3)
аналогично массив из символов
массив = %i(:символ1 :символ2)
Для извлечения значения из хэша лучше использовать fetch с указанием значения по умолчанию
хэш.fetch(ключ, значение по умолчанию)
С версии 2.3.0 безопасное извлечение по ключу (вернет nil, если ключ не найден) выглядит так (в хэше ищется значение с ключем1, затем в найденном значении, которое также является хешем ищется значение с ключем2 и т.д.):
хэш.dig(:ключ1, :ключ2, :ключN)
Вычисления вычисляемых значений по умолчанию в fetch лучше указывать в блоке
хэш.fetch(ключ) {вычисление значения по умолчанию}
Извлечение нескольких значений по ключам
переменная1, переменная2 = хэш.values_at(ключ1, ключ2)
Локальное время (local) - время в данном часовом поясе (UTC + смещение).
Экземпляр класса Time хранит и дату и время (внутри это время в секундах с 1 января 1970 г.).
Time.new
или
Time.now
Time.new(массив)
или в секундах от 1 января 1970
Time.at(секунды)
где массив это [год, месяц, число, час, минута, секунда]
Time.utc(массив)
или
Time.gm(массив)
Для получения обратно массива
время.to_a
При вычитании из одного объекта Time, другого будет получена разница в секундах
Time.new(1977, 18, 4, 5, 5,5) - Time.new(1977, 18, 4, 5, 5,5)
К экземпляру можно добавлять секунды, результатом будет новый объект Time
Time.new(1977, 18, 4, 5, 5,5) + 100
время.zone
время.utc_offset
С версии 1.9 Time по функционалу практически тоже, что и DateTime.
Возможные даты
Стандартный Time в Ruby работает с UTC и определенной в ОС часовым поясом, а Ruby on Rails расширяет его функционал до работы с различными часовыми поясами.
ActiveSupport::TimeWithZone расширяет Time, Date и DataTime (напрямую с классом TimeWithZone работать не надо, все операции выполняются через методы in_time_zone, Time, Date и DataTime).
Использование Time и DataTime в Ruby on Rails, то есть с расширением ActiveSupport::TimeWithZone -
Классу Time добавляются методы current и zone, а экземпляру in_time_zone.
Также добавляются методы типа 1.day.ago и т.п.
Time.zone
Time.zone = зона
Time.zone.now
Time.new(1977, 18, 4, 5, 5,5).in_time_zone(зона)
Time.current
Date.current
Локальные время и дата в ОС
Time.now
Date.today
Для сравнения объектов используется миксин Comparable, который подмешивается в класс сравниваемых объектов.
В этом миксине используется метод <=>, через который реализуются все остальные операторы сравнения: <, <=, ==, >=, > и метод between?, которые возвращают true или false.
Результат при сравнении левого операнда с правым методом<=>:
Для того что бы в пользовательском классе реализовать методы сравнения
Class
include Comparable
def <=>(объект)
код выдающий -1, 0, 1 при сравнении
значений self.величина и объект.величина
end
end
где величина - это тот параметр, по которому объекта класса будут сравниваться между собою
Операторы определения равенства 1) операнды один и тот же объект (см. объект.object_id) equal?
2) значения операндов и их классы равны eql?
Оператор === проверяет, что
Класс === объект
(1..100) === объект
/выражение/ === строка
объект === объект
Этот оператор используется (неявно) в case выражениях, то есть
case объект1
when объект2
end
фактически будет
case объект1
объект2 === объект1
end
а еще точнее
объект2.===(объект1)
end
Соответственно результат будет зависеть от того, как реализован метод === в классе объекта2.
Если объект2 это Класс, то результат может быть неожиданным, например в when может проверяться что объект1 это экземпляр объекта2 (Класса) – Класс.is_a?(объект1).
Например
String === 'строка' # вернет true
что аналогично
'строка'.is_a?(String) # вернет true
Если объект1 и объект2 это объекты, то поведение === аналогично ==.
Вместо сравнения с нулем, пустой строкой вместо оператора == лучше использовать соответствующие методы
объект.zero? # вместо объект == 0
объект.empty? # вместо объект == ''
условие && действие
или
действие if условие
условие || действие
или
действие unless условие
Условный однострочный оператор с альтернативным действием
условие ? действие : альтернатива
def метод
return unless условие
# код
end
loop do
# код
end
Для прерывания цикла можно использовать break.
Класс.ancestors
объект.object_id
объект.class
Просмотреть родительский класс класса объекта
объект.class.cuperclass
объект.methods — объект.class.superclass.methods
Класс определяемый внутри другого класса лучше определять внутри отдельного файла в папке с именем класса (класс/вложенный_класс.rb), при этом определение вложенного класса определяется так
class Класс
class ВложенныйКласс
end
end
class Класс < СуперКласс
def initialize
super(параметры)
end
end
Если класс служит только для хранения набора переменных экземпляра его целесообразно представить в виде новой структуры (Struct)
Person = Struct.new(:переменная1, :переменная2)
или с методом
Person = Struct.new(:переменная1, :переменная2) do
def метод
end
end
объект = Person.new (атрибут1, атрибут2)
Структуры часто применяют внутри классов для группировки атрибутов, относящихся к одной области.
OpenStruct аналогичен Struct, за исключением того, что позволяет “на лету” определять свойства и значения объекта
require 'ostruct'
s = OpenStruct.new
s.переменная = значение
Создание метода типа объект[параметр]
def [](параметр)
end
Модули используются для 1) Размещения функций и констант, которые будут использоваться в различных местах кода
module Модуль
MY_CONSTANT=1
def self.функция
# …
end
end
Модуль могут быть вложены один в другой
module Модуль1
module Модуль2
end
end
или так
module Модуль1
end
module Модуль1::Модуль2
end
Для использования файла с модулем
require 'модуль'
а затем можно использовать константы и функции модуля
Модуль::КОНСТАНТА
Модуль.функция
Что бы не указывать имена модулей при использовании их методов можно включить пространство имен модуля в текущее пространство имен директивой include
include Модуль
КОНСТАНТА
функция
Для повторного использования код руби можно разбивать на отдельные части – файлы (библиотеки).
Для защиты пространства имен код в файлах-библиотеках лучше оформлять в виде модулей.
Для включения в код таких файлов используется директива require
require 'файл_с_модулем'
Файл для загрузки через require будет искаться в папках из переменной окружения $LOAD_PATH.
Для того что бы require искал файлы с кодом, например в папке lib проекта, перед require используется следующий код
$LOAD_PATH << './lib'
или с 1.9.2 (с этой версии в $LOAD_PATH не попадает текущая директория) require_relative (ищет файлы относительно файла из которого вызывается require_relative)
require_relative 'файл_с_модулем'
Require не вызовет загрузку кода из файла, если код из этого файла ранее в скрипте загружен оператором require.
В отличии от него оператор load ‘файл’ загружает файл каждый раз при вызове load.
2) Создания Миксинов (вместо множественного наследования) – добавление методов определенных в модуле в различные классы (добавление методов экземпляра в класс)
module Модуль
def функция
end
end
для использования файла с модулем
requiry 'модуль'
class Класс
include Модуль
end
Добавить методы класса с помощью include в класс нельзя, для этого используется extend (метод может применяться к объектам в том числе и классам, в отличии от **include)
class Класс
extend Модуль
end
или
Класс.extend Модуль
Для эмуляции include через extend (реализуется тем, что при создании объектов класса всегда вызывается initialize):
class Класс
def initialize
extend Модуль
end
end
Для того что бы методы подмешиваемого к классу модуля искались прежде методов класса (переопределяли методы класса) используется директива
class Класс
prepend Модуль
end
3) Добавления методов модуля в объект, т.е. создание синглтон методов (добавление методов в eigenclass объекта)
obj.extend MyModule
Класс в Ruby это «усовершенствованный модуль» (родителем Class является Module)
Для того что бы можно было использовать определенные в модуле методы (методы, определенные в модуле без явного указания приемника – то есть не def self.метод) без его подмешивания в класс используется module_function
module Модуль
module_function
def метод
end
end
после чего метод можно использовать
Модуль.метод
Этого же результата можно достичь следующими способами (но так делать не рекомендуется) 1)
module Модуль
class << self
def метод
end
end
end
Запись class « объект означает выполнение последующего кода в контексте объекта — в данном случае Модуля.
2)
module Модуль
extend self
def метод
end
end
Если определенные таким способом методы модуля «подмешать» в класс (директивой include), то такие методы станут private методами экземпляров этого класса.
Глобальные переменные ($переменная) лучше заменять переменными модуля
module Модуль
class << self
attr_accesor :переменная
end
end
Модуль.переменная
ввод = gets.chomp
Библиотека предоставляет пользователю историю введенных строк, автодополнение.
require 'readline'
Readline.readline('строка приглашения', true)
где true означает сохранение введенной пользователем строки в истории.
IO объекты могут быть
Константы STDOUT (puts), STDIN (gets), STDERROR (warn) ссылаются на соответствующие объекты ввода-вывода IO.
файл =File.new('путь', 'режим')
Для определения пути вместо строки лучше использовать pathname
require 'pathname'
path = Pathname.new('строка')
Где режимы
Для закрытия файла
файл.close
Альтернативным способом является использование метода open – если в качестве параметра этому методу передать блок, то файл автоматически закроется после выполнения кода блока
File.open('путь','режим'){|файл| код}
При этом если файл отсутствует он будет создан методом open.
Большие файлы лучше считывать построчно методом
File.foreach(‘путь’,’режим’){|строка| код}
файл.readlines
Чтение файла
файл.read
или в блоке
File.open(‘путь’,’режим’){|файл| файл.read}
У экземпляра file (как и любого объекта класса IO) существуют следующие методы и итераторы
Чтение из файла
файл.each_line { | строка | код} – считывается каждая строка файла |
файл.each_byte { | байт | код} – считывается каждый байт файла |
файл.puts ['строка1', 'строка1'] # добавление в файл строк
файл.print 'строка' # добавление в строку файла текста
файл.write 'строка' # добавление в строку файла текста
файл << 'строка' # добавление в строку файла текста
Константа с именем файла исполняемого в настоящее время ruby скрипта:
__FILE__
File.expand_path(файл)
File.join(‘папка’, ’папка’)
File.dirname(«путь/файл»)
файл.gets
файл.read(количество считываемых символов)
файл.chomp
Текущая позиция в файле – файл.pos Установить позицию – файл.pos=(символ) Установить позицию на начало файла – файл.rewind Текущая строка – файл.lineno Проверка текущей позиции на конец файла – файл.eof?
файл.stat файл.stat.size
Переименование файла – File.rename Удаление файла – File.delete Проверка что файл — это обычный файл – File.file? Проверка доступности файла на чтение – File.readble?
require ‘fileutils’
FileUtils.copy
FileUtils.move
FileUtils - cd, chmod, chown, pwd, ln, touch, mkdir, rmdir
Работа с папками
Dir[«/папка/*»] – массив из имен всех файлов папки
Dir.pwd – текущая папка
Dir.chdir(путь)
Dir.entries
Dir.mkdir(имя)
Dir.delete(имя)
begin
код в котором контролируется появление исключений
rescue Исключение
код выполняемый при возникновении исключения
else
код выполняемый при отсутствии исключения
ensure
код выполняемый всегда
end
Если параметр Исключение в rescue не задан, по умолчанию будут отлавливаться ошибка StandardError Если в качестве параметра указать Exception, то будут перехватываться все исключения (Exception является родительским классом для всех остальных исключений). В приложении лучше перехватывать только StandardError, так как нет смысла обрабатывать ошибки синтаксиса, прерывания и т.п. (т.е. все Exception) в приложении.
Для того что бы передать сведения об ошибке в код выполняемые в rescue используется следующая конструкция:
rescue Исключение => переменная
Код использующий переменную
End
Если одни и те же исключения обрабатываются несколько раз их обработку лучше обернуть в метод:
def метод
yield
rescue IOError
обработка ошибки класса IOError
end
метод { контролируемый код }
В Ruby catch и throw используются для перехода от места вызова throw к выполнению кода, следующего за концом блока catch (аналог goto):
catch :метка do
код1
throw :метка if условие
код2
end
код3 # если сработает throw, то вместо кода 2 будет выполнен код3
Для протоколирования событий приложения используется библиотека Logger. Создание объекта – логера: require ‘logger’ журнал = Loger.new(вывод, level: уровень, progname: ‘программа’)
где вывод – куда протоколировать событий – STDOUT уровень – :info, :debug и т.д. progname – имя программы ruby (действия которой протоколируются)
Запись сообщения в журнал: журнал.уровень(‘сообщение’)
Создание CLI приложений require ‘thor’
Структуры данных
Массив следует использовать для хранения данных, если: требуется их упорядоченное хранение (сортировка)
Массив не следует использовать если: требуется осуществлять проверку наличия элемента в массиве и/или обращение к произвольному элементу массива по его значению. В таком случае в качестве структуры данных лучше использовать: 1) Set – как структуру (неупорядоченный набор неповторяющихся записей) с фиксированным временем поиска наличия элемента (можно использовать с #each, #include?, #map). 2) хэш – как структуру с фиксированным временем поиска элемента по ключу.
require ‘set’ a = Set.new [1, 2, 3] a.include? 2
Метапрограммирование в Ruby
Объект Объект представляет собой совокупность переменных экземпляра и ссылок на методы экземпляра в классе. Объекты взаимодействуют между собой через методы.
Переменные экземпляра Переменные экземпляра уникальны для каждого экземпляра. Если добавить в экземпляр переменную она будет только у конкретного экземпляра. А вот методы у всех экземпляров общие и определены в классе.
Класс как объект Класс – это тоже объект класса Class. Суперклассом класса Class (его родительским классом) является класс Module. В ruby имена, начинающиеся с большой буквы, являются константами. Имена классов – это константы. Важно понимать, что в ruby весь код в тексте программы, кроме кода в определении методов def, исполняется сразу. То есть при определении класса код в нем будет выполняться сразу, а не использоваться как «шаблон» при создании экземпляра.
Методы класса Все методы определенные в классе как self.define или Класс.define являются методами класса (т.е. при их вызове приёмником будет класс): сlass Класс def self.метод … end end
или сlass Класс class « self def метод … end end
end
Вызывается такой метод следующим образом: Класс.метод
И еще раз – так как ruby интерпретируемый язык, то определенный в классе код выполняется как только встречается в тексте программы, кроме заключенного в def кода методов.
Приватные методы (private) не могут быть вызваны с явным указанием приемника, приемник должен быть неявным (а это в таком случае всегда self экземпляра в контексте которого выполняется код) то есть: class Класс private def priv_met end self.priv_met # не сработает priv_met # сработает end
obj= Класс.new obj.priv_met # не сработает
protectd методы - методы который могут быть использованы потомками одного класса.
Методы синглтоны – это методы, имеющиеся только у одного объекта. Методы класса – это методы синглтоны, так как они имеются только у одного объекта – Класса. Синглтон методы могут также быть у модуля. Метод синглтон у объекта можно создать так:
объект = ’строка’ def объект.метод … end
или в контексте класса eigenclass: class « объект def метод … end end
При создании метода синглтона между объектом и его классом создается класс eigenclass (eigen, айген – собственный), который содержит добавленный метод синглтон. Суперклассом класса eigenclass является класс объекта. Для того что бы попасть в контекст класса eigenclass используется следующая конструкция: class « объект код в контексте eigenclass end
где объектом может быть как экземпляр класса так и Классом
Обобщая:
Hook Method – методы объектов класса Class или методы класса Module (то есть эти методы имеются у всех модулей, классов и экземпляров класса), вызываемые при определенных событиях. По умолчанию Hook Method методы ничего не делают, но их можно переопределить. Например, вызов определенных действий при событии – наследование класса:
class A def self.inherited(subclass) … end end
определенный выше метод будет вызван при наследовании:
class B < A … end
Другие Hook Method: включение модуля в класс – included(модуль). Пример автоматического вызова и создание в классе методов класса, определенного в модуле ClassMethods, при включении модуля М в класс С:
module M def self.included(klass) klass.extend(ClassMethods) end module ClassMethods def метод_класса ‘a class method’ end end end
class C include M end
Переопределение методов. При переопределении методов, новый метод полностью заменяет старый. При переопределении метода в суперклассе переопределятся методы всех субклассов. При переопределении методов субкласса методы суперкласса не изменяются. Переопределяющий метод (метод субкласса) может вызывать функционал переопределяемого метода (метода суперкласса) через super: class A def met end end
class B < A def met super end end
Модули Модули служат для разделения пространств имен. Для избегания конфликтов имен константы (в том числе классы и другие модули) следует помещать в модули. Обращение на константу в модуле осуществляется следующим образом: Модуль::Константа
Где константой может быть КОНСТАНТА, Модуль, Класс.
Для того что бы сделать метод доступным в любом месте программы его надо определить в модуле Kernel: module Kernel def метод … end end
::Константа – сошлется на самый верхний уровень
Классы и модули, определенные в модуле будут доступны только по пути (если не использовать include Модуль): Модуль::Класс Модуль:: Модуль
Класс Object (класс от которого наследуется все объекты) включает в себя модуль Kernel, который включает в себя все основные методы (функции языка) ruby (puts и т.д.) которые доступны всем потомкам Object, то есть доступны во всех контекстах программы (в том числе и в объекте main).
Прокси классы При включении модуля в класс с помощью инструкции include создается анонимный класс, который будет суперклассом класса, в который включается модуль. При этом методы модуля, теперь становятся методами анонимного класса (его еще называют прокси-классом) и доступны всем потомкам класса, включившего в себя модуль. Для добавления в класс методов модуля: module Модуль def метод … end end
class Класс include Модуль end
Расширение Класса - если в класс необходимо добавить методы класса из модуля, т.е. создать для класса eigenclass и включить в него синглтон метод: module Модуль def метод … end end
class Класс class « self include Модуль end end
или с помощью специального метода extend: class Класс extend Модуль end
или Класс.extend Модуль
Расширение Объекта - если в объект (экземпляр класса) необходимо добавить синглтон методы из модуля: class « объект include Модуль end
или с помощью специального метода extend: объект.extend Модуль
Надо вызвать его так: объект.метод = значение или self.метод = значение
Иначе метод нельзя будет отличить от присвоения значения локальной переменной.
Приемник нельзя указать явно для приватных (private) методов (в этом случае приёмник всегда текущий экземпляр класса, то есть – self). При поиске вызванного у объекта метода вызванный метод последовательно ищется в цепочке классов объекта: 1) сначала в eigenclass (если такой имеется), 2) затем в классе, 3) затем у суперкласса класса и т.д., включая методы из анонимных классов (прокси-классов), из модулей подключенных к классам объекта. 4) Если метод найден не будет, то вызовется метод модуля Kernel method_missing.
Динамическая отправка метода (Dynamic Dispatch) это способ вызова метода, при котором имя вызываемого метода определяется динамически. Реализуется это с помощью методов send (вызывает и приватные и публичные методы) или public_send (вызывает только публичные методы, с ruby 1.9): объект.send(:метод) объект.public_send(:метод)
Динамическое создание метода (Dynamic Method) это когда метод создается в процессе выполнения кода класса: define_method метод { | параметры создаваемого метода | код }
Динамическое создание метода класса: define_singleton_method метод { | параметры создаваемого метода | код }
Динамически созданный метод можно сделать приватным: private :метод
Пример использования (создание методов типа attr_accessor): class Суперкласс def self.метод1(параметр метода1) define_method(:метод2) do |параметр метода2| код end end end
Динамическое создание методов по типу attr_accessor (Pattern Dispatch или Class Macros): class Суперкласс def self.метод1 def метод2 … end end end
и в классе потомке: сlass Класс < Суперкласс метод1 # будет вставлен метод2 end
или class Суперкласс def self.метод1(параметр метода1) define_method(:метод2) do |параметр метода2| … end end end
и в классе потомке : сlass Класс < Суперкласс метод1 :параметр метода1 # будет вставлен метод2 end
Несуществующий метод (Ghost Method): def method_missing(name,*args) end
Псевдонимы методов – создается копия оригинального метода: alias new_method old_method
Вместо ключевого слова alias для создания псевдонима можно использовать метод Module#alias_method: alias_method :псевдоним, :метод
Можно создать псевдоним метода, затем можно переопределить старый метод организовав вызов внутри него старого метода по его новому псевдониму. Достигается это за счет того, что если методу назначен псевдоним, а затем метод переопределен, то псевдоним продолжает ссылаться на старую версию метода (до переопределения). Эта техника называется Around Alias.
def method … end
alias :simple_method, :method def method simple_method … end
alias :extended_method, :method
ActiveSupport в Rails добавляет метод Mudule#alias_method_chain который автоматически задает для оригинального метода два псевдонима – один без улучшения, а другой с улучшением
alias_method_chain :метод, :улучшение
Создаст следующие псевдонимы: метод_without_улучшение метод_with_улучшение
останется только переопределить метод с улучшением def метод_with_улучшение метод_without_улучшение … end
6. Области видимости локальных переменных (Scope Gate) Scope Gate это конструкции языка ruby устанавливающие окружение (ландшафт, привязки) выполнения текущих операторов ruby: module ... end class ... end def ... end Scope Gate ограничивают видимость локальных переменных, но не переменных экземпляра (начинаются с @). Границей видимости переменных экземпляра является объект (экземпляр), на текущий экземпляр указывает self (фактически self выдает информацию о том какой объект является текущим и в контексте какого объекта выполняется программа). Стоит отметить, что весь код ruby исполняется в контексте объекта main.
Flat Scope это область, в которой нет Scope Gate конструкций (class, def, module) и соответственно доступны все локальны переменные области. Реализуется такая область путем замены ключевых слов, используемых для определения классов и методов вызовом функции их создающих: Class.new Module.new define_method
Share Scope это общая область видимости переменных Flat Scope обернутая в Scope Gate. Пример реализации такой области внутри модуля (MyClass и my_method будут иметь общую область видимости переменных): module MyModule MyClass=Class.new do var1=1 end define_method (:my_method) do var1=var1+1 # теперь var=2 end end
Одним из способов смешивания областей видимости является использование метода instance_eval, который позволяет выполнить блок кода в контексте экземпляра, но при этом блоку доступны локальные переменные из области определения блока: a = 1 obj = Objet.new obj. instance_eval{puts a} С ruby 1.9 появился метод instance_exec, который умеет передавать параметр в блок: a = 1 obj = Objet.new obj.instance_exec(‘параметр+’){ | x | puts x + a } # выведет параметр + a
class_eval и module_eval выполняют код соответственно в контексте класса или модуля.
def метод a = 1 yeild end a = 2 метод { puts a } # будет выведено 2
Метод который выполняет код блока в контексте экземпляра: instance_eval {блок}
Методу в качестве параметра можно передать блок: метод(параметры) {блок}
Когда в методе исполнение кода дойдет до yield, произойдет вызов кода в блоке, при этом блоку будут переданы параметры, заданные при вызове yield:
def method(param) yield(param) end метод(‘УРА!’) {|word_to_print| puts word_to_print } # выведет УРА!
или c ruby 1.9: lamda_obj=- > ( параметры){блок} # создаем lambda proc_obj=proc{|параметры| блок} # создаем proc
Вызов на исполнение блока кода в объекте Proc: proc_obj.call(параметры) или lamda_obj.call(параметры)
Отличие lambda от proc: 1) Lambda проверяет количество параметров, переданных при вызове кода блока на исполнение, а proc нет (все параметры lambda являются одинаковыми); 2) Lambda выполняется в контексте lambda и команда return завершит выполнение блока кода в объекте lambda, а команда return в блоке объекта proc завершит выполнение не блока, а того контекста в котором был сделан вызов на исполнение кода в proc (например функции в которой запущен на выполнение код proc). Для автоматического преобразования блока в объект Proc, при передаче блока в качестве параметра методу, последним параметром в определении метода указывается имя параметра начинающееся с &:
def my_method(value, &my_block) other_mehod(&my_block) end
Также приставка & вызывает обратное преобразование объекта Proc в блок:
proc_obj = proc{ puts 1 } obj = Object.new obj.instance_eval(&proc_obj)
Команда & преобразует ее параметр (&параметр) в объект proc, то есть вызывает для своего аргумента метод to_proc (& = параметр.to_proc)
Clean Room – объект который создается только для выполнения блока в его контексте: obj = Objet.new obj.instance_eval{ puts 1 }
Прочее
Уверенный (правильный) код Ruby
Цель данной методики – получить код метода, который будет читаться как некая последовательность действий («история») по выполнению решаемой данным методом задачи.
Этапы проектирования структуры метода и объектной модели приложения 1) Определение сообщений – директив (будущих методов), посылаемых объектам приложения (переменным) для выполнения задачи этого приложения. Название сигналов целесообразно выбирать в терминологии области в которой решается задача. Сигналы используемые в методе должны быть с одного уровня абстракции (капитан не отдает указаний о количестве угля в топку или какой рукой рулевой вращает штурвал, он оперирует направлением и скоростью). 2) Формирование перечня ролей (переменных) которым будут предназначаться сообщения (у которых будут иметься соответствующие методы), то есть которые отвечают за выполнение соответствующих сообщений. 3) Описание в терминах ролей и принимаемых ими сообщений (переменных соответствующих объектов и их методов) последовательности работы метода. 4) Оформление полученного на этапе 3 описания как метода. В идеале роли должны совпасть с имеющимися в приложении классами.
Структура метода 1) Сбор данных 2) Выполнение работы 3) Выдача результата 4) Обработка ошибок
Сбор данных Исходными данными для метода могут быть cследующие внешние по отношению к методу источники: Параметр метода; Константа; Вызов метода класса (непрямой способ сбора данных, большое количество непрямых способов сбора увеличивает вероятность «поломки» приложения при внесении изменений в его структуру).
3.1 Использование встроенных в Ruby преобразований типов
Неявное (implicit) преобразование типов – метод преобразующий тип вызывается автоматически для параметра (объекта) передаваемого какой либо функции. Поменяется когда нам надо быть уверенным, что преобразование будет проведено если такой метод имеется у объекта к которому оно применяется. Пример: class EmacsConfigFile def initialize @filename=”#{ENV[‘HOME’]}/.emacs” end def to_path @filename end end emacs_config = EmacsConfigFile.new File.open(emacs_config).lines.count # к параметру передаваемому File.open автоматически (неявно) будет вызван метод #to_path
Явное (explicit) преобразование типов – метод преобразования явно вызывается у объекта, который преобразуется. Явное преобразование реализуется ядром Ruby, а не объектом к которому оно применяется. Поменяется когда нам надо гарантировано преобразовать в нужный тип. Примеры: объект.to_s объект.to_i
3.2 Условный вызов преобразования типов
def my_open(filename) filename = filename.to_path if filename.respond_to?(:to_path) filename = filename.to_str
end
3.4 Создание собственных преобразований типов def draw_line(start, endpoint) start = start.to_coords if start.respond_to?(:to_coords) start = start.to_ary end
Преобразование созданных программистом типов def draw_line(start, endpoint) start = start.to_coords if start.respond_to?(:to_coords) start = start.to_ary end
Прочее DevKit
DevKit/bin и rubyXXX/bin должны быть прописаны в PATH. Все пути (к DebKit, rubyXXX) должны быть без пробелов, иначе будет вываливаться ошибка неправильной опции RUBYOЗT (первая буква после пробела в пути из PATH будет расцениваться как опция RUYOPT). Перед началом использования в devkit надо выполнить ruby dk.rb init ruby dk.rb install При этом если до этого использовалась другая версия rubyXXX (произошли изменения в confog.yaml) необходимо выполнить: ruby dk.rb install –f
DevKit/bin и rubyXXX/bin должны быть прописаны в PATH.
Паттерны Ruby
Не использовать super! При кастомизации методов родительского класса при инициализации дочернего, вместо вызова super лучше переопределять пустые родительские методы, вызываемые при инициализации родительского класса. Такой подход избавляет от необходимости вызывать метод super при инициализации дочернего класса.
class AClass def initialize код _custom # вызов пустого метода end def _custom # определение пустого метода end end
class BClass < AClass
def _custom # кастомизация (переопределение) пустого род. метода
end
end
Так как при инициализации внучатого класса все равно придется вызывать super, в более сложных случаях наследования лучше воспользоваться обратными вызовами из ActiveSupport. require ‘active_support/callbacks’ class AClass include ActiveSupport::Callbacks define_callbacks :метод def initialize код run_callbacks :метод end end def метод end end
class BClass < AClass set_callbacks :метод, after :метод2 #вызов родительского метода после дочернего
def метод2
end
end
Замер производительности кода require ‘benchmark’
puts Benchmark.measure { код }
Refinement
Для того что бы monkey patching (переопределение методов базовых классов) ограничивался определенной областью используется Refinement: module Модуль refine Класс do def метод код end end end после чего переопределенный метод будет доступен только в области в которой используется вызов using: using Модуль Класс. Метод
C 2.3 using может указываться внутри определения класса.
Модуль Enumerable Модуль используется (подмешиванием) для создания методов у пользовательских объектов использующихся для хранения коллекций - элементов которые могут сравниваться, сортироваться, перечисляться. При включении модуля Enumerable в пользовательский класс и определении в этом классе метода #each, для экземпляров класса станут доступны методы модуля Enumerable. class Класс include Enumerable
def initialize
@коллекция = [] # или {}W
end
def each &block
@коллекция.each {|элемент| block.call(элемент) }
end end При вызове у объекта в класс которого подмешан enumerable некоторых методов, в том числе each, map без параметров будет возвращен экземпляр класса Enumerator. Это дает возможность использовать методы Enumerable по цепочке. К примеру - аналог each with index: ['a', 'b', 'c'].map.each_with_index {|element, index| код}
Методы модуля Enumerable – преобразование в блоке каждого элемента; colleсt или map {|элемент| выражение преобразующее элемент}
поиск элементов, для которых выражение в блоке возвращает true: select {|элемент| выражение с элементом}
поиск первого элемента, для которого выражение в блоке возвращает true: detect {|элемент| выражение с элементом}
проверка что выражение в блоке хотя бы для одного элемента возвращает true: any?{|элемент| выражение с элементом}
проверка что выражение в блоке для всех элементов возвращает true: all? {|элемент| выражение с элементом}
отбросить элементы, для которых выражение в блоке возвращает true: reject {|элемент| выражение с элементом}
формирует аккумулятор последовательно размещая (замещая) в нем результат последнего вычисления (выражения) в блоке: inject или reduce(начальное значение) {|аккумулятор, элемент| выражение с элементом и аккумулятором}
для суммирования элементов можно использовать inject или reduce(:+)
или c 2.3 просто sum
Формирование результата - объекта (аккамулятора) из последовательности: each_with_object(объект) { |элемент, объект| код с объектом}
reduce (inject) и each_with_object отличаются следующим образом: 1) в reduce (inject) аккумулятор (объект) указывается первым параметром в блоке в each_with_object аккумулятор (объект) указывается вторым параметром в блоке 2) в reduce (inject) в аккумулятор в каждой итерации помещается результат последнего выражения в блоке в each_with_object аккумулятор (объект) аккумулятор надо изменять в блоке В итоге reduce (inject) лучше подходит для формирования агегированых результатов действий над элементами последовательности как одного значения (предельный случай такого использование метод sum) each_with_objec лучше подходит для формирования новых последовательностей (например для преобразования массива или хэша или получени нового массива или хэша)
zip
проверка того, что коллекция содержит объект: include? Объект
перебор элементов и получение их индекса: each_with_index {|элемент, индекс| выражение с элементом и индексом}
Объект в который подмешан Enumerable можно сделать ленивым: (1..Float::INFINITY).lazy .select { блок } .to_a .first(10) # вместо бесконечного перечисления, будут перечислены первые 10 элементов
Наваждение примитивов
Использование объектов простых классов (строк, массивов, хэшей), для хранения структур данных имеет следующие недостатки – не очевидно, какие данные хранит объект; необходимость изменить код их использующий при измени внутренней структуры. Что бы избежать этого необходимо использовать имеющиеся (Pathname, URI) или собственные классы вместо примитивов.
Работа с IP Создание объекта - IP адреса (из строки): require ‘ipaddr’ ip = IPAddr.new(’10.1.1.1’)
или из целого числа ip = IPAddr.new(167837953, Socket::AF_INET)
Новое в 2.3.0
«Безопасное» обращение к значению хэша по ключу (аналог try из Active Support) хэш.dig(:ключ1, :ключ2, :ключN)
«Безопасный» вызов метода по цепочке (аналог объект.метод1 && объект .метод1.методN)
объект&.метод1&.метод2&.методN
Конкурентное программирование
Три основных подхода к конкурентному программированию: 1) использование нескольких отдельных приложений, копий основного приложения (forks). Преимущество – изолированность данных каждого процесса от другого обеспечивает целостность этих данных. Недостаток – каждый процесс содержит одну и туже копию программного кода в памяти. Пример – unicorn. 2) использование нескольких потоков в рамках одного приложения. Преимущество – код программы общий и не дублируется (не происходит перерасходывания памяти). Недостаток – целостность данных не гарантируется (например, параллельно выполняемые потоки могут, не зная о действиях друг друга, одновременно изменять одну и туже область памяти). Этой проблемы нет в MRI (GIL не позволяет потокам работать одновременно (одновременно может осуществляться только ввод-вывод), но тогда теряется возможность утилизировать все ядра процессора). В тоже время rubinius и jruby позволяют утилизировать все ядра (в них нет GIL), при этом не будет гарантироваться целостность данных. Пример – puma. 3) Event-loop Преимущество – способность обслуживать большое количество конкурентных запросов. Пример – thin. В руби поток создается следующим образом: threads « Tyhread.new() do код потока end threads.each(&:join) # основная программа будет ждать завершения всех потоков (.join) — не прекратит работу пока все они не завершатся
Новое в 2.4
Быстрая проверка соответствия (без возврата совпавшего значения): /регулярное выражение/.match?(строка)
Создание хэша именованных совпадений в регулярном выражении: результат = /(?<имя>выражение)/.match.named_captures аналогично, но только результат - массив значений из перечисленных именованных групп: результат = /(?<имя>выражение)/.match.namvalues_at(:ключ1, ключ2)имя>имя>
Проверка пустых папок и файлов: Dir.empty?(папка) File.empty?(файл)
Метод перевода целого числа в массив цифр его составляющих: число.digist
Метод #sum для модуля Enumerable (суммирование элементов массива, значения хэша, конкатенация массива строк): массив.sum
так как по умолчанию начальное значение аккамулятора - 0, для объединения строк его надо указывать явно: массив.sum(‘’)
Новое в 2.5
Новый метод yield_self Метод yield_self улучшает читабельность кода, при вызове методов по цепочке - вместо объект.метод2(метод1(объект))
можно вызывать: объект.yield_self { | объект | результат } .yield_self { | объект | результат }
Отличия между yield_self, try и tap Все три метода передают в блок объект, который является приёмником этого метода. Отличаются они возвращаемым результатом: yield_self – возвращает результат последнего оператора в блоке объект.yield_self { | объект | результат } # вернет результат
try – возвращает результат последнего оператора в блоке, является частью Rails и не вернёт результат выражения в блоке если исходный объект nil nil. try { | объект | результат } # вернет nil
tap – возвращает исходный объект (объект не меняется в блоке) объект.tap { | объект | результат } # вернет объект