23. locale の積極的な利用

前書き

app/views/layout/application.html.haml を見返してみると、ページタイトル部に以下の記述を見つけた。

    %title= content_for?(:title) ? yield(:title) : “webcit3”

調べてみるとテープレート内に :title というコンテンツがあれば、それを yield してくれるとのこと。これは、いろんなことに使えそうだと早速リファクタリングする。

まずは、すでに実装されているタイトル表示の問題を修正する。問題の一例をここに示す。現在、teacher/years#index は、年度一覧を表示するものであり、その
ページタイトルはそのまま「年度一覧」と記載している。ただし、このページでは年度の変更処理しかしないので、teacher/kyoumus#index からのリンクは「年度変更」と記載している。旧システムではこのようにページごとに表記が乱れていた。

locale の設定

この問題を解決するために、今回作成しているシステムでは、このようなタイトル部分などにlocale を使用することにした。今回は、ページのタイトルの locale をまとめた config/locales/views/titles/ja.yml というファイルを作成し、以下のように記載する(この部分は後日修正する)。

ja:
  views:
    teacher_years:
      index:
        title: 年度変更

view の修正

app/views/teacher/years/index.html.haml の先頭を以下のように修正する。ここでは、:title に相当する content_for を用意する。先に書いた layout ファイルからこの部分がメソッドとして呼ばれることになる。実際にページタイトルを記述するとき再度呼ぶのも莫迦らしいので、@title にキャッシュしておく。また、ページ内のタイトル表示である %h1 は消しておく。

- content_for :title do
  = @title = page_title

%h1 を消したのは、ページのタイトル表示である %h1 はほとんどのページで記載するはずだからである。そうであればその記述は、app/views/layout/application.html.haml に記載すればよい。該当部は以下のようになる。%h1 の記載が必要ないページであれば、contnt_for の中で @title に代入しなければよい。このため、%h1 は以下のように layout に移行した。

    .container-fluid
      -if @title
        %h1= @title
      = yield

page_title の実装

page_title は自分自身のパスから locale を引き出すメソッドである(このメソッドは後日なくなる)。ほとんど view から呼ばれるのだが、一部 controller から呼ばれる可能性もあるため、今回は app/controllers/application_controller.rb に記述し、helper_method とする。request.path で現在のパスを取得し、それを title_from_path メソッドに渡す。

  # @return [String] 現在のページタイトルを取得する。
  def page_title
    title_from_path(request.path)
  end
  helper_method :page_title

title_from_path は path 文字列から locale 用の文字列を取り出すメソッドである。まず、routes の recognize_path により teacher/years という文字列から path のハッシュを取り出す。次にこのハッシュから :controller と :action の文字列を取り出す。この場合の中身はそれぞれ、’teacher/years’ と ‘index’ になる。「/」は locale 文字列に使えないので、gsub で _ に変換することで、最終的にviews.teacher_years.index.title という文字列を得る。これを t メソッドで変換することで、「年度変更」を取得できる。

  # @param [String] URL
  # @return [String] タイトル
  def title_from_path(path)
    path = Rails.application.routes.recognize_path(path)
    c, a = path.values_at(:controller, :action)
    value = t([ 'views', c.gsub('/', '_'), a, 'title' ].join('.'))
  end
  helper_method :title_from_path

上記の修正でページのタイトルが年度変更と表示されるようになった。turnip の feature は「年度一覧」が表示されているかをテストしているので、テストに失敗してしまった。こちらはテストを修正した。

breadcrumb の view の修正

ここまで breadcrumb の表示部は Home へのリンクだけで、後は文字列を表示する処理だけを記述していた。この部分も content_for で対応する。app/views/shared/_top_link_bar.html.haml を以下のように修正する。

= yield :navi if content_for(:navi)

先ほどの app/views/teacher/years/index.html.haml の :title コンテンツの下に :navi コンテンツを追加する。navi メソッドにタイトルとパスを併記した配列を渡す。タイトルを nil にした場合にはパスから自動的に読み取ることとする。当初配列の配列にするつもりだったが、表記を楽にするために 2n 個の要素を持つ配列にした。

- content_for :navi do
  = navi [ nil, teacher_years_path ]

navi は app/helpers/application_helper.rb に記述する。これは、render するだけの簡単なもの。

  # @note ナビゲーションリンクを表示
  def navi(array)
    render(partial: 'shared/navi_link_bar', locals:{ navi: array })
  end

app/views/shared/_navi_link_bar.html.haml は以下のようになる。each_slice(2) で配列から二つずつ取り出し、タイトルとパスを取得する。タイトルが nil の場合には title_from_path を使ってタイトル文字列を自動生成する。

%ul.breadcrumb
  %li= "#{now_year.wareki_nendo}・ユーザ名"
  %li= link_to lh('Home'), root_path
  - last = navi.last
  - navi.each_slice(2) do |(value, path)|
    %li
      - if !path || path == last
        = value || title_from_path(path)
      -else
        -if value
          = link_to value, path
        -else
          = link_to *title_with_path(path)

また、パン屑リストの最後は自分自身を示すので、リンクは作成しない。このため、リンクを省略した書き方もできる。例えば、app/views/teacher/kyoumus/index.html.haml では以下のように書いている。

- content_for :navi do
  = navi [ @title ]

逆に、今後 teacher/year#show などを追加することがあるようであれば、以下のように #index における表記に追記するだけでよくなる。このため、パンくずリストが進むようなことが可能性がある場合は、基本的に前者のような記載をする予定である。

- content_for :navi do
  = navi [ nil, teacher_years_path, nil, teacher_year_path ]

最後の title_with_path は title_from_path と path を並べたものである。これは view でしか使用しないので、app/helpers/applicaiton_helper.rb に記述する。今後あちこちの view で活躍するメソッドになると期待する。

  # @param [String] URL
  # @return [Array<String>] 翻訳文字列と URL の配列
  def title_with_path(path)
    [ title_from_path(path), path ]
  end

今日はここまで。

written by iHatenaSync