Membina Elixir Blog Dalam 15 Minit Menggunakan Phoenix - Langkah Demi Langkah
04 Mar 2016- Dokumen asal: Elixir blog in 15 minutes using Phoenix framework - Step by Step
- Penulis asal: Jakub Cieslar
- Ringkasan: Artikel ini memperkenalkan penggunaan framework Phoenix utuk membina satu aplikasi blog yang ringkas.
Saya jangkakan masih ramai yang mengingati ‘blog post’ membina blog sendiri dalam masa 15 minit menggunakan Ruby On Rails. Membina blog sendiri menggunakan Ruby On Rails adalah semudah menulis aplikasi ‘Hello World’ di dalam lain-lain bahasa. Sekarang ini, semakin banyak artikel mengenai Elixir dan nampaknya semakin ramai pengguna Ruby yang ‘jatuh hati’ dengan Elixir. Oleh sebab itu, saya mengambil cabaran untuk melihat samada mudah (atau susah ;)) untuk membina blog melalui Elixir dengan menggunakan Phoenix, iaitu satu ‘web framework’ Elixir.
Saya juga cadangkan anda meneliti tutorial Elixir ini dan tutorial Phoenix ini. Dan untuk mereka yang masih suka membaca, saya cadangkan buku ‘Programming Elixir’ oleh Dave Thomas.
Tujuan utama artikel ini adalah untuk menarik perhatian anda kepada Elixir dan Phoenix secara keseluruhannya. Saya tidak akan memberikan penjelasan mendalam bagaimana keduanya berfungsi. Sebaliknya saya cuma mahu menunjukkan betapa mudahnya untuk kita menulis satu aplikasi yang semudah blog menggunakan Elixir.
Persediaan
Pertama sekali, kita perlu memasang Elixir: http://elixir-lang.org/install.html. Seterusnya, pasang ‘Hex package manager’ dan Phoenix:
$ mix local.hex
$ mix archive.install
https://github.com/phoenixframework/phoenix/releases/download/v0.16.1/phoenix_new-0.16.1.ezLangkah-langkah terperinci dijelaskan di sini.
Langkah 1
Mulakan projek baru bertajuk ‘blog_phoenix’:
$ mix phoenix.new blog_phoenixKita akan dapat melihat fail-fail berikut dihasilkan:
* creating blog_phoenix/config/config.exs
* creating blog_phoenix/config/dev.exs
* creating blog_phoenix/config/prod.exs
* creating blog_phoenix/config/prod.secret.exs
* creating blog_phoenix/config/test.exs
* creating blog_phoenix/lib/blog_phoenix.ex
* creating blog_phoenix/lib/blog_phoenix/endpoint.ex
* creating blog_phoenix/test/controllers/page_controller_test.exs
* creating blog_phoenix/test/views/error_view_test.exs
* creating blog_phoenix/test/views/page_view_test.exs
* creating blog_phoenix/test/views/layout_view_test.exs
* creating blog_phoenix/test/support/conn_case.ex
* creating blog_phoenix/test/support/channel_case.ex
* creating blog_phoenix/test/test_helper.exs
* creating blog_phoenix/web/channels/user_socket.ex
* creating blog_phoenix/web/controllers/page_controller.ex
* creating blog_phoenix/web/templates/layout/app.html.eex
* creating blog_phoenix/web/templates/page/index.html.eex
* creating blog_phoenix/web/views/error_view.ex
* creating blog_phoenix/web/views/layout_view.ex
* creating blog_phoenix/web/views/page_view.ex
* creating blog_phoenix/web/router.ex
* creating blog_phoenix/web/web.ex
* creating blog_phoenix/mix.exs
* creating blog_phoenix/README.md
* creating blog_phoenix/lib/blog_phoenix/repo.ex
* creating blog_phoenix/test/support/model_case.ex
* creating blog_phoenix/priv/repo/seeds.exs
* creating blog_phoenix/.gitignore
* creating blog_phoenix/brunch-config.js
* creating blog_phoenix/package.json
* creating blog_phoenix/web/static/css/app.scss
* creating blog_phoenix/web/static/js/app.js
* creating blog_phoenix/web/static/assets/robots.txt
* creating blog_phoenix/web/static/vendor/phoenix.js
* creating blog_phoenix/web/static/assets/images/phoenix.png
* creating blog_phoenix/web/static/assets/images/favicon.icoKemudian kita perlu memasang ‘dependencies’:
$ cd blog_phoenix
$ mix deps.getSelepas mengkompil kita akan mendapat aplikasi yang sedia untuk digunakan di: http://localhost:4000.
Langkah 2
Sekarang kita sudah bersedia untuk menulis fungsi-fungsi teras pada aplikasi ini. Kita memerlukan kemudahan untuk membuat kerja-kerja CRUD untuk setiap post dan juga kemudahan untuk membuat comment kepada setiap post (ianya satu aplikasi blog yang ringkas). Untuk mencapai tujuan ini, Phoenix menyediakan 4 jenis generator:
$ mix phoenix.gen.html → yang menghasilkan 'model', 'view', 'controllers', 'repository', 'templates', 'tests'
$ mix phoenix.gen.channel → yang menghasilkan 'channel' dan 'tests'
$ mix phoenix.gen.json → untuk kegunaan API, menghasilkan 'model', 'view', 'controllers', 'repository', 'tests'
$ mix phoenix.gen.model → yang menghasilkan 'model' dan 'repository'Kita gunakan ‘generator’ pertama utuk meghasilkan kesemua ‘resource’ dan ‘action’ - lebih kurang sama dengan kegunaan ‘generator’ Ruby on Rails. Kita perlu untuk menggunakan nama dalam betuk ‘singular’ dan ‘plural’, dan seterusnya nama ‘field’ bersama dengan ‘type’.
$ mix phoenix.gen.html Post posts title:string body:textSekarang kemudahan CRUD untuk entiti Post sudah tersedia.
Berikut adalah senarai fail yang dihasilkan:
* creating priv/repo/migrations/20150730233126_create_post.exs
* creating web/models/post.ex
* creating test/models/post_test.exs
* creating web/controllers/post_controller.exs
* creating web/templates/post/edit.html.eex
* creating web/templates/post/form.html.eex
* creating web/templates/post/index.html.eex
* creating web/templates/post/new.html.eex
* creating web/templates/post/show.html.eex
* creating web/views/post_view.ex
* creating test/controllers/post_controller_test.exsSebelum kita ‘refresh’ browser, kita perlu menambah ‘endpoint’ baru di dalam fail web/router.ex.
resources "/posts", PostControllerUntuk melihat senarai routing kita boleh gunakan:
$ mix phoenix.routesyang juga agak sama dengan yang di dalam dunia Rails. Phoenix menggunaka Ecto secara ‘default’ untuk berkomunikasi dan berinteraksi dengan pangkala data. Ecto menyediakan ‘adapter’ untuk PostgreSQL, MySQL dan SQLite (jumlah pangkalan data yang disokong semakin bertambah). Kita boleh dapatkan penjelasan yang terperinci untuk ‘library’ Ecto di bawah dokumentasi Phoenix di Github.
Ecto memberikan kita kemudahan untuk menghasilkan ‘table’ Post di dalam pangkalan data dengan menjalankan skrip ‘migration’:
$ mix ecto.migrate Senarai fail yang dihasilkan oleh skrip ini berada di priv/repo/migrations/.
Jika kita jalankan kod di atas, kita akan mendapat ralat: kerana kita masih belum membuat pangkalan data, jadi untuk itu kita perlu jalankan:
$ mix ecto.create
$ mix ecto.migrateAnda mungkin nampak yang ‘command’ mix ecto.something adalah lebih kurang sama dengan rake db:something di dalam Rails.
Melalui http://localhost:4000/post kita akan dapat melihat bagaimana kemudahan CRUD untuk entiti Post telah pun dijanakan. Sila uji kemudahan tersebut.
Langkah 3
Langkah seterusnya di dalam aplikasi blog kita adalah untuk menyediakan kemudahan ‘comment’, iaitu kemudahan untuk melihat senarai komen dan menambah komen baru. Kita akan membenarkan banyak komen untuk setiap post. Kita akan gunakan ‘generator’ yang akan menghasilkan ‘model’ dan ‘migration’ untuk entiti Comment:
$ mix phoenix.gen.model Comment comments name:string content:text post_id:references:postsUntuk membuat hubungkait antara entiti Post dan entiti Comment, kita menggunakan post:references - sama seperti di dalam Rails. Kita juga perlu untuk menambah has_many: comments, BlogPhoenix.Comment ke dalam fail web/models/post.ex seperti berikut:
defmodule BlogPhoenix.Post do
use BlogPhoenix.Web, :model
schema "posts" do
field :title, :string
field :body, :string
has_many :comments, BlogPhoenix.Comment
timestamps
end
endKemudian kita jalankan ‘migration’ seperti:
$ mix ecto.migrateSeterusnya kita perlu menambah fungsi add_comment di dalam fail web/router.ex:
resources "/posts", PostController do
post "/comment", PostController, :add_comment
endKita baru menambah ‘resource’ untuk comment di dalam ‘resource’ untuk post. Kita boleh melihat ‘routing table’ yang baru seperti berikut:
$ mix phoenix.routes
post_post_path POST /posts/:post_id/comment BlogPhoenix.PostController :add_commentSeterusnya kita perlu membuat perubahan pada PostController(iaitu fail web/controllers/post_controller.ex). Untuk mendapat akses kepada ‘model’ Comment, kita perlu menambah:
alias BlogPhoenix.CommentBaca mengenai ‘alias’ di sini.
Seterusnya tambah ‘scrub params’ pada bahagian atas ‘controller’. ‘Scrub params’ adalah sama dengan ‘strong parameters’ di dalam Rails. Baca mengenai ‘scrub params’ di sini.
plug :scrub_params, "comment" when action in [:add_comment]dan fungsi add_comment:
def add_comment(conn, %{"comment" => comment_params, "post_id" => post_id}) do
changeset = Comment.changeset(%Comment{}, Map.put(comment_params, "post_id", post_id))
post = Post |> Repo.get(post_id) |> Repo.preload([:comments])
if changeset.valid? do
Repo.insert(changeset)
conn
|> put_flash(:info, "Comment added.")
|> redirect(to: post_path(conn, :show, post))
else
render(conn, "show.html", post: post, changeset: changeset)
end
endFungsi changeset yang digunakan di dalam kod di atas boleh di dapati di dalam fail web/models/comment.ex. Baca mengenai Ecto changeset di sini.
Seterusnya kita perlu mengubah ‘function’ show seperti berikut:
def show(conn, %{"id" => id}) do
post = Repo.get(Post, id) |> Repo.preload([:comments])
changeset = Comment.changeset(%Comment{})
render(conn, "show.html", post: post, changeset: changeset)
enduntuk membenarkan kita meletakkan borang komen di dalam paparan untuk post.
Seterusnya kita perlu membuat borang komen tersebut di dalam ‘template’, iaitu fail web/templates/post/comment_form.html.eex:
<%= form_for @changeset, @action, fn f -> %>
<%= if f.errors != [] do %>
<div class="alert alert-danger">
<p>Oops, something went wrong! Please check the errors below:</p>
<ul>
<%= for {attr, message} <- f.errors do %>
<li><%= humanize(attr) %> <%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="form-group">
<label>Name</label>
<%= text_input f, :name, class: "form-control" %>
</div>
<div class="form-group">
<label>Content</label>
<%= textarea f, :content, class: "form-control" %>
</div>
<div class="form-group">
<%= submit "Add comment", class: "btn btn-primary" %>
</div>
<% end %>dan ‘render’ ‘template’ tersebut di dalam fail web/templates/post/show.html.eex.
<%= render "comment_form.html", post: @post, changeset: @changeset,
action: post_path(@conn, :add_comment, @post) %>Langkah 4
Sekarang kita boleh menambah komen kepada ‘post’ kita, tetapi masih belum boleh melihat hasil dari operasi tersebut. Untuk melihat semua komen yang tambah kepada ‘post’, kita perlu membuat satu ‘partial’ baru, iaitu web/templates/post/comments.html.eex dan di dalamnya kita akan ‘iterate’ kesemua komen kepada post tersebut dan memperlihatkan ‘author’ dan kandungan setiap komen.
<h3> Comments: </h3>
<table class="table">
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<%= for comment <- @post.comments do %>
<tr>
<td><%= comment.name %></td>
<td><%= comment.content %></td>
</tr>
<% end %>
</tbody>
</table>Seterusnya kita perlu ‘render’ ‘template’ tersebut di dalam web/templates/post/show.html.eex.
<%= render "comments.html", post: @post %>Langkah 5
Langkah terakhir adalah kita mahu menunjukkan jumlah komen kepada sesatu ‘post’ di dalam senarai blog post kita. Kita perlu menyediakan satu ‘query’ di mana kita boleh menjumlahkan komen-komen tersebut. Ianya boleh dilakukan dengan membuat penambahan berikut kepada fail web/models/post.ex:
defmodule BlogPhoenix.Post do
use BlogPhoenix.Web, :model
import Ecto.Query
...
def count_comments(query) do
from p in query,
group_by: p.id,
left_join: c in assoc(p, :comments),
select: {p, count(c.id)}
end
endKita akan gunakan ‘function’ count_comments di atas di dalam ‘function’ index di dalam fail web/controllers/post_controller.ex:
def index(conn, _params) do
posts = Post
|> Post.count_comments
|> Repo.all
render(conn, "index.html", posts: posts)
endSeterusnya kita perlu membuat perubahan kepada fail web/templates/post/index.html.eex kepada berikut:
<h2>Listing posts</h2>
<table class="table">
<thead>
<tr>
<th>Title</th>
<th>Comments</th>
<th></th>
</tr>
</thead>
<tbody>
<%= for {post, count} <- @posts do %>
<tr>
<td><%= post.title %></td>
<td><%= count %></td>
<td class="text-right">
<%= link "Show", to: post_path(@conn, :show, post), class: "btn btn-default btn-xs" %>
<%= link "Edit", to: post_path(@conn, :edit, post), class: "btn btn-default btn-xs" %>
<%= link "Delete", to: post_path(@conn, :delete, post), method: :delete, class: "btn btn-danger btn-xs" %>
</td>
</tr>
<% end %>
</tbody>
</table>Kita boleh melihat bagaimana blog post kita berfungsi di:
http://localhost:4000/posts
Ringkasan:
‘Source code’ untuk aplikasi ini boleh di dapati di Github. Aplikasi blog ini ada amat ringkas dan ianya cuma satu percubaan untuk menunjukkan bagaimana mudahnya untuk bermain dengan Elixir dan Phoenix jika kita mempunyai latarbelakang Ruby On Rails. Sepertimana yang dapat kita lihat, ianya semudah jika ianya dibina menggunakan Rails! Kita dapat melihat beberapa kesamaan antara Elixir/Phoenix dan Ruby On Rails dari segi ‘convention’ yang digunakan. Framework ini menyediakan banyak konsep yang telah dikenali seperti ‘models’, ‘routing’, ‘controllers’ dan ‘form helpers’, dan juga beberapa konsep baru seperti ‘repositories’ dan ‘changesets(channels)’. Dengan itu kita merasa selesa untuk menulis kod, tetapi kita perlu ingat bahawa kita tidak lagi menggunakan kaedah ‘Object Oriented Programming’. Oleh itu kita perlu mengubah minda kita kepada mod ‘Functional Programming’ - dan ianya mengujakan!
Saya berharap ini akan menggalakkan anda untuk melihat lebih dekat akan Elixir dan Phoenix. Siapa tahu, mungkin Elixir on Phoenix menjadi standard untuk generasi baru web?