ECTO加入动态建立的条件

tet*_*anz 6 elixir ecto

我正在尝试使用左联接建立Ecto查询,并在联接上带有可选的附加条件。我将尝试通过典型的帖子和评论示例对其进行描述。

发表has_many留言评论属于留言。

假设“评论”具有两个布尔字段,即“已批准”和“精选”。

我想获取所有帖子,无论它们是否有评论,因此请保留联接。我要预加载注释,但最好是一个SQL查询。我想有选择地过滤已批准和精选的评论。

我正在尝试编写类似这样的函数,如果批准或推荐的功能不为nil,它们将被包含在联接中;如果为nil,它们将被忽略。我没有找到比这样更好的方法了:

def posts_with_comments(approved, featured, some_var) do
  query = Post
  |> where([p], p.some_field == ^some_var

  cond do
    !is_nil(approved) and !is_nil(featured)
      -> join(query, :left, [p], c in Comment, [post_id: p.id, approved: ^approved, featured: ^featured])

    !is_nil(approved)
      -> join(query, :left, [p], c in Comment, [post_id: p.id, approved: ^approved])

    !is_nil(featured)
      -> join(query, :left, [p], c in Comment, [post_id: p.id, featured: ^featured])

    true -> join(query, :left, [p], c in Comment, [post_id: p.id])
  end

  |> preload([p, c], [comments: c])
  |> select([p], p)
  |> Repo.all

end
Run Code Online (Sandbox Code Playgroud)

可以,但是必须有更好的方法。如果我有第三个参数,那就太疯狂了。我正在寻找一种动态构建该列表的on参数的方法join()。由于需要固定,我的尝试失败了。

我不能将这些条件放在a中,where因为如果我做类似的事情,where t.approved == true我只会收到批准的帖子。

tet*_*anz 3

我认为答案是使用动态函数。

这有效。(忽略我之前的 some_var 条件)。

def posts_with_comments(approved, featured) do
  query = Post
  join(query, :left, [p], c in Comment, ^do_join(approved, featured))
  |> preload([p, c], [comments: c])
  |> Repo.all
end

defp do_join(approved, featured) do
  dynamic = dynamic([p, c], c.post_id == p.id)

  dynamic =
  case approved do
    nil -> dynamic
    _ -> dynamic([p, c], ^dynamic and c.approved == ^approved)
  end

  case featured do
    nil -> dynamic
    _ -> dynamic([p, c], ^dynamic and c.featured == ^featured)
  end
end
Run Code Online (Sandbox Code Playgroud)

这比我的第一次尝试要好得多,因为这是一个简单的串联,随着条件的增加而变得更长,而不是条件的爆炸。

作为练习,我无法通过向其提供字段列表并使用诸如“reduce”之类的东西来使其更加通用。我遇到的问题是使字段名称(例如,c.approved)通过变量工作。

join似乎支持两种类型的on参数。关键字列表(我认为这意味着 ==)和更具表现力的格式。 dynamic似乎不适用于关键字列表。它尝试将 p.id 扩展为 p.id()。

我无法让 @mudasobwa 的基于宏的解决方案发挥作用。我还不是一个宏专家,但我不明白 nil 匹配如何在运行时工作。

关于宏观解决方案的另一件事。由于某种原因,它也不适用于关键字列表。我希望像这样的简单宏能够工作:

defmacrop do_join do
  quote do
    [post_id: p.id]
  end
end
Run Code Online (Sandbox Code Playgroud)

但事实并非如此。它尝试将 p.id 扩展为 p.id()