缆索护栏cad_干燥的rb可以提供更好的护栏服务

缆索护栏cad

The idea of using service objects came into my mind when I first read about the DCI architecture in Clean Ruby.

当我第一次阅读Clean Ruby中的DCI体系结构时,就想到了使用服务对象的想法。

The paradigm separates the domain model (data) from use cases (context) and Roles that objects play (interaction). DCI is complementary to model–view–controller (MVC). MVC as a pattern language is still used to separate the data and its processing from presentation.

该范式将域模型(数据)与用例(上下文)和对象扮演的角色(交互)分开。 DCI是模型视图控制器(MVC)的补充。 MVC作为一种模式语言,仍用于将数据及其处理与表示分离。

As the book and the quote from Wikipedia suggest, using this architecture in our code makes our models only contain data (we will even migrate data validations to service objects) and controller actions just call one of these interactions and render the result.

就像这本书和Wikipedia的引用所暗示的那样,在我们的代码中使用这种体系结构使我们的模型仅包含数据(我们甚至会将数据验证迁移到服务对象),并且控制器动作仅调用这些交互之一并呈现结果。

There are many cool libraries that I’ve used as the interaction layer like ActiveInteraction, Interactor, or Trailblazer Operations. But each of them misses something that I like which is present in another library. So I decided to pick and combine every piece that I like from these solutions and create mine.

我已经使用了许多很酷的库作为交互层,例如ActiveInteractionInteractorTrailblazer Operations 。 但是他们每个人都错过了我喜欢的东西,这些东西存在于另一个图书馆中。 因此,我决定从这些解决方案中挑选并组合我喜欢的每件作品,并创建自己的作品。

To better understand the upcoming concepts, let’s consider a very simple blog app where it has a Post model with title and body.

为了更好地理解即将到来的概念,让我们考虑一个非常简单的博客应用程序,该应用程序具有带有titlebodyPost模型。

基本API (Basic API)

The first thing we need to do is to define a basic API that all our service objects implement. The one that I prefer is a .call class. All services must have a call method which is the interface for interacting with it. This method receives a hash as an argument, does all logic needed by the operation and returns the result.

我们要做的第一件事是定义所有服务对象都实现的基本API。 我更喜欢的是.call类。 所有服务都必须具有调用方法,该方法是与其进行交互的接口。 此方法接收哈希作为参数,执行操作所需的所有逻辑并返回结果。

Let’s go back to our simple example. Suppose that we need a service to create a blog post. We will define a class named Posts::Create and will add a call class method to it that receives a params hash.

让我们回到简单的例子。 假设我们需要创建博客文章的服务。 我们将定义一个名为Posts::Create的类,并将向其添加一个接收params哈希值的调用类方法。

module Posts
  class Create
    def self.call(params)
      # Method body
    end
  end
end

添加验证 (Adding validations)

At the next step, I want to add the code to validate the input and the super simple business logic to create the post.

在下一步,我想添加代码以验证输入,并添加超级简单的业务逻辑来创建帖子。

module Posts
  class Create
    include Dry::Monads[:result, :do]
    
    ValidationSchema = Dry::Schema.Params do
      required(:title).filled(:str?)
      optional(:body).maybe(:str?)
    end


    def self.call(params)
      new.execute(params)
    end


    private


    def execute(params)
      yield validate_params(params)
      create_post(params)
    end


    def validate_params(params)
      validation_outcome = ValidationSchema.call(params)


      return Failure(validation_outcome.errors.to_h) if validation_outcome.failure?


      Success()
    end


    def create_post(params)
      post = Post.create(params)
      Success(post)
    end
  end
end

Ok. I agree that in comparison to the previous code snippet, I added a lot of things to this one. But believe me, they’re all super simple. Let’s check what we’ve added. For now, let’s skip the line number 3 and start with line number 5. Here we’ve added the validation schema for our service object. For this purpose, I’ve used dry-schema gem. It’s easy to understand and way more flexible than ActiveSupport validations. Check out all the available schema validation rules at dry-schema documentation. As you see, here we’ve added two rules:

好。 我同意与之前的代码段相比,我在此代码段中添加了很多内容。 但请相信我,它们都超级简单。 让我们检查一下我们添加的内容。 现在,让我们跳过第3行,从第5行开始。这里,我们为服务对象添加了验证模式。 为此,我使用了dry-schema宝石。 与ActiveSupport验证相比,它更容易理解并且更加灵活。 在dry-schema文档中检查所有可用的模式验证规则。 如您所见,这里我们添加了两个规则:

  1. Title is required and must be a string

    标题为必填项,必须为字符串
  2. Body is optional, but if it’s present, it should be also a string.

    Body是可选的,但如果存在,则它也应该是字符串。

Then in the .call class method, we instantiate an instance of the service and call it’s execute method and will pass all the parameters to it. In the execute, method, we’ll call all the steps needed to fulfill all the requirements of this operation. As you see, every step is simply a method and we call them one after another.

然后,在.call类方法中,我们实例化该服务的实例并调用它的execute方法,并将所有参数传递给它。 在execute,方法中,我们将调用满足此操作的所有要求所需的所有步骤。 如您所见,每个步骤都只是一个方法,我们将它们称为一个接一个的步骤。

We’ll use Success and Failure functions to specify if the result of each step was successful or a failure. These functions are provided in dry-monads gem which you can read more about in its documentation website. To have access to these functions in our service objects, we need to include the result monad using

我们将使用SuccessFailure功能来指定每个步骤的结果是成功还是失败。 这些功能在dry-monads gem中提供,您可以在其文档网站上了解更多信息。 要在我们的服务对象中访问这些功能,我们需要使用

include Dry::Monads[:result]

specified in line 3 of the previous code snippet. They’ll wrap the result of each step in a Success or a Failure object and then we can unwrap them using the yield keyword that does two things in this case:

在上一个代码段的第3行中指定。 它们会将每个步骤的结果包装在Success或Failure对象中,然后我们可以使用yield关键字解包它们,在这种情况下,它可以做两件事:

  1. Unwraps the value of a Success or a Failure object.

    解包SuccessFailure对象的值。

  2. Halts the execution of the method if there a failure in any of the steps.

    如果任何一个步骤失败,则停止该方法的执行。

To be able to use this monad, we need to add the do monad to our include statement:

为了能够使用此monad,我们需要将do monad添加到我们的include语句中:

include Dry::Monads[:result, :do]

Now if for example, the validation step returns a failure object, then the execution of the operation halts, and a Failure object containing the errors will be returned.

现在,例如,如果验证步骤返回了一个失败对象,则操作的执行将停止,并且将返回包含错误的Failure对象。

处理输出 (Handling output)

One of the things I liked about Traiblazer Operations was how you can handle the output of the service by passing a block to it. This is what we will be able to do after adding the next changes:

我喜欢Traiblazer Operations一件事是如何通过将一个块传递给服务来处理服务的输出。 添加以下更改后,我们将可以执行以下操作:

class PostsController < ApplicationController
  # ...
  def create
    Posts::Create.call(post_params.to_h) do |result|
      result.success do |post|
        render json: post, status: :ok
      end
      
      result.failure do |errors|
        render json: errors, status: :unprocessable_entity
      end
    end
  end
  # ...
end

Beautiful, right? Let’s add it to our service object. We only need to use ResultMatcher from the dry-matchers gem for that. In the .call method, if a block is given, then we need to wrap the response in the ResultMatcher object:

美丽吧? 让我们将其添加到我们的服务对象中。 我们只需要使用ResultMatcherdry-matchers宝石为。 在.call方法中,如果给出了一个块,则需要将响应包装在ResultMatcher对象中:

require 'dry/matcher/result_matcher'


module Posts
  class Create
    include Dry::Monads[:result, :do]
    
    ValidationSchema = Dry::Schema.Params do
      required(:title).filled(:str?)
      optional(:body).maybe(:str?)
    end


    def self.call(params, &block)
      service_outcome = self.new.execute(params)
      if block_given?
        Dry::Matcher::ResultMatcher.call(service_outcome, &block)
      else
        service_outcome
      end
    end


    private


    def execute(params)
      yield validate_params(params)
      create_post(params)
    end


    def validate_params(params)
      validation_outcome = ValidationSchema.call(params)


      return Failure(validation_outcome.errors.to_h) if validation_outcome.failure?


      Success()
    end


    def create_post(params)
      post = Post.create(params)
      Success(post)
    end
  end
end

使其可重用 (Making it reusable)

Ok, all done. Now let’s make our code reusable. We’ll create a module named ApplicationService and will move all the shared logic to it. Here I moved the code for the .call method to this base module. I also moved the validation schema execution here. So if you’ve defined a ValidationSchema constant in your service object, then it’ll execute it and then will pass the params to the execute method of the parent class.

好的,一切都完成了。 现在,让我们的代码可重用。 我们将创建一个名为ApplicationService的模块,并将所有共享逻辑移至该模块。 在这里,我将.call方法的代码移至该基本模块。 我还将验证模式执行移到了这里。 因此,如果您在服务对象中定义了ValidationSchema常量,则它将执行该常量,然后将参数传递给父类的execute方法。

require 'dry/matcher/result_matcher'


module ApplicationService
  module ClassMethods
    def call(params, &block)
      service_outcome = self.new.execute(params)
      if block_given?
        Dry::Matcher::ResultMatcher.call(service_outcome, &block)
      else
        service_outcome
      end
    end
  end


  module InstanceMethods
    include Dry::Monads[:result, :do]


    def execute(params)
      yield validate_params(params)
      super(params)
    end
    
    def validate_params(params)
      if self.class.constants.include? :ValidationSchema
        validation_outcome = self.class.const_get(:ValidationSchema).call(params)
  
        return Failure(validation_outcome.errors.to_h) if validation_outcome.failure?  
      end


      Success(params)
    end
  end


  def self.included(klass)  
    klass.prepend InstanceMethods
    klass.extend ClassMethods
  end
end

After separating the base logic from the main class, your operation classes will be as simple as this:

在将基本逻辑与主类分离之后,您的操作类将像这样简单:

module Posts
  class Create
    include ApplicationService
    
    ValidationSchema = Dry::Schema.Params do
      required(:title).filled(:str?)
      optional(:body).maybe(:str?)
    end


    def execute(params)
      create_post(params)
    end


    def create_post(params)
      post = Post.create(params)
      Success(post)
    end
  end
end

You only need to inlcude the ApplicationServiceand define an execute method in your service object and if you add the ValidationSchema constant, then it’ll automatically pick and call it.

您只需要包括ApplicationService并在服务对象中定义一个execute方法,如果您添加ValidationSchema常量,则它将自动选择并调用它。

We also can go further and add dry-validation to make our validations more powerful. But I need to leave the computer and make a cup of tea for myself, so I’ll give it over to you.

我们还可以进一步进行添加dry-validation以使我们的验证功能更强大。 但是我需要离开电脑自己为自己喝杯茶,所以我把它交给你。

Happy hacking :)

快乐黑客:)

翻译自: https://medium.com/@arefaslani/better-rails-service-objects-with-dry-rb-702687394e3d

缆索护栏cad

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值