Làm quen với Elixir on Phoenix qua phương trình bậc 2
Bài đăng này đã không được cập nhật trong 8 năm
Ở bài trước chúng ta đã lướt qua được cách cài đặt và khởi động được server của Elixir trên Phoenix. Đối với những tài liệu ở bài trước thì các bạn hoàn toàn có thể tìm hiểu được chi tiết qua các đề mục đó. Nhưng với mình thì việc tìm hiểu một cách nhanh nhất và hiệu quả nhất là bắt tay vào làm một trang web và tìm được những gì mình thực sự cần chứ không phải đọc một cách lan man. Chính vì vậy, ở bài này mình sẽ làm một trang web đơn về việc giải phương trình bậc 2. Trang web này có 1 trang chính giới thiệu rồi link tới trang điền thông số để tính toán.
I. Tạo trang introduction
Chúng ta đã có thể vào localhost và hiển thị trang chủ. Đó chính là trang default được định nghĩa sẵn.
Để có thể có một trang web như vậy thì ta cần phải làm các bước sau:
- Tạo controller có action nào đó
defmodule HelloPhoenix.PageController do
use HelloPhoenix.Web, :controller
def index(conn, _params) do
render conn, "index.html"
end
def introduction(conn, _params) do
render conn, "introduction.html"
end
end
Ở đây mình đã tạo thêm action introduction trong PageController (/web/controllers/page_controller.ex
). Trong đó hàm index chính là link tới trang chủ.
- Tạo file view tương ứng với action đó.
View thì được đặt tại thư mục
web/templates/controller
với trang introduction thì sẽ làweb/templates/page/introduction.html.eex
<h2>Lê Văn Ban</h2>
Chương trình tính giải phương trình bậc 2: <img src="<%= static_path(@conn, "/images/ptb2.png") %>">
Nhập vào các giá trị a,b,c rồi ấn nút Giải</br>
Chương trình mang tính chất giải trí và làm quen với Elixir on Phoenix</br>
</br>
Ấn <%= link "vào đây", to: log_path(@conn, :index), class: "btn btn-default" %> để đến chương trình giải phương trình bậc 2
- Tạo routes
Để chỉ đường dẫn trang web tương ứng với action nào thì chúng ta phải đặt chúng tại file web/router.ex
scope "/", HelloPhoenix do
pipe_through :browser # Use the default browser stack
get "/", PageController, :index
get "introduction", PageController, :introduction
end
Ta thêm get "introduction", PageController, :introduction
vào trong scope "/" để link đường dẫn /introduction vào action trong PageController.
Ở view web/templates/page/introduction.html.eex
các bạn thấy được ta sử dụng các hàm của Phoenix trong view chính là được đặt trong dấu <%= ... %>
hoặc <% .. %>
điều này cũng tương tự như trong rails. Nhưng với Phoenix thì ta chỉ sử dụng dấu "
chứ không sử dụng được '
như rails
"<%= static_path(@conn, "/images/ptb2.png") %>"
=> OK"<%= static_path(@conn, '/images/ptb2.png') %>"
=> EROR Để sử dụng đường dẫn static_path này với các file ảnh, js hay css thì ta thường đặt trong thư mụcweb/static
và phải định nghĩa chúng ở trong filelib/hello_phoenix/endpoint.ex
plug Plug.Static,
at: "/", from: :hello_phoenix, gzip: false,
only: ~w(css fonts images js favicon.ico robots.txt framgia.jpg)
Với file ptb2.png
thì được đặt trong thư mục images
nên không cần phải thêm. Còn file framgia.jpg thì đặt ở trong thư mục asset nên ta phải thêm vào danh sách trên.
Nó cũng gần giống với asset preconpile của rails.
Đối với Phoenix thì routes có khác biệt một chút so với rails.
- Rails sử dụng hàm:
introduction_page_path
- Phoenix sử dụng hàm:
page_path(@conn, :introduction)
Như vậy với Phoenix thì thường sẽ là dùng tên controller để gọi hàm và truyền tên action vào chứ không sinh sẵn path như rails.
II. Tạo controller, model, view
Với rail thì ta có rails generate controller (model) còn với Phoenix thì ta có thử sử dụng
mix phoenix.gen.html
tạo ra cả html, controller, model với đủ 7 action theo chuẩnmix phoenix.gen.model
chỉ tạo model
Tham khảo tại đây
Ví dụ khi tạo controller với model và view Log để lưu lại các phương trình bậc 2 thì ta sử dụng lệnh sau
mix phoenix.gen.html Log logs a:integer b:integer c:integer result:text
Khi đó các file sẽ được tạo tự động và ta chỉ cần vào chỉnh sửa và điều hướng theo ý của mình là xong.
Với rails thì ta hay sử dụng f.number hay f.lable còn với Phoenix thì lại sử dụng như sau:
<%= form_for @changeset, @action, fn f -> %>
<%= label f, :a, class: "control-label" %>
<%= number_input f, :a, class: "form-control" %>
<% end %>
Điểm giống nhau giữa 2 cái là đều có form_for
với end
để bắt đầu và kết thúc một block.
Trong rails ta có active-record để gọi vào database lấy các đối tượng qua model với phoenix ta có Ecto
Trong file web/web.ex
có định nghĩa như sau:
def model do
quote do
use Ecto.Schema
import Ecto
import Ecto.Changeset
import Ecto.Query
end
end
def controller do
quote do
use Phoenix.Controller
alias HelloPhoenix.Repo
import Ecto
import Ecto.Query
import HelloPhoenix.Router.Helpers
import HelloPhoenix.Gettext
end
end
Như vậy ở trong view ta sử dụng Repo để gọi các hàm tương ứng lấy dữ liệu từ trong database ra. VD:
logs = Repo.all(Log) # Lấy tất cả
log = Repo.get(Log, 2) # Log với id = 3
Chi tiết tại ecto
Model Log
defmodule HelloPhoenix.Log do
use HelloPhoenix.Web, :model
schema "logs" do
field :a, :integer
field :b, :integer
field :c, :integer
field :result, :string
timestamps()
end
@doc """
Builds a changeset based on the `struct` and `params`.
"""
def changeset(struct, params \\ %{}) do
struct
|> cast(params, [:a, :b, :c, :result])
|> validate_required([:a, :b, :c, :result])
|> validate_exclusion(:a, [0], message: "a phải khác 0")
end
end
Ở phoenix ta thấy ngay cả cấu trúc schema của bảng ở đây, và ta cũng định nghĩa luôn các attributes được cài đặt và validate trong hàm changeset.
=> Để tạo một đối tương log mới tương tự với Log.new(a: 1,...)
như rails thì với Phoenix ta dùng phương thức sau:
changeset = Log.changeset(%Log{}, %{a: 1, b: 0, c: 0})
Repo.insert(changeset) # Lưu object đó vào database
Ví dụ với hàm create
trong log_controller:
def create(conn, %{"log" => log_params}) do
changeset = Log.changeset(%Log{}, log_params)
{a, _} = Integer.parse(log_params["a"])
{b, _} = Integer.parse(log_params["b"])
{c, _} = Integer.parse(log_params["c"])
delta = b * b - 4 * a * c
case delta do
del when del < 0 ->
log_params = Map.merge(log_params, %{"result" => "Phương trình vô nghiệm"})
del when del == 0 ->
log_params = Map.merge(log_params, %{"result" => "Phương trình có nghiệm kép x1 = x2 = #{-b / (2 * a)}"})
_ ->
:math.sqrt(delta)
log_params = Map.merge(log_params, %{"result" => "Phương trình vô nghiệm"})
end
changeset = Log.changeset(%Log{}, log_params)
case Repo.insert(changeset) do
{:ok, _log} ->
conn
|> put_flash(:info, "Log created successfully.")
|> redirect(to: log_path(conn, :index))
{:error, changeset} ->
render(conn, "new.html", changeset: changeset)
end
end
Như các bạn thấy thì log_params
tương tự như params ở rails. Và nó chỉ chứ các giá trị được gửi từ form lên. Nó là một dạng Map nó gần giống như hash trong ruby, nó cũng có key và value nhưng cách sử dụng không được linh hoạt bằng ruby.
Để thay đổi hay thêm giá trị của 1 key nào đó ta thường dùng Map.merge(params1, params2)
Khi các key của params2 trùng với params1 thì nó sẽ được lấy giá trị của params2.
Như vậy ta đã làm quen với các khái niệm model, controller, view, routes, kiểu dữ liệu map và case when...
Qua bài viết này các bạn có thể tạo được một trang web cơ bản thêm, sửa, xóa, thống kê với database, thêm ảnh trong asset và gọi ra trên view... Bài viết sau sẽ đi sâu thêm vào các phần. Hi vọng các bạn sẽ theo dõi
Video demo
Source code: tại đây
III. Tài liệu tham khảo
All rights reserved