Обтекание объекта методами из другого класса - PullRequest
2 голосов
/ 05 ноября 2011

Допустим, у меня есть модель с именем Article:

class Article < ActiveRecord::Base
end

А затем у меня есть класс, предназначенный для добавления поведения к объекту article (декоратор):

class ArticleDecorator

  def format_title
  end

end

Если бы я хотел расширить поведение объекта статьи, я мог бы сделать ArticleDecorator модулем, а затем вызвать article.extend (ArticleDecorator), но я бы предпочел что-то вроде этого:

article = ArticleDecorator.decorate(Article.top_articles.first) # for single object

или

articles = ArticleDecorator.decorate(Article.all) # for collection of objects

Как мне реализовать этот метод декорации?

Ответы [ 4 ]

5 голосов
/ 18 ноября 2011

Что именно вы хотите от 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

Он делает то же самое, но:

  1. Избегает проверки типа аргументов, опирающихся на Kernel#Array method.
  2. Вызывает Object#extend напрямую (это публичный метод, поэтому нет необходимости вызывать его через send).
  3. Object#extend включает только методы экземпляра, поэтому мы можем их поместитьпрямо в ArticleDecorator, не оборачивая их другим модулем.
1 голос
/ 22 ноября 2011

Могу ли я предложить решение, которое не использует миксины модуля и тем самым предоставляет вам больше гибкости.Например, используя решение, немного более похожее на традиционный GoF-декоратор, вы можете развернуть свою статью (вы не можете удалить миксин, если он был применен один раз), и это даже позволяет вам заменить обернутую статью на другую во время выполнения.

Вот мой код:

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 и выше.

0 голосов
/ 22 ноября 2011
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 .

0 голосов
/ 05 ноября 2011

Вы бы расширили экземпляр класса article, вызвали alias_method и направили его на любой метод, который вы хотите (хотя он звучит как модуль, а не класс, по крайней мере, прямо сейчас). Новая версия получает возвращаемое значение и обрабатывает его как обычно.

В вашем случае звучит так, как будто вы хотите сопоставить такие вещи, как «формат _. *», С соответствующими получателями их свойств.

Какая часть вас сбивает с толку?

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