Làm quen với Elixir on Phoenix qua phương trình bậc 2
Ở 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 ...
Ở 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ục web/static và phải định nghĩa chúng ở trong file lib/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ẩn
- mix 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