はじめに
ユーザー ⇄ SIer ⇄ 開発者(←私) といった立ち位置で、お客さんが業務で使ってるエクセルの情報なんかをうまいことアプリケーションにしましょうというよくありそうなプロジェクト。
環境
ruby '2.5.3'
rails '5.2.2'
なにをしたかったのか
こういう帳票をWebアプリケーションで扱えるようにモデルを開発
・チームは大会に紐づいている
・チームに対して、戦士/魔法使い/盗賊のメンバーを追加でき、横一列で確認できる
・ユーザーはこの形式で慣れ親しんでおり、ここから大きく変更することができない
・チームのメンバーを戦士/魔法使い/盗賊で複数人追加することもできる、その場合、一行目は空白で二行目にだけ入力があるといったケースも存在する。
・一行目は空白で二行目にだけ入力があるといったケースのとき、表示は入力された通りにしたい。
設計その1
モデル定義
class Tournament < ApplicationRecord
has_many :teams, -> { order('team.order') }
has_many :rows, through: :team
has_many :warriors, through: :row
has_many :wizards, through: :row
has_many :thiefs, through: :row
accepts_nested_attributes_for :teams
accepts_nested_attributes_for :warriors
accepts_nested_attributes_for :wizards
accepts_nested_attributes_for :thiefs
end
class Team < ApplicationRecord
belongs_to :tournament
has_many :rows, dependent: :destroy
end
class Row < ApplicationRecord
belongs_to :team
has_one :warrior, dependent: :destroy
has_one :wizard, dependent: :destroy
has_one :thief, dependent: :destroy
end
#wizard, fhief も同様
class warrior < ApplicationRecord
belongs_to :row
end
このモデル定義で以下のように検索がかけられる
tournament.warriors.max(:hit_point) #=> 大会に参加してる戦士の中でのmaxのHP
tournament.warriors.sum(:hit_point) #=> 大会に参加してる戦士の合計のHP
View側はfields_forでnestする
= form_with model: @tournament, url: tournament_path(@tournament), html: { role: "form"} do |f|
= f.fields_for :teams do |team|
# team のインプット
= f.fields_for :warriors do |warrior|
# warrior のインプット
= f.fields_for :wizards do |wizard|
# wizard のインプット
= f.fields_for :thiefs do |thief|
# thief のインプット
Controller側では、モデルのaccepts_nested_attributes_forを利用してtournamentのupdate_attributesに引っ掛けて、関連するteam, warriors, wizards, fhiefsすべての値が更新される
def update
@tournament.update_attributes(params)
end
メリット
・viewとcontrollerがめちゃくちゃスッキリしている
・rowとwarrior, wizard, thiefを has_oneでリレーションしているので1行に複数データを追加できない
・大会のモデルから参加チームすべてのwarrior, wizard, thiefを横串で確認できる
設計その2
設計その1を提案したところ、以下のフィードバックを受けたので設計を修正した
・テーブルの一覧性が悪い
・戦士、魔法使い、盗賊はそれぞれに似ている項目があるので、ひとつのテーブルにまとめる
魔法使いの専用カラムや盗賊用の専用カラムができてしまうが、単一テーブル継承で設計し直した
設計その3
設計その2を提案したところ、以下のフィードバッグを受けた
・INSERT文を書くときに、typeによって、どこのカラムを利用するのかを変えないといけなくて面倒なので、HP、攻撃力、防御力、MP、盗み成功率を、単位1,値1 ~ 単位4, 値4まで持たせて自由にかけるようにして欲しい(INSERT文...書く気なのか...)
しかも、
魔法使いはkey_1にHP、key_2にMP、key_3に攻撃力、key_4に防御力
と固定化できず、
MPが入力されてない場合は、key_1にHP、key_2に攻撃力、key_3に防御力
その状態で保存し、さらにMPを追加した場合は、key_1にHP、key_2に攻撃力、key_3に防御力、key_4にMPが爆誕するという...
まだ試行錯誤中だが、DBの値からハッシュ構造を作り、対応させようかと考え中
member.point
=> {
member.key_1: member.val_1,
member.key_2: member.val_2,
member.key_3: member.val_3,
member.key_4: member.val_4
}
const HP = 1
member.point[HP]
member.attribute_for_update
=> {
member.key_1: ‘val_1’.to_sym,
member.key_2: ’val_2‘.to_sym,
member.key_3: ‘val_3’.to_sym,
member.key_4: ‘val_4’.to_sym
}
member.update_attributes = { member.attribute_for_update[HP] => 200 }
最終的にどんな実装になるやら。。。