Последние дни мы с Карлом работали над системой плагинов. В частности, мы прошлись по Rails Plugin Guide. Читая гайд, мы заметили много излишеств в представленных там идиомах.
Я не упрекаю автора гайда; представленные идиомы в точности повторяют те, которые использовались с самых ранних дней Rails. С другой стороны, глядя на них я вспоминал те дни, когда при виде такого кода мне казалось, что Ruby полон магических заклинаний и относительно простые вещи требуют проведения каких-то особых церемоний (вроде танца с бубном. — Прим. перев.).
Вот пример:
Copy Source | Copy HTML
module Yaffle
def self.included(base)
base.send :extend, ClassMethods
end
module ClassMethods
# любой метод здесь будет применяться классом, например, Hickwall
def acts_as_something
send :include, InstanceMethods
end
end
module InstanceMethods
# любой метод тут будет применяться экземпляром, например, @hickwall
end
end
Начнем с того, что send вообще не нужен. Метод
acts_as_something будет вызываться в самом классе, что даст ему доступ к приватному методу include.Этот код будет использоваться следующим образом:
Copy Source | Copy HTML
class ActiveRecord::Base
include Yaffle
end
class Article < ActiveRecord::Base
acts_as_yaffle
end
Этот код
- Регистрирует хук для того, чтобы при инклуде модуля класс расширялся методами из
ClassMethods - В нем (в
ClassMethods) объявляет метод, который инклудитInstanceMethods - Чтобы вы могли использовать
acts_as_somethingв своем коде
Copy Source | Copy HTML
module Yaffle
# любой метод здесь будет применяться классом, например, Hickwall
def acts_as_something
send :include, InstanceMethods
end
module InstanceMethods
# любой метод тут будет применяться экземпляром, например, @hickwall
end
end
Чтобы потом использовать в:
Copy Source | Copy HTML
class ActiveRecord::Base
extend Yaffle
end
class Article < ActiveRecord::Base
acts_as_yaffle
end
В двух словах, нет смысла оверрайдить
include, чтобы он вел себя как extend, если в Ruby есть они оба!Вы можете сделать:
Copy Source | Copy HTML
module Yaffle
# любой метод здесь будет доступен экземплярам, например, @hickwall,
# потому что это то, как работают модули!
end
Чтобы потом использовать в:
Copy Source | Copy HTML
class Article < ActiveRecord::Base
include Yaffle
end
На самом деле, начальный код (оверрайд хука для инклуда, чтобы расширить класс через
extend, который дальше инклудит модуль) — это два слоя абстракции вокруг простого инклуда в Ruby!Давайте рассмотрим еще несколько примеров:
Copy Source | Copy HTML
module Yaffle
def self.included(base)
base.send :extend, ClassMethods
end
module ClassMethods
def acts_as_yaffle(options = {})
cattr_accessor :yaffle_text_field
self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s
end
end
end
ActiveRecord::Base.send :include, Yaffle
Снова, идиома оверрайда
include, чтобы он вел себя как extend (вместо простого вызова extend!).Решение получше:
Copy Source | Copy HTML
module Yaffle
def acts_as_yaffle(options = {})
cattr_accessor :yaffle_text_field
self.yaffle_text_field = options[:yaffle_text_field].to_s || "last_squawk"
end
end
ActiveRecord::Base.extend Yaffle
В этом случае следует использовать
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
module Yaffle
def self.included(base)
base.send :extend, ClassMethods
end
module ClassMethods
def acts_as_yaffle(options = {})
cattr_accessor :yaffle_text_field
self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s
send :include, InstanceMethods
end
end
module InstanceMethods
def squawk(string)
write_attribute(self.class.yaffle_text_field, string.to_squawk)
end
end
end
ActiveRecord::Base.send :include, Yaffle
Снова include оверрайдят для выполнения
extend, и вызывают send, хотя это не нужно. Идентичная функциональность:Copy Source | Copy HTML
module Yaffle
def acts_as_yaffle(options = {})
cattr_accessor :yaffle_text_field
self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s
include InstanceMethods
end
module InstanceMethods
def squawk(string)
write_attribute(self.class.yaffle_text_field, string.to_squawk)
end
end
end
ActiveRecord::Base.extend Yaffle
Конечно, можно сделать и так:
Copy Source | Copy HTML
module Yaffle
def squawk(string)
write_attribute(self.class.yaffle_text_field, string.to_squawk)
end
end
class ActiveRecord::Base
def self.acts_as_yaffle(options = {})
cattr_accessor :yaffle_text_field
self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s
include Yaffle
end
end
Так как модули всегда инклудятся в
ActiveRecord::Base, предыдущий код с дополнительными модулями и использованием extend не хуже простого переоткрытия класса и добавления метода acts_as_yaffle напрямую. Теперь можно положить метод squawk прямо внутрь модуля Yaffle, откуда он легко заинклудится.Может это и не сильно важно, но так заметно уменьшается количество обманчивой магии в шаблоне написания плагинов, делая его более доступным пользователю. В добавок, новый пользователь имеет возможность быстро вникнуть в работу
include и extend без ложного впечатления необходимости магических заклинаний, использования send и специальных модулей типа ClassMethods для того, чтобы плагины заработали.Чтобы было ясно, я не говорю, что эти идиомы не нужны в некоторых специальных, продвинутых случаях. С другой стороны, я говорю, что в наиболее распространенных случаях они сильно загромождают код, что скрывает реальную функциональность и вводит пользователя в тупик.



комментарии (3)