joeljuca / swiss_schema

A Swiss Army knife for your Ecto schemas
https://hex.pm/packages/swiss_schema
Apache License 2.0
26 stars 4 forks source link

Insertion functions need to perform differently of creation functions #20

Open zoedsoupe opened 5 months ago

zoedsoupe commented 5 months ago

Currently we define:

That have the exactly same logic with the additional issue that creation functions call insertion functions. So let’s deep dive on this issue:

Semantics

Following Phoenix context and Ecto semantics, insertion function should receive the already built struct of the schema OR a %Ecto.Changeset{} that represents the current schema via the data attribute.

On the other hand, creation functions should receive raw parameters as maps, trigger the changeset function to parse this parameter and then leverage the insertion function.

There’s no idea of “creation” on Ecto.Repo, as it leverages direct database API functions.

What’s on currently?

Currently the insertion and creation functions have the exactly same logic, as:

      @impl SwissSchema
      def create(%{} = params, opts \\ []) do
        r = Keyword.get(opts, :repo, unquote(repo))
        insert = Function.capture(r, :insert, 2)
        default_changeset = Function.capture(__MODULE__, :changeset, 2)
        changeset = Keyword.get(opts, :changeset, default_changeset)

        struct(__MODULE__)
        |> changeset.(params)
        |> insert.(opts)
      end

      @impl SwissSchema
      def insert(%{} = params, opts \\ []) do
        repo = Keyword.get(opts, :repo, unquote(repo))
        insert = Function.capture(repo, :insert, 2)
        default_changeset = Function.capture(__MODULE__, :changeset, 2)
        changeset = Keyword.get(opts, :changeset, default_changeset)

        struct(__MODULE__)
        |> changeset.(params)
        |> insert.(opts)
      end
  1. get repo
  2. get the insert/2 function from the repo
  3. pass params trough a changeset/2 function
  4. builds a struct from __MODULE__, passes it to the changeset function and then to the local insert function

Proposal

I would like to make a suggestion:

  1. insertion functions would receive the already built struct or a changeset of the same schema (to match the library concept), something like:
    
    @impl SwissSchema
    def insert(source, opts \\ [])

def insert(%Ecto.Changeset{data: %mod{}} = changeset, opts) do case mod do MODULE -> performinsert(changeset, opts) -> {:error, :not_same_schema_module} end end

def insert(%mod{} = struct, opts) when is_struct(struct) do case mod do MODULE -> performinsert(struct, opts) -> {:error, :not_same_schema_module} end end

defp perform_insert(source, opts) do repo = Keyword.get(opts, :repo, unquote( insert = Function.capture(repo, :insert, 2) insert.(source, opts) end



2. creation functions would do the same logic as now, no need to change this api.
joeljuca commented 2 months ago

FYI: I'm prioritizing this one for the next iterations I'll perform on the project. :)