質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

91.25%

  • Ruby

    5452questions

    Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

  • Ruby on Rails

    5348questions

    Ruby on Railsは、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

フォームオブジェクトで、複数のモデルが絡むバリデーションを行いたい

受付中

回答 0

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 98

zendendo

score 17

前提・実現したいこと

ruby(ruby on rails)で、単一ポイントの取引機能(振込機能)があり、
支払ユーザーが保有する口座の残高を超える取引額を不可にしたい(支払ユーザーの口座残高未満の取引額ならモデルに保存・更新したい)と考えています。

ただし、残高を記録しているモデルと取引額を記録しているモデルは別々になっています。

いわゆる複数のモデルが絡むバリデーションになると思うのですが、そうゆう場合は、フォームオブジェクトで設定した方がいいと聞き、
フォームオブジェクトでバリデーションを作成してみたのですが、
よくわからず、うまくいかず困っています。

現状では、入力フォームで「支払先の口座番号」と「取引額」を入力すると、
3つのモデルが同時に動くようになっています。
図にするとこんな感じです。
イメージ説明
データのやりとりはこんな感じです。
イメージ説明
例えば、支払ユーザーの口座残高が10000(1万)なら、1万1以上の数値の入力を
AmmountTransaction(取引)モデルのamount(取引額)、
DepositsAndwithdrawal(入出金)モデルのamount(取引額)、
BasicIncomeAccount(口座)モデルのbalance(残高)を更新する際に使う増減額、
で禁止(バリデーション)できるようにしたいのですが、
その場合、formオブジェクトやコントローラ等のファイルには、どんな記述をすれば
いいのでしょうか?

教えて頂ければ幸いです。

試しに書いたソースコード

フォームオブジェクトのコードになります。
app/models/form/account_transaction_form.rb

class AccountTransactionForm

    include ActiveModel::Model

    attr_accessor :erroes, :amount, :deposit_account_id

    def initialize(amount, withdrawal_account_id, deposit_account_id, basic_income_account)
        @amount = amount
        @withdrawal_account_id = withdrawal_account_id
        @deposit_account_id = deposit_account_id
        @balance = basic_income_acccount.balance
        @errors = ActiveModel::Errors.new(self)
    end

    #検証に関する記述
    def valid?
        errors.clear
        if @balance < @amount
            errors.add(:amount, "残高以上の送金はできません")
        end
        errors.empty?
    end
end

コントローラのコードです。3つのモデルを動かす取引処理の記述が書かれています。
取引処理は複数のモデルが絡む固有の処理(ビジネスロジックというもの?)なので、
この固有の処理もフォームオブジェクトやサービスクラスに
フォームオブジェクトに移動させた方がいいのですが、質問の本題から離れるのでひとまずおいておきます。
コントローラには、フォームオブジェクトを呼び出すコードを記述をすると思うのですが、
仮に複数のモデルが絡むバリデーションだけを行うフォームオブジェクトの場合、 どこにどんな記述をすればいいのでしょうか

class AccountTransactionsController < ApplicationController

  #取引を作成する 
  def new
    @account_transaction = AccountTransaction.new
  end

  #取引処理をする為に3つのモデルを同時に保存・更新している
  def create
    #取引モデルに1件のレコードを新規作成
    AccountTransaction.transaction do
      @account_transaction = AccountTransaction.new(        
        withdrawal_account_id: current_user.basic_income_account.id,
        deposit_account_id: BasicIncomeAccount.find_by(account_number: params[:account_transaction][:deposit_account_id]).id,
        amount: params[:account_transaction][:amount]
        )
      @account_transaction.save!
      #入出金モデルに2件のレコードを新規作成
      DepositsAndWithdrawal.transaction do
        #出金側レコードを作成保存
        @account_transaction.deposits_and_withdrawals.build(
          transaction_type: "出金",
          basic_income_account_id: current_user.basic_income_account.id,
          amount: -1 * params[:account_transaction][:amount].to_f
          )
        @account_transaction.save!
        #出金側レコードを作成保存
        @account_transaction.deposits_and_withdrawals.build(
          transaction_type: "入金",
          basic_income_account_id: BasicIncomeAccount.find_by(account_number: params[:account_transaction][:deposit_account_id]).id,
          amount: params[:account_transaction][:amount].to_f
          )
        @account_transaction.save!
        #口座モデルを更新(残高の増減)
        BasicIncomeAccount.transaction do
          @withdrawal_basic_income_account = current_user.basic_income_account
          @deposit_basic_income_account = BasicIncomeAccount.find_by(account_number: params[:account_transaction][:deposit_account_id])

          @withdrawal_amount = params[:account_transaction][:amount].to_f
          @deposit_amount = params[:account_transaction][:amount].to_f

          @withdrawal_basic_income_account.update(balance: @withdrawal_basic_income_account.balance - @withdrawal_amount)
          @deposit_basic_income_account.update(balance: @deposit_basic_income_account.balance + @deposit_amount)
        end
      end
    end
    redirect_to root_path
  rescue => e
    render plain: e.message

  end


  private
  def account_transaction_params
  params.require(:account_transaction).permit(:deposit_account_id, :amount)
  end
end


入力フォーム画面のコードです。

<%= form_for @account_transaction do |f|%>
 <% if @account_transaction.errors.any? %>
  <h3>入力内容にエラーが<%= @account_transaction.errors.count %>件あります</h3>
  <ul>
      <% @account_transaction.errors.full_messages.each do |message| %>
      <li><%= message %></li>
      <% end %>
  </ul>
 <% end %>
<div class="field">
  <%= f.label :送金相手の口座番号 %>
  <%= f.text_field :deposit_account_id %>
  <%= f.label :送金額 %>
  <%= f.number_field :amount,min:1,max:999999999999999 %>
  <%= f.submit"送金確認" %>
</div>
<% end %>

3つのモデルの状態です。

 create_table "account_transactions", force: :cascade do |t|
    t.decimal "amount"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.integer "withdrawal_account_id"
    t.integer "deposit_account_id"
    t.index ["deposit_account_id"], name: "index_account_transactions_on_deposit_account_id"
    t.index ["withdrawal_account_id"], name: "index_account_transactions_on_withdrawal_account_id"
  end

  create_table "basic_income_accounts", force: :cascade do |t|
    t.integer "user_id"
    t.string "account_number"
    t.decimal "balance"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["user_id"], name: "index_basic_income_accounts_on_user_id"
  end

  create_table "deposits_and_withdrawals", force: :cascade do |t|
    t.integer "account_transaction_id"
    t.string "transaction_type"
    t.decimal "amount"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.integer "basic_income_account_id"
    t.index ["account_transaction_id"], name: "index_deposits_and_withdrawals_on_account_transaction_id"
    t.index ["basic_income_account_id"], name: "index_deposits_and_withdrawals_on_basic_income_account_id"
  end

補足情報(言語/FW/ツール等のバージョンなど)

Rails 5.1.3
ruby 2.4.1
devise (4.3.0)

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

まだ回答がついていません

15分調べてもわからないことは、teratailで質問しよう!

ただいまの回答率

91.25%

関連した質問

同じタグがついた質問を見る

  • Ruby

    5452questions

    Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

  • Ruby on Rails

    5348questions

    Ruby on Railsは、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。