どうも、靖宗です。
前回かなり中途半端なところで終わりましたが、気にせず続けます。
Cross-context dependencies
前回はCMS
ってContextを作ろう!というところで終わってました。なので作って行きます。
スキーム
今回は
- title:string タイトル。文字列。
- body:string 本文。文字列。
- views:integer 閲覧数?数値。
というモデルになっているようです。
mix phx.gen.html
でContext生成
PS \hello> mix phx.gen.html CMS Page pages title:string body:text views:integer --web CMS * creating lib/hello_web/controllers/cms/page_controller.ex * creating lib/hello_web/templates/cms/page/edit.html.eex * creating lib/hello_web/templates/cms/page/form.html.eex * creating lib/hello_web/templates/cms/page/index.html.eex * creating lib/hello_web/templates/cms/page/new.html.eex * creating lib/hello_web/templates/cms/page/show.html.eex * creating lib/hello_web/views/cms/page_view.ex * creating test/hello_web/controllers/cms/page_controller_test.exs * creating lib/hello/cms/page.ex * creating priv/repo/migrations/20190403003143_create_pages.exs * creating lib/hello/cms.ex * injecting lib/hello/cms.ex * creating test/hello/cms/cms_test.exs * injecting test/hello/cms/cms_test.exs Add the resource to your CMS :browser scope in lib/hello_web/router.ex: scope "/cms", HelloWeb.CMS, as: :cms do pipe_through :browser ... resources "/pages", PageController end Remember to update your repository by running migrations: $ mix ecto.migrate
復習にはなりますがmix phx.gen.html Context名 モデル名(コントローラとかで使われる) スキーム名 スキーム (--web 名前空間)
でリソースに必要なファイルやContextが生成できます。
--web 名前空間
のオプションですが、今回Page
という名前のモデル?モジュール名を利用しているので、そのまま使うとPageController
とかが被ってしまいます。
そういうときにはこのオプションを利用するようです。(基本的にContext毎に分けた方がいいのでは?とは思いますが、まだ慣れてないので今はそういう物だと考えておきます。)
生成されているファイルがlib/hello_web/controllers/cms/page_controller.ex
となっている所からも重複が回避されているのが分かります。
スキームに合わせたテンプレートの調整
views
という項目はユーザーが直接編集するものではないのでフォームのビューからは消しておきます。lib/hello_web/templates/cms/page/form.html.eex
のviews
に該当する箇所を消去します。
<%= form_for @changeset, @action, fn f -> %> <%= if @changeset.action do %> <div class="alert alert-danger"> <p>Oops, something went wrong! Please check the errors below.</p> </div> <% end %> <%= label f, :title %> <%= text_input f, :title %> <%= error_tag f, :title %> <%= label f, :body %> <%= textarea f, :body %> <%= error_tag f, :body %> <div> <%= submit "Save" %> </div> <% end %>
あっても動くとは思いますがドキュメントに従いましょう。
スキームに合わせたchangeset
の変更
上記と同様の理由でスキームに付属するchangeset
のバリデーションも変更します。
Contextで対応するのかな?と思っていましたがchangeset
で落としてしまうようです。
lib/hello/cms/page.ex
を編集します。
... @doc false def changeset(page, attrs) do page |> cast(attrs, [:title, :body]) # :viewsを消去 |> validate_required([:title, :body]) # :viewsを消去 end end
マイグレーションファイルの調整
priv/repo/migrations
の該当するファイル(mix phx.gen.html
で生成されたやつ)を編集します。
defmodule Hello.Repo.Migrations.CreatePages do use Ecto.Migration def change do create table(:pages) do add :title, :string add :body, :text add :views, :integer, default: 0 # この箇所にデフォルト値を設定 timestamps() end end end
今回はスキームの変更によるものでは無いですが、おおよそスキームを変更した場合はマイグレーションファイルの調整が必要になるでしょう。
ルーティングの追加(router.ex)
CMSのContextを追加したのでPageのリソースが見れる様にします。
lib/hello_web/router.ex
を修正します。
... scope "/", HelloWeb do pipe_through :browser get "/", PageController, :index resources "/users", UserController resources "/sessions", SessionController, only: [:new, :create, :delete], singleton: true end scope "/cms", HelloWeb.CMS, as: :cms do pipe_through [:browser, :authenticate_user] resources "/pages", PageController end ...
scope "/", HelloWeb do
句を真似て書けば問題無いと思います。
ただし、今回はCMSに関連するページは認証が必要なようにしたいので:authenticate_user
プラグを適応するようpipe_through [:browser, :authenticate_user]
となっています。
マイグレートと認証機能の確認
ここまで来ると機能は実装されてないものの、だいたいの表示はできる筈です。
mix ecto.migrate
でデータベースをマイグレートしてサーバーを起動してみます。
PS \hello> mix ecto.migrate Compiling 6 files (.ex) Generated hello app [info] == Running 20190403003143 Hello.Repo.Migrations.CreatePages.change/0 forward [info] create table pages [info] == Migrated 20190403003143 in 0.0s PS \hello> mix phx.server
生成したPageのURLはhttp://localhost:4000/cms/pages
です。ここにアクセスしてみます。
ログインしていないのでput_flash
で怒られています。
http://localhost:4000/sessions/new
にて、前回までに作成したユーザーでログインしてみます。
ヨシ!
認証系の機能がここまで簡単に追加できるのは驚愕です。
Plugの拡張性の高さヤバイ。
Authorの追加
ではPageを追加・・・と行きたいところですが、作成者の情報を紐付けておかないと面倒なことになりそうなので先にそちらを済ませましょう。
emailの情報をAccountsのContextに追加したようにphx.gen.context
でCMSのContextにAuthorの情報を追加していきます。
スキームはこんなかんじ。
- bio:text 出身?text形?昔のバージョンによく見られるっぽいんですが、たぶん:stringと同義。生成されたスキームでは:stringになってた。
- role:string 役割。文字列。
- genre:string ジャンル。文字列。
- user_id:references:users:unique ユーザーID、ユーザーの情報と関連している
PS \hello> mix phx.gen.context CMS Author authors bio:text role:string genre:string user_id:references:users:unique You are generating into an existing context. The Hello.CMS context currently has 6 functions and 1 files in its directory. * It's OK to have multiple resources in the same context as long as they are closely related * If they are not closely related, another context probably works better If you are not sure, prefer creating a new context over adding to the existing one. Would you like to proceed? [Yn] Y * creating lib/hello/cms/author.ex * creating priv/repo/migrations/20190403013056_create_authors.exs * injecting lib/hello/cms.ex * injecting test/hello/cms/cms_test.exs Remember to update your repository by running migrations: $ mix ecto.migrate
UserとAuthorのContextを分けておくことで開発上いろんな利点がありそうです。
Authorに別情報が必要になってCMSのContextを編集する際でもAccounts側のUserは変更不要ですしCMS側にAcountsのUserデータ以外が流出することもないです。
マイグレーションファイルの調整
Accountsで認証情報を追加したときのように、従属関係にある際はon_delete
とnull: false
を設定しておいた方が良いです。(不用意にデータが残ってしまう可能性がある。)
なのでpriv/repo/migrations
に先ほどのmix phx.gen.context
で生成されたマイグレーションファイルを調整します。
defmodule Hello.Repo.Migrations.CreateAuthors do use Ecto.Migration def change do create table(:authors) do add :bio, :text add :role, :string add :genre, :string add :user_id, references(:users, on_delete: :delete_all), null: false # ここを修正 timestamps() end create unique_index(:authors, [:user_id]) end end
これでAuthorに関しては問題ありませんが、まだ従属関係はあります。
PageがAuthorに関連付いているので、この関連付けをデータベースに登録するためにマイグレーションファイルを作成します。
mix ecto.gen.migration
で任意のマイグレーションファイルを作成できるようです。
PS \hello> mix ecto.gen.migration add_author_id_to_pages
Compiling 2 files (.ex)
Generated hello app
* creating priv/repo/migrations/20190403015018_add_author_id_to_pages.exs
いつものようなタイムスタンプ+名前.exsが生成されました。
一応中身を見てみます。
defmodule Hello.Repo.Migrations.AddAuthorIdToPages do use Ecto.Migration def change do end end
このchange
関数の中に処理を記載してマイグレーションすれば良さそうです。
いつものマイグレーションファイルはcreate table(hogehoge)do
などが記載されています。
今回はドキュメントに従い、下記のようにしました。
defmodule Hello.Repo.Migrations.AddAuthorIdToPages do use Ecto.Migration def change do alter table(:pages) do add :author_id, references(:authors, on_delete: :delete_all), null: false end create index(:pages, [:author_id]) end end
create table
でテーブル作成ですが、alter table
でテーブルにカラムを追加できるようです。
add以降は割といつも通りでしょうか。
作成者が削除されたらページも削除されてしまうのはなんだか寂しい気がしますが、今回はCMSを作る練習と言うことでスルーします。
マイグレーションファイルの調整が完了すれば、やることは一つ。mix ecto.migrate
でマイグレーションします。
PS \hello> mix ecto.migrate warning: variable "null" does not exist and is being expanded to "null()", please use parentheses to remove the ambiguity or change the variable name priv/repo/migrations/20190403013056_create_authors.exs:9 ** (CompileError) priv/repo/migrations/20190403013056_create_authors.exs:9: undefined function add/4 (elixir) src/elixir_locals.erl:107: :elixir_locals."-ensure_no_undefined_local/3-lc$^0/1-0-"/2 (elixir) src/elixir_locals.erl:107: anonymous fn/3 in :elixir_locals.ensure_no_undefined_local/3 (stdlib) erl_eval.erl:680: :erl_eval.do_apply/6 (elixir) lib/code.ex:715: Code.load_file/2 (ecto_sql) lib/ecto/migrator.ex:489: Ecto.Migrator.load_migration/1 (elixir) lib/enum.ex:1327: Enum."-map/2-lists^map/1-0-"/2 (ecto_sql) lib/ecto/migrator.ex:435: Ecto.Migrator.do_migrate/4 (ecto_sql) lib/ecto/migrator.ex:429: Ecto.Migrator.migrate/4 (ecto_sql) lib/ecto/adapters/sql.ex:820: anonymous fn/3 in Ecto.Adapters.SQL.checkout_or_transaction/4 (db_connection) lib/db_connection.ex:1415: DBConnection.run_transaction/4 (ecto_sql) lib/ecto/adapters/sql.ex:727: Ecto.Adapters.SQL.lock_for_migrations/5 (ecto_sql) lib/ecto/migrator.ex:318: Ecto.Migrator.lock_for_migrations/3 (ecto_sql) lib/mix/tasks/ecto.migrate.ex:110: anonymous fn/4 in Mix.Tasks.Ecto.Migrate.run/2
null: false
をnull, false
って打ってた、アホス。
修正して再実行。
PS \hello> mix ecto.migrate [info] == Running 20190403013056 Hello.Repo.Migrations.CreateAuthors.change/0 forward [info] create table authors [info] create index authors_user_id_index [info] == Migrated 20190403013056 in 0.0s [info] == Running 20190403015018 Hello.Repo.Migrations.AddAuthorIdToPages.change/0 forward [info] alter table pages [info] create index pages_author_id_index [info] == Migrated 20190403015018 in 0.0s
問題無さそう。
データベース周りの準備は良さそうなのでCMSとしての機能を実装していきます。
かなり長くなってるのでまた区切ります。
流石に次回アタリでケリをつけたい。