Permalink
Browse files

Avoid allocating structs unless necessary, closes #2557

  • Loading branch information...
josevalim committed Sep 27, 2018
1 parent cd39685 commit 415ba3df0b3810e5b3393d32a208745e28bd5c20
Showing with 46 additions and 24 deletions.
  1. +25 −16 lib/ecto.ex
  2. +15 −8 lib/ecto/association.ex
  3. +6 −0 test/ecto/schema_test.exs
View
@@ -558,36 +558,45 @@ defmodule Ecto do
@spec put_meta(Ecto.Schema.schema, meta) :: Ecto.Schema.schema
when meta: [source: Ecto.Schema.source, prefix: Ecto.Schema.prefix,
context: Ecto.Schema.Metadata.context, state: Ecto.Schema.Metadata.state]
def put_meta(struct, opts) do
update_in struct.__meta__, &update_meta(opts, &1)
def put_meta(%{__meta__: meta} = struct, opts) do
case put_or_noop_meta(opts, meta, false) do
:noop -> struct
meta -> %{struct | __meta__: meta}
end
end
defp put_or_noop_meta([{key, value}|t], meta, updated?) do
case meta do
%{^key => ^value} -> put_or_noop_meta(t, meta, updated?)
_ -> put_or_noop_meta(t, put_meta(meta, key, value), true)
end
end
defp update_meta([{:state, state}|t], meta) do
defp put_or_noop_meta([], meta, true), do: meta
defp put_or_noop_meta([], _meta, false), do: :noop
defp put_meta(meta, :state, state) do
if state in [:built, :loaded, :deleted] do
update_meta t, %{meta | state: state}
%{meta | state: state}
else
raise ArgumentError, "invalid state #{inspect state}"
end
end
defp update_meta([{:source, source} | t], meta) do
update_meta t, %{meta | source: source}
end
defp update_meta([{:prefix, prefix} | t], meta) do
update_meta t, %{meta | prefix: prefix}
defp put_meta(meta, :source, source) do
%{meta | source: source}
end
defp update_meta([{:context, context} | t], meta) do
update_meta t, %{meta | context: context}
defp put_meta(meta, :prefix, prefix) do
%{meta | prefix: prefix}
end
defp update_meta([], meta) do
meta
defp put_meta(meta, :context, context) do
%{meta | context: context}
end
defp update_meta([{k, _}], _meta) do
raise ArgumentError, "unknown meta key #{inspect k}"
defp put_meta(_meta, key, _value) do
raise ArgumentError, "unknown meta key #{inspect key}"
end
defp assert_struct!(module, %{__struct__: struct}) do
View
@@ -308,6 +308,10 @@ defmodule Ecto.Association do
"""
def merge_source(schema, query)
def merge_source(%{__meta__: %{source: source}} = struct, {source, _}) do
struct
end
def merge_source(struct, {source, _}) do
Ecto.put_meta(struct, source: source)
end
@@ -317,14 +321,17 @@ defmodule Ecto.Association do
end
@doc false
def update_parent_prefix(changeset, parent) do
case parent do
%{__meta__: %{prefix: prefix}} ->
update_in changeset.data, &Ecto.put_meta(&1, prefix: prefix)
_ ->
changeset
end
end
def update_parent_prefix(
%{data: %{__meta__: %{prefix: prefix}}} = changeset,
%{__meta__: %{prefix: prefix}}
),
do: changeset
def update_parent_prefix(changeset, %{__meta__: %{prefix: prefix}}),
do: update_in(changeset.data, &Ecto.put_meta(&1, prefix: prefix))
def update_parent_prefix(changeset, _),
do: changeset
@doc """
Performs the repository action in the related changeset,
@@ -87,6 +87,12 @@ defmodule Ecto.SchemaTest do
assert Ecto.get_meta(schema, :context) == "foobar"
end
test "preserves schema on up to date metadata" do
old_schema = %Schema{}
new_schema = Ecto.put_meta(old_schema, source: "my schema", state: :built, prefix: nil)
assert :erts_debug.same(old_schema, new_schema)
end
test "inspects metadata" do
schema = %Schema{}
assert inspect(schema.__meta__) == "#Ecto.Schema.Metadata<:built, \"my schema\">"

0 comments on commit 415ba3d

Please sign in to comment.