Ruby on Rails 画面表示情報に関する悩み
Railsを書いていて、画面に渡す情報が変わったときに、やりたいことはシンプルなのに内部ロジックの変更は多くて、しかも自分のやり方が正しいのかどうかもわからないのでいつもすごくもやもやする。
抽象化できてなくてわかりづらいと思いますが、備忘録としてつらつらと書いていきます。
前提
下記のようなテーブルがあるとする。
users
column_name | desc |
---|---|
id | なし |
name | ユーザ名 |
password | パスワード |
user_name_timestamps
column_name | desc |
---|---|
id | なし |
user_id | users テーブルのID |
name | ユーザ名 |
created_at | レコード生成日時 |
updated_at | レコード更新日時 |
users
とuser_name_timestamps
は1:N
で結びついていて、
ユーザがユーザ名を変更するたびに、変更前のユーザ名をuser_name_timestamps
レコードとして保存しておく。
/app/models/user.rb
class User < ApplicationRecord has_many :user_name_timestamps end
/app/models/user_name_timestamp.rb
class UserNameTimestamp < ApplicationRecord belongs_to :user def stamp!(user) self.create!(user_id: user.id, name: user.name) end end
UsersController
は下記のようになっている。
/app/controllers/users_controller.rb
class UsersController < ApplicationController ... def show @user = User.find_by(params[:id]) end end
User#show
画面に「ユーザ名変更履歴」というリンクがあって、リンクをクリックするとUsers::NameTimestamps#index
に飛ぶ。
app/controllers/users/name_timestamps_controller.rb
class Users::NameTimestampsController < ApplicationController def index @user = User.includes(:user_name_timestamps).find_by(params[:user_id]) @name_timestamps = @user.user_name_timestamps end end
viewでは@user.id
や@user.name
を表示してどのユーザかをわかるようにし、
@name_timestamps.each do |t|
でタイムスタンプをリスト表示するようにしている。
悩みごと
下記のようなビジネス要件が来たとする。
『ユーザ詳細画面で、タイムスタンプのリンクをクリックするのがめんどくさい』というユーザからの意見が多い。 リンクをクリックしなくても、ユーザ詳細画面で見れるようにしてほしい。
現状の実装に改修を加えるとすると、素朴に考えたらこうだろうか。
/app/controllers/users_controller.rb
class UsersController < ApplicationController ... def show @user = User.includes(:user_name_timestamps).find_by(id: params[:id]) @name_timestamps = user.user_name_timestamps end end
resourceが2つあるのが気持ち悪いので、UserWithNameTimestamp
クラスを定義してまとめる。
/app/usecases/user_with_name_timestamp.rb
class UserWithNameTimestamp attr_reader :user, :name_timestamps def initialize(user_id) @user = User.includes(:user_name_timestamps).find_by(id: user_id) @name_timestamps = user.try!(:user_name_timestamps) end end
これでUsers#show
のresourceが1つにまとまった。
/app/controllers/users_controller.rb
class UsersController < ApplicationController ... def show @user_with_name_timestamps = UserWithNameTimestamp.new(params[:id]) end end
UsersController
が意識するresourceは1つになったけど、これ、扱っているresourceがUsers
じゃなくてUserWithNameTimestamp
だからUsersController
じゃないな。命名を変更するか。
と思ったけど、よく見たら他のactionではUser
がresourceとして使われている。
class UsersController < ApplicationController ... def create User.create!(params) redirect_to users_index_path end def show @user_with_name_timestamps = UserWithNameTimestamp.new(params[:id]) end end
…じゃあ別のControllerを作るしかない。
Users::WithNameTimeStampsController
を定義する。
/app/controllers/users/with_name_timestamps_controller.rb
class Users::WithNameTimestampsController < ApplicationController def show @user_with_name_timestamps = UserWithNameTimestamp.new(params[:id]) end end
これでUsers#show
は機能としては要らなくなるけど、user_id
パラメータはなんとかして渡さなきゃいけないし、
Users#show
actionに当たるものは存在しますよ、と明示しておきたいので、下記のようにする。
/app/controllers/users_controller.rb
class UsersController < ApplicationController ... def show redirect_to users_with_name_timestamps_show_path, {id: params[:id]} end end
Users::NameTimestampsController
は不要になる。画面としては消しておきたいので、actionをコメントアウトしておいて、後々ファイルごと削除する。
/app/controllers/users/name_timestamps_controller.rb
class Users::NameTimestampsController < ApplicationController # def index # @user = User.includes(:user_name_timestamps).find_by(params[:user_id]) # @name_timestamps = @user.user_name_timestamps # end end
…やることは終わったけど、これってやり方としてどうなんだろう。
「resrouce1つに対してcontrollerは1つ」を守るならば、改修後のUsers#show
はそれを守っていると言えるのか?と言われたら微妙だと思う。UsersControllerはなくして、User
に対するCRUDは全部UserWithNameTimestamp
クラスのメソッドとして定義し直せば、Users::WithNameTimestampsController
に1つのリソースとしてまとめられるけど、そんなことわざわざする…? とも思うし。
言いたいことの説明が下手くそすぎるんですけど、もしご意見、感想などありましたら是非いただきたいです。