1

私は、タスク、ユーザー、および応答の3つのモデルを持っています。モデル内から別のレコードを作成する:悪い練習ですか?

ユーザーがタスクを完了すると、結果が応答として保存されます。その応答時間の間に、ユーザはクレジットポイントを獲得する。

私の最初の質問は、ポイント属性を更新するロジックはどこに行くのですか?タスク、ユーザー、または応答モデルの内部?現在のところ、Response.task.pointsを取得してその値をUser.task.pointsに追加するResponseモデルにあります。

ようResponse.createが、その後になります。二つ目は、私はすべてのポイントの取引を記録したいということです

# 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 

。ですから、私はpoints_transactionという別のモデルを作成しました。私の他の質問は、どこにpoints_transactionを作成すればよいのでしょうか? Response.createコントローラでは?応答モデルでは?

レスポンスの作成メソッドからPointsTransactionを作成するのは間違っているようですが、モデル内から作成することも同様に間違っているようです。どちらがMVC正しいのですか?

マイResponseオブジェクトは、次のようになります。

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

このタイプの問題の一般的なアプローチは、すべての処理コードを1つの「ユニット」のコードにまとめることができるサービスオブジェクトを作成することです。サービスオブジェクトにResponseをチェックさせてから、ユーザーのポイントを更新してPointTransactionを追跡することで、コントローラのスキニーを保つことができ、モデルが他のモデルに触れたり、不明瞭なその他の副作用を起こさないようにできます。

アプリケーションディレクトリの内部にサービス名のサービスを作成し、サービスクラスをそこに配置するとします。

その後
# 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

私はあなたが簡単なMVCを越えて行く必要があるとき、あなたは間違いなく、状況に直面したと思います。

まず、理想的な世界モデルでは、お互いを全く知るべきではありません。したがって、他のどのモデルもResponseから参照するべきではありません。一方、コントローラは間違いなく悪化し、それをモデルに入れます。

第2に、2つの場所の間にコードを配置する場所が疑わしい場合。それから、両方とも十分ではなく、第3のものを探す必要があります。

それはサービスオブジェクトがゲームに来たときです。それはかなり広く普及しており、一般的なパターンがレールにあります。私はそれが完璧なソリューションかどうかは分かりませんが、コードをデカップリングし、コードをきれいにして簡単にテストできるようにしてください。あまりにも多くのサービスオブジェクトを持っていることを除いて、私はそのようなアプローチの問題をまだ見つけていません。

ここでは、プロジェクトのいくつかのモデル(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常に真偽を返す
  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

Hmmm。面白い。サービスモデルはどこに行きますか? libで? – JCDJulian

+0

また、誰がサービスオブジェクトを呼び出しますか?サービスオブジェクトは別のコントローラであるべきですか?または私はそれをResponse.createから呼び出す必要がありますか?応答をログに記録し、応答内からアトミック操作としてユーザーに報酬を与えることを意味しますか? – JCDJulian

+0

私の答えにすべてが含まれています。なぜ私はlibと一緒に行かないだろう。 Libは主に別のライブラリで他のプロジェクトでも使用できるモジュールです。だから、私にとっては、libはほとんどが技術的なヘルパーのようなものであり、ビジネスロジックとの関係は決してありません。ビジネスロジックに関連するものはすべて 'app /'フォルダに入れてください。 – AntonTkachov

関連する問題