Этот пост был опубликован 12 ноября 2009 года, но я думаю он не потерял своей актуальности, потому что плагины к Rails (и не только) все так же актуальны.
Последние дни мы с Карлом работали над системой плагинов. В частности, мы прошлись по Rails Plugin Guide. Читая гайд, мы заметили много излишеств в представленных там идиомах.
Я не упрекаю автора гайда; представленные идиомы в точности повторяют те, которые использовались с самых ранних дней Rails. С другой стороны, глядя на них я вспоминал те дни, когда при виде такого кода мне казалось, что Ruby полон магических заклинаний и относительно простые вещи требуют проведения каких-то особых церемоний (вроде танца с бубном. — Прим. перев.).
Вот пример:
Начнем с того, что send вообще не нужен. Метод
Этот код будет использоваться следующим образом:
Этот код
Чтобы потом использовать в:
В двух словах, нет смысла оверрайдить
Вы можете сделать:
Чтобы потом использовать в:
На самом деле, начальный код (оверрайд хука для инклуда, чтобы расширить класс через
Давайте рассмотрим еще несколько примеров:
Снова, идиома оверрайда
Решение получше:
В этом случае следует использовать
Еще один «более продвинутый» случай:
Снова include оверрайдят для выполнения
Конечно, можно сделать и так:
Так как модули всегда инклудятся в
Может это и не сильно важно, но так заметно уменьшается количество обманчивой магии в шаблоне написания плагинов, делая его более доступным пользователю. В добавок, новый пользователь имеет возможность быстро вникнуть в работу
Чтобы было ясно, я не говорю, что эти идиомы не нужны в некоторых специальных, продвинутых случаях. С другой стороны, я говорю, что в наиболее распространенных случаях они сильно загромождают код, что скрывает реальную функциональность и вводит пользователя в тупик.
Последние дни мы с Карлом работали над системой плагинов. В частности, мы прошлись по Rails Plugin Guide. Читая гайд, мы заметили много излишеств в представленных там идиомах.
Я не упрекаю автора гайда; представленные идиомы в точности повторяют те, которые использовались с самых ранних дней Rails. С другой стороны, глядя на них я вспоминал те дни, когда при виде такого кода мне казалось, что Ruby полон магических заклинаний и относительно простые вещи требуют проведения каких-то особых церемоний (вроде танца с бубном. — Прим. перев.).
Вот пример:
Copy Source | Copy HTML<br/>module Yaffle<br/> def self.included(base)<br/> base.send :extend, ClassMethods<br/> end<br/> <br/> module ClassMethods<br/> # любой метод здесь будет применяться классом, например, Hickwall<br/> def acts_as_something<br/> send :include, InstanceMethods<br/> end<br/> end<br/> <br/> module InstanceMethods<br/> # любой метод тут будет применяться экземпляром, например, @hickwall<br/> end<br/>end <br/>
Начнем с того, что send вообще не нужен. Метод
acts_as_something
будет вызываться в самом классе, что даст ему доступ к приватному методу include
.Этот код будет использоваться следующим образом:
Copy Source | Copy HTML<br/>class ActiveRecord::Base<br/> include Yaffle<br/>end<br/> <br/>class Article < ActiveRecord::Base<br/> acts_as_yaffle<br/>end <br/>
Этот код
- Регистрирует хук для того, чтобы при инклуде модуля класс расширялся методами из
ClassMethods
- В нем (в
ClassMethods
) объявляет метод, который инклудитInstanceMethods
- Чтобы вы могли использовать
acts_as_something
в своем коде
Copy Source | Copy HTML<br/>module Yaffle<br/> # любой метод здесь будет применяться классом, например, Hickwall<br/> def acts_as_something<br/> send :include, InstanceMethods<br/> end<br/> <br/> module InstanceMethods<br/> # любой метод тут будет применяться экземпляром, например, @hickwall<br/> end<br/>end <br/>
Чтобы потом использовать в:
Copy Source | Copy HTML<br/>class ActiveRecord::Base<br/> extend Yaffle<br/>end<br/> <br/>class Article < ActiveRecord::Base<br/> acts_as_yaffle<br/>end <br/>
В двух словах, нет смысла оверрайдить
include
, чтобы он вел себя как extend
, если в Ruby есть они оба!Вы можете сделать:
Copy Source | Copy HTML<br/>module Yaffle<br/> # любой метод здесь будет доступен экземплярам, например, @hickwall, <br/> # потому что это то, как работают модули!<br/>end<br/> <br/>
Чтобы потом использовать в:
Copy Source | Copy HTML<br/>class Article < ActiveRecord::Base<br/> include Yaffle<br/>end<br/> <br/>
На самом деле, начальный код (оверрайд хука для инклуда, чтобы расширить класс через
extend
, который дальше инклудит модуль) — это два слоя абстракции вокруг простого инклуда в Ruby!Давайте рассмотрим еще несколько примеров:
Copy Source | Copy HTML<br/>module Yaffle<br/> def self.included(base)<br/> base.send :extend, ClassMethods<br/> end<br/> <br/> module ClassMethods<br/> def acts_as_yaffle(options = {})<br/> cattr_accessor :yaffle_text_field<br/> self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s<br/> end<br/> end<br/>end<br/> <br/>ActiveRecord::Base.send :include, Yaffle <br/>
Снова, идиома оверрайда
include
, чтобы он вел себя как extend
(вместо простого вызова extend
!).Решение получше:
Copy Source | Copy HTML<br/>module Yaffle<br/> def acts_as_yaffle(options = {})<br/> cattr_accessor :yaffle_text_field<br/> self.yaffle_text_field = options[:yaffle_text_field].to_s || "last_squawk"<br/> end<br/>end<br/> <br/>ActiveRecord::Base.extend Yaffle <br/>
В этом случае следует использовать
acts_as_yaffle
, так как мы предлагаем дополнительные опции, которые не могли быть инкапсулированы с помощью нормального extend. (Загадочная фраза. В оригинале: In this case, it’s appropriate to use an acts_as_yaffle, since you’re providing additional options which could not be encapsulated using the normal Ruby extend. — Прим. перев.)Еще один «более продвинутый» случай:
Copy Source | Copy HTML<br/>module Yaffle<br/> def self.included(base)<br/> base.send :extend, ClassMethods<br/> end<br/> <br/> module ClassMethods<br/> def acts_as_yaffle(options = {})<br/> cattr_accessor :yaffle_text_field<br/> self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s<br/> send :include, InstanceMethods<br/> end<br/> end<br/> <br/> module InstanceMethods<br/> def squawk(string)<br/> write_attribute(self.class.yaffle_text_field, string.to_squawk)<br/> end<br/> end<br/>end<br/> <br/>ActiveRecord::Base.send :include, Yaffle <br/>
Снова include оверрайдят для выполнения
extend
, и вызывают send
, хотя это не нужно. Идентичная функциональность:Copy Source | Copy HTML<br/>module Yaffle<br/> def acts_as_yaffle(options = {})<br/> cattr_accessor :yaffle_text_field<br/> self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s<br/> include InstanceMethods<br/> end<br/> <br/> module InstanceMethods<br/> def squawk(string)<br/> write_attribute(self.class.yaffle_text_field, string.to_squawk)<br/> end<br/> end<br/>end<br/> <br/>ActiveRecord::Base.extend Yaffle <br/>
Конечно, можно сделать и так:
Copy Source | Copy HTML<br/>module Yaffle<br/> def squawk(string)<br/> write_attribute(self.class.yaffle_text_field, string.to_squawk)<br/> end<br/>end<br/> <br/>class ActiveRecord::Base<br/> def self.acts_as_yaffle(options = {})<br/> cattr_accessor :yaffle_text_field<br/> self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s<br/> include Yaffle<br/> end<br/>end <br/>
Так как модули всегда инклудятся в
ActiveRecord::Base
, предыдущий код с дополнительными модулями и использованием extend
не хуже простого переоткрытия класса и добавления метода acts_as_yaffle
напрямую. Теперь можно положить метод squawk
прямо внутрь модуля Yaffle
, откуда он легко заинклудится.Может это и не сильно важно, но так заметно уменьшается количество обманчивой магии в шаблоне написания плагинов, делая его более доступным пользователю. В добавок, новый пользователь имеет возможность быстро вникнуть в работу
include
и extend
без ложного впечатления необходимости магических заклинаний, использования send
и специальных модулей типа ClassMethods
для того, чтобы плагины заработали.Чтобы было ясно, я не говорю, что эти идиомы не нужны в некоторых специальных, продвинутых случаях. С другой стороны, я говорю, что в наиболее распространенных случаях они сильно загромождают код, что скрывает реальную функциональность и вводит пользователя в тупик.