|
8 | 8 |
|
9 | 9 | ## Features
|
10 | 10 |
|
11 |
| -- Supports MongoDB versions 3.2, 3.4, 3.6, 4.x, 5.x |
12 |
| -- Connection pooling ([through DBConnection 2.x](https://github.com/elixir-ecto/db_connection)) |
13 |
| -- Streaming cursors |
14 |
| -- Performant ObjectID generation |
15 |
| -- Aggregation pipeline |
16 |
| -- Replica sets |
17 |
| -- Support for SCRAM-SHA-256 (MongoDB 4.x) |
18 |
| -- Support for GridFS ([See](https://github.com/mongodb/specifications/blob/master/source/gridfs/gridfs-spec.rst)) |
19 |
| -- Support for change streams api ([See](https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.rst)) |
20 |
| -- Support for bulk writes ([See](https://github.com/mongodb/specifications/blob/master/source/crud/crud.rst#write)) |
| 11 | +- supports MongoDB versions 3.2, 3.4, 3.6, 4.x, 5.x |
| 12 | +- connection pooling ([through DBConnection 2.x](https://github.com/elixir-ecto/db_connection)) |
| 13 | +- streaming cursors |
| 14 | +- performant ObjectID generation |
| 15 | +- aggregation pipeline |
| 16 | +- replica sets |
| 17 | +- support for SCRAM-SHA-256 (MongoDB 4.x) |
| 18 | +- support for GridFS ([See](https://github.com/mongodb/specifications/blob/master/source/gridfs/gridfs-spec.rst)) |
| 19 | +- support for change streams api ([See](https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.rst)) |
| 20 | +- support for bulk writes ([See](https://github.com/mongodb/specifications/blob/master/source/crud/crud.rst#write)) |
21 | 21 | - support for driver sessions ([See](https://github.com/mongodb/specifications/blob/master/source/sessions/driver-sessions.rst))
|
22 | 22 | - support for driver transactions ([See](https://github.com/mongodb/specifications/blob/master/source/transactions/transactions.rst))
|
23 | 23 | - support for command monitoring ([See](https://github.com/mongodb/specifications/blob/master/source/command-monitoring/command-monitoring.rst))
|
24 | 24 | - support for retryable reads ([See](https://github.com/mongodb/specifications/blob/master/source/retryable-reads/retryable-reads.rst))
|
25 | 25 | - support for retryable writes ([See](https://github.com/mongodb/specifications/blob/master/source/retryable-writes/retryable-writes.rst))
|
| 26 | +- support for simple structs using the Mongo.Encoder protocol |
| 27 | +- support for complex and nested documents using the `Mongo.Collection` macros |
26 | 28 |
|
27 | 29 | ## Data Representation
|
28 | 30 |
|
|
44 | 46 | max key :BSON_max
|
45 | 47 | decimal128 Decimal{}
|
46 | 48 |
|
47 |
| -Since BSON documents are ordered Elixir maps cannot be used to fully represent them. This driver chose to accept both maps and lists of key-value pairs when encoding but will only decode documents to lists. This has the side-effect that it's impossible to discern empty arrays from empty documents. Additionally the driver will accept both atoms and strings for document keys but will only decode to strings. |
| 49 | +Since BSON documents are ordered Elixir maps cannot be used to fully represent them. This driver chose to accept both maps and lists of key-value pairs when encoding but will only decode documents to lists. This has the side-effect that it's impossible to discern empty arrays from empty documents. Additionally, the driver will accept both atoms and strings for document keys but will only decode to strings. |
48 | 50 |
|
49 | 51 | BSON symbols can only be decoded.
|
50 | 52 |
|
@@ -91,6 +93,118 @@ it will be written to database, as:
|
91 | 93 | }
|
92 | 94 | ```
|
93 | 95 |
|
| 96 | +## Collections |
| 97 | + |
| 98 | +While using the `Mongo.Encoder` protocol give you the possibility to encode your structs into maps the opposite way to decode those maps into structs is missing. To handle it you can use the `Mongo.Collection` which provides some boilerplate code for a better support of structs while using the MongoDB driver |
| 99 | + |
| 100 | +* automatic load and dump function |
| 101 | +* reflection functions |
| 102 | +* type specification |
| 103 | +* support for embedding one and many structs |
| 104 | +* support for `after load` function |
| 105 | +* support for `before dump` function |
| 106 | +* support for id generation |
| 107 | +* support for default values |
| 108 | +* support for derived values |
| 109 | + |
| 110 | +When using the MongoDB driver only maps and keyword lists are used to represent documents. |
| 111 | +If you would prefere to use structs instead of the maps to give the document a stronger meaning or to emphasize |
| 112 | +its importance, you have to create a `defstruct` and fill it from the map manually: |
| 113 | + |
| 114 | +```elixir |
| 115 | +defmodule Label do |
| 116 | + defstruct name: "warning", color: "red" |
| 117 | +end |
| 118 | + |
| 119 | +iex> label_map = Mongo.find_one(:mongo, "labels", %{}) |
| 120 | + %{"name" => "warning", "color" => "red"} |
| 121 | +iex> label = %Label{name: label_map["name"], color: label_map["color"]} |
| 122 | +``` |
| 123 | + |
| 124 | +We have defined a module `Label` as `defstruct`, then we get the first label document |
| 125 | +the collection `labels`. The function `find_one` returns a map. We convert the map manually and |
| 126 | +get the desired struct. |
| 127 | + |
| 128 | +If we want to save a new structure, we have to do the reverse. We convert the struct into a map: |
| 129 | + |
| 130 | +```elixir |
| 131 | +iex> label = %Label{} |
| 132 | +iex> label_map = %{"name" => label.name, "color" => label.color} |
| 133 | +iex> {:ok, _} = Mongo.insert_one(:mongo, "labels", label_map) |
| 134 | +``` |
| 135 | + |
| 136 | +Alternatively, you can also remove the `__struct__` key from `label`. The MongoDB driver automatically |
| 137 | +converts the atom keys into strings. |
| 138 | + |
| 139 | +```elixir |
| 140 | +iex> Map.drop(label, [:__struct__]) |
| 141 | +%{color: :red, name: "warning"} |
| 142 | +``` |
| 143 | +If you use nested structures, the work becomes a bit more complex. In this case, you have to use the inner structures |
| 144 | +convert manually, too. If you take a closer look at the necessary work, two basic functions can be derived: |
| 145 | + |
| 146 | +* `load` Conversion of the map into a struct. |
| 147 | +* `dump` Conversion of the struct into a map. |
| 148 | + |
| 149 | +`Mongo.Collection` provides the necessary macros to automate this boilerplate code. The above example can be rewritten as follows: |
| 150 | + |
| 151 | +```elixir |
| 152 | +defmodule Label do |
| 153 | + use Collection |
| 154 | + |
| 155 | + document do |
| 156 | + attribute :name, String.t(), default: "warning" |
| 157 | + attribute :color, String.t(), default: :red |
| 158 | + end |
| 159 | +end |
| 160 | +``` |
| 161 | +This results in the following module: |
| 162 | + |
| 163 | +```elixir |
| 164 | +defmodule Label do |
| 165 | + |
| 166 | + defstruct [name: "warning", color: "red"] |
| 167 | + |
| 168 | + @type t() :: %Label{String.t(), String.t()} |
| 169 | + |
| 170 | + def new()... |
| 171 | + def load(map)... |
| 172 | + def dump(%Label{})... |
| 173 | + def __collection__(:attributes)... |
| 174 | + def __collection__(:types)... |
| 175 | + def __collection__(:collection)... |
| 176 | + def __collection__(:id)... |
| 177 | + |
| 178 | +end |
| 179 | +``` |
| 180 | + |
| 181 | +You can now create new structs with the default values and use the conversion functions between map and structs: |
| 182 | + |
| 183 | +```elixir |
| 184 | +iex(1)> x = Label.new() |
| 185 | +%Label{color: :red, name: "warning"} |
| 186 | +iex(2)> m = Label.dump(x) |
| 187 | +%{color: :red, name: "warning"} |
| 188 | +iex(3)> Label.load(m, true) |
| 189 | +%Label{color: :red, name: "warning"} |
| 190 | +``` |
| 191 | + |
| 192 | +The `load/2` function distinguishes between keys of type binarys `load(map, false)` and keys of type atoms `load(map, true)`. The default is `load(map, false)`: |
| 193 | + |
| 194 | +```elixir |
| 195 | +iex(1)> m = %{"color" => :red, "name" => "warning"} |
| 196 | +iex(2)> Label.load(m) |
| 197 | +%Label{color: :red, name: "warning"} |
| 198 | +``` |
| 199 | +If you would now expect atoms as keys, the result of the conversion is not correct in this case: |
| 200 | + |
| 201 | +```elixir |
| 202 | +iex(3)> Label.load(m, true) |
| 203 | +%Label{color: nil, name: nil} |
| 204 | +``` |
| 205 | + |
| 206 | +The background is that MongoDB always returns binarys as keys and structs use atoms as keys. For more information look at the module documentation [Mongo.Collection](https://hexdocs.pm/mongodb_driver/Mongo.Collection.html#content) |
| 207 | + |
94 | 208 | ## Usage
|
95 | 209 |
|
96 | 210 | ### Installation
|
@@ -173,7 +287,7 @@ cursor
|
173 | 287 | |> IO.inspect
|
174 | 288 | ```
|
175 | 289 |
|
176 |
| -If you're using pooling it is recommend to add it to your application supervisor: |
| 290 | +If you're using pooling it is recommended to add it to your application supervisor: |
177 | 291 |
|
178 | 292 | ```elixir
|
179 | 293 | def start(_type, _args) do
|
|
0 commit comments