1

세 가지 모델이 있습니다 : 작업, 사용자 및 응답.모델 내에서 다른 레코드 만들기 : 나쁜 습관?

사용자가 작업을 완료하면 결과가 응답으로 저장됩니다. 이 응답 시간 동안 사용자는 대변 점수를받습니다.

내 첫 번째 질문은, 포인트 속성을 업데이트하기위한 논리가 어디로 가야합니까? 작업, 사용자 또는 응답 모델 내부? 현재 Response 클래스에서 Response.task.points를 잡고 User.task.points에 해당 값을 추가합니다.

# POST /responses 
    # POST /responses.json 
    def create 
    @response = Response.new(response_params) 

    respond_to do |format| 
     if @response.save 
     @response.reward_user 

     format.html { redirect_to @response, notice: 'Response was successfully created.' } 
     format.json { render :show, status: :created, location: @response } 
     else 
     format.html { render :new } 
     format.json { render json: @response.errors, status: :unprocessable_entity } 
     end 
    end 
    end 

두 번째 것은 내가 모든 점 트랜잭션을 기록 할 것입니다 : 같은

Response.create는 보인다. 그래서 저는 points_transaction이라는 또 다른 모델을 만들었습니다. 내 다른 질문은, 어디서 points_transaction을 작성해야합니까? Response.create 컨트롤러에서? 응답 모델에서?

Response create 메소드 내에서 PointsTransaction을 만드는 것이 잘못되었지만 모델 내에서 PointsTransaction을 만드는 것도 잘못되었습니다. MVC가 정확한 것은 어느 것입니까?

내 Response 개체는 다음과 같습니다 : 당신이 간단한 MVC 넘어 갈 필요가있을 때

class Response < ApplicationRecord 
    belongs_to :task, optional: true 
    belongs_to :user, optional: true 

    def reward_user 
    point_value = task.point_value 
    user.points += point_value 

    PointTransaction.new({/*params go here*/}) 
    end 
end 
+0

좋은 질문이 있습니다. 나는 당신이 당신의 아키텍처 솔루션의 다음 단계로 이동할 필요가있는 시점에 도달했다고 생각합니다. – AntonTkachov

답변

1

이 유형의 문제에 대한 일반적인 접근법은 모든 처리 코드를 하나의 '단위'코드로 감쌀 수있는 서비스 객체를 만드는 것입니다. 서비스 개체가 Response을 확인한 다음 사용자의 포인트를 업데이트하고 PointTransaction을 추적하면 컨트롤러가 마른 체형을 유지할 수 있으며 모델이 다른 모델을 만지거나 다른 부작용이 명확하지 않을 수 있습니다.

app 디렉토리 내에 폴더 이름 서비스를 만들고 여기에 서비스 클래스를 추가한다고 가정 해 보겠습니다. 그런 다음

# app/services/response_checker.rb 
class ResponseChecker 
    attr_reader :success 

    def initialize 
    end 

    def call(response, task, user) 
    @success = if response.save 
     user.points += task.point_value 
     point_trans = PointTransaction.new(/*params go here*/) 

     user.save && point_trans.save 
    else 
     false 
    end 
    end 
end 

, 컨트롤러의 서비스를 사용 :

# app/controllers/response_controller.rb 
def create 
    @response = Response.new(response_params) 
    @response_checker = ResponseChecker.new.call(@response, @response.task, @response.user) 

    respond_to do |format| 
    if @response_checker.success 
     # conditional controller response logic 
    end 
    end 
end 

당신이 오류를 노출 한 후 서비스를 실행하는 동안 오류에 대한 정보를 수집하고 서비스 객체에 속성을 만들 수 있습니다 누구에게 서비스를 사용하고 있는지 (이 경우 컨트롤러).

+0

빠른 질문. 초기화 함수의 목적은 무엇입니까? 호출과 초기화 된 변수로 무엇을 전달해야합니까? – JCDJulian

+0

'initialize' 메쏘드는 실제로 여기에 특별한 일을하지 않는다는 것을 분명히했습니다. 우리는 그것을 버릴 수 있었고 모든 것이 여전히 동일하게 작동 할 것입니다. 서비스를 인스턴스화하고 상호 작용하는 방법에 대한 몇 가지 다른 방법이 있지만, 내가 좋아하는 한 가지 방법은 'new' /'initialize'를 사용하여 필요한 다른 서비스 유형 종속성을 주입하고'call'을 사용하여 우리가 작업을 수행하는 데 사용해야하는 상태의 객체를 전달하십시오. 이 방법으로 테스트하는 것이 더 쉽습니다. 여기에 몇 가지 예가 나와 있습니다. https://hackernoon.com/going-further-with-service-objects-in-ruby-on-rails-b8aac13a7271 – DRSE

1

내가, 당신은 확실히 상황에 직면 생각합니다.

첫째, 이상적인 세계 모델에서는 서로에 대해 전혀 알지 않아야합니다. 따라서 다른 모델은 Response에서 참조하면 안됩니다. 반면에 컨트롤러는 확실히 더 나빠진 다음 모델에 넣습니다.

둘째, 코드를 두 위치 사이에 넣을 위치가 의심스러운 경우입니다. 그럼 둘 다 좋지 않아서 세 번째를 찾아야 해.

그 때 서비스 오브젝트이 게임에 나왔습니다. 꽤 널리 보급되어 있으며 일반적인 패턴이 레일에 있습니다. 완벽한 솔루션인지는 확실치 않지만 코드를 분리하여 코드를 깨끗하고 쉽게 테스트 할 수있게하십시오. 너무 많은 서비스 개체가있는 것을 제외하고는 아직 이러한 접근법에 대한 문제점을 발견하지 못했습니다.

다음은 로직이있는 샘플로 프로젝트 (app/services/active_site_service)의 여러 모델을 다루고 있습니다.RB) :

class ActivateSiteService 
    attr_reader :error 

    def initialize(user, template, password) 
    @user = user 
    @template = template 
    @activation = @user.activation_for(@template) 
    @password = password 
    end 

    def call 
    return false unless self.valid? 

    generate_site_service = GenerateSiteService.new(@user, @template) 
    generate_site_service.call 

    @activation.update(quantity: @activation.quantity - 1) 

    @user.transactions.create(status: :success, 
           target: generate_site_service.site, 
           amount: 0, 
           transaction_type: :site_activation) 
    true 
    end 

    protected 
    def valid? 
    validate_password && validate_activation 
    end 

    def validate_password 
    return true if @user.valid_password?(@password) 
    @error = 'Неправильный пароль' 
    false 
    end 

    def validate_activation 
    return true if @activation.present? && @activation.quantity > 0 
    @error = 'У вас нет предоплаченных активаций' 
    false 
    end 
end 

규칙, 우리는 따라야 :

  1. 개념적으로 서비스 개체가
  2. 이름은 항상
  3. 서비스 개체 만이 동사로 시작하는 여러 모델을 포함하는 비즈니스 프로세스입니다 2 가지 방법 : initializecall
  4. call 항상 true/false 만 반환
  5. 만 2 attr_reader 변수 허용 - result을 오류를 얻을 서비스 또는 error 일부 데이터 또는 개체를 가져

컨트롤러 :

class ActivationsController < ApplicationController 
    def create 
    template = Site.templates.find(params[:template_id]) 

    activate_site_service = ActivateSiteService.new(current_user, template, params[:password]) 

    if activate_site_service.call 
     redirect_to sites_path, notice: 'Активация сайта прошла успешно' 
    else 
     redirect_to new_purchase_path(template_id: template.id), alert: activate_site_service.error 
    end 
    end 
end 

그런 다음,이 규칙에 해당 할 수없는 경우 그것은 대개 서비스 객체가 아닙니다. 당신은 서비스 객체에 대해 더 많은 것을 할 수 있습니다.

+0

흠. 흥미 롭 군. 서비스 모델은 어디에 있습니까? lib? – JCDJulian

+0

또한 누가 서비스 개체를 호출합니까? 서비스 객체가 다른 컨트롤러 여야합니까? 아니면 Response.create에서 호출해야합니까? 반응을 기록하고 Response 내에서 원자 작업으로 사용자에게 보상한다는 의미입니까? – JCDJulian

+0

내 답변에 모든 것을 포함 시켰습니다. 왜 나는 lib와 함께 가지 않을 것이다. Lib은 대부분 별도의 라이브러리이며 다른 프로젝트에서 사용할 수있는 모듈입니다. 그래서 나를 위해 lib 디렉토리는 대부분 기술적 인 보조자와 같으며 비즈니스 로직과 전혀 관련이 없습니다. 비즈니스 로직과 관련된 모든 것은'app /'폴더에 들어가야합니다. – AntonTkachov

관련 문제