如何在 Ecto 2.0 中通过 JSON 属性在 Repo.preload 中执行自定义 select 查询?
How to perform in Ecto 2.0 custom select query in Repo.preload by JSON attribute?
我有 Post 类别和许多标签,标签和类别包含 hstore name
文件,其中包含键中的翻译。
如何通过纯 Ecto 查询预加载相关的 post.tags
和 select JSON tag.name
例如 "pl" 键?在这种情况下,我想将区域设置键 "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 支持预加载查询中的自定义 select 字段,所以这是答案:
|> Repo.preload(tags: from(t in Myapp.Tag, select: %{id: t.id, data: fragment("?::json->?", t.name, "pl")}))
一种方法是使用 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)
Returns 一个列表
["narty", "wspinaczka"]
另一种方法是使用 Ecto.Query
模块并使用 Ecto 查询。
尽管它与 Repo.preload 方法有点不同,所以我不确定它是否正是您要找的。
基于之前的many-one数据模型:
query = from(from t in MyApp.Tag, where: t.post_id == ^id, select: t)
Repo.all(query) |> Enum.map(&(&1.name.pl))
根据编辑后的 many-many 数据模型,这应该更接近:
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")
)
不确定您是否可以覆盖 Ecto.Association
(即):tags
,但是您可以过滤预加载并映射值。
Repo.get!(Post, id) |> Repo.preload(tags: from(t in Tag, where: t.name == 'pl'))
查看 documentation 了解更多详情。
我有 Post 类别和许多标签,标签和类别包含 hstore name
文件,其中包含键中的翻译。
如何通过纯 Ecto 查询预加载相关的 post.tags
和 select JSON tag.name
例如 "pl" 键?在这种情况下,我想将区域设置键 "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 支持预加载查询中的自定义 select 字段,所以这是答案:
|> Repo.preload(tags: from(t in Myapp.Tag, select: %{id: t.id, data: fragment("?::json->?", t.name, "pl")}))
一种方法是使用 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)
Returns 一个列表
["narty", "wspinaczka"]
另一种方法是使用 Ecto.Query
模块并使用 Ecto 查询。
尽管它与 Repo.preload 方法有点不同,所以我不确定它是否正是您要找的。
基于之前的many-one数据模型:
query = from(from t in MyApp.Tag, where: t.post_id == ^id, select: t)
Repo.all(query) |> Enum.map(&(&1.name.pl))
根据编辑后的 many-many 数据模型,这应该更接近:
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")
)
不确定您是否可以覆盖 Ecto.Association
(即):tags
,但是您可以过滤预加载并映射值。
Repo.get!(Post, id) |> Repo.preload(tags: from(t in Tag, where: t.name == 'pl'))
查看 documentation 了解更多详情。