Repository: elixir-ecto/ecto
Pull request #2075 (2017-05-25 17:40:00)

added a pattern to schema_for_association_join! for map updates
Support was recently added for map updates in select statements from subqueries. In order to use `assoc/2` with the result of those subqueries, this pattern needs to be added to `schema_for_association_join!/3`

ebert[bot]: Hello, @zachdaniel! This is your first Pull Request that will be reviewed by [Ebert](https://ebertapp.io/), an automatic Code Review service. It will leave comments on this diff with potential issues and style violations found in the code as you push new commits. You can also see all the issues found on this Pull Request on its [review page](https://ebertapp.io/github/elixir-ecto/ecto/pulls/2075). Please check our [documentation](https://ebertapp.io/docs) for more information. 
zachdaniel: I wasn't quite sure where to add the appropriate test case, so if someone can't point me to it I'll be more than happy to add a test case, and to update any relevant documentation.
josevalim: @zachdaniel please check the commit that added update support and see where it added the test. I think it was in test/ecto/query/planner_test.exs. :)
zachdaniel: This seemed kind of cross domain, so I wasn't sure if I should put this in the same place w/ the other subquery tests in `integration_test/sql/subquery.exs` or if I should put it in association tests or in the assoc tests in `integration_test/cases/assoc.exs`, or both. I'll write up what I think they would both look like and put them here.
josevalim: I don't think we need an integration test for this change, so it needs to go in `test`. If planner is too large, then we can break it apart as part of another PR.
zachdaniel: @josevalim Added a test case that illustrates the source being set appropriately.

Patch:
diff --git a/lib/ecto/query/planner.ex b/lib/ecto/query/planner.ex
index 4d51e31e..6710c3a2 100644
--- a/lib/ecto/query/planner.ex
+++ b/lib/ecto/query/planner.ex
@@ -427,6 +427,8 @@ defmodule Ecto.Query.Planner do
         schema
       %Ecto.SubQuery{query: %{select: %{expr: {:&, _, [ix]}}, sources: sources}} when is_integer(ix) ->
         schema_for_association_join!(query, join, elem(sources, ix))
+      %Ecto.SubQuery{query: %{select: %{expr: {:%{}, _, [{:|, _, [{:&, _, [ix]}, _fields]}]}}, sources: sources}} when is_integer(ix) ->
+        schema_for_association_join!(query, join, elem(sources, ix))
       %Ecto.SubQuery{} ->
         error! query, join, "can only perform association joins on subqueries " <>
                             "that return a single source in select"


Test patch:
diff --git a/test/ecto/changeset/has_assoc_test.exs b/test/ecto/changeset/has_assoc_test.exs
index dd84f82f..dbc01a01 100644
--- a/test/ecto/changeset/has_assoc_test.exs
+++ b/test/ecto/changeset/has_assoc_test.exs
@@ -786,13 +786,17 @@ defmodule Ecto.Changeset.HasAssocTest do
 
   ## Other
 
-  test "put_assoc/4" do
+  test "put_assoc/4 with has_one" do
     base_changeset = Changeset.change(%Author{})
 
     changeset = Changeset.put_assoc(base_changeset, :profile, %{name: "michal"})
     assert %Ecto.Changeset{} = changeset.changes.profile
     assert changeset.changes.profile.action == :insert
 
+    changeset = Changeset.put_assoc(base_changeset, :profile, [name: "michal"])
+    assert %Ecto.Changeset{} = changeset.changes.profile
+    assert changeset.changes.profile.action == :insert
+
     changeset = Changeset.put_assoc(base_changeset, :profile, %Profile{name: "michal"})
     assert %Ecto.Changeset{} = changeset.changes.profile
     assert changeset.changes.profile.action == :insert
@@ -804,6 +808,28 @@ defmodule Ecto.Changeset.HasAssocTest do
     refute Map.has_key?(changeset.changes, :profile)
   end
 
+  test "put_assoc/4 with has_many" do
+    base_changeset = Changeset.change(%Author{})
+
+    changeset = Changeset.put_assoc(base_changeset, :posts, [%{title: "hello"}])
+    assert [%Ecto.Changeset{}] = changeset.changes.posts
+    assert hd(changeset.changes.posts).action == :insert
+
+    changeset = Changeset.put_assoc(base_changeset, :posts, [[title: "hello"]])
+    assert [%Ecto.Changeset{}] = changeset.changes.posts
+    assert hd(changeset.changes.posts).action == :insert
+
+    changeset = Changeset.put_assoc(base_changeset, :posts, [%Post{title: "hello"}])
+    assert [%Ecto.Changeset{}] = changeset.changes.posts
+    assert hd(changeset.changes.posts).action == :insert
+
+    base_changeset = Changeset.change(%Author{posts: [%Post{title: "hello"}]})
+    empty_update_changeset = Changeset.change(%Post{title: "hello"})
+
+    changeset = Changeset.put_assoc(base_changeset, :posts, [empty_update_changeset])
+    refute Map.has_key?(changeset.changes, :posts)
+  end
+
   test "put_assoc/4 when replacing" do
     profile = %Profile{id: 1, name: "michal"} |> Ecto.put_meta(state: :loaded)
     base_changeset = Changeset.change(%Author{profile: profile})
diff --git a/test/ecto/query/planner_test.exs b/test/ecto/query/planner_test.exs
index 19c7942b..0f582452 100644
--- a/test/ecto/query/planner_test.exs
+++ b/test/ecto/query/planner_test.exs
@@ -289,6 +289,20 @@ defmodule Ecto.Query.PlannerTest do
     end
   end
 
+  test "prepare: subqueries with map updates in select can be used with assoc" do
+    query =
+      Post
+      |> select([post], %{post | title: ^"hello"})
+      |> subquery()
+      |> join(:left, [subquery_post], comment in assoc(subquery_post, :comments))
+      |> prepare()
+      |> elem(0)
+
+    assert %JoinExpr{on: on, source: source, assoc: nil, qual: :left} = hd(query.joins)
+    assert source == {"comments", Comment}
+    assert Macro.to_string(on.expr) == "&1.post_id() == &0.id()"
+  end
+
   test "prepare: subqueries do not support preloads" do
     query = from p in Post, join: c in assoc(p, :comments), preload: [comments: c]
     assert_raise Ecto.SubQueryError, ~r/cannot preload associations in subquery/, fn ->