Skip to content

Commit

Permalink
Support selecting into structs (#1947)
Browse files Browse the repository at this point in the history
  • Loading branch information
michalmuskala authored and josevalim committed Feb 14, 2017
1 parent bb16ea1 commit 9ad38bd
Show file tree
Hide file tree
Showing 6 changed files with 39 additions and 0 deletions.
14 changes: 14 additions & 0 deletions integration_test/cases/repo.exs
Expand Up @@ -892,6 +892,10 @@ defmodule Ecto.Integration.RepoTest do

## Query syntax

defmodule Foo do
defstruct [:title]
end

test "query select expressions" do
%Post{} = TestRepo.insert!(%Post{title: "1", text: "hai"})

Expand All @@ -914,6 +918,9 @@ defmodule Ecto.Integration.RepoTest do
p.title => p.text,
"text" => p.text
})

assert [%Foo{title: "1"}] ==
TestRepo.all(from p in Post, select: %Foo{title: p.title})
end

test "query select map update" do
Expand All @@ -922,13 +929,20 @@ defmodule Ecto.Integration.RepoTest do
assert [%Post{:title => "new title", text: "hai"}] =
TestRepo.all(from p in Post, select: %{p | title: "new title"})

assert [%Post{title: "new title", text: "hai"}] =
TestRepo.all(from p in Post, select: %Post{p | title: "new title"})

assert_raise KeyError, fn ->
TestRepo.all(from p in Post, select: %{p | unknown: "new title"})
end

assert_raise BadMapError, fn ->
TestRepo.all(from p in Post, select: %{p.title | title: "new title"})
end

assert_raise BadStructError, fn ->
TestRepo.all(from p in Post, select: %Foo{p | title: p.title})
end
end

test "query select take with structs" do
Expand Down
2 changes: 2 additions & 0 deletions lib/ecto/query.ex
Expand Up @@ -684,6 +684,8 @@ defmodule Ecto.Query do
from(c in City, select: {c.name, c.population})
from(c in City, select: [c.name, c.county])
from(c in City, select: %{n: c.name, answer: 42})
from(c in City, select: %{c | alternative_name: c.name})
from(c in City, select: %Data{name: c.name})
It is also possible to select a struct and limit the returned
fields at the same time:
Expand Down
7 changes: 7 additions & 0 deletions lib/ecto/query/builder/select.ex
Expand Up @@ -44,6 +44,13 @@ defmodule Ecto.Query.Builder.Select do
{expr, params_take}
end

# Struct
defp escape({:%, _, [name, map]}, params_take, vars, env) do
name = Macro.expand(name, env)
{escaped_map, params_take} = escape(map, params_take, vars, env)
{{:{}, [], [:%, [], [name, escaped_map]]}, params_take}
end

# Map
defp escape({:%{}, _, [{:|, _, [data, pairs]}]}, params_take, vars, env) do
{data, params_take} = escape(data, params_take, vars, env)
Expand Down
2 changes: 2 additions & 0 deletions lib/ecto/query/planner.ex
Expand Up @@ -850,6 +850,8 @@ defmodule Ecto.Query.Planner do
do: collect_fields([data|pairs], query, take, from)
defp collect_fields({:%{}, _, pairs}, query, take, from),
do: collect_fields(pairs, query, take, from)
defp collect_fields({:%, _, [_name, expr]}, query, take, from),
do: collect_fields(expr, query, take, from)
defp collect_fields(list, query, take, from) when is_list(list),
do: Enum.flat_map_reduce(list, from, &collect_fields(&1, query, take, &2))
defp collect_fields(expr, _query, _take, from) when is_atom(expr) or is_binary(expr) or is_number(expr),
Expand Down
11 changes: 11 additions & 0 deletions lib/ecto/repo/queryable.ex
Expand Up @@ -274,6 +274,17 @@ defmodule Ecto.Repo.Queryable do
end
end

defp transform_row({:%, _, [name, map]}, take, from, values) do
case transform_row(map, take, from, values) do
{%{__struct__: ^name} = struct, acc} ->
{struct, acc}
{%{__struct__: _} = struct, _acc} ->
raise BadStructError, struct: name, term: struct
{map, acc} when is_map(map) ->
{struct(name, map), acc}
end
end

defp transform_row(list, take, from, values) when is_list(list) do
Enum.map_reduce(list, values, &transform_row(&1, take, from, &2))
end
Expand Down
3 changes: 3 additions & 0 deletions test/ecto/query/builder/select_test.exs
Expand Up @@ -37,6 +37,9 @@ defmodule Ecto.Query.Builder.SelectTest do
assert {[{:{}, [], [{:{}, [], [:., [], [{:{}, [], [:&, [], [0]]}, :y]]}, [], []]},
{:{}, [], [:^, [], [0]]}], {%{0 => {1, :any}}, %{}}} ==
escape(quote do [x.y, ^1] end, [x: 0], __ENV__)

assert {{:{}, [], [:%, [], [Foo, {:{}, [], [:%{}, [], [a: {:{}, [], [:&, [], [0]]}]]}]]}, {%{}, %{}}} ==
escape(quote do %Foo{a: a} end, [a: 0], __ENV__)
end

@fields [:field]
Expand Down

0 comments on commit 9ad38bd

Please sign in to comment.