Poison.Encoder how to preload associations?

2019-07-13 04:06发布

问题:

I have the Ecto model below. When I try to render I get an error. How can I modify the @derive so it will preload? Or do I have to write out the implementation? What is the recommended way of dealing with this?

** (RuntimeError) cannot encode association :tilemap_layers from MyProject.Tilemap to JSON because the association was not loaded. Please make sure you have preloaded the association or remove it from the data to be encoded

The model is here:

defmodule MyProject.Tilemap do
  use MyProject.Web, :model

  @derive {Poison.Encoder, only: [
    :name,
    :tile_width,
    :tile_height,
    :width,
    :height,
    :orientation,
    :tilemap_layers,
    :tilesets
  ]}

  schema "tilemaps" do

    field :name, :string
    field :tile_width, :integer
    field :tile_height, :integer
    field :width, :integer
    field :height, :integer
    field :orientation, :string

    has_many :tilemap_layers, MyProject.TilemapLayer
    has_many :tilesets, MyProject.Tileset

    timestamps
  end

  @required_fields ~w(tile_width tile_height width height)
  @optional_fields ~w()

  @doc """
  Creates a changeset based on the `model` and `params`.

  If no params are provided, an invalid changeset is returned
  with no validation performed.
  """
  def changeset(model, params \\ :empty) do
    model
    |> cast(params, @required_fields, @optional_fields)
  end
end

回答1:

The short answer is you shouldn't. Preloading the data is not the responsibility of the view layer.

You should perform the preload when you are fetching your resource (usually the controller or a function called from the controller.)

For example using Ecto.Repo.preload/3:

def index(_conn, _params)
  timemaps = Tilemap |> Repo.all() |> Repo.preload(:timemap_layers)
  render("index.json", tilemaps: tilemaps)
end

You can also perform a preload in a query with Ecto.Query.preload/3:

query = from t in Tilemap,
  preload: [:tilemap_layers]
Repo.all(query)