diff --git a/lib/mongo_ecto.ex b/lib/mongo_ecto.ex index 6f5ec0f..2b8ec7e 100644 --- a/lib/mongo_ecto.ex +++ b/lib/mongo_ecto.ex @@ -363,6 +363,7 @@ defmodule Mongo.Ecto do alias Mongo.Ecto.NormalizedQuery.AggregateQuery alias Mongo.Ecto.ObjectID alias Mongo.Ecto.Connection + alias Mongo.Ecto.Conversions ## Adapter @@ -583,31 +584,18 @@ defmodule Mongo.Ecto do end defp process_document(document, %{fields: fields, pk: pk}, preprocess) do - document = to_ecto_pk(document, pk) + document = Conversions.to_ecto_pk(document, pk) Enum.map(fields, fn {:field, name, field} -> preprocess.(field, Map.get(document, Atom.to_string(name)), nil) {:value, value, field} -> - preprocess.(field, to_ecto_pk(value, pk), nil) + preprocess.(field, Conversions.to_ecto_pk(value, pk), nil) field -> preprocess.(field, document, nil) end) end - def to_ecto_pk(%{__struct__: _} = value, _pk), - do: value - def to_ecto_pk(map, pk) when is_map(map) do - Enum.into(map, %{}, fn - {"_id", value} -> {Atom.to_string(pk), to_ecto_pk(value, pk)} - {key, value} -> {key, to_ecto_pk(value, pk)} - end) - end - def to_ecto_pk(list, pk) when is_list(list), - do: Enum.map(list, &to_ecto_pk(&1, pk)) - def to_ecto_pk(value, _pk), - do: value - ## Storage # Noop for MongoDB, as any databases and collections are created as needed. diff --git a/lib/mongo_ecto/conversions.ex b/lib/mongo_ecto/conversions.ex new file mode 100644 index 0000000..b282559 --- /dev/null +++ b/lib/mongo_ecto/conversions.ex @@ -0,0 +1,87 @@ +defmodule Mongo.Ecto.Conversions do + @moduledoc false + + import Mongo.Ecto.Utils + + def to_ecto_pk(%{__struct__: _} = value, _pk), + do: value + def to_ecto_pk(map, pk) when is_map(map) do + Enum.into(map, %{}, fn + {"_id", value} -> {Atom.to_string(pk), to_ecto_pk(value, pk)} + {key, value} -> {key, to_ecto_pk(value, pk)} + end) + end + def to_ecto_pk(list, pk) when is_list(list), + do: Enum.map(list, &to_ecto_pk(&1, pk)) + def to_ecto_pk(value, _pk), + do: value + + def inject_params(doc, params, pk) when is_keyword(doc), + do: document(doc, params, pk) + def inject_params(list, params, pk) when is_list(list), + do: map(list, &inject_params(&1, params, pk)) + def inject_params({:^, _, [idx]}, params, pk), + do: elem(params, idx) |> inject_params(params, pk) + def inject_params(%{__struct__: _} = struct, _params, pk), + do: from_ecto_pk(struct, pk) + def inject_params(map, params, pk) when is_map(map), + do: document(map, params, pk) + def inject_params(value, _params, pk), + do: from_ecto_pk(value, pk) + + def from_ecto_pk(%{__struct__: change, field: field, value: value}, pk) + when change in [Mongo.Ecto.ChangeMap, Mongo.Ecto.ChangeArray] do + case from_ecto_pk(value, pk) do + {:ok, value} -> {:ok, {field, value}} + :error -> :error + end + end + def from_ecto_pk(%{__struct__: _} = value, _pk), + do: {:ok, value} + def from_ecto_pk(map, pk) when is_map(map), + do: document(map, pk) + def from_ecto_pk(keyword, pk) when is_keyword(keyword), + do: document(keyword, pk) + def from_ecto_pk(list, pk) when is_list(list), + do: map(list, &from_ecto_pk(&1, pk)) + def from_ecto_pk(value, _pk), + do: {:ok, value} + + defp document(doc, pk) do + map(doc, fn {key, value} -> + pair(key, value, pk, &from_ecto_pk(&1, pk)) + end) + end + + defp document(doc, params, pk) do + map(doc, fn {key, value} -> + pair(key, value, pk, &inject_params(&1, params, pk)) + end) + end + + defp pair(key, value, pk, fun) do + case fun.(value) do + {:ok, {subkey, encoded}} -> {:ok, {"#{key}.#{subkey}", encoded}} + {:ok, encoded} -> {:ok, {key(key, pk), encoded}} + :error -> :error + end + end + + defp key(pk, pk), do: :_id + defp key(key, _), do: key + + defp map(list, fun) do + return = + Enum.flat_map_reduce(list, :ok, fn elem, :ok -> + case fun.(elem) do + {:ok, value} -> {[value], :ok} + :error -> {:halt, :error} + end + end) + + case return do + {values, :ok} -> {:ok, values} + {_values, :error} -> :error + end + end +end diff --git a/lib/mongo_ecto/encoder.ex b/lib/mongo_ecto/encoder.ex deleted file mode 100644 index 01d32d3..0000000 --- a/lib/mongo_ecto/encoder.ex +++ /dev/null @@ -1,100 +0,0 @@ -defmodule Mongo.Ecto.Encoder do - @moduledoc false - - import Mongo.Ecto.Utils - - def encode(doc, params, pk) when is_keyword(doc), - do: document(doc, params, pk) - def encode(list, params, pk) when is_list(list), - do: map(list, &encode(&1, params, pk)) - def encode({:^, _, [idx]}, params, pk), - do: elem(params, idx) |> encode(params, pk) - def encode(%{__struct__: _} = struct, _params, pk), - do: encode(struct, pk) # Pass down other structs - def encode(map, params, pk) when is_map(map), - do: document(map, params, pk) - def encode(value, _params, pk), - do: encode(value, pk) - - @epoch :calendar.datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}) - - def encode(doc, pk) when is_keyword(doc), - do: document(doc, pk) - def encode(int, _pk) when is_integer(int), - do: {:ok, int} - def encode(float, _pk) when is_float(float), - do: {:ok, float} - def encode(string, _pk) when is_binary(string), - do: {:ok, string} - def encode(atom, _pk) when is_atom(atom), - do: {:ok, atom} - def encode(list, pk) when is_list(list), - do: map(list, &encode(&1, pk)) - def encode(%BSON.ObjectId{} = objectid, _pk), - do: {:ok, objectid} - def encode(%BSON.JavaScript{} = js, _pk), - do: {:ok, js} - def encode(%BSON.Regex{} = regex, _pk), - do: {:ok, regex} - def encode(%BSON.DateTime{} = datetime, _pk), - do: {:ok, datetime} - def encode(%BSON.Binary{} = binary, _pk), - do: {:ok, binary} - def encode(%{__struct__: change, field: field, value: value}, pk) - when change in [Mongo.Ecto.ChangeMap, Mongo.Ecto.ChangeArray] do - case encode(value, pk) do - {:ok, value} -> {:ok, {field, value}} - :error -> :error - end - end - def encode(%{__struct__: _}, _pk), - do: :error # Other structs are not supported - def encode(map, pk) when is_map(map), - do: document(map, pk) - def encode({{_, _, _} = date, {hour, min, sec, usec}}, _pk) do - seconds = - :calendar.datetime_to_gregorian_seconds({date, {hour, min, sec}}) - @epoch - {:ok, %BSON.DateTime{utc: seconds * 1000 + div(usec, 1000)}} - end - def encode(_value, _pk) do - :error - end - - defp document(doc, pk) do - map(doc, fn {key, value} -> - pair(key, value, pk, &encode(&1, pk)) - end) - end - - defp document(doc, params, pk) do - map(doc, fn {key, value} -> - pair(key, value, pk, &encode(&1, params, pk)) - end) - end - - defp pair(key, value, pk, fun) do - case fun.(value) do - {:ok, {subkey, encoded}} -> {:ok, {"#{key}.#{subkey}", encoded}} - {:ok, encoded} -> {:ok, {key(key, pk), encoded}} - :error -> :error - end - end - - defp key(pk, pk), do: :_id - defp key(key, _), do: key - - defp map(list, fun) do - return = - Enum.flat_map_reduce(list, :ok, fn elem, :ok -> - case fun.(elem) do - {:ok, value} -> {[value], :ok} - :error -> {:halt, :error} - end - end) - - case return do - {values, :ok} -> {:ok, values} - {_values, :error} -> :error - end - end -end diff --git a/lib/mongo_ecto/helpers.ex b/lib/mongo_ecto/helpers.ex index bd8c429..ff00856 100644 --- a/lib/mongo_ecto/helpers.ex +++ b/lib/mongo_ecto/helpers.ex @@ -20,14 +20,7 @@ defmodule Mongo.Ecto.Helpers do """ @spec javascript(String.t, Keyword.t) :: Mongo.Ecto.JavaScript.t def javascript(code, scope \\ []) do - case Mongo.Ecto.Encoder.encode(scope, nil) do - {:ok, []} -> - %Mongo.Ecto.JavaScript{code: code} - {:ok, scope} -> - %Mongo.Ecto.JavaScript{code: code, scope: scope} - :error -> - raise ArgumentError, "invaid expression for JavaScript scope" - end + %Mongo.Ecto.JavaScript{code: code, scope: scope} end @doc """ diff --git a/lib/mongo_ecto/normalized_query.ex b/lib/mongo_ecto/normalized_query.ex index d8980c7..c90e60b 100644 --- a/lib/mongo_ecto/normalized_query.ex +++ b/lib/mongo_ecto/normalized_query.ex @@ -32,7 +32,7 @@ defmodule Mongo.Ecto.NormalizedQuery do defstruct coll: nil, pk: nil, fields: [], pipeline: [], database: nil, opts: [] end - alias Mongo.Ecto.Encoder + alias Mongo.Ecto.Conversions alias Ecto.Query.Tagged alias Ecto.Query @@ -317,14 +317,14 @@ defmodule Mongo.Ecto.NormalizedQuery do do: raise(Ecto.QueryError, query: query, message: message) defp value(expr, pk, place) do - case Encoder.encode(expr, pk) do + case Conversions.from_ecto_pk(expr, pk) do {:ok, value} -> value :error -> error(place) end end defp value(expr, params, pk, query, place) do - case Encoder.encode(expr, params, pk) do + case Conversions.inject_params(expr, params, pk) do {:ok, value} -> value :error -> error(query, place) end