如何在Repo.preload中通过JSON属性在Ecto 2.0自定义选择查询中执行?

时间:2016-01-15 09:45:55

标签: elixir phoenix-framework ecto postgresql-json

我通过标记发布了类别和标签,标签和类别包含hstore name字段,其中包含密钥中的翻译。

如何通过纯Ecto查询预加载相关的post.tags并选择带有例如“pl”键的JSON tag.name?我想在这种情况下传递locale键“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")}))

3 个答案:

答案 0 :(得分: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"]

答案 1 :(得分: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")
) 

答案 2 :(得分:1)

不确定是否可以覆盖:tags(即)Repo.get!(Post, id) |> Repo.preload(tags: from(t in Tag, where: t.name == 'pl')) 但是您可以过滤预加载并映射该值。

usort($council_members->d, function($a, $b){
        return $a->PositionDisplayOrder > $b->PositionDisplayOrder;
   });

查看documentation了解详情。