代码之家  ›  专栏  ›  技术社区  ›  leon

如何在Ecto 2.0中执行Repo中的自定义选择查询。通过JSON属性预加载?

  •  2
  • leon  · 技术社区  · 10 年前

    我有帖子与类别和许多标签通过标签,标签和类别包含hstore name 包含关键字翻译的字段。

    如何预加载相关 post.tags 并选择JSON tag.name 例如,使用纯Ecto查询的“pl”键?我想将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 回复  |  直到 10 年前
        1
  •  1
  •   ham-sandwich Breck    10 年前

    一种方法是使用Enum过滤标签。地图功能。

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

        2
  •  1
  •   stephen_m    10 年前

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

    这与回购有点不同。预加载方法,所以我不确定这是否正是您想要的。

    基于先前的多个一数据模型:

    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")
    ) 
    
        3
  •  1
  •   Siva    10 年前

    不确定是否可以覆盖 Ecto.Association (即) :tags 但是您可以根据预加载进行筛选并映射值。

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

    检查 documentation 了解更多详细信息。