6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Phoenix で Blog を超簡単に作る

Last updated at Posted at 2022-10-24

Phoenix の認証システム (phx.gen.auth) を使って、マルチユーザ版の Blog システムを、超簡単に作ってみるというのが本記事の趣旨です。ブログ投稿も phx.gen.html を使って一発で作成します。それら2つのテーブルを Ecto Association を使って one-to-many で結合します。ちなみに素朴なアイディアを実現するために、手早くザックリ作るのが趣旨ですので、細部は荒いままです。投稿と一覧表示のみ対応します。

  • 1. phx.gen.auth で認証システムを生成する (usersテーブル)
  • 2. phx.gen.html でブログ投稿システムを生成する (postsテーブル)
  • 3. Ecto Association で postsテーブルにuser_id の外部キーを設定する
  • 4. 生成されたコードを、Ecto Association を考慮して修正する

【関連過去記事】

1. 認証システム (usersテーブル)

まずプロジェクトの作成です。

mix phx.new blog
cd blog

認証システムを生成します。

mix phx.gen.auth Accounts User users

生成された設定などを反映させます。

mix deps.get
mix ecto.setup

PostgreSQL のポートが正しくないと、mix ecto.setup でエラーになります。私の Windows の環境ではデフォルトの 5432 ではなく 5433 だったので以下のように変更する必要がありました。

config\dev.exs
config :liveview_people, LiveviewPeople.Repo,
  username: "postgres",
  password: "postgres",
  hostname: "localhost",
  database: "liveview_people_dev",
  port: 5433,
  stacktrace: true,
  show_sensitive_data_on_connection_error: true,
  pool_size: 10

2. 投稿システム (postsテーブル)

投稿システムを生成します。

mix phx.gen.html Posts Post posts title:string  story:text

投稿システムを認証システムに組み込むために、router.ex に以下の一行を追加します。これで認証されたユーザのみが投稿システムのリソースにアクセスできるようになります。

lib/blog_web/router.ex
  scope "/", BlogWeb do
    pipe_through [:browser, :require_authenticated_user]
    ---
    resources "/posts", PostController   # 追加
  end

テーブルを作成します。

mix ecto.migrate

3. Ecto Association (one-to-many)

usersテーブルpostsテーブルとの Associations を追加します。

postsテーブルを変更する migration を作成します。

mix ecto.gen.migration post_belongs_to_user

どのように変更するかを記述します。ここでは user_idusers テーブルを参照する外部キーとして追加するように変更します。

priv/repo/migrations/20221024010750_post_belongs_to_user.exs
defmodule Blog.Repo.Migrations.PostBelongsToUser do
  use Ecto.Migration

  def change do
    alter table(:posts) do
      add :user_id, references(:users)
    end
  end
end

Post schema を修正(追加)します。ここでは belongs_to field を追加します。これにより post.userusers テーブルにアクセスできるようになります。

lib/blog/posts/post.ex
defmodule Blog.Posts.Post do
  use Ecto.Schema
  import Ecto.Changeset

  schema "posts" do
    field :story, :string
    field :title, :string
    belongs_to :user, Blog.Accounts.User   # 追加

    timestamps()
  end
  ---
end

User schema を修正(追加)します。これによる usersテーブルの変更はありませんが、user.posts によって posts テーブルへアクセスできるようになります。

lib/blog/accounts/user.ex
defmodule Blog.Accounts.User do
  use Ecto.Schema
  import Ecto.Changeset

  schema "users" do
    field :email, :string
    field :password, :string, virtual: true, redact: true
    field :hashed_password, :string, redact: true
    field :confirmed_at, :naive_datetime
    has_many :posts, Blog.Posts.Post      # 追加

    timestamps()
  end
---

migrate を実行して、実際にテーブルを変更します。

mix ecto.migrate

4.生成コードの修正

Ecto Association で、postsテーブルに外部キーの user_id を追加したことにより必要となる生成コードの修正を行います。今回はPost投稿とPost一覧表示に関する部分のみ修正します。

Post Schemachangeset の各行に user_id を追加します。

lib/blog/posts/post.ex
defmodule Blog.Posts.Post do
  ---
  def changeset(post, attrs) do
    post
    |> cast(attrs, [:title, :story, :user_id])        # user_id を追加
    |> validate_required([:title, :story, :user_id])  # user_id を追加
  end
end

post_controller.excreate() 関数に以下の2行を加えます。Postを作成するときに外部キーuser_id も指定するようにします。

lib/blog_web/controllers/post_controller.ex
  ---
  def create(conn, %{"post" => post_params}) do
    user = conn.assigns.current_user                        # 追加
    post_params = Map.put(post_params, "user_id", user.id)  # 追加
    case Posts.create_post(post_params) do
      {:ok, post} ->
        conn
        |> put_flash(:info, "Post created successfully.")
        |> redirect(to: Routes.post_path(conn, :show, post))

      {:error, %Ecto.Changeset{} = changeset} ->
        render(conn, "new.html", changeset: changeset)
    end
  end
  ---

post_controller.exindex() 関数を修正します。Post一覧を取得するときにログインユーザのPostのみを取得するように変更します。

lib/blog_web/controllers/post_controller.ex
  def index(conn, _params) do
    user = conn.assigns.current_user   # 追加
    posts = Posts.list_posts(user)     # 修正
    render(conn, "index.html", posts: posts)
  end

posts.exlist_posts/1 を追加します

lib/blog/posts.ex
defmodule Blog.Posts do
  ---
  alias Blog.Accounts.User   # 追加
  ---
  def list_posts(user) do    # 追加
    user = Repo.get(User, user.id) |> Repo.preload(:posts)
    user.posts
  end
  ---

プリロード
ここでは user.posts へのアクセスを可能とするためプリロードを行っています。
belongs_to 、 has_many 、そして has_one マクロからの公開する関連レコードにアクセスできるようにするために、関連したスキーマを プリロード する必要があります。プリロードしない限り、それらの関連したデータにアクセスすることはできません。ここではフェッチされたレコードのプリロードを行っています。
Ecto Query

Ecto Query
list_posts(user)の実装は少し真面目に考えれば、以下の方が好ましいかもしれない。

  def list_posts(user) do    # 追加
    query = from p in "posts",
            order_by: [desc: p.inserted_at], 
            where: p.user_id == ^user.id,
            select: %Post{:id => p.id, :title => p.title, :story => p.story}
    Repo.all(query)
  end

投稿の編集や削除なども同様に行えると想像しますが、今回の「超簡単に作る」という趣旨に反するのでこれ以上突っ込みません。

5. 動作確認

動作を見る前に、予習的に認証システムのメニューのコードを見ておきます。

lib/blog_web/templates/layout/_user_menu.html.heex
<ul>
<%= if @current_user do %>
  <li><%= @current_user.email %></li>
  <li><%= link "Settings", to: Routes.user_settings_path(@conn, :edit) %></li>
  <li><%= link "Log out", to: Routes.user_session_path(@conn, :delete), method: :delete %></li>
<% else %>
  <li><%= link "Register", to: Routes.user_registration_path(@conn, :new) %></li>
  <li><%= link "Log in", to: Routes.user_session_path(@conn, :new) %></li>
<% end %>
</ul>

http:localhost:4000 にアクセスすると通常のPhoenixのトップ画面ですが、赤い枠のところに認証システムのメニューが表示されます

image.png

http:localhost:4000/posts にアクセスするとログイン画面にリダイレクトされる
image.png

ユーザ登録画面です
image.png

ログイン画面です
image.png

ログインすると認証システムのメニューが変わります
image.png

http://localhost:4000/posts にアクセスすると、認証されたユーザの画面になります。ちなみにログインしていない状態でアクセスすると、トップページにリダイレクトされます。
image.png

投稿ページです
image.png

別ユーザでログインし直すと、このユーザのPost一覧が表示されます。
image.png

今回は以上です

6
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?