2

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

如何通过纯 Ecto 查询预加载相关并使用例如“pl”键post.tags选择 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")}))
4

3 回答 3

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"]

于 2016-01-15T12:23:57.700 回答
1

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

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

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

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")
) 
于 2016-01-15T14:16:48.647 回答
1

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

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

查看文档以获取更多详细信息。

于 2016-01-15T16:29:20.380 回答