ぜったいってなに

Software Engineerのブログです。

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 レコード更新日時


usersuser_name_timestamps1: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つのリソースとしてまとめられるけど、そんなことわざわざする…? とも思うし。



言いたいことの説明が下手くそすぎるんですけど、もしご意見、感想などありましたら是非いただきたいです。