OSDN Git Service

Regular updates
[twpd/master.git] / phoenix-ecto.md
1 ---
2 title: "Phoenix: Ecto"
3 category: Elixir
4 layout: 2017/sheet
5 tags: [WIP]
6 updated: 2017-08-30
7 ---
8
9 ## Schemas
10 {: .-three-column}
11
12 ### Generating
13
14 ```bash
15 $ mix phx.gen.html \
16     Accounts \       # domain
17     Profile \        # schema
18     profiles \       # table name
19     email:string \
20     age:integer
21 ```
22
23 ### Schema
24
25 ```elixir
26 defmodule Myapp.Accounts.User do
27   use Ecto.Schema
28
29   schema "users" do
30     field :name
31     field :age, :integer
32     field :password, virtual: true
33
34     timestamps()
35   end
36 end
37 ```
38
39 ### Field types
40
41 | Field |
42 | --- |
43 | `:id` |
44 | `:binary` |
45 | `:boolean` |
46 | `:string` |
47 | --- |
48 | `:integer` |
49 | `:float` |
50 | `:decimal` |
51 | --- |
52 | `{:array, inner_type}` |
53 | `:map` |
54 {: .-left-align}
55
56 ## Changesets
57
58 ### Changesets
59
60 ```elixir
61 def changeset(user, params \\ :empty) do
62   %User{}
63   |> Ecto.Changeset.change   # basic casting to changeset
64
65   user
66   |> cast(params, ~w(name email), ~w(age)) # params to Changeset
67
68   |> validate_format(:email, ~r/@/)
69
70   |> validate_inclusion(:age, 18..100)
71   |> validate_exclusion(:role, ~w(admin superadmin))
72   |> validate_subset(:pets, ~w(cat dog parrot whale))
73
74   |> validate_length(:body, min: 1)
75   |> validate_length(:body, min: 1, max: 160)
76   |> validate_length(:partners, is: 2)
77
78   |> validate_number(:pi, greater_than: 3)
79   |> validate_number(:pi, less_than: 4)
80   |> validate_number(:pi, equal_to: 42)
81
82   |> validate_change(:title, fn _, _ -> [])
83   |> validate_confirmation(:password, message: "does not match")
84
85   |> unique_constraint(:email)
86   |> foreign_key_constraint(:post_id)
87   |> assoc_constraint(:post)      # ensure post_id exists
88   |> no_assoc_constraint(:post)   # negative (useful for deletions)
89 end
90 ```
91
92 ### Changeset fields
93
94 ```elixir
95 changeset.valid?
96 changeset.errors     #=> [title: "empty"]
97
98 changeset.changes    #=> %{}
99 changeset.params[:title]
100
101 changeset.required   #=> [:title]
102 changeset.optional   #=> [:body]
103 ```
104
105 ### Updating
106
107 ```elixir
108 changeset #(or model)
109 |> change(title: "New title")
110 |> change(%{ title: "New title" })
111 |> put_change(:title, "New title")
112 |> force_change(:title, "New title")
113 |> update_change(:title, &(&1 <> "..."))
114
115 |> delete_change(:title)
116 |> merge(other_changeset)
117
118 |> add_error(:title, "empty")
119 ```
120
121 ### Getting
122
123 ```elixir
124 get_change(changeset, :title)    #=> "hi" (if changed)
125 get_field(changeset, :title)     #=> "hi" (even if unchanged)
126
127 fetch_change(changeset, :title)  #=> {:ok, "hi"} | :error
128 fetch_field(changeset, :title)   #=> {:changes | :model, "value"} | :error
129 ```
130
131 ## Repo
132
133 ### Get one
134
135 ```elixir
136 Repo.get(User, id)
137 Repo.get_by(User, email: "john@hello.com")  #=> %User{} | nil
138
139 # also get! get_by!
140 ```
141
142 ### Create/update
143
144 ```elixir
145 changeset |> Repo.update
146 changeset |> Repo.insert
147 changeset |> Repo.insert_or_update
148 ```
149
150 ```
151 User
152 |> Ecto.Changeset.change(%{name: "hi"})
153 |> Repo.insert
154 ```
155
156 ## Many
157
158 ### Queries
159
160 ```elixir
161 from p in Post,
162   where: p.title == "Hello",
163   where: [state: "Sweden"],
164
165   limit: 1,
166   offset: 10,
167
168   order_by: c.name,
169   order_by: [c.name, c.title],
170   order_by: [asc: c.name, desc: c.title],
171
172   preload: [:comments],
173   preload: [comments: {c, likes: l}],
174
175   join: c in assoc(c, :comments),
176   join: p in Post, on: c.post_id == p.id,
177   group_by: p,
178
179   select: p,
180   select: {p.title, p.description},
181   select: [p.title, p.description],
182 ```
183
184 ### Get many
185
186 ```elixir
187 Repo.all(User)
188 ```
189
190 ### Update many
191
192 ```elixir
193 Repo.update_all(Post, set: [title: "Title"])
194 Repo.update_all(Post, inc: [views: 1])
195 ```
196
197 ### Chaining `_all` with queries
198
199 ```elixir
200 from(p in Post, where: p.id < 10)
201 |> Repo.update_all(...)
202
203 from(p in Post, where: p.id < 10)
204 |> Repo.all()
205 ```
206
207 ## References
208 {: .-one-column}
209
210 - Based on Ecto 1.3.