Pull to refresh

Покорим Ruby вместе! Капля одиннадцатая

Reading time 5 min
Views 17K
Еще одна капля в наш стакан Руби (1, 2, 3, 4, 5, 6, 7, 8, 9, 10). Поговорим о self, работе с CSV и обработке исключений.

Описывая и обсуждая компьютерные программы, мы часто используем образные и человеческие метафоры. Например, мы говорим, что находимся в классе или возвращаемся из вызова метода. Иногда имеет смысл говорить во втором лице, например, object.respond_to?("x"): “Эй, объект, ты ответишь на х? И пока программа интепретируется контекст меняется снова и снова.

Некоторые объекты везде означают одно и тоже, например, числа и ключевые слова вроде def и class. Однако значение большинства элементов зависит от контекста.

self и текущий объект


Один из краеугольных камней Руби – это текущий объект, доступный через ключевое слово self. В каждый момент работы программы есть только один self, узнать этот объект можно с помощью нескольких правил. Прежде всего необходимо знать контекст, типов его немного, вот они: высший уровень (top level), блоки определения классов, блоки определения модулей и блоки определения методов.

Высший уровень относится к коду, написанному вне каких-либо классов и модулей. Например, если мы откроем новый .rb файл и запишем только x=1, то мы создадим локальную переменную высшего уровня х. Если запишем
def m
end

то получим метод высшего уровня. В высшем уровне Руби предоставляет нам начальный (start up) self и, при попытке идентифицировать его, вернет main. main – это специальный термин, который использует объект self по умолчанию при обращении к самому себе.

В определении класса или модуля self – сам объект класса, вот демо:
class C
    
puts "Started class C:"
    
puts self     # C
    
module M
        
puts "Module C::M:"
        
puts self     # C::M
    
end
    
puts "Back C:"
    
puts self    # C
end
self внутри определения метода экземпляра хитрый, и вот в чем дело. Когда интерпретатор считывает блок def/end, он определяет метод незамедлительно. Однако код внутри метода исполняется только при его вызове объектом, который и будет self для этого метода.

Вот небольшая сводка поведения self:

image

БД в .txt


Многим приложениям необходимо хранить и манипулировать данными. Иногда для этого открываются файлы, делаются нужные изменения и результат выводится на экран либо сохраняется назад в файл. В некоторых ситуациях, однако, требуется база данных. База данных – это система для организации и хранения данных на компьютере в систематическом виде. База данных может быть простой как текстовый файл, который манипулируется самой программой или сложной, например, состоять из террабайтов данных, распределенных на выделенных серверах. Между прочим Руби можно использовать и в этом случае, однако мы рассмотрим вариант с самой простой БД.

База данных может представлять собой простой текстовый файл, как в случае с CSV (Comma-Separated Values), где для каждого пункта данных можно хранить атрибуты, разделенные запятыми. Давайте создадим файл db.txt, в который запишем данные CSV, например, такие:

Fred Bloggs,Manager,Male,45
Laura Smith,Cook,Female,23
Debbie Watts,Professor,Female,38

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

Руби содержит стандартную библиотеку cvs, позволяющую использовать текстовые файлы, содержащие CSV данные, как простую базу данных, с которой легко манипулировать. Библиотека содержит класс CSV, который и поможет нам в работе. Загрузим данные из CVS файла в массив с помощью метода read класса CSV и прочитаем и изменим что-нить из массива:
require 'csv'
a=CSV.read('db.txt')
puts a.inspect
puts a[0][0] # Fred Bloggs
puts a[1][0] # Laura Smith
puts a[2][0] # Debbie Watts
a[1][1] = "Marine"
p(a[1]) # ["Laura Smith", "Marine", "Female", "23"]

Теперь, когда мы получили необходимые нам данные, нужно сохранить CSV обратно, модуль csv сделает все за нас:
CSV.open('bd.txt', 'w') do |csv|
    a.each do |a|
        csv << a
    end
end


Исключения


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

Менее радикальное решение – каждый метод должен возвращать что-то вроде статусных данных, в которых указано, была ли обработка успешна, а затем необходимо протестировать полученные от метода данные. Однако тестирование каждого результата сделает код плохочитаемым.

Еще один альтернативный подход – использовать исключения. Когда что-то идет не так (т.е. появляется условие исключения) выдаются исключения. На высоком уровне в программе будет кусочек кода (обработчик исключений), который будет следить за появлением такого сигнала и реагировать на него определенным способом.

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

Представим себе текстовый редактор. Пользователь должен ввести имя в диалоге SaveAs и нажать ОК. Так как пользователь сам решает, какие данные вводить, мы не можем знать, имеет ли он права для записи этого файла, есть ли свободное место на диске. Будем использовать исключения:
text = editor()
location = ask_user()
begin
    
File.open(location, w) do |file|
        save_work(file, text)
end
rescue
    
puts "Сохранение не удалось. Ошибка: #{$!}"
end

Теперь если что-то пойдет не так, то программа не завершит работу, данные не потеряются и у нас будет второй шанс. Все, что находится между begin и rescue защищено. Если появляется исключение, то контроль передается блоку между rescue и end. Глобальная переменная $! передает сообщение об ошибке и оно выводится на экран. Для того, чтобы контролировать только отдельные виды исключений, мы записываем из в rescue. Например, чтобы обрабатывать только ошибки при записи файла, используем выражение rescue IOError. Если мы хотим перехватывать несколько видов исключений в один обработчик, то перечисляем их через запятую, либо (что удобнее) написать обработчик для каждого вида:
rescue IOError
puts Не удалось записать на диск -- $!.
rescue SystemCallError
puts Произошла ошибка системного вызова -- $!.
end


Эпилог


Да, я не забываю о чистом Руби и по мере возможности и необходимости буду продолжать писать. Может быть куски получаются отрывистые, но я пишу то, что интересует в данный момент — надеюсь, понравится и вам. Как всегда — жду комментарии!
Tags:
Hubs:
+24
Comments 16
Comments Comments 16

Articles