0

Giới thiệu về Elixir (Phần 2) - Ecto Library trong Elixir

Mở đầu

Bài trước chúng ta đã tìm hiểu những khái niệm sơ khai nhất về 1 ứng dụng Elixir. Trong bài này ta sẽ nghiên cứu về cách làm việc với Database khi phát triển ứng dụng Elixir. Và bộ thư viện Ecto sẽ giúp chúng ta thực hiện phần việc này.

Ecto Library

Nói ngắn gọn thì Ecto là thư viện chính của Elixir để thao tác với database. Nó sẽ cho chúng ta một công cụ tác động đến DB thông qua API.

Để tìm hiẻu về ecto, ta sẽ tạo 1 project sample

Tạo ứng dụng

mix new ecto_sample --sup

Từ khoá sup ở đây có tác dụng tự động sinh ra những đoạn code cần thiết cho 1 ứng dụng OTP (xem lại bài trước để rõ hơn về khái niệm OTP). Ta sẽ nói cụ thể về những đoạn code đó ở phần sau của bài.

Thiết lập các dependencies

Bây giờ ta sẽ thay đổi một chút file mix.exs và chọn apdater là MYSQL. Ta sẽ update application funtion trong mix.exs

def application do
  [applications: [:logger, :ecto, :mariaex],
   mod: {EctoSample, []}]
end

defp deps

defp deps do
  [{:ecto, "~> 1.1.5"}, # or "~> 2.0" for Ecto 2
   {:mariaex, "~> 0.6.0"}] # or "~> 0.7.0" for Ecto 2
end

Bây giờ ta sẽ fetch các dependencies bằng mix deps.get

Tiếp đó ta sẽ tích hợp các dependencies này vào ứng dụng. Đầu tiên ta sẽ khai báo EctoSample.repo trong lib/ecto_sample/repo.ex

defmodule EctoSample.Repo do
  use Ecto.Repo, otp_app: :ecto_sample
end

chú ý là module này có đường dẫn mặc định là lib/app_name/repo.ex và khi chúng ta dùng câu lệnh mix ecto thì repo mà chúng ta đã định nghĩa luôn được tìm trong AppName.Repo.

EctoSample.Repo giúp chúng ta có thể sử dụng Octo để thao tác với database. Cơ chế là inject các hàm từ Repo của module thư viện Ecto (sẽ cung cập DB query API), tiếp đến đặt tên cho ứng dụng OTP :sample_ecto.

  • Ecto repo giúp chúng ta 1 lớp interface để thao tác với tầng DB (được qui định trong adapter đang sử dụng).

Bây giờ ta sẽ định nghĩa SampleOcto.Repo module. ta phải update code dưới đây vào supervision tree trong SampleOcto module, cụ thể là trong lib/sample_octo.ex, ta update function start

def start(_type, _args) do
  import Supervisor.Spec, warn: false

  children = [
    supervisor(SampleEctor.Repo, []),
  ]

  opts = [strategy: :one_for_one, name: SampleOcto.Supervisor]
  Supervisor.start_link(children, opts)
end

Ta đã thêm module SampleEctor.Repo là 1 child supervisor (supervisor có nhiệm vụ giám sát các process) và ở đây bản thân module SampleEctor.Repo có được giám sát các bởi ứng dụng OTP của chúng ta và OTP chịu trách nhiệm khởi động nó mỗi khi ứng dụng được khởi động.

Mỗi một connection được tạo ra bởi Ecto thì sẽ dùng các process tách biệt. Vậy mỗi câu querry sẽ được thực hiền động thời và sẽ tư thực hiện lại khi request fail. Do đó, ứng dụng của chúng ta sẽ cần có OTP bởi các process của Ecto cần phải được giám sát (bao gồm cây giám sát để giám sát các pool của các kết nối database).

Tiếp đến ta cần config adapter để có thể giao tiếp với database. ta config nư dưới đấy trong config/config.exs ( tuỳ thuộc vào yêu cầu trong ứng dụng của bạn )

config :sample_ecto, SampleEcto.Repo,
  adapter: Ecto.Adapters.MySQL,
  database: "sample_ecto",
  username: "root",
  password: "root",
  hostname: "localhost"

Ta khai báo tên ứng dụng OTP là :sample_ecto để kết nối database. Các thông tin config bên dưới thì cũng giống như bạn config database trong ứng dụng rails.

Ecto cung cấp cho chúng ta 1 shortcut để thiết lập repo module bằng câu lênh mix

mix ecto.gen.repo

Sau khi chạy xong hệ thông sẽ đc sinh ra những thư mục module và update file config.exs với mọt vài config cơ bản. Nếu bạn muốn thay đổi CSDL sang dùng Postgres thì sẽ phải thay đổi phần adapter trong config.

Bây giờ ta sẽ theo dõi process của hệ thống. Chạy câu lệnh iex -S mix

iex -S mix

iex(1)> :observer.start
:ok

Cửa sổsổ giao diện của observer sẽ hiện lên.

Click vào tab application ta sẽ thấy đc tất cả các tiến trình làm việc của hệ thống. Trong đó ta cũng nhin ra đc process nào là có chức năng giám sát

Image

Bây giờ ta sẽ tạo database và bảng dử liệu. Giống như trong rails, trước tiên ta phải tạo databáe bằng câu lệnh

mix ecto.gen.migration

Để tạo bảng dữ liệu, ta cũng sử dụng migration giống rails

mix ecto.gen.migration create_notes_table

Sau khi chạy câu lệnh trên thì thư mục migration được tạo ra trong priv/repo/migrations. ta sẽ tạo bảnh bằng cácdh thay đổi và thêm nội dung vào hàm change. Dưới đây ta sẽ tạo bảng notes với 2 trường note_name và note_content

defmodule SampleElixir.Repo.Migrations.CreateNotesTable do
  use Ecto.Migration
  def change do
    create table(:notes) do
      add :note_name, :string
      add :note_content, :string
    end
  end
end

Sau đó ta chạy mix ecto.migrate để thực thi việc tạo bảng. Tiếp đến ta sẽ khởi tạo model cho bảng dữ liẹu. Cũng như model trong các mô hinhgf MVC khác thì model trong elixir chịu trách nhiệm khai báo trường, validate trường của bảng dự liệu, ngoài ra model cung sẽ chứa các business logic tác động đến database.

Ta sẽ khai báo SampleElixir.note trong lib/sample_elixir/note.ex và nội dung có dạng như sau

defmodule SampleElixir.Note do
  use Ecto.Schema

  schema "notes" do
    field :note_name, :string
    field :note_content, :string
  end
end

Bây giờ ta có thể sử dụng iEX để thử querry DB

 iex(1)> import Ecto.Query
nil

 iex(2)> SampleElixir.Repo.all(from n in SampleElixir.Note, select: n.note_name)

Ở đây ta đã import thư module query của Ecto. Câu lệnh thứ 2 ta gọi ra tất cả các bản ghi trong bảng note và chỉ select ra trường note_name. Ở đây kết quả trả về rỗng vì ta chưa tạo bản ghi nào trong bảng note cả . Bây giờ ta sẽ thực hiện tạo bản ghi

 iex(2)> changeset = Ecto.Changeset.change(%SampleElixir.Note{note_name: "To Do List", note_content: "Finish this article"})
%Ecto.Changeset{action: nil, changes: %{}, constraints: [], errors: [],
 filters: %{},
 model: %SampleElixir.Note{__meta__: #Ecto.Schema.Metadata<:built>, id: nil,
  note_content: "Finish this article", note_name: "To Do List"}, optional: [], opts: [],
 params: nil, prepare: [], repo: nil, required: [],
 types: %{id: :id, note_content: :string, note_name: :string}, valid?: true,
 validations: []}

iex(3)> SampleElixir.Repo.insert(changeset)
{:ok,
 %Notex.Note{__meta__: #Ecto.Schema.Metadata<:loaded>, id: 2,
  note_content: "Finish this article", note_name: "To Do List"}}

iex(4)> SampleElixir.Repo.all(from n in Notex.Note, select: n.note_name)
["To Do List"]

Câu lệnh đầu tiên ta đã dùng hàm change của Ecto.Changeset để tạo changeset %Notex.Note{}, changeset này sau đó sẽ đc insert vào db và có thể truy xuất để lấy dữ liệu.

Changeset có thể hiểu là một đối tượng băts buộc mà ta cần để thao tác với database. Bất cứ 1 thao tác nào ví dụ như insert, truy xuất hay validate dữ liệu đều cần phải thông qua changeset. Ví dụ ta muốn kiểm tra dữ liệu có valid hay không chỉ cần gọi changeset.valid? và nếu changeset đó không valid thì ta gọi changeset.errors để hiển thị danh sách lỗi.

Kết luận

Bài hôm nay ta đã tìm hiểu thêm những kiến thức cơ bản về việc tạo và kết nối database, get và thêm dữ liệu thông qua thư viện Ecto. Bài sau ta sẽ di sâu tìm hiểu về cách thức tạo class, hàm và cú phap của Elixir, từ đó ta sẽ tạo 1 model và sử dụng Ecto để tạo 1 ứng dụng demo có cac thao tác thêm sửa xoá dữ liệu.


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.