Перейти к содержанию

Ruby/Методика самопознания

Материал из Викиучебника — открытых книг для открытого мира

Методика самопознания

[править]

Ruby очень динамичный язык программирования, а это значит, что с ходом выполнения программы состояние/структура объектов/классов может изменяться до неузнаваемости. Казалось бы, программисты на Ruby -- самые настоящие мазохисты. Возможно это и так, но не из-за того, что они программируют на Ruby, так как в него встроены достаточно развитые средства самопознания (унаследованные от языка Smalltalk).

Что такое самопознание?

[править]

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

Информация
  • Под текущим состоянием мы будем понимать совокупность методов, атрибутов и констант, из которых состоит объект или класс в определенный момент времени выполнения программы
  • В остальных учебниках самопознание именуется как отражение или рефлексия (на английском языке это понятие звучит как reflection)

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

Информация

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

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

Информация

Совокупность методов самомодификации и самопознания называют методами метапрограммирования

Какому классу принадлежит объект?

[править]

Язык программирования Ruby является строго типизированным (как и большинство других языков), то есть никогда не существует неопределённости по поводу класса того или иного объекта. Долгое время Ruby считали не строго типизированным языком, но это заблуждение, которое возникло из динамической структуры языка: класс переменной определяется объектом, на который ссылается эта переменная и вместе со сменой объекта может поменяться и класс переменной.

Информация

Авторы дают неверную интерпретацию понятия "строго типизированный"

Исходя из вышеизложенного, мы можем оказаться в ситуации, когда необходимо узнать, какой класс хранится в той или иной переменной (или возвращается каким либо методом). Для этой цели служит метод .class ("неожиданно", правда?), который возвращает класс, которому принадлежит данный объект.

"типичная строка".class #-> String
0xface.class            #-> Fixnum
9876543210.class        #-> Bignum
1234.class.class        #-> Class
Fixnum.class            #-> Class

Обратите внимание, что метод .class возвращает объект класса Class (очередная неожиданность). А почему же не строка? Дело в том, что от класса можно сразу же вызывать методы этого класса. Например, нужно создать объект точно такого же класса, как и у переменной my_variable. Тогда ваш код может выглядеть следующим образом:

my_variable = [1, 2, 3, 4]
my_variable.class           #-> Array
my_variable.class.new       #-> []
Array.new                   #-> []

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

Информация

У метода .class есть синоним .type, но последние версии интерпретатора выдают предупреждение, что метод .type устарел и в дальнейшем исчезнет из языка

От какого класса унаследован этот класс?

[править]

Другим инструментом исследования классовой иерархии является метод .superclass, который предназначен для получения класса родителя. Заметим, что метод .superclass можно вызывать только для объекта класса Class, который, в свою очередь, можно получить посредством метода .class. Какое-то сложное определение получилось, но давайте рассмотрим работу метода .superclass на примере:

Integer.superclass                #-> Numeric
123.class.superclass              #-> Integer
123.class.superclass.superclass   #-> Numeric
Numeric.superclass                #-> Object
Object.superclass                 #-> nil

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

Информация
  • Все пользовательские классы унаследованы от класса Object, даже если в явном виде это не указано
  • Класс Object единственный класс, который не имеет родителя (суперкласса) и поэтому метод .superclass вынужден вернуть nil
  • В версии Ruby 2.0 класс Object имеет родителя BasicObject

Какие классы/модули использовались этим классом?

[править]

На первый взгляд может показаться странным такой вопрос, так как мы уже знаем метод superclass, что дает нам возможность получить достоверную информацию о иерархии родительских классов. Дело в том, что в языке Ruby отсутствует множественное наследование, которое компенсируется механизмом примесей. Вот для того, чтобы получить массив всех классов/модулей, которые относятся к данному классу и существует метод .ancestors. Посмотрим его в деле:

Fixnum.ancestors #-> [Fixnum, Integer, Precision, Numeric, Comparable, Object, Kernel]
Информация

В соответствии с негласным правилом, методы, имена которых заканчиваются на s возвращают массив. Это правило получило свое дальнейшее развитие в Ruby on Rails и его следует придерживаться всем разработчикам на Ruby

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

Как получить список примесей класса?

[править]

Поработав с методом .ancestors может возникнуть резонный вопрос, а что если нужно получить только список примесей без родительских классов? В принципе, для этого надо знать только то, что все примеси имеют класс Module. Узнать это можно, если выполнить простенькую программу:

[Fixnum, Integer, Precision, Numeric, Comparable, Object, Kernel].
  map{ |c| c.class } #-> [Class, Class, Module, Class, Module, Class, Module]

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

Fixnum.ancestors.select{ |c| c.class == Module } #-> [Precision, Comparable, Kernel]

Но зачем писать больше, если для этого есть специальный метод .included_modules, который возвращает список подключенных примесей.

Fixnum.included_modules #-> [Precision, Comparable, Kernel]

Ну, а если результат одинаковый, то для решения этой задачи лучше вызвать один метод .included_modules вместо двух (ancestors и .select).

Как получить список всех родительских классов?

[править]

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

my_variable = 123
class_array = my_variable.class.ancestors - 
  my_variable.class.included_modules  #-> [Fixnum, Integer, Numeric, Object]
class_array.join(" < ")               #-> "Fixnum < Integer < Numeric < Object"

Последняя строка примера мне особенно нравится. В ней наглядно представлена вся иерархия наследования для класса Fixnum. Очень странно, что такой вопрос довольно часто возникает, а специального метода для его решения -- нет. Непорядок!

class Class
  def hierarchy
    ancestors - included_modules
  end
end

Fixnum.hierarchy.join(" < ")   #-> "Fixnum < Integer < Numeric < Object"

Для поиска списка всех родительских классов мы создали метод .hierarchy в классе Class. Родительские классы возвращаются в виде массива объектов класса Class. Вот теперь порядок!

Информация

Следует помнить, что подключенные примеси и используемые классы -- это понятия, которые действительны только для классов (то есть объектов класса Class)

Является ли данный объект экземпляром этого класса?

[править]

Вопрос дурацкий, но тем не менее стоит того, чтобы его осветили. Почему дурацкий? Да потому, что мы запросто можем узнать класс объекта при помощи метода .class и сравнить два класса при помощи методов == и !=.

Fixnum == 5.class   #-> true
[].class != Array   #-> false
String == Array     #-> false

Для подобной же цели придуман специальный метод .instance_of?, который позволяет заменить связку методов .class и ==.

5.instance_of?( Fixnum )     #-> true
5.instance_of?( Integer )    #-> false
[].instance_of?( Array )     #-> true
String.instance_of?( Array ) #-> false
String.instance_of?( Class ) #-> true
Информация

Метод .instance_of? можно перевести на русский, как вопрос "является ли экземпляром класса?"

Обратите внимание, что число пять хоть и является целым, но метод .instance_of? не признает его экземляром класса Integer. В принципе он прав: число пять является экземпляром класса Fixnum, а не Integer. Это означает, что метод .instance_of? излишне строг.

И что же нам делать, если эта строгость нам ни к чему? Для этого можно воспользоваться методами .class, .ancestors и .include?, которые будут учитывать не только текущий класс, но и его родительские классы с примесями.

my_number = 5
my_number.class.ancestors.include?( Fixnum )      #-> true
my_number.class.ancestors.include?( Integer )     #-> true
my_number.class.ancestors.include?( Enumerable )  #-> false
my_number.class.ancestors.include?( Comparable )  #-> true

Подобный код замечательно решает поставленную нами задачу, но выглядит громоздким. Немудрено, что для подобной задачи применяется специальный метод .is_a? (и его менее популярный псевдоним .kind_of?).

my_number = 5
my_number.is_a?( Fixnum )      #-> true
my_number.is_a?( Integer )     #-> true
my_number.is_a?( Enumerable )  #-> false
my_number.is_a?( Comparable )  #-> true

Результаты примера следует интерпретировать так: число пять является целым (класс Integer), но не слишком большим (класс Fixnum); при этом, элементы этого типа можно сравнивать между собой (примесь Comparable), но перечислимым множеством они не являются (примесь Enumerable).

Информация
  • Метод .is_a? используется чаще своего псевдонима .kind_of?, так как количество символов в этом методе меньше (на целых три). А если не видно разницы, то зачем писать больше?
  • Метод .is_a? можно перевести на русский, как вопрос "является ли?"
  • Псевдоним .kind_of? создан для переселенцев с других языков программирования, вроде C++ и Java

А как сравнивать классы между собой?

[править]

Сравнением классов мы занимались и ранее, но использовали только методы != и ==. Помимо них, можно использовать и все остальные: >, <, >=, <= и даже <=>.

Информация

Подобное разнообразие методов сравнения связано с тем, что в класс Class включили примесь Comparable, то есть сделали объекты класса Class сравнимыми между собой

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

Fixnum < Numeric #-> true
Object > Integer  #-> true
Float < Integer   #-> nil
IO <= File        #-> false

Метод "меньше" воспринимается Ruby, как вопрос "является ли потомком?", то есть первый пример можно расценить как вопрос вида: "класс Fixnum является ли потомком класса Numeric?" Если мы взглянем на иерархию наследования чисел, то увидим, что утвердительный ответ (true) на этот вопрос вполне оправдан.

Следуя той же логике, метод "больше" воспринимается как вопрос "является ли родителем?", то есть второй пример можно расценить как вопрос вида: "класс Object является ли родителем класса Integer?" Так как все классы унаследованы от класса Object (кроме самого Object), то утвердительный ответ (true) не должен нас сильно удивить.

Третий пример нам демонстрирует одну интересную особенность методов сравнения: если классы не состоят в отношениях "родитель-потомок", то они являются несравнимыми и поэтому метод сравнения отреагировал на эту ситуацию возвратом nil.

Информация

Напомним, что в логическом контексте объект nil равносилен объекту false (то есть они являются взаимозаменяемыми)

Последний пример демонстрирует работу метода "меньше или равно". Он соединяет в себе методы "меньше" и "равно", то есть воспринимается как вопрос вида: "является ли класс IO классом File или его потомком?" Исходя из того, что класс File является потомком класса IO, а не наоборот, ответ на этот вопрос отрицательный (false).

Какие константы доступны?

[править]

Чтобы узнать какие константы объявлены в том или ином классе/модуле необходимо вызвать метод .constants (еще одна "неожиданность") от этого класса/модуля.

class MyClass
  MY_CONST = "все равно какое значение"
end

MyClass.constants #-> ["MY_CONST"]
Math.constants    #-> ["E", "PI"]
Math::E           #-> 2.71828182845905
Math::PI          #-> 3.14159265358979
MyClass::MY_CONST #-> "все равно какое значение"
Информация
  • Имена всех констант начинаются с ПРОПИСНОЙ латинской буквы, но обычно их записывают полностью ПРОПИСНЫМИ буквами
  • Все константы являются элементом какого либо класса/модуля, то есть всегда привязаны в какому либо классу/модулю. Если класс/модуль для константы явно не указан, то имеется в виду базовый класс Object
  • Обратите внимание, что метод .constants возвращает не значения констант, а только их имена в виде строк

Эта константа объявлена?

[править]

С высоты полученных ранее знаний, мы можем решить эту задачу следующим образом:

Math.constants.include?( "Pi" )           #-> false
Math.constants.include?( "PI" )           #-> true

Для решения задачи использовалась комбинация методов .constants и .include?, что не совсем рационально, так как метод .constants возвращает массив, содержащий список всех констант модуля или класса. В нашем случае это не совсем критично, но существуют классы (вроде Object), где количество констант может составлять десятки, а то и сотни. Поэтому, правильней будет использовать специальный метод .const_defined?, который сделает то же самое, но без промежуточного массива (чем сэкономит нам массу ценнейших микросекунд и байт памяти).

Math.const_defined?( "Pi" )           #-> false
Math.const_defined?( "PI" )           #-> true

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

Информация

Существует способ создать класс, но не присваивать ему имя. Только этот способ мы пока рассматривать не будем

Object.const_defined?("Math")     #-> true
Object.const_defined?("Complex")  #-> false

Из примера следует, что модуль Math с математическими методами доступен, а вот классу комплексных чисел повезло меньше.

Как по имени константы получить ее значение?

[править]

Давайте еще раз посмотрим на пример получения доступных констант:

Math.constants    #-> ["E", "PI"]

Мы лишь узнали имена констант, но их значения нам пока не доступны. Для того, чтобы получить значения константы существует метод .const_get (который также следует вызывать от конкретного класса или модуля). Для примера, давайте заменим имена констант из предыдущего примера на их значения:

Math.constants.map{ |const_name| 
  Math.const_get( const_name ) 
}#-> [2.71828182845905, 3.14159265358979]
  
Math.const_get( "PI" )  #-> 3.14159265358979
Math::PI                #-> 3.14159265358979
Информация

Обратите внимание, что внутри итератора .map нам приходится указывать конкретный модуль (Math), чтобы метод .const_get нормально работал. Иначе возникнет ошибка вида NoMethodError

Какие классы доступны для использования?

[править]

Так как имена всех классов являются константами, то сведем задачу к предыдущей -- найдем все константы класса Object (он является базовым для всех остальных классов), которые являются объектами класса Class:

classes = Object.constants.select{ |const_name| 
  Object.const_get( const_name ).is_a? Class 
}.sort  #-> ["ArgumentError", "Array", ..., "ZeroDivisionError"]

classes.size  #-> 79

Последняя сортировка (метод .sort) нужна для красоты, так как в исходном массиве (который вернул итератор .select) имена констант не упорядочены.

Информация

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

Все бы хорошо, но почти восемьдесят классов -- это перебор. Надо бы удалить классы, которые являются классами ошибок, так как они не очень нам интересны. Для этого надо избавиться от классов, имена которых заканчиваются на слово Error. Чтобы это осуществить, необходимо добавить к предыдущему примеру следующий код:

without_error = classes.select{ |class_name| 
  !class_name[/Error$/] 
} #-> ["Array", "BasicSocket", ..., "UnboundMethod"]

without_error.size  #-> 54

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

classes - without_error  #-> ["ArgumentError", ..., "ZeroDivisionError"]

Упорядоченность результата в последнем примере унаследована от упорядоченности массива classes.

Как изменить значение константы?

[править]

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

Информация

Любые попытки поменять значение константы будут вызывать бурную реакцию со стороны интерпретатора. Он будет выдавать предупреждение вида: "значение этой константе уже присвоено"

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

Pi = 3.14
Pi        #-> 3.14
Math::Pi = 3.1415
Math::Pi  #-> 3.1415
Информация

Создавать новую константу можно не только для корневого класса (Object), но и для любого другого модуля или класса (имя которого необходимо указать перед вызовом метода)

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

Object.const_set("Pi",3.14)
Pi                      #-> 3.14
Object.const_get("Pi")  #-> 3.14
Math.const_set("Pi",3.1415)
Math::Pi                #-> 3.1415
Math.const_get("Pi")    #-> 3.1415
Информация

Напоминаем, что для получения значения константы, заданной только своим именем, осуществляется методом .const_get

Еще немножко усложним задание. Пусть теперь в виде строки задано не только имя константы, но и имя класса. Тогда решение может выглядеть следующим образом:

Object.const_get("Math").const_set("Pi",3.1415)
Object.const_get("Math").const_get("Pi")    #-> 3.1415
Object.const_get("Math").const_get("PI")    #-> 3.14159265358979
Информация

Все это время мы создавали константу Pi в модуле Math. Имейте в виду, что в реальности это не требуется, так как в этом самом модуле уже задана константа PI, которая имеет достаточно точное значение числа Пи

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

Какие переменные доступны?

[править]

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

Какие глобальные переменные доступны?

[править]

Для того, чтобы получить список глобальных переменных надо вызвать метод global_variables (который следует вызывать без указания класса, так как он является закрытым).

global_variables                  #-> ["$-v", "$FILENAME", ..., "$KCODE"]
global_variables.include?( "$a" ) #-> false
$a = 1
global_variables.include?( "$a" ) #-> true

Метод .include? применен для того, чтобы читателю не пришлось просматривать весь список глобальных переменных в поисках $a.

Информация

Имена глобальных переменных всегда имеют префикс $. Переменные называются глобальными потому, что их область видимости распространяется на всю программу, а не только на какой-то конкретный класс, объект или метод


Какие локальные переменные доступны?

[править]

Список локальных переменных доступен посредством вызова метода local_variables (который также следует вызывать без указания класса, так как он тоже является закрытым).

local_variables   #-> []
a = 1
local_variables   #-> ["a"]
Информация

Имена всех локальных переменных начинаются со строчной latinskoi' буквы или знака _. Локальными они называются потому, что их область видимости распространяется только на конкретный блок или метод

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

Какую информацию можно получить о переменных экземпляра?

[править]

Теперь перейдем к более полезным и востребованным видам переменных: экземпляра (объекта) и класса. Начнем по-порядку, с переменных экземпляра.

Информация
  • Имена переменных экземпляра иногда называют атрибутами экземпляра, но мы будем называть переменные экземпляра атрибутами только в том случае, если они имеют внешний интерфейс (открытые методы изменения и получения значения)
  • Имена переменных экземпляра всегда имеют префикс @. Они называются переменными экземпляра потому, что их область видимости распространяется не дальше конкретного экземпляра, то есть текущее значение этих переменных определяет текущее состояние данного экземпляра

Метод получения массива имен переменных экземпляра называется .instance_variables (префикс instance_ практически всегда указывает на принадлежность к экземпляру).

class Sample
  def initialize
    @variable_1, @variable_2 = 1, "а можно не только число"
  end
end

my_object = Sample.new
my_object.instance_variables #-> ["@variable_1", "@variable_2"]

В данном примере мы придумали класс Sample и создали конструктор этого класса .initialize, который вызвали посредством обращения Sample.new. Конструктор вернул нам экземпляр класса Sample, от которого мы и вызвали метод .instance_variables.

Теперь мы можем получить значение любой из этих переменных посредством вызова метода .instance_variable_get:

my_object.instance_variable_get( "@variable_1" ) #-> 1
my_object.instance_variable_get( "@variable_2" ) #-> "а можно не только число"

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

my_object.instance_variable_set( "@variable_1", 2 )
my_object.instance_variable_get( "@variable_1" )    #-> 2

Помимо рассмотренных методов получения/изменения, есть еще и метод удаления переменной экземпляра .remove_instance_variable, то есть можно в любой момент времени удалить переменную экземпляра. Давайте рассмотрим, что из этого получится:

class Sample
  def initialize
    @variable_1, @variable_2 = 1, "а можно и число"
  end
  
  def remove( name )
    remove_instance_variable( name )
  end
end

my_object = Sample.new
my_object.instance_variables    #-> ["@variable_1", "@variable_2"]
my_object.remove("@variable_1")
my_object.instance_variables    #-> ["@variable_2"]

Создавать дополнительный метод .remove в классе Sample пришлось потому, что метод .remove_instance_variable является закрытым, то есть он может быть использован только внутри метода экземпляра. Если создание дополнительного метода не требуется, то можно использовать метод .instance_eval, который выполняет любой программный код в контексте экземпляра. Перепишем наш пример с использованием этого метода.

Информация

Блок метода .class_eval может иметь параметр -- текущий экземпляр (он же self), в контексте которого выполняется блок

class Sample
  def initialize
    @variable_1, @variable_2 = 1, "а можно и число"
  end
end

my_object = Sample.new
my_object.instance_variables        #-> ["@variable_1", "@variable_2"]
my_object.instance_eval{ remove_instance_variable( "@variable_1" ) }
my_object.instance_variables        #-> ["@variable_2"]
my_object.instance_eval{ remove_instance_variable( "@variable_2" ) }
my_object.instance_variables        #-> []

Кстати, метод .instance_eval можно использовать для получения значения переменной экземпляра. Например вот так:

Sample.new.instance_eval{ @variable_1 }   #-> 1

Какую информацию можно получить о переменных класса?

[править]

Работа с переменными класса осуществляется практически также, как и с переменными экземпляра. С той лишь разницей, что в названии методов вместо слова instance_ надо писать class_. На этом можно было бы и закончить, но давайте все таки рассмотрим примеры применения этих методов.

Информация
  • Имена переменных класса иногда называют атрибутами класса, но мы будем называть переменные класса атрибутами только в том случае, если они имеют внешний интерфейс (открытые методы изменения и получения значения)
  • Имена переменных класса всегда имеют префикс @@. Они называются переменными класса потому, что их область видимости распространяется не дальше конкретного класса, то есть текущее значение этих переменных определяет текущее состояние данного класса

В соответствии с вышеизложенным правилом, заменяем в названии метода .instance_variable слово instance_ на class_ и получаем название метода, который получает массив имен переменных класса.

class Sample
  @@variable_1, @@variable_2 = 1, "а можно и число"
end

Sample.class_variables  #-> ["@@variable_2", "@@variable_1"]

Как и прежде, мы создали класс Sample, но на этот раз не стали реализовывать конструктор .initialize, так как для работы с классом совсем не обязательно создавать его экземпляр (методом .new).

Пришло время рассмотреть методы получения и изменения значения переменной класса. В примере мы изменим значение переменной класса (методом class_variable_set) и сразу же получим ее новое значение (методом class_variable_get). Основная проблема заключается в том, что оба метода являются закрытыми. Поэтому их вызов возможен только в контексте метода класса. Для решения этой проблемы мы создадим метод Sample.change, который будет менять переменную и возвращать ее новое значение в качестве своего результата.

class Sample
  def Sample.change( name, value )
    class_variable_set( name, value )
    class_variable_get( name )
  end
end

Sample.change( "@@variable_1", 2 )  #-> 2

Честно говоря, идея создавать метод класса для того, чтобы изменить переменную класса -- не самая лучшая идея. Проблема заключается в том, что мы порождаем новую сущность, которая может нам и не понадобиться. Как раз для того, чтобы выполнить нужные нам закрытые методы без создания еще одного и существует метод .class_eval. Он позволяет выполнять произвольный программный код в контексте класса, а в качестве параметра принимает блок кода, который необходимо выполнить в нужном контексте. Перепишем наш пример с применением метода .class_eval:

Sample.class_eval{
  class_variable_set( "@@variable_1", 2 )
  class_variable_get( "@@variable_1" )  
}   #-> 2
Информация

Блок метода .class_eval может иметь параметр -- класс (объект класса Class), в контексте которого выполняется блок. В нашем случае он абсолютно бесполезен, но возможно, что кому-то и пригодится (например, если класс, от которого вызывается метод .class_eval -- неизвестен)

Ну и напоследок рассмотрим увлекательный процесс удаления переменных класса. Делается это при помощи метода remove_class_variable и при помощи уже известного нам метода .class_eval (так как метод remove_class_variable является закрытым).

class Sample
  @@variable_1, @@variable_2 = 1, "а можно и число"
end

Sample.class_variables        #-> ["@@variable_1", "@@variable_2"]
Sample.class_eval{ remove_class_variable( "@@variable_1" ) }
Sample.class_variables        #-> ["@@variable_2"]
Sample.class_eval{ remove_class_variable( "@@variable_2" ) }
Sample.class_variables        #-> []
Информация

Получить значение переменной класса без помощи метода class_variable_get (только при помощи .class_eval) у меня не получилось. Возможно, что это получится у вас

Какие методы доступны?

[править]

Посмотреть список доступных методов вызвав .methods При этом необходимо учитывать, что когда мы производим вызов метода для класса, там не будут содержаться методы, определенные в нем (и доступные для экземпляров этого класса). Так например

class Sample
  def method1
    puts '1st method'
  end
  def method2
    '2nd method'
  end
end
a = Sample.new

puts Sample.methods.include?(:method1)
puts a.methods.include?(:method1)

для первого случая вернет false, в то время, как при проверке наличия метода method1 в экземпляре(a) класса(Simple) вернет true

Можно ли вызвать этот метод?

[править]

Для проверки этого используется метод .respond_to?. Этот метод возвращает, отвечает ли класс на вызов переданного в .respond_to? параметры(имени метода) По аналогии с предыдущим примером

class Sample
  def method1
    puts '1st method'
  end
  def method2
    '2nd method'
  end
end
a = Sample.new

puts Sample.respond_to?(:method1)
puts a.respond_to?(:method1)

вернет

 false
 true

так же возможен вызов, когда методу передаются не символы, а стока - имя проверяемого метода:

puts Sample.respond_to?('method1')
puts a.respond_to?('method1')