場合分けのあるアクションをどのように綺麗に書くか


#1

例えばUsersController#showで、表示するユーザに

  • 自分の場合
  • 友達の場合
  • 自分でも友達でもない場合

の3パターンが有り、それぞれでビューテンプレートを切り替えて、かつ表示する項目が増減したりhookのような処理があるとします。

これをそれなりに素直に実装すると下記のようなコード(これは実際の savanna.io のコードです)になりますが、もっときれいに書けるのでは?という気もします。なにか良いアイデアはありますでしょうか?

  def show
    @user = User.find_by_screen_name!(params[:screen_name])
    @boards = @user.created_boards.readable(current_user).opened.page(1).per(5)
    if current_user == @user
      render 'me', layout: 'user'
    elsif current_user.friends_with?(@user)
      Notification::ConditionReader.call(
        reader: current_user, conditions: @user.latest_condition
      )
      render 'friend', layout: 'user'
    else
      @mutual_friends = MutualFriendsFinder.find(@user, current_user, limit: 9)
      render 'not_friend', layout: 'user'
    end
  end

#2

layoutについてですが。
もしこのコントローラで共通のレイアウトファイルを使っているなら、現在の user.html.erbusers.html.erb に名称変更して、/app/views/layout に置いておけば render メソッドでレイアウトオプションの定義が不要になりますね。
あるいは今トーラのクラス名の直下あたりに layout: 'user' と書いても良いですが。

このアクションだけで使用する場合は、

def show
  render layout: 'user`
  @user = User.find_by_screen_name!(params[:screen_name])
    :

というふうに書けば後続の render メソッドでも省略できるのかな?


#3

layoutは質問の本筋ではないので無視していただければ… :pray:

def show
  render layout: 'user`
  @user = User.find_by_screen_name!(params[:screen_name])

↑こう書いてしまうと、最初のrenderでテンプレートを探しに行ってActionView::MissingTemplateになりますね


#4

これを ViewModel と呼ぶのにはちょっと抵抗があるんですが、View を知っているモデルを使って

class UserViewModel
  def initialize(user:)
    @user = user
  end

  def boards
    @user.created_boards.readable(current_user).opened.page(1).per(5)
  end

  def view_name
    # ActiveSupport::CurrentAttributes で登録済みの current_user を参照する
    if CurrentAttributes.user == @user
      'me'
    elsif
      # ...
    end
  end

  def layout
    'user'
  end
end
def show
  @user_vm = UserViewModel.new(
    user: User.find_by_screen_name!(params[:screen_name])
  )
  render @user_vm.view_name, layout: @user_vm.layout
end

のように書くのはどうですか?

ActiveSupport::CurrentAttributes を使えばモデル内でも current_user へアクセス可能ですし(Model 内で参照する ActiveSupport::CurrentAttributes で設定した値を参照するのは異論はあると思うんですが) もしそれが気持ち悪いなら context として渡してあげても良さそうです。