【问题标题】:How to move logic out of a Rails model into a service module?如何将逻辑从 Rails 模型移到服务模块中?
【发布时间】:2021-11-14 08:24:55
【问题描述】:

我想将 Rails 模型中的一些功能移到服务模块中。

这个问题不是正确的,因为它只适用于一个模型,我只是想在其他地方整理一些代码。

我似乎无法对模型进行基本调用,这是我的设置:

/app/models/account.rb

class Account < ApplicationRecord
    include SomeService
end

而且在不同的位置:

app/services/some_service.rb

module SomeService

  def test_code
    "abc"
  end

  def self.test_code_2
    "xyz"
  end

end

在这种情况下,我希望 Account.first.test_code 输出 abcAccount.test_code_2 输出 zyx

如何将这样的功能从模型中移出,而不是移入关注点?我觉得我离这个工作很近了。

【问题讨论】:

    标签: ruby-on-rails model activesupport-concern


    【解决方案1】:

    这段代码实际上并没有定义类方法:

    module SomeService
      def test_code
        "abc"
      end
    
      def self.test_code_2
        "xyz"
      end
    end
    

    它在模块本身上声明了一个方法,您可以通过运行SomeService.test_code_2 来验证它。那是因为self 不像其他语言中的static 那样是“类方法关键字”——它只是对当前词法范围的引用。在这种情况下是模块本身。

    在类中声明方法时:

    class Foo
      def self.bar
        "Hello world"
      end
    end
    

    self 是所谓的单例类 - Class 类的一个实例。所以它在Foo 类上定义了一个方法。

    当您include 类中的模块时,您将该模块添加到该类的祖先链中。因此,它可以调用模块的 instance 方法,就好像它在它自己的位置一样。您可以将此与 extend 进行对比,后者将模块的方法导入类(test_code 成为类方法)。

    因此ClassMethods 模式使用内部模块when its included 扩展类:

    module SomeService
      def self.included(base)
        base.extend ClassMethods
      end
    
      def test_code
        "abc"
      end
    
      module ClassMethods
        def test_code_2
          "xyz"
        end
      end
    end
    

    如何将这样的功能从模型中移出,而不是移到关注点中?

    您正在做的事情令人担忧。 Rails 中的“关注”一词实际上只是模糊地表示“混合到类中的模块”之类的东西。唯一真正的定义是app/models/concernsapp/controllers/concerns 是自动加载根,而ActiveSupport::Concern 的存在只是简化了编写mixin 时所需的样板代码。比如你可以使用它的class_methods 宏来缩短上面的代码。

    对于关注点应该包含什么或其在应用程序中的作用没有实际定义,也没有任何要求将其混合到多个类中。

    但是……

    如果您将逻辑从模型(或任何类)中移出并移入模块中,那么如果您随后将其重新包含到模型中,则实际上并没有任何效果。您只是通过将代码改组为多个文件来掩盖代码。

    责任的数量和复杂性保持不变。

    如果你真的想重新分配职责,你想创建一个可以独立运行并执行工作单元的对象:

    class SomeService
      def initialize(thing)
         @thing = thing
      end
    
      def perform
        # do something awesome with @thing
      end
    
      def self.perform(thing)
        new(thing).perform
      end
    end
    

    这通常被称为 服务对象模式 - 服务模块不是 AFAIK 的东西。这具有一组定义的职责并卸载模型。 ActiveJob 就是这种模式的一个例子。

    您正在做的事情被称为方法提取 - 基本上只是将类似上帝的对象拆分为模块,因为modules are good and big classes are evil。对?没有。依旧是神级。这在 Rails 引入关注文件夹的时候变得非常流行。

    另一个不容忽视的解决方案是查看模型是否确实做了很多事情,是否应该将其拆分为具有更明确职责的多个模型。

    【讨论】:

      【解决方案2】:

      您可以在您的模块中定义一个ClassMethods 模块并在基类中定义include。这样,普通的模块方法将作为实例方法使用,ClassMethods 中定义的方法将作为基类中的类方法使用。

      app/services/some_service.rb
      
      module SomeService
      
        def self.included(base)
          base.extend ClassMethods
        end
      
        def test_code
          "abc"
        end
      
        module ClassMethods
          def test_code_2
            "xyz"
          end
        end
      end
      

      【讨论】:

        猜你喜欢
        • 2015-07-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-03-14
        • 2019-10-26
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多