Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 80 additions & 2 deletions lib/mongo/collection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,28 @@ defmodule Mongo.Collection do
"modified" : ISODate("2020-05-19T15:15:14.374Z"),
"title" : "Vega"
}

## Example `Inlined Collections`

You can define an embedded collection inline. In the following example, the `Person.Friend`
and `Person.Pet` modules are automatically defined for you.

defmodule Person do
use Mongo.Collection

collection "persons" do
attribute :name, String.t()

embeds_one :friend, Friend do
attribute :name, String.t()
end

embeds_many :pets, Pet, default: [] do
attribute :name, String.t()
end
end
end

## Example `timestamps`

defmodule Post do
Expand Down Expand Up @@ -879,8 +901,25 @@ defmodule Mongo.Collection do
@doc """
Adds the struct to the `embeds_one` list. Calls `__embeds_one__`
"""
defmacro embeds_one(name, mod, opts \\ []) do
defmacro embeds_one(name, mod, opts \\ [])

defmacro embeds_one(name, mod, do: block) do
quote do
embeds_one(unquote(name), unquote(mod), [], do: unquote(block))
end
end

defmacro embeds_one(name, mod, opts) do
quote do
Collection.__embeds_one__(__MODULE__, unquote(name), unquote(mod), unquote(opts))
end
end

defmacro embeds_one(name, mod, opts, do: block) do
mod = expand_nested_module_alias(mod, __CALLER__)

quote do
Collection.__embeds_module__(__ENV__, unquote(mod), unquote(Macro.escape(block)))
Collection.__embeds_one__(__MODULE__, unquote(name), unquote(mod), unquote(opts))
end
end
Expand All @@ -895,8 +934,26 @@ defmodule Mongo.Collection do
@doc """
Adds the struct to the `embeds_many` list. Calls `__embeds_many__`
"""
defmacro embeds_many(name, mod, opts \\ []) do
defmacro embeds_many(name, mod, opts \\ [])

defmacro embeds_many(name, mod, do: block) do
quote do
embeds_many(unquote(name), unquote(mod), [], do: unquote(block))
end
end

defmacro embeds_many(name, mod, opts) do
quote do
type = unquote(Macro.escape({{:., [], [mod, :t]}, [], []}))
Collection.__embeds_many__(__MODULE__, unquote(name), unquote(mod), type, unquote(opts))
end
end

defmacro embeds_many(name, mod, opts, do: block) do
mod = expand_nested_module_alias(mod, __CALLER__)

quote do
Collection.__embeds_module__(__ENV__, unquote(mod), unquote(Macro.escape(block)))
type = unquote(Macro.escape({{:., [], [mod, :t]}, [], []}))
Collection.__embeds_many__(__MODULE__, unquote(name), unquote(mod), type, unquote(opts))
end
Expand All @@ -910,6 +967,19 @@ defmodule Mongo.Collection do
Module.put_attribute(mod, :embed_manys, {name, target, add_name(mod, opts, name)})
end

def __embeds_module__(env, mod, block) do
block =
quote do
use Collection

document do
unquote(block)
end
end

Module.create(mod, block, env)
end

@doc """
Adds the attribute to the attributes list. It call `__attribute__/4` function.
"""
Expand Down Expand Up @@ -1080,4 +1150,12 @@ defmodule Mongo.Collection do
|> Map.new()
end
end

defp expand_nested_module_alias({:__aliases__, _, [Elixir, _ | _] = alias}, _env),
do: Module.concat(alias)

defp expand_nested_module_alias({:__aliases__, _, [h | t]}, env) when is_atom(h),
do: Module.concat([env.module, h | t])

defp expand_nested_module_alias(other, _env), do: other
end
70 changes: 70 additions & 0 deletions test/collections/inlined_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
defmodule Collections.InlinedTest do
use MongoTest.Case, async: true

defmodule Person do
use Mongo.Collection

collection "persons" do
attribute :name, String.t(), default: "new name"

embeds_one :friend, Friend, default: &Person.Friend.new/0 do
attribute :name, String.t(), default: "new friend"
end

embeds_one :hobby, Hobby do
attribute :name, String.t()
end

embeds_many :pets, Pet, default: [] do
attribute :name, String.t(), default: "new pet"
end

embeds_many :things, Thing do
attribute :name, String.t()
end
end
end

test "created the appropriate modules", _c do
Code.ensure_compiled!(Person)
Code.ensure_compiled!(Person.Friend)
Code.ensure_compiled!(Person.Hobby)
Code.ensure_compiled!(Person.Pet)
Code.ensure_compiled!(Person.Thing)
end

test "can create a document for an inlined collection", _c do
new_person = %{
Person.new()
| friend: %{Person.Friend.new() | name: "new friend"},
hobby: %{Person.Hobby.new() | name: "new hobby"},
pets: [%{Person.Pet.new() | name: "new pet"}],
things: [%{Person.Thing.new() | name: "new thing"}]
}

map_person = Person.dump(new_person)
struct_person = Person.load(map_person, false)

assert %{
"name" => "new name",
"friend" => %{"name" => "new friend"},
"hobby" => %{"name" => "new hobby"},
"pets" => [%{"name" => "new pet"}],
"things" => [%{"name" => "new thing"}]
} = map_person

assert %Person{
name: "new name",
hobby: %Person.Hobby{name: "new hobby"},
friend: %Person.Friend{name: "new friend"},
things: [%Person.Thing{name: "new thing"}],
pets: [%Person.Pet{name: "new pet"}]
} = struct_person

new_person = Person.new()
map_person = Person.dump(new_person)
struct_person = Person.load(map_person, false)
assert %{"name" => "new name", "friend" => %{"name" => "new friend"}, "pets" => []} = map_person
assert %Person{name: "new name", friend: %Person.Friend{name: "new friend"}, pets: []} = struct_person
end
end
Loading