Giới thiệu về Elixir (Phần 2) - Ecto Library trong Elixir
Bài đăng này đã không được cập nhật trong 8 năm
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
và 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
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