Skip to content

Commit 76b2064

Browse files
authored
Inline collections (#283)
* allow Mongo.Collection's to be defined inline * add some docs
1 parent b868998 commit 76b2064

File tree

2 files changed

+150
-2
lines changed

2 files changed

+150
-2
lines changed

lib/mongo/collection.ex

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,28 @@ defmodule Mongo.Collection do
440440
"modified" : ISODate("2020-05-19T15:15:14.374Z"),
441441
"title" : "Vega"
442442
}
443+
444+
## Example `Inlined Collections`
445+
446+
You can define an embedded collection inline. In the following example, the `Person.Friend`
447+
and `Person.Pet` modules are automatically defined for you.
448+
449+
defmodule Person do
450+
use Mongo.Collection
451+
452+
collection "persons" do
453+
attribute :name, String.t()
454+
455+
embeds_one :friend, Friend do
456+
attribute :name, String.t()
457+
end
458+
459+
embeds_many :pets, Pet, default: [] do
460+
attribute :name, String.t()
461+
end
462+
end
463+
end
464+
443465
## Example `timestamps`
444466
445467
defmodule Post do
@@ -879,8 +901,25 @@ defmodule Mongo.Collection do
879901
@doc """
880902
Adds the struct to the `embeds_one` list. Calls `__embeds_one__`
881903
"""
882-
defmacro embeds_one(name, mod, opts \\ []) do
904+
defmacro embeds_one(name, mod, opts \\ [])
905+
906+
defmacro embeds_one(name, mod, do: block) do
907+
quote do
908+
embeds_one(unquote(name), unquote(mod), [], do: unquote(block))
909+
end
910+
end
911+
912+
defmacro embeds_one(name, mod, opts) do
913+
quote do
914+
Collection.__embeds_one__(__MODULE__, unquote(name), unquote(mod), unquote(opts))
915+
end
916+
end
917+
918+
defmacro embeds_one(name, mod, opts, do: block) do
919+
mod = expand_nested_module_alias(mod, __CALLER__)
920+
883921
quote do
922+
Collection.__embeds_module__(__ENV__, unquote(mod), unquote(Macro.escape(block)))
884923
Collection.__embeds_one__(__MODULE__, unquote(name), unquote(mod), unquote(opts))
885924
end
886925
end
@@ -895,8 +934,26 @@ defmodule Mongo.Collection do
895934
@doc """
896935
Adds the struct to the `embeds_many` list. Calls `__embeds_many__`
897936
"""
898-
defmacro embeds_many(name, mod, opts \\ []) do
937+
defmacro embeds_many(name, mod, opts \\ [])
938+
939+
defmacro embeds_many(name, mod, do: block) do
899940
quote do
941+
embeds_many(unquote(name), unquote(mod), [], do: unquote(block))
942+
end
943+
end
944+
945+
defmacro embeds_many(name, mod, opts) do
946+
quote do
947+
type = unquote(Macro.escape({{:., [], [mod, :t]}, [], []}))
948+
Collection.__embeds_many__(__MODULE__, unquote(name), unquote(mod), type, unquote(opts))
949+
end
950+
end
951+
952+
defmacro embeds_many(name, mod, opts, do: block) do
953+
mod = expand_nested_module_alias(mod, __CALLER__)
954+
955+
quote do
956+
Collection.__embeds_module__(__ENV__, unquote(mod), unquote(Macro.escape(block)))
900957
type = unquote(Macro.escape({{:., [], [mod, :t]}, [], []}))
901958
Collection.__embeds_many__(__MODULE__, unquote(name), unquote(mod), type, unquote(opts))
902959
end
@@ -910,6 +967,19 @@ defmodule Mongo.Collection do
910967
Module.put_attribute(mod, :embed_manys, {name, target, add_name(mod, opts, name)})
911968
end
912969

970+
def __embeds_module__(env, mod, block) do
971+
block =
972+
quote do
973+
use Collection
974+
975+
document do
976+
unquote(block)
977+
end
978+
end
979+
980+
Module.create(mod, block, env)
981+
end
982+
913983
@doc """
914984
Adds the attribute to the attributes list. It call `__attribute__/4` function.
915985
"""
@@ -1080,4 +1150,12 @@ defmodule Mongo.Collection do
10801150
|> Map.new()
10811151
end
10821152
end
1153+
1154+
defp expand_nested_module_alias({:__aliases__, _, [Elixir, _ | _] = alias}, _env),
1155+
do: Module.concat(alias)
1156+
1157+
defp expand_nested_module_alias({:__aliases__, _, [h | t]}, env) when is_atom(h),
1158+
do: Module.concat([env.module, h | t])
1159+
1160+
defp expand_nested_module_alias(other, _env), do: other
10831161
end

test/collections/inlined_test.exs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
defmodule Collections.InlinedTest do
2+
use MongoTest.Case, async: true
3+
4+
defmodule Person do
5+
use Mongo.Collection
6+
7+
collection "persons" do
8+
attribute :name, String.t(), default: "new name"
9+
10+
embeds_one :friend, Friend, default: &Person.Friend.new/0 do
11+
attribute :name, String.t(), default: "new friend"
12+
end
13+
14+
embeds_one :hobby, Hobby do
15+
attribute :name, String.t()
16+
end
17+
18+
embeds_many :pets, Pet, default: [] do
19+
attribute :name, String.t(), default: "new pet"
20+
end
21+
22+
embeds_many :things, Thing do
23+
attribute :name, String.t()
24+
end
25+
end
26+
end
27+
28+
test "created the appropriate modules", _c do
29+
Code.ensure_compiled!(Person)
30+
Code.ensure_compiled!(Person.Friend)
31+
Code.ensure_compiled!(Person.Hobby)
32+
Code.ensure_compiled!(Person.Pet)
33+
Code.ensure_compiled!(Person.Thing)
34+
end
35+
36+
test "can create a document for an inlined collection", _c do
37+
new_person = %{
38+
Person.new()
39+
| friend: %{Person.Friend.new() | name: "new friend"},
40+
hobby: %{Person.Hobby.new() | name: "new hobby"},
41+
pets: [%{Person.Pet.new() | name: "new pet"}],
42+
things: [%{Person.Thing.new() | name: "new thing"}]
43+
}
44+
45+
map_person = Person.dump(new_person)
46+
struct_person = Person.load(map_person, false)
47+
48+
assert %{
49+
"name" => "new name",
50+
"friend" => %{"name" => "new friend"},
51+
"hobby" => %{"name" => "new hobby"},
52+
"pets" => [%{"name" => "new pet"}],
53+
"things" => [%{"name" => "new thing"}]
54+
} = map_person
55+
56+
assert %Person{
57+
name: "new name",
58+
hobby: %Person.Hobby{name: "new hobby"},
59+
friend: %Person.Friend{name: "new friend"},
60+
things: [%Person.Thing{name: "new thing"}],
61+
pets: [%Person.Pet{name: "new pet"}]
62+
} = struct_person
63+
64+
new_person = Person.new()
65+
map_person = Person.dump(new_person)
66+
struct_person = Person.load(map_person, false)
67+
assert %{"name" => "new name", "friend" => %{"name" => "new friend"}, "pets" => []} = map_person
68+
assert %Person{name: "new name", friend: %Person.Friend{name: "new friend"}, pets: []} = struct_person
69+
end
70+
end

0 commit comments

Comments
 (0)