在Phoenix / Ecto中混合范围和关联

时间:2015-06-01 22:20:24

标签: elixir phoenix-framework ecto

在Rails中,如果我有以下设置:

class Post < ActiveRecord::Base
  has_many :comments
end

class Comment < ActiveRecord::Base
  belongs_to :post

  def self.approved
    where(approved: true)
  end
end

然后我可以这样做:

post = Post.find(100)
comments = post.comments.approved

快速获取给定Post的所有已批准评论。

我怎样才能在Ecto中做类似的事情?

defmodule MyApp.Post do
  use Ecto.Model

  schema "posts" do
    #columns omitted
    has_many :comments, MyApp.Comment
  end
end

defmodule MyApp.Comment do
  use Ecto.Model

  schema "comments" do
    #columns omitted
    belongs_to :post, MyApp.Post
  end
end

我已预先加载post comments

post = MyApp.Post
       |> MyApp.Repo.get(100)
       |> MyApp.Repo.preload(:comments)

我甚至不确定从approved开始使用MyApp.Comment范围。

3 个答案:

答案 0 :(得分:10)

允许预加载接收查询。所以你可以像这样过滤相关的评论。

post = 
  MyApp.Post
  |> Ecto.Query.preload(comments: ^MyApp.Comment.approved(MyApp.Comment))
  |> MyApp.Repo.get(100)

并在您的Comment模型中

def approved(query) do
  from c in query,
  where: c.approved == true
end

答案 1 :(得分:3)

我不认为使用当前版本的Ecto是可能的。预加载不允许过滤。另一种方法是通过查询获取评论:

(from comment in MyApp.Comment, 
  where: comment.post_id == ^post_id 
    and comment.approved == true,
select: comment) |> Repo.all

答案 2 :(得分:0)

我参加这个聚会真的很晚,但是有一些空闲时间,想出一个答案可以帮助刚接触Elixir的人们。

如果您来自Ruby / Rails,则要记住的一件事是Elixir / Erlang中的数据是无状态的,因为值是不可变的。因此,我们没有办法操纵帖子并将评论加载到数据结构中。我们可以通过两种方法达到相同的最终结果:

#1返回一个新的结构/映射,其中已合并注释

post_with_comments = %{post | comments: comments} # or Map.put(post, :comments, comments)

其中的注释类似于:

comments = MyApp.Repo.get_by(MyApp.Comment, where: post_id == ^post.id)

#2通过构建查询以一次全部抓取将数据预加载到发布数据结构中。我们可以通过将查询传递到查询中来做到这一点,见下文。

defmodule MyApp.Post.Query do
  def approved_with_comments(id) do
    get_post(id) |> with_approval(true) |> with_comments()
  end

  def get_post(id) do
    from p in MyApp.Post, where: p.id == ^id
  end

  def with_approval(query, approval) do
    from q in query, where: approved == ^approval
  end

  def with_comments(query) do
    from q in query, preload: [:comments]
  end
end

通常,您将希望始终预加载关联,因为它对数据库更有效。我个人喜欢Ecto中的这种行为,因为它迫使您不要用N + 1个查询将自己丢在脚下,或者使它们变得显而易见。

通过使用与模式匹配相同的函数名称,可以使上面的Query模块之类的界面更符合人体工程学:

def query(query, :by_id, id), do: from q in query, where: q.id == ^id
def query(query, :by_approval, approval), do: # ....

然后,您将把您的参数所组成的约简映射到单个查询对象中,然后最终用Repo.one进行加载,或者将任何适合您的幻想映射。