スポンサーリンク

2015年8月18日

[Rails Tutorial for Phoenix]Pagination refactoring

Goal

ページネーションをリファクタリングする。

Dev-Environment

OS: Windows8.1
Erlang: Eshell V6.4, OTP-Version 17.5
Elixir: v1.0.4
Phoenix Framework: v0.13.1
PostgreSQL: postgres (PostgreSQL) 9.4.4

Wait a minute

ソースコードが汚い、汚い!!と騒いでいた時期がありましたね(笑)
まずは、私の中で一番目に付くページネーションの部分をリファクタリングします。
内容的にリファクタリングと言えるのか分かりませんが・・・
私の中では、ソースコードの改善 = リファクタリングだと思っています。
細かいことは、まぁいいでしょう。
重要なのは、読みやすいか読み辛いかですね。

Index

Paginate refactoring
|> User List of header layout
|> User profile of header layout
|> Duplicate processing in the action
|> Add function of pagination
|> Fix mistakes

User List of header layout

レイアウトのヘッダー部分にあるユーザ一覧リンクを修正します。
ファイル: web/templates/layout/header.html.eex
ヘッダーのリンク部分を修正します。
修正前
<li><%= link "All Users", to: add_first_page_param(user_path(@conn, :index)) %><li>
修正後
<li><%= link "All Users", to: user_path(@conn, :index) %><li>
ファイル: web/controllers/user_controller.ex
送信されるパラメータに値がなくても動作するように修正します。
以下の行を削除して下さい。
plug :scrub_params, "select_page" when action in [:index, :show]
修正前
def index(conn, %{"select_page" => select_page}) do
  page = SampleApp.User.paginate(select_page)

  ...
end
修正後
def index(conn, params) do
  select_page = params["select_page"]

  if is_nil(select_page) do
    select_page = "1"
  end

  page = SampleApp.User.paginate(select_page)

  ...
end
この時点では、あまりよろしくない方法を取っています。
ですが、安心して下さい。
この記事の後ろの方で重複する処理を再度修正します。

User profile of header layout

同じく、レイアウトのヘッダー部分にあるユーザプロファイルへのリンクを修正します。
修正する量が少し多いので、ミスに注意して下さい。
ファイル: web/templates/layout/header.html.eex
ヘッダーのリンク部分を修正します。
修正前
<li><%= link "Profile", to: add_first_page_param(user_path(@conn, :show, current_user(@conn).id)) %><li>
修正後
<li><%= link "Profile", to: user_path(@conn, :show, current_user(@conn)) %><li>
ファイル: web/controllers/user_controller.ex
送信されるパラメータに値がなくても動作するように修正します。
修正前
def show(conn, %{"id" => id, "select_page" => select_page}) do
  user = Repo.get(SampleApp.User, id) |> Repo.preload(:relationships) |> Repo.preload(:reverse_relationships)
  ids_list = list_map_to_value_list(user.followed_users, :followed_id)

  page = SampleApp.Micropost.paginate(user.id, select_page, ids_list)
  changeset = SampleApp.Micropost.new_changeset(
    %SampleApp.Micropost{}, %{content: "", user_id: user.id})

  ...
end
修正後
def show(conn, params) do
  select_page = params["select_page"]
  id = params["id"]

  if is_nil(select_page) do
    select_page = "1"
  end

  user = Repo.get(SampleApp.User, id) |> Repo.preload(:relationships) |> Repo.preload(:reverse_relationships)
  ids_list = list_map_to_value_list(user.followed_users, :followed_id)

  page = SampleApp.Micropost.paginate(user.id, select_page, ids_list)
  changeset = SampleApp.Micropost.new_changeset(
    %SampleApp.Micropost{}, %{content: "", user_id: user.id})

  ...
end
ファイル: web/templates/user/show_follow.html.eex
上記以外にもリンクを作っているところがあります。
同様の修正を行います。
修正前
<div><%= link "view my profile", to: add_first_page_param(user_path(@conn, :show, @user)), class: "btn btn-default btn-xs" %></div>
修正後
<div><%= link "view my profile", to: user_path(@conn, :show, @user), class: "btn btn-default btn-xs" %></div>
修正前
<div style="clear: left; margin-top: 30px;">
  <%= if @users do %>
    <div class="user_avatars">
      <%= for follow_user <- @users do %>
        <a href="<%= add_first_page_param(user_path(@conn, :show, follow_user)) %>">
          <img src="<%= get_gravatar_url(follow_user) %>" class="gravatar">
        </a>
      <% end %>
    </div>
  <% end %>
</div>
修正後
<div style="clear: left; margin-top: 30px;">
  <%= if @users do %>
    <div class="user_avatars">
      <%= for follow_user <- @users do %>
        <a href="<%= user_path(@conn, :show, follow_user) %>"> <!-- 修正部分 -->
          <img src="<%= get_gravatar_url(follow_user) %>" class="gravatar">
        </a>
      <% end %>
    </div>
  <% end %>
</div>
修正前
<div style="clear: right; margin-left: 250px;">
  <%= if @users do %>
    <ul>
      <%= for follow_user <- @users do %>
        <li>
          <a href="<%= add_first_page_param(user_path(@conn, :show, follow_user)) %>">
            <img src="<%= get_gravatar_url(follow_user) %>" class="gravatar">
            <h4><%= follow_user.name %></h4>
          </a>
        </li>
      <% end %>
    </ul>

    ...
  <% end %>
</div>
修正後
<div style="clear: right; margin-left: 250px;">
  <%= if @users do %>
    <ul>
      <%= for follow_user <- @users do %>
        <li>
          <a href="<%= user_path(@conn, :show, follow_user) %>"> <!-- 修正部分 -->
            <img src="<%= get_gravatar_url(follow_user) %>" class="gravatar">
            <h4><%= follow_user.name %></h4>
          </a>
        </li>
      <% end %>
    </ul>

    ...
  <% end %>
</div>
ファイル: web/templates/user/index.html.eex
上記以外にもリンクを作っているところがあります。
同様の修正を行います。
修正前
<a href="<%= add_first_page_param(user_path(@conn, :show, user)) %>">
修正後
<a href="<%= user_path(@conn, :show, user) %>">
ファイル: web/views/user_view.ex
不要になった関数を削除します。
def add_first_page_param(action) do
  "#{action}?select_page=1"
end
ファイル: web/views/layout_view.ex
不要になった関数を削除します。
def add_first_page_param(action) do
  "#{action}?select_page=1"
end

Duplicate processing in the action

Userコントローラのindex、show、following、followersで重複している処理がありますね。
この重複部分を一つにまとめます。
PaginationHelperにて同じような処理をしているのを覚えていますか?
この部分で対応することにしましょう。
ファイル: lib/helpers/pagination_helper.ex
ページ遷移できない場合は、問答無用で最初のページ情報を返すように修正します。
以下の定数を追加。
@first_page "1"
修正前
def paginate(query, select_page) do
  if is_able_to_paginate?(select_page) do
    query |> SampleApp.Repo.paginate(page: select_page, page_size: @page_size)
  else
    nil
  end
end
修正後
def paginate(query, select_page) do
  if is_able_to_paginate?(select_page) do
    query |> SampleApp.Repo.paginate(page: select_page, page_size: @page_size)
  else
    query |> SampleApp.Repo.paginate(page: @first_page, page_size: @page_size)
  end
end
ファイル: web/controllers/user_controller.ex
index、show、following、followersアクション内にある重複している処理を削除します。
以下の部分を削除します。
if is_nil(select_page) do
  select_page = "1"
end

Add function of pagination

show_follow.html.eexでのユーザ表示をするため、
専用のページネーションの関数をUserモデルへ追加します。
ファイル: web/models/user.ex
ページネーション用の関数を追加します。
def show_follow_paginate(select_page, ids_list) do
  SampleApp.Helpers.PaginationHelper.paginate(
    from(u in SampleApp.User, where: u.id in ^ids_list, order_by: [asc: :name]),
    select_page)
end
ファイル: web/controllers/user_controller.ex
上記で定義した関数を使うように修正します。
修正前
def followers(conn, params) do
  ...
  user = Repo.get(SampleApp.User, id) |> Repo.preload(:relationships) |> Repo.preload(:reverse_relationships)
  ids_list = list_map_to_value_list(user.followers, :follower_id)

  page = SampleApp.Helpers.PaginationHelper.paginate(
    from(u in SampleApp.User, where: u.id in ^ids_list, order_by: [asc: :name]),
    select_page)
  ...
end
修正後
def following(conn, params) do
  ...
  user = Repo.get(SampleApp.User, id) |> Repo.preload(:relationships) |> Repo.preload(:reverse_relationships)
  page = SampleApp.User.show_follow_paginate(
    select_page, list_map_to_value_list(user.followed_users, :followed_id))
  ...
end
修正前
def followers(conn, params) do
  ...
  user = Repo.get(SampleApp.User, id) |> Repo.preload(:relationships) |> Repo.preload(:reverse_relationships)
  ids_list = list_map_to_value_list(user.followers, :follower_id)

  page = SampleApp.Helpers.PaginationHelper.paginate(
    from(u in SampleApp.User, where: u.id in ^ids_list, order_by: [asc: :name]),
    select_page)
  ...
end
修正後
def followers(conn, params) do
  ...
  user = Repo.get(SampleApp.User, id) |> Repo.preload(:relationships) |> Repo.preload(:reverse_relationships)
  page = SampleApp.User.show_follow_paginate(
    select_page, list_map_to_value_list(user.followers, :follower_id))
  ...
end

Fix mistakes

以前、修正するのを忘れていた部分を見つけました。
ファイル: web/controllers/user_controller.ex
テンプレートへ送る名前を間違っていました。
followed_users: -> users:
修正前
def following(conn, params) do
  ...

  if page do
    ...
  else
    conn
    |> put_flash(:error, "Invalid page number!!")
    |> render("following.html", user: user, followed_users: [])
  end
end
修正後
def following(conn, params) do
  ...

  if page do
    ...
  else
    conn
    |> put_flash(:error, "Invalid page number!!")
    |> render("following.html", user: user, users: [])
  end
end
修正前
def followers(conn, params) do
  ...
  if page do
    ...
  else
    conn
    |> put_flash(:error, "Invalid page number!!")
    |> render("followers.html", user: user, followed_users: [])
  end
end
修正後
def followers(conn, params) do
  ...
  if page do
    ...
  else
    conn
    |> put_flash(:error, "Invalid page number!!")
    |> render("followers.html", user: user, users: [])
  end
end

Speaking to oneself

少しは読みやすくなったでしょうか?
リファクタリング一回目としては、こんなところにしておきます。
次は、ビュー関連のリファクタリングをすることにします。
変更点の一覧については以下のリンク先にあります。
変更点: Github - darui00kara/phoenix_tutorial (Pagination refactoring)

Bibliography

特になし

人気の投稿