代码之家  ›  专栏  ›  技术社区  ›  Schwern

rails 5中模型功能的decorator模式

  •  2
  • Schwern  · 技术社区  · 6 年前

    我正在按照“胖模型/瘦控制器”模式开发一个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_association decorates_finders 为了避免这种情况,德雷珀的文件说要避免这种情况…

    装饰者的行为应该非常像他们装饰的模型,因此在控制器操作开始时装饰对象,然后在整个过程中使用装饰者是非常诱人的。不要。

    然而,draper(以及我能找到的大多数rails+decorator文章)似乎专注于视图功能……

    因为decorator是被视图使用的,所以您应该只在那里访问它们。在渲染视图之前,操纵模型使其准备就绪,然后在最后一分钟进行装饰。这避免了在创建decorator(特别是集合decorator)之后试图修改它们时出现的许多常见陷阱。

    与视图功能不同,在视图功能中,您有一个控制器来确保它传递给视图的模型是修饰的,而我的装饰器是用于模型功能的。装潢师是 主要地 为了便于代码组织和测试,几乎所有的东西都应该使用修饰模型。

    使用decorators添加到模型功能的最佳实践是什么?总是用装饰过的模特?更激进的做法,比如将订阅和取消订阅移到另一个类中?

    1 回复  |  直到 6 年前
        1
  •  1
  •   max Mike Williams    6 年前

    我觉得这不适合做装饰。在rails中,decorators主要用视图中使用的表示逻辑包装模型对象。它们作为单个对象的扩展,允许您在逻辑上分离对象的不同任务。

    例如:

    class User
      def born_on
        Date.new(1989, 9, 10)
      end
    end
    
    class UserDecorator < SimpleDelegator
      def birth_year
        born_on.year
      end
    end
    

    当涉及到多个对象交互的类似过程的操作时,decorator不是一个很合适的方法。

    相反,您应该看到的是服务对象模式,在该模式中,您可以创建执行单个任务的单用途对象:

    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 例如,模型不应该直接参与订阅单个用户。如果您的模型越来越胖,这可能是一个迹象,表明您将太多内容塞进了一组太小的业务逻辑对象中,或者表明数据库设计设置得很差。