【问题标题】:How to perform in Ecto 2.0 custom select query in Repo.preload by JSON attribute?如何通过 JSON 属性在 Repo.preload 中执行 Ecto 2.0 自定义选择查询?
【发布时间】:2016-01-15 09:45:54
【问题描述】:

我通过标记发布了带有类别和许多标签的帖子,标签和类别包含 hstore name 字段,其中包含键中的翻译。

如何通过纯 Ecto 查询预加载相关的 post.tags 并使用例如“pl”键选择 JSON tag.name?在这种情况下,我想将语言环境键“pl”作为参数传递,但我不知道如何插入它,没有任何作用。

defmodule Myapp.Tag do
  use Myapp.Web, :model

  schema "tags" do
    field :name, :map

    has_many :taggings, Myapp.Tagging
    has_many :posts, through: [:taggings, :post]

    belongs_to :post, Myapp.Post
    timestamps
  end
end

defmodule Myapp.Tagging do
  use Myapp.Web, :model

  schema "taggings" do
    belongs_to :post, Myapp.Post
    belongs_to :tag, Myapp.Tag
  end
end

defmodule Myapp.Post do
  use Myapp.Web, :model

  schema "posts" do
    field :title, :string

    belongs_to :category, Myapp.Category
    has_many :taggings, Myapp.Tagging
    has_many :tags, through: [:taggings, :tag]
    timestamps
  end
end

测试:

post = Repo.get!(Post, id) |> Repo.preload([:tags, :category])
post.tags # => "tags":[{"name":{"pl":"narty","en":"ski"}},{"name":{"pl":"wspinaczka","en":"climbing"}}]

#how preload all with selected key?

posts = Post 
  |> Repo.all 
  |> Repo.preload(tags: from(t in Myapp.Tag, select: fragment("?::json->?", t.name, "pl")))

错误:

** (BadMapError) expected a map, got: "narty"
    (stdlib) :maps.find(:id, "narty")
    (elixir) lib/map.ex:27: Map.fetch!/2
    (elixir) lib/enum.ex:1387: Enum."-reduce/3-lists^foldl/2-0-"/3
    (elixir) lib/enum.ex:1043: anonymous fn/3 in Enum.map/2
    (elixir) lib/enum.ex:1387: Enum."-reduce/3-lists^foldl/2-0-"/3
    (elixir) lib/enum.ex:1043: Enum.map/2
    (elixir) lib/enum.ex:1043: anonymous fn/3 in Enum.map/2
    (elixir) lib/enum.ex:1387: Enum."-reduce/3-lists^foldl/2-0-"/3
    (elixir) lib/enum.ex:1043: Enum.map/2
    (stdlib) erl_eval.erl:669: :erl_eval.do_apply/6
    (stdlib) erl_eval.erl:438: :erl_eval.expr/5
       (iex) lib/iex/evaluator.ex:117: IEx.Evaluator.handle_eval/5

我想要:

post.tags # => "tags":["narty","wspinaczka"]

迁移:

defmodule Myapp.Repo.Migrations.CreateTag do
  use Ecto.Migration

  def change do
    create table(:tags) do
      add :name, :map
      add :category_id, references(:categories)

      timestamps
    end

  end
end

defmodule Myapp.Repo.Migrations.CreateJoinTableTaggings do
  use Ecto.Migration

  def change do
    create table(:taggings) do
      add :post_id, references(:posts)
      add :tag_id, references(:tags)
    end
    create index(:taggings, [:post_id, :tag_id])
  end
end

更新

Ecto 2.0 支持预加载查询中的自定义选择字段,所以这是答案:

|> Repo.preload(tags: from(t in Myapp.Tag, select: %{id: t.id, data: fragment("?::json->?", t.name, "pl")}))

【问题讨论】:

    标签: elixir phoenix-framework ecto postgresql-json


    【解决方案1】:

    一种方法是使用 Enum.map 函数过滤标签。

    Repo.get!(Post, id) |> Repo.preload([:tags])
    |> Map.get(:tags)
    |> Enum.map(&(&1.name.pl))
    

    这是一样的,只是语法不同:

    Repo.get!(Post, id) |> Repo.preload([:tags])
    |> Map.get(:tags)
    |> Enum.map(fn(tag) -> tag.name.pl end)
    

    返回一个列表 ["narty", "wspinaczka"]

    【讨论】:

    • 我考虑过在 Post 模型中使用 Ecto 查询的解决方案,以标准方式进入第一步发布,然后通过自定义预加载函数选择带有“pl”键的名称,如果可能的话,我想避免提取处理在管道运算符链中列出。
    【解决方案2】:

    另一种方法是使用 Ecto.Query 模块并使用 Ecto 查询。

    虽然它与 Repo.preload 方法有点不同,所以我不确定它是否正是您要寻找的。​​p>

    基于之前的多一数据模型:

    query = from(from t in MyApp.Tag, where: t.post_id == ^id, select: t)
    Repo.all(query) |> Enum.map(&(&1.name.pl))
    

    根据已编辑的较新的多对多数据模型,这应该更接近:

    post_id = "some_id" # <-- enter your id here
    query = Ecto.Query.from tagging in MyApp.Tagging,
            join: tag in assoc(tagging, :tags),
            where: tagging.post_id == ^post_id,
            select: tag
    Repo.all(query) |> Enum.map(&(&1.name.pl))
    

    您可以尝试一下这些方法吗... 我不确定它是否会起作用,因为我无法运行它,可能需要修改 json 片段查询..

    Repo.all(
      from tagging in MyApp.Tagging,
      join: tag in assoc(tagging, :tags), 
      where: tagging.post_id == ^post_id, 
      select: fragment("?::json#>'{?,?}'", tag, ^"name", ^"pl")
    ) 
    

    【讨论】:

    • 我无法通过名称后的点获取 hstore 密钥:** (Ecto.Query.CompileError) 't.name().pl()' is not a valid query expression
    • 能否将迁移文件放入 Myapp.Tag 架构的原始帖子中?我想知道 :name 字段的 postgresql 数据类型是什么,它是一个地图。
    • 目前看来您使用 Enum.map 的解决方案是正确的github,我的想法将在 echo 2.0 中得到支持
    【解决方案3】:

    不确定您是否可以覆盖 Ecto.Association(即):tags,但您可以过滤预加载并映射值。

    Repo.get!(Post, id) |> Repo.preload(tags: from(t in Tag, where: t.name == 'pl'))
    

    查看documentation了解更多详情。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-11-07
      • 1970-01-01
      • 1970-01-01
      • 2013-09-03
      • 1970-01-01
      • 1970-01-01
      • 2019-04-16
      • 1970-01-01
      相关资源
      最近更新 更多