Rails Service Objects를 사용하여 모델 및 컨트롤러 슬림화

디자인: tartila/Freepik
"Fat models, skinny controllers"는 Rails 애플리케이션 리팩터링을 위한 모범 사례 중 하나입니다. 그러나 애플리케이션이 커지고 복잡해짐에 따라 모델에 비즈니스 로직이 더 많아지고 단일 책임 원칙을 적용하기가 더 어려워집니다. "스키니 모델, 스키니 컨트롤러"를 달성하기 위해 비즈니스 로직을 서비스 객체로 추출하는 것을 고려해야 할 때입니다.

서비스 개체란 무엇입니까?



OOP의 캡슐화를 기억하십니까? Rails의 서비스 객체는 기본적으로 하나의 단일 작업을 실행하도록 설계된 Ruby 객체로 비즈니스 로직을 캡슐화합니다. 서비스 개체는 어디에서나 호출할 수 있으며 테스트하기도 쉽습니다. 다음과 같은 경우 사용을 고려하십시오.
  • 동작이 복잡하다
  • 작업이 여러 모델에서 호출됨
  • 작업이 외부 서비스/API와 상호 작용함

  • 서비스 개체 추출



    실제로 그것을 보자. 사용자가 이미지를 게시할 때 Google Cloud Vision API을 사용하여 해시태그를 생성하는 Rails 애플리케이션을 만들었습니다. 에서 소개한 샘플 코드를 사용하고 있습니다.
    get_tags 모델에 Post 방법이 있습니다.

    # app/models/post.rb
    
    class Post < ApplicationRecord
      def get_tags(image)
        api_key = ENV['GOOGLE_API_KEY']
        api_url = "https://vision.googleapis.com/v1/images:annotate?key=#{api_key}"
        base64_image = Base64.strict_encode64(File.new(image, 'rb').read)
    
        body = {
          requests: [{
            image: {
              content: base64_image
            },
            features: [
              {
                type: 'LABEL_DETECTION',
                maxResults: 5
              }
            ]
          }]
        }.to_json
    
        uri = URI.parse(api_url)
        https = Net::HTTP.new(uri.host, uri.port)
        https.use_ssl = true
        request = Net::HTTP::Post.new(uri.request_uri)
        request['Content-Type'] = 'application/json'
        response = https.request(request, body)
        results = JSON.parse(response.body)
        results['responses'][0]['labelAnnotations'].each do |result|
          tag = Tag.find_or_create_by(name: result['description'])
          PostTag.create(post_id: id, tag_id: tag.id)
        end
      end
    end
    


    모델은 이미지에 태그를 지정하는 방법에 대해 너무 많이 알고 있습니다. 작업을 서비스 개체로 추출하여 리팩터링해 보겠습니다.

    먼저 app/services 폴더와 그 아래에 tag_generator.rb를 생성합니다. 모든 논리를 get_tags에서 클래스.call 메서드로 이동합니다.

    # app/services/tag_generator.rb
    
    class TagGenerator
      def self.call(post, image)
        api_key = ENV['GOOGLE_API_KEY']
        api_url = "https://vision.googleapis.com/v1/images:annotate?key=#{api_key}"
        base64_image = Base64.strict_encode64(File.new(image, 'rb').read)
    
        body = {
          requests: [{
            image: {
              content: base64_image
            },
            features: [
              {
                type: 'LABEL_DETECTION',
                maxResults: 5
              }
            ]
          }]
        }.to_json
    
        uri = URI.parse(api_url)
        https = Net::HTTP.new(uri.host, uri.port)
        https.use_ssl = true
        request = Net::HTTP::Post.new(uri.request_uri)
        request['Content-Type'] = 'application/json'
        response = https.request(request, body)
        results = JSON.parse(response.body)
        results['responses'][0]['labelAnnotations'].each do |result|
          tag = Tag.find_or_create_by(name: result['description'])
          PostTag.create(post_id: post.id, tag_id: tag.id)
        end
      end
    end
    


    이제 TagGenerator 클래스의 클래스 메서드입니다. post_controller.rb도 업데이트합시다.

    전에:

    # app/controllers/posts_controller.rb
    
    class PostsController < ApplicationController
      def create
        @post = current_user.posts.new(post_params)
    
        if @post.save
          @post.get_tags(params[:post][:image])
          redirect_to @post
        else
          render :new
        end
      end
    


    후에:

    # app/controllers/posts_controller.rb
    
    class PostsController < ApplicationController
      def create
        @post = current_user.posts.new(post_params)
    
        if @post.save
          TagGenerator.call(@post, params[:post][:image])
          redirect_to @post
        else
          render :new
        end
      end
    


    이제 우리는 Post 모델의 크기를 축소했을 뿐만 아니라 태그 생성기 작업을 유지 관리하고 재사용할 수 있게 만들었습니다.
    TagGenerator.call를 실행한 결과입니다. 이미지에서 자동 생성된 해시태그:




    참조:
    7 Patterns to Refactor Fat ActiveRecord Models

    좋은 웹페이지 즐겨찾기