Goal
フォロー/アンフォローのWebインターフェースを実装する。
また、フォロー/フォロワーのユーザ一覧ページを実装する。
また、フォロー/フォロワーのユーザ一覧ページを実装する。
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
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
少し、Rails Tutorialと実施する順番が異なる。
最初に書いておきます。
ソースコードが汚い。本当に汚い。
最低のスパゲティコードです。
ソースコードが汚い。本当に汚い。
最低のスパゲティコードです。
記事を見て下さっている方には大変申し訳ないのですが、
修正は後回しにさせて頂きます。
修正は後回しにさせて頂きます。
他人にお見せするソースコードではないことは、重々承知の上です。
笑われるのを承知の上で公開します。
コメントでm9(^Д^)してくれても構いません。
笑われるのを承知の上で公開します。
コメントでm9(^Д^)してくれても構いません。
それでもTutorialを全部終わらせるのを優先させて下さい。
約束します。必ず修正します。
お目汚しのソースコードを公開して申し訳ない。
お目汚しのソースコードを公開して申し訳ない。
独り言にロードマップ(?)を書いてありますので、
詳しいところはそちらを見て頂ければと思います。m(_ _)m
詳しいところはそちらを見て頂ければと思います。m(_ _)m
(よし・・・言い訳完了)
Index
Web interface for the user who follows
|> Following/Followers User List
|> Display Follow/Unfollow Button
|> Relationship Controller
|> Extra
|> Following/Followers User List
|> Display Follow/Unfollow Button
|> Relationship Controller
|> Extra
Following/Followers User List
フォローしているユーザ一覧とフォロワーユーザ一覧を表示させます。
ファイル: web/router.ex
ルーティングの追加。
ルーティングの追加。
get "user/:id/following", UserController, :following
get "user/:id/followers", UserController, :followers
ルーティングを取得。
>mix phoenix.routes
...
user_path GET /user/:id/following SampleApp.UserController.following/2
user_path GET /user/:id/followers SampleApp.UserController.followers/2
...
ユーザのプロファイルページにfollowingとfollowersの値とリンクを表示させる。
ファイル: web/controllers/user_controller.ex
ユーザデータの取得時にpreloadを追加。
ユーザデータの取得時にpreloadを追加。
def show(conn, %{"id" => id, "select_page" => select_page}) do
user = Repo.get(SampleApp.User, id) |> Repo.preload(:relationships) |> Repo.preload(:reverse_relationships)
...
end
ファイル: web/templates/user/show.html.eex
以下のようにリンクを追加。
以下のようにリンクを追加。
<div style="float: left; margin-top: 20px; margin-right: 20px;">
<img src="<%= get_gravatar_url(@user) %>" class="gravatar">
<h2><%= @user.name %></h2>
<strong style="float: left; margin-right: 5px">
<a href="<%= user_path(@conn, :following, @user) %>">Following (<%= Enum.count(@user.followed_users) %>)</a>
</strong>
<strong style="float: right;">
<a href="<%= user_path(@conn, :followers, @user) %>">Followers (<%= Enum.count(@user.followers) %>)</a>
</strong>
</div>
今のままではリンク先がありません。
followingから実装します。
followingから実装します。
ファイル: web/controllers/user_controller.ex
認可に追加。
plug SampleApp.Plugs.SignedInUser when action in [:index, :show, :edit, :update, :delete, :following, :followers]
アクションを追加。
def following(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 = to_followed_user_ids_list(user.followed_users)
page = SampleApp.Helpers.PaginationHelper.paginate(
from(u in SampleApp.User, where: u.id in ^ids_list, order_by: [asc: :name]),
select_page)
if page do
render(conn, "following.html",
user: user,
users: page.entries,
current_page: page.page_number,
total_pages: page.total_pages,
page_list: Range.new(1, page.total_pages))
else
conn
|> put_flash(:error, "Invalid page number!!")
|> render("following.html", user: user, followed_users: [])
end
end
関数を追加。
defp list_map_to_value_list(repo_result, key) do
for map <- repo_result do Map.get(map, key) end
end
ファイル: web/templates/user/following.html.eex
テンプレートを作成。
テンプレートを作成。
<h2>Followed users</h2>
<%= render "show_follow.html", action: user_path(@conn, :following, @user), conn: @conn,
user: @user,
users: @users,
current_page: @current_page,
total_pages: @total_pages,
page_list: @page_list %>
続いて、followersを実装します。
ファイル: web/controllers/user_controller.ex
アクションを追加。
アクションを追加。
def followers(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 = to_follower_user_ids_list(user.followers)
page = SampleApp.Helpers.PaginationHelper.paginate(
from(u in SampleApp.User, where: u.id in ^ids_list, order_by: [asc: :name]),
select_page)
if page do
render(conn, "followers.html",
user: user,
users: page.entries,
current_page: page.page_number,
total_pages: page.total_pages,
page_list: Range.new(1, page.total_pages))
else
conn
|> put_flash(:error, "Invalid page number!!")
|> render("followers.html", user: user, followed_users: [])
end
end
ファイル: web/templates/user/followers.html.eex
テンプレートを作成。
テンプレートを作成。
<h2>Follower users</h2>
<%= render "show_follow.html", action: user_path(@conn, :followers, @user), conn: @conn,
user: @user,
users: @users,
current_page: @current_page,
total_pages: @total_pages,
page_list: @page_list %>
ファイル: web/templates/user/show_follow.html.eex
共通のテンプレートを作成。
共通のテンプレートを作成。
<div style="float: left; margin-top: 20px; margin-right: 20px;">
<img src="<%= get_gravatar_url(@user) %>" class="gravatar">
<h1><%= @user.name %></h1>
<div><%= link "view my profile", to: user_path(@conn, :show, @user), class: "btn btn-default btn-xs" %></div>
<strong style="float: left; margin-right: 5px">
Following (<%= Enum.count(@user.followed_users) %>)
</strong>
<strong style="float: right;">
Followers (<%= Enum.count(@user.followers) %>)
</strong>
</div>
<div style="clear: left; margin-top: 30px;">
<%= if @users do %>
<div class="user_avatars">
<%= for follow_user <- @users do %>
<img src="<%= get_gravatar_url(follow_user) %>" class="gravatar">
<% end %>
</div>
<% end %>
</div>
<div style="clear: right; margin-left: 250px;">
<%= if @users do %>
<ul>
<%= for follow_user <- @users do %>
<li>
<img src="<%= get_gravatar_url(follow_user) %>" class="gravatar">
<h4><%= follow_user.name %></h4>
</li>
<% end %>
</ul>
<%= render "pagination.html",
action: @action,
current_page: @current_page,
page_list: @page_list,
total_pages: @total_pages %>
<% end %>
</div>
Display Follow/Unfollow Button
フォロー/アンフォローのボタンを表示させます。
ファイル: web/templates/user/show.html.eex
以下を追加。(この時点では暫定)
以下を追加。(この時点では暫定)
<%= if !current_user?(@conn, @user) do %>
<%= if !following?(@conn, @user.id) do %>
<%= form_tag(user_path(@conn, :following, @conn.assigns[:current_user]), method: :post) %>
<input type="hidden" name="follow_id" value="<%= @user.id %>">
<%= submit "Follow", class: "btn btn-primary" %>
</form>
<% else %>
<%= form_tag(user_path(@conn, :followers, @conn.assigns[:current_user]), method: :delete) %>
<input type="hidden" name="unfollow_id" value="<%= @user.id %>">
<%= submit "Unfollow", class: "btn btn-primary" %>
</form>
<% end %>
<% end %>
ファイル: web/views/user_view.ex
関数を追加。
関数を追加。
def following?(conn, follow_user_id) do
SampleApp.Relationship.following?(conn.assigns[:current_user].id, follow_user_id)
end
def current_user?(conn, user) do
conn.assigns[:current_user].id == user.id
end
ファイル: web/controllers/user_controller.ex
別ユーザのプロファイルを参照できるように、showを削除。
別ユーザのプロファイルを参照できるように、showを削除。
plug :correct_user? when action in [:edit, :update, :delete]
Relationship Controller
フォローする、フォロー解除を画面から行えるようにします。
ファイル: web/router.ex
ルーティングを追加。
ルーティングを追加。
resources "/relationship", RelationshipController, only: [:create, :delete]
ファイル: web/controllers/relationship_controller.ex
コントローラを作成。
コントローラを作成。
defmodule SampleApp.RelationshipController do
use SampleApp.Web, :controller
plug SampleApp.Plugs.CheckAuthentication
plug SampleApp.Plugs.SignedInUser
plug :action
def create(conn, params) do
if SampleApp.Relationship.follow!(params["id"], params["follow_id"]) do
conn = put_flash(conn, :info, "Follow successfully!!")
else
conn = put_flash(conn, :error, "Follow failed!!")
end
redirect(conn, to: static_pages_path(conn, :home))
end
def delete(conn, params) do
SampleApp.Relationship.unfollow!(params["id"], params["unfollow_id"])
conn
|> put_flash(:info, "Unfollow successfully!!")
|> redirect(to: static_pages_path(conn, :home))
end
end
ファイル: web/templates/user/show.html.eex
以下のように修正。
以下のように修正。
<%= if !current_user?(@conn, @user) do %>
<%= if !following?(@conn, @user.id) do %>
<%= form_tag(relationship_path(@conn, :create), method: :post) %>
<input type="hidden" name="id" value="<%= @conn.assigns[:current_user].id %>">
<input type="hidden" name="follow_id" value="<%= @user.id %>">
<%= submit "Follow", class: "btn btn-primary" %>
</form>
<% else %>
<%= form_tag(relationship_path(@conn, :delete, @conn.assigns[:current_user]), method: :delete) %>
<input type="hidden" name="unfollow_id" value="<%= @user.id %>">
<%= submit "Unfollow", class: "btn btn-primary" %>
</form>
<% end %>
<% end %>
Extra
リスト内のデータ数をカウントしたいことがあると思う。
以下のようにすれば、Ectoを使ってDBから取得したデータの数を取得できる。
以下のようにすれば、Ectoを使ってDBから取得したデータの数を取得できる。
iex> users = SampleApp.Repo.all(SampleApp.User)
iex> Enum.count(users)
Ectoにおける副問合せ。
以下のようにして副問合せができる。
以下のようにして副問合せができる。
iex> SampleApp.Repo.all(from u in SampleApp.User, where: u.id in [2,3] or u.id == 1)
Speaking to oneself
すいませんすいませんすいませんす
いませんすいませんすいませんすい
ませんすいませんすいませんすいま
せんすいませんすいませんすいませ
んすいませんすいませんすいません
いませんすいませんすいませんすい
ませんすいませんすいませんすいま
せんすいませんすいませんすいませ
んすいませんすいませんすいません
ソースコードが汚いし整合性が取れてないですよね。
後で直すんで今はご勘弁を・・・
(ページネーションの部分も変わります)
後で直すんで今はご勘弁を・・・
(ページネーションの部分も変わります)
説明も全然書いてない・・・
(いつものことな気がするが・・・)
(いつものことな気がするが・・・)
今後の予定です。毎度のことですが暫定です。
かなりの気分屋なので変わるかもです。
(でもソースコードのリファクタリングは必ず実施します)
かなりの気分屋なので変わるかもです。
(でもソースコードのリファクタリングは必ず実施します)
- v0.1 ・・・ 一通りやり通す
- v0.2 ・・・ ソースコードの大修正(リファクタリング)
- v0.3 ・・・ 記事改稿、記事中のソースを移動/修正
- v0.4 ・・・ v0.16.1(その時の最新)に対応(バージョンアップ)
- v0.5 ・・・ ???
- v?.? ・・・ 自作のページネーションライブラリを組込む
こういうのもロードマップと言うんだろうか・・・
当初、Ectoで副問合せするためには、
SQLを自分で作成して投げる必要があると勘違いしていました。
SQLを自分で作成して投げる必要があると勘違いしていました。
しかし、調査不足でした。
“Ecto.Query.API”の存在を知らず、自分で実装しようと思った。
“Ecto.Query.API”の存在を知らず、自分で実装しようと思った。
見つかったのは完全に偶然の産物でした。
良かった・・・本当に良かった・・・
良かった・・・本当に良かった・・・
何と言うか・・・ちゃんと調べましょうってのが今回の教訓ですねorz
Twitterで喚いていた基地外は自分です・・・orz
Twitterで喚いていた基地外は自分です・・・orz