#ruby-on-rails #ruby #methods
#ruby-on-rails #ruby #методы
Вопрос:
Допустим, у меня есть модель под названием Article:
class Article < ActiveRecord::Base
end
И затем у меня есть класс, который предназначен для добавления поведения к объекту article (декоратору):
class ArticleDecorator
def format_title
end
end
Если бы я хотел расширить поведение объекта article, я мог бы сделать ArticleDecorator модулем, а затем вызвать article.extend(ArticleDecorator), но я бы предпочел что-то вроде этого:
article = ArticleDecorator.decorate(Article.top_articles.first) # for single object
или
articles = ArticleDecorator.decorate(Article.all) # for collection of objects
Как бы я реализовал этот метод decorate?
Комментарии:
1. PS: Я знаком с драгоценным камнем draper, но не хочу использовать эту реализацию для проблемы, которую я пытаюсь решить.
2. Взгляните на github.com/karmi/tire/blob/master/lib/tire/model/search.rb . Он делает нечто подобное.
Ответ №1:
Что именно вы хотите от decorate
метода? Должен ли он просто добавлять некоторые новые методы к переданным объектам или он должен автоматически обертывать методы этих объектов соответствующими методами формата? И почему вы хотите ArticleDecorator
быть классом, а не просто модулем?
Обновлено:
Похоже, что решение от nathanvda — это то, что вам нужно, но я бы предложил немного более чистую версию:
module ArticleDecorator
def format_title
"#{title} [decorated]"
end
def self.decorate(object_or_objects_to_decorate)
object_or_objects_to_decorate.tap do |objects|
Array(objects).each { |obj| obj.extend ArticleDecorator }
end
end
end
Он делает то же самое, но:
- Позволяет избежать проверки типа аргументов, зависящих от
Kernel#Array
метода. - Вызывает
Object#extend
напрямую (это общедоступный метод, поэтому нет необходимости вызывать его черезsend
). Object#extend
включает только методы экземпляра, поэтому мы можем поместить их прямо вArticleDecorator
, не оборачивая их другим модулем.
Ответ №2:
Могу ли я предложить решение, которое не использует смешанные модули и тем самым предоставляет вам больше гибкости. Например, используя решение, немного похожее на традиционный GoF decorator, вы можете развернуть свою статью (вы не можете удалить mixin, если он применяется один раз), и это даже позволяет вам заменить обернутую статью на другую во время выполнения.
Вот мой код:
class ArticleDecorator < BasicObject
def self.[](instance_or_array)
if instance_or_array.respond_to?(:to_a)
instance_or_array.map {|instance| new(instance) }
else
new(instance_or_array)
end
end
attr_accessor :wrapped_article
def initialize(wrapped_article)
@wrapped_article = wrapped_article
end
def format_title
@wrapped_article.title.upcase
end
protected
def method_missing(method, *arguments)
@wrapped_article.method(method).call(*arguments)
end
end
Теперь вы можете расширить одну статью, вызвав
extended_article = ArticleDecorator[article]
или несколькими статьями путем вызова
articles = [article_a, article_b]
extended_articles = ArticleDecorator[articles]
Вы можете восстановить исходную статью, вызвав
extended_article.wrapped_article
Или вы можете обменять обернутую статью внутри следующим образом
extended_article = ArticleDecorator[article_a]
extended_article.format_title
# => "FIRST"
extended_article.wrapped_article = article_b
extended_article.format_title
# => "SECOND"
Поскольку ArticleDecorator расширяет класс BasicObject, в котором почти нет уже определенных методов, даже такие вещи, как #class и #object_id, остаются неизменными для обернутого элемента:
article.object_id
# => 123
extended_article = ArticleDecorator[article]
extended_article.object_id
# => 123
Обратите внимание, что BasicObject существует только в Ruby 1.9 и выше.
Комментарии:
1. Это сработает, поскольку Ruby следует TMTOWTDI, но этого можно достичь с помощью object.extend(module) и module.included(class)
2. @philpirozhkov как, если можно спросить? Удаление модуля? Изменение неотделанного объекта во время выполнения? Я так не думаю.
Ответ №3:
Вы бы расширили экземпляр класса article, вызвали alias_method и указали его на любой метод, который вы хотите (хотя это звучит как модуль, а не класс, по крайней мере, прямо сейчас). Новая версия получает возвращаемое значение и обрабатывает его как обычно.
В вашем случае, похоже, вы хотите сопоставить такие вещи, как «format_.*», с их соответствующими получателями свойств.
Какая часть сбивает вас с толку?
Комментарии:
1. Я немного прояснил свой вопрос.
Ответ №4:
module ArticleDecorator
def format_title
"Title: #{title}"
end
end
article = Article.top_articles.first.extend(ArticleDecorator) # for single object
Должно работать нормально.
articles = Article.all.extend(ArticleDecorator)
Также может работать в зависимости от поддержки ActiveRecord для расширения набора объектов.
Вы также можете рассмотреть возможность использования ActiveSupport::Concern.