Rails 5

时间:2018-05-30 22:33:36

标签: ruby-on-rails model-view-controller decorator

我正在按照"胖模型/瘦控制器"开发Rails 5应用程序。图案。当我添加诸如记录和验证之类的东西时,我发现我的模型变得有点过于肥胖。例如,这里是订阅列表的方法的草图......

class SubscriberList < ApplicationRecord
  # relationships and validations

  def subscribe!(args)
    log that a subscription is attempted

    begin
      do the subscription
    rescue errors
      log the failure and reason
      rethrow
    end

    log successful subscription
    log other details about the subscription

    SubscriptionValidationJob.perform_later( new_subscriber )

    return new_subscriber
  end
end

它越来越多地将日志记录和验证与订阅行为联系起来。我理解我应该通过使用decorators将日志记录和验证移动到draper来解决这个问题。

我对装饰师没有多少经验。我主要担心的是由于代码使用未修饰的模型时应该使用装饰模型的错误。或相反亦然。接口是相同的,变化是副作用,因此很难检测。

我很想大量使用decorates_associationdecorates_finders来避免这种情况,但Draper文档说要避免这种情况......

  

装饰器应该与它们装饰的模型非常相似,因此在控制器动作开始时装饰对象非常诱人,然后在整个过程中使用装饰器。唐&#39;吨

然而,Draper(以及我能找到的大多数Rails + Decorator文章)似乎都专注于视图功能......

  

因为装饰器被设计为由视图使用,所以您应该只在那里访问它们。操作模型以准备好事物,然后在渲染视图之前的最后一刻进行装饰。这避免了在创建装饰器之后尝试修改装饰器(特别是集合装饰器)时出现的许多常见陷阱。

与视图功能不同,你有一个控制器来确保传递给视图的模型被装饰,我的装饰器用于模型功能。装饰器主要是用于代码组织和易于测试,几乎所有东西都应该使用装饰模型。

使用装饰器添加到模型功能的最佳做法是什么?总是使用装饰模型?更像是将订阅和取消订阅转移到另一个类中更激进了吗?

1 个答案:

答案 0 :(得分:1)

我认为这不适合装饰者。在rails装饰器中,主要使用视图中使用的表示逻辑来包装模型对象。它们作为单个对象的扩展,可以让您在逻辑上分离对象的不同任务。

例如:

class User
  def born_on
    Date.new(1989, 9, 10)
  end
end

class UserDecorator < SimpleDelegator
  def birth_year
    born_on.year
  end
end

当谈到像多个对象交互的操作时,装饰器不适合。

您应该关注的是服务对象模式,您可以在其中创建执行单个任务的单一目的对象:

class SubscriptionService

  attr_accessor :user, :list

  def initialize(user, list)
    @user = user
    @list = list
  end

  def self.perform(user, list)
    self.new(user, list).perform
  end

  def perform
     @subscription = Subscription.new(user: @user, list: @list)
     log_subscription_attempted
     if @subscription.create
       send_welcome_email
       # ...
     else
       log_failure_reason
       # ...
     end

     @subscription
  end

  private

    def send_welcome_email
      # ...
    end

    def log_subscription_attempted
      # ...
    end

    def log_failure_reason
      # ...
    end
end

但是你也应该考虑你是否正确地编写模型。在此示例中,您希望将三个模型互连为:

class User
  has_many :subscriptions
  has_many :subscription_lists, through: :subscriptions
end

class Subscription
  belongs_to :user
  belongs_to :subscription_list
  validates_uniqueness_of :user_id, scope: :subscription_list_id
end

# or topic
class SubscriptionList 
  has_many :subscriptions
  has_many :users, through: :subscriptions
end

每个模型应该处理应用程序中的一个单独的实体/资源。因此,SubscriptionList模型不应直接参与订阅单个用户。如果你的模型变得很胖,那么可能表明你太过于过分地使用太少的业务逻辑对象,或者数据库设计设置不当。