Skip to content

Can't preload embedded_schema struct #3238

Closed

Description

@ZhengQingchen

Environment

  • Elixir version (elixir -v):
    Elixir 1.9.4 (compiled with Erlang/OTP 22)
  • Database and version (PostgreSQL 9.4, MongoDB 3.2, etc.):
    PostgreSQL 10.6
  • Ecto version (mix deps):
    ecto_sql 3.3.3 (Hex package) (mix)
    locked at 3.3.3 (ecto_sql) 3d5b8e14
  • Database adapter and version (mix deps):
    postgrex 0.15.3 (Hex package) (mix)
    locked at 0.15.3 (postgrex) 4737ce62
  • Operating system:
    MacOS

Current behavior

There is an embedded_schema which has a belongs_to relationship with other database schema. When I try to preload the embedded_schema. It's got a match error

iex(102)> Repo.preload completion_cache, :completed_by
** (MatchError) no match of right hand side value: CompletionCache{completed_at: nil, completed_by: #Ecto.Association.NotLoaded<association :completed_by is not loaded>, completed_by_uuid: "30d8ab0d-0cc5-4130-ad18-bc5c944bd03c"}

The preload schema is.

defmodule CompletionCache do
  @derive Jason.Encoder
  use Airbase.Schema, primary_key: false

  embedded_schema do
    field :completed_at, :naive_datetime
    belongs_to :completed_by, User, foreign_key: :completed_by_uuid, references: :uuid 
  end
end

The user schema just a normal database schema.
It's cause by this patch match

%{__meta__: %{prefix: prefix}} = sample

So Can I use the belongs_to in an embedded_schema?
Or could I just ignore the prefix when we do preload the embedded_schema struct like this?

  defp preload_prefix(opts, sample) do
    case Keyword.fetch(opts, :prefix) do
      {:ok, prefix} ->
        prefix
      :error ->
        case sample do
          %{__meta__: %{prefix: prefix}} -> prefix
          _ -> nil
        end
    end
  end

Activity

  1. josevalim commented on Feb 26, 2020

    @josevalim
    Member

    Embedded schemas are always loaded when you load entries. You don't have to preload them as they are not in separate tables.

  2. erlandsona commented on Mar 11, 2020

    @erlandsona

    @josevalim I'm trying to do the same thing and running into a different error, but what do you mean by...

    Embedded schemas are always loaded when you load entries

    Here's a Command which will be run like Command.run(CreateEncounterQuestionResponse) which just calls things like Repo.transaction and some other book keeping stuff for us.

    defmodule CreateEncounterQuestionResponse do
      typed_embedded_schema do
        field(:response, :string, null: false)
        field(:responded_by_id)
    
        belongs_to(:encounter, Encounter)
      end
    
      def run(params) do
        Multi.new()
        |> Multi.run(:encounter, fn repo, _ ->
          params
          |> repo.preload(
            encounter: [
              questions: from(q in Question, order_by: [q.submitted_at, :desc], limit: 1)
            ]
          )
        end)
        |> Multi.update(:encounter_question, &Question.update/1)
        end)
      end
    end
    

    Then in Question.ex

      @spec update(map()) :: Ecto.Changeset.t(t())
      def update(params) do
        whitelist = [
          :response,
          :responded_by_id,
          :encounter_id
        ]
    
        params.encounter.questions
        |> List.first
        |> cast(params, whitelist)
        |> cast_event(:responded)
        |> validate_required(whitelist)
      end
    

    The idea would be that when this runs the embedded_schema would run a Repo.get with the encounter_id that came in the request and I'd like to use that to get the last unanswered question to provide a response for.

    The error I'm getting is...

    iex(43)> %{"encounter_id" => 167045, "responded_by_id" => "0051C000008oKwNQAU", "response" => "blah stuff"} |> Command.run(CreateEncounterQuestionResponse)
    ** (KeyError) key :__struct__ not found in: %{encounter: #Ecto.Association.NotLoaded<association :encounter is not loaded>, encounter_id: 167045, id: nil, responded_by_id: "0051C000008oKwNQAU", response: "blah stuff"}
        (ecto) lib/ecto/multi.ex:585: Ecto.Multi.apply_operation/5
        (elixir) lib/enum.ex:1940: Enum."-reduce/3-lists^foldl/2-0-"/3
        (ecto) lib/ecto/multi.ex:569: anonymous fn/5 in Ecto.Multi.apply_operations/5
        (ecto_sql) lib/ecto/adapters/sql.ex:903: anonymous fn/3 in Ecto.Adapters.SQL.checkout_or_transaction/4
        (db_connection) lib/db_connection.ex:1427: DBConnection.run_transaction/4
        (ecto) lib/ecto/repo/transaction.ex:20: Ecto.Repo.Transaction.transaction/4
        (ok) lib/ok.ex:29: OK.map/2
        (api) lib/command.ex:44: Command.run/2
    

    Which I'm assuming to mean that an embedded_schema is not a "real" Ecto object of sorts? Or just that I'm not using it like it was designed?
    Weird thing is embedded_schemas are documented / blogged about being used to take in form parameters which is basically what I'm doing here only for JSON post data...

  3. erlandsona commented on Mar 11, 2020

    @erlandsona

    Scratch that error, I was converting the struct to a map after running apply_action(:insert) on the changeset from the incoming params. I commented out that struct to map conversion and now I'm getting @ZhengQingchen's same MatchError.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions