/
table.ex
218 lines (166 loc) · 6.1 KB
/
table.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
defmodule ExAirtable.Table do
@moduledoc """
The `Table` behaviour allows you to define your own modules that use Airtables.
It is a thin wrapper around `Service`, but often more convenient to use.
## Examples
defmodule MyTable do
use ExAirtable.Table
def base, do: %ExAirtable.Config.Base{
id: "your base ID",
api_key: "your api key"
}
def name, do: "My Airtable Table Name"
end
iex> MyTable.list()
%ExAirtable.Airtable.List{}
iex> MyTable.retrieve("rec123")
%ExAirtable.Airtable.Record{}
"""
alias ExAirtable.{Airtable, Config, Service}
@optional_callbacks list_params: 0, schema: 0
@doc """
A valid %ExAirtable.Config.Base{} config for your table.
Often this will end up being in the application configuration somewhere, for example:
# ... in your mix.config
config :my_app, Airtable.Base, %{
id: "base id",
api_key: "api key"
}
# ... in your table module
def base do
struct(ExAirtable.Config.Base, Application.get_env(:my_all, Airtable.Base))
end
"""
@callback base() :: Config.Base.t()
@doc """
(Optional) A map of parameters that you wish to send to Airtable when either `list/1` or `list_async/1` is called.
You would define this if you want your local `MyTable.list` and `ExAirtable.list(MyTable)` functions to always return a filtered query from Airtable.
## Examples
# To filter records by a boolean "Approved" field, and only return the "Name" and "Picture", your params might look like this:
def list_params do
[
filterByFormula: "{Approved}",
fields: "Name",
fields: "Picture"
]
end
See [here](https://codepen.io/airtable/full/rLKkYB) for more details about the available Airtable List API options.
"""
@callback list_params() :: Keyword.t()
@doc "The name of your table within Airtable"
@callback name() :: String.t()
@doc """
(Optional) A map converting Airtable field names to local schema field names.
This is handy for situations (passing attributes to ecto schemas, for
example) where you may want different in-app field names than the fields you
get from Airtable.
If you don't define this method, the default is to simply use Airtable field
names as the schema.
## Examples
# If you want atom field names for your schema map...
def schema do
%{
"Airtable Field Name" => :local_field_name,
"Other Airtable Field" => :other_local_field
}
end
iex> ExAirtable.Airtable.Record.to_schema(record, MyTable.schema)
%{airtable_id: "rec1234", local_field_name: "value", other_local_field: "other value"}
# If you want string field names for your schema map...
def schema do
%{
"Airtable Field Name" => "local_field_name",
"Other Airtable Field" => "other_local_field"
}
end
iex> ExAirtable.Airtable.Record.to_schema(record, MyTable.schema)
%{"airtable_id" => "rec1234", "local_field_name" => "value", "other_local_field" => "other value"}
See also `to_schema/1` and `from_schema/1`.
"""
@callback schema() :: map()
@doc false
def update_params(list_params, opts) do
params =
Keyword.get(opts, :params, [])
|> Keyword.merge(list_params)
Keyword.put(opts, :params, params)
end
defmacro __using__(_) do
quote do
@behaviour ExAirtable.Table
@doc """
Create a record in your Airtable. See `Service.create/2` for details.
"""
def create(%Airtable.List{} = list) do
Service.create(table(), list)
end
@doc """
Delete a single record (by ID) from an Airtable
"""
def delete(id) when is_binary(id) do
Service.delete(table(), id)
end
@doc """
Convert an attribute map back to an %ExAirtable.Airtable.Record().
Airtable field names are converted to local field names based on the
`%{"Schema" => "map"}` defined (or overridden) in `schema/1`.
See `ExAirtable.Airtable.Record.from_schema/2` for more details about the conversion.
"""
def from_schema(attrs) when is_map(attrs) do
Airtable.Record.from_schema(__MODULE__, attrs)
end
@doc """
Get all records from your Airtable. See `Service.list/3` for details.
"""
def list(opts \\ []) do
Service.list(table(), ExAirtable.Table.update_params(list_params(), opts))
end
defoverridable list: 1
@doc """
Similar to `list/1`, except results aren't automatically concatenated
with multiple API requests.
Typically called automatically by a TableSynchronizer process.
"""
def list_async(opts \\ []) do
Service.list_async(table(), ExAirtable.Table.update_params(list_params(), opts))
end
defoverridable list_async: 1
@doc false
def list_params, do: []
defoverridable list_params: 0
@doc """
Get a single record from your Airtable, matching by ID.
See `Service.retrieve/2` for details.
"""
def retrieve(id) when is_binary(id) do
Service.retrieve(table(), id)
end
# Make overrideable for testing mocks
defoverridable retrieve: 1
@doc false
def schema, do: nil
defoverridable schema: 0
@doc """
Utility function to return the table struct
"""
def table() do
%Config.Table{base: base(), name: name()}
end
@doc """
Convert a record to an attribute map.
Airtable field names are converted to local field names based on the
`%{"Schema" => "map"}` defined (or overridden) in `schema/1`.
See `ExAirtable.Airtable.Record.to_schema/2` for more details about the conversion.
"""
def to_schema(%Airtable.Record{} = record) do
Airtable.Record.to_schema(record, schema())
end
@doc """
Update a record in your Airtable. See `Service.update` for details.
"""
def update(%Airtable.List{} = list, opts \\ []) do
Service.update(table(), list, opts)
end
end
end
end