Ghost Methods в Ruby (перевод)

от автора

Предлагаю вашему вниманию перевод фрагмента книги Metaprogramming Ruby 2 за авторством Паоло Перротта (Paolo Perrotta).

Что такое method_missing?

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

class Lawyer; end nick = Lawyer.new nick = talk_simple  NoMethodError: undefine method 'talk_simple' for #<Lawyer:0x007f801aa81938> 

Помните ли вы как работает поиск методов? Когда вы вызываете метод talk_simple, Ruby идет в класс объекта nick, и перебирает там методы. Если он не может найти метод там, он ищет его в родителей данного класса, потом в Object и наконец в BasicObject. Ну и поскольку Ruby не может нигде найти метод talk_simple, он запускает метод method_missing для nick’a. Ruby знает что этот метод есть, потому, что это приватный метод BasicObject от которого наследуются все объекты.

Вы можете сами вызвать для эксперимента этот метод. Поскольку метод приватный, вы может сделать это так:

nick.send :method_missing, :my_method  NoMethodError: undefine method 'talk_simple' for #<Lawyer:0x007f801aa81938> 

Только что вы сделали то что делает Ruby, Вы сказали объекту “Я только что попытался вызвать метод :my_method, а его нет, так что, верни мне ошибку.” BasicObject#method_missing возвращает NoMethodError, по сути для этого он и существует.

Переопределение метода method_missing

Скорее всего вам никогда не нужно будет вызывать метод method_missing самими. Вместо этого вы можете переписать этот метод для перехвата неизвестных вызовов. Каждый такой вызов хранит в себе информацию о названия метода и аргументы которые были переданы.

class Lawyer   def method_missing(method, *args)     puts "You called: #{method}(#{args.join(', ')})"     puts "(You also passed it a block)" if block_given?   end end  bob = Lawyer.new bob.talk_simple('a', 'b') do # a block end  You called: talk_simple(a, b) (You also passed it a block) 

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

Ghost Methods

Когда вам нужно объявить множество подобных методов, спастись от этого поможет вызовов их через method_missing. Это как сказать объекту, что “Если вызывают метод которого не существует, просто сделай это”. Со стороны вызова метода который работает через method_missing, все выглядит как обычный вызов обычного метода, но внутри класса, этого метода просто не существует. Именно этот трюк и называют Призрачным Методом (Ghost Method). Давайте посмотрим на примеры.

Hashie пример

Гем Hashie содержит немного магии под названием Hashie::Mash. Mash это более мощная версия стандартной библиотеки Ruby OpenStruct, это хеш-подобный объект, атрибуты которого работают как переменные Ruby. Если вам нужен новый атрибут, вы просто объявляет значения для данного атрибута. Вот как это работает.

require 'hashie'  icecream = Hashie::Mash.new icecream.flavor = 'strawberry' icecream.flavor                        # => 'strawberry'  

Это работает потому что Hashie::Mash субклас Ruby Hash, и эти атрибуты по сути являются призрачными методами. Вот как здесь реализован method_missing:

module Hashie   class Mash< Hashie::Hash     def method_missing(method_name, *args, &blk)       return self.[](method_name, &blk) if key?(method_name)       match = method_name.to_s.match(/(.*?)([?=!]?)$/)       case match[2]       when "="         self[match[1]] = args.first                 # ...             else         default(method_name, *args, &blk)       end     end      # ...   end end 

Если имя вызываемого метода это имя ключа в хеше (как flavor), тогда Hashie::Mash#method_missing просто вызывает [] метод для возврата соответствующего значения. Если же имя заканчивается “=”, тогда method_missing отрезает лишние символы и получает значение которое нужно сохранить. Если же имя не содержит что то другое, тогда method_missing просто возвращает какое-то стандартное значение. Hashie::Mash также содержит и другие специальные символы, например “?”.

respond_to_missing?

Если вы захотите проверить призрачный метод с помощью respond_to?, то понятное дело Ruby вам соврет.

john = Lawyer.new john.respond_to?(:talk_simple)        # => false 

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

respond_to? вызывает метод respond_to_missing? который возвращает true если это призрачный метод. Для того что бы это не приносило больше проблем при разработке, переопределяйте каждый раз вместе с method_missing и respond_to_missing.

class Lawyer   # ...    def respond_to_missing?(method, include_private = false)     methods.include?(method) || super   end end  bill = Lawyer.new bill.respond_to?(:talk_simple)          # => true 

Немного раньше рубисты переопределяли сам respond_to? метод. Но теперь есть respond_to_missing? и переопределение respond_to? рассматривается как не правильный вариант работы с method_missing.

Ghost Methods — это лишь малая часть интересных возможностей в Ruby 2.

ссылка на оригинал статьи http://habrahabr.ru/post/269785/


Комментарии

Добавить комментарий

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