lib/trans/gen_function_migration.ex

if Code.ensure_loaded?(Ecto.Adapters.SQL) do
  defmodule Mix.Tasks.Trans.Gen.TranslateFunction do
    use Mix.Task

    import Mix.Generator
    import Mix.Ecto, except: [migrations_path: 1]
    import Macro, only: [camelize: 1, underscore: 1]

    @shortdoc "Generates an Ecto migration to create the translate_field database function"

    @moduledoc """
    Generates a migration to add a database function
    `translate_field` that uses the `Trans` structured
    translation schema to resolve a translation for a field.

    """

    @doc false
    @dialyzer {:no_return, run: 1}

    def run(args) do
      no_umbrella!("trans_gen_translate_function")
      repos = parse_repo(args)
      name = "trans_gen_translate_function"

      Enum.each(repos, fn repo ->
        ensure_repo(repo, args)
        path = Path.relative_to(migrations_path(repo), Mix.Project.app_path())
        file = Path.join(path, "#{timestamp()}_#{underscore(name)}.exs")
        create_directory(path)

        assigns = [mod: Module.concat([repo, Migrations, camelize(name)])]

        content =
          assigns
          |> migration_template
          |> format_string!

        create_file(file, content)

        if open?(file) and Mix.shell().yes?("Do you want to run this migration?") do
          Mix.Task.run("ecto.migrate", [repo])
        end
      end)
    end

    defp timestamp do
      {{y, m, d}, {hh, mm, ss}} = :calendar.universal_time()
      "#{y}#{pad(m)}#{pad(d)}#{pad(hh)}#{pad(mm)}#{pad(ss)}"
    end

    defp pad(i) when i < 10, do: <<?0, ?0 + i>>
    defp pad(i), do: to_string(i)

    if Code.ensure_loaded?(Code) && function_exported?(Code, :format_string!, 1) do
      @spec format_string!(String.t()) :: iodata()
      @dialyzer {:no_return, format_string!: 1}
      def format_string!(string) do
        Code.format_string!(string)
      end
    else
      @spec format_string!(String.t()) :: iodata()
      def format_string!(string) do
        string
      end
    end

    if Code.ensure_loaded?(Ecto.Migrator) &&
         function_exported?(Ecto.Migrator, :migrations_path, 1) do
      def migrations_path(repo) do
        Ecto.Migrator.migrations_path(repo)
      end
    end

    if Code.ensure_loaded?(Mix.Ecto) && function_exported?(Mix.Ecto, :migrations_path, 1) do
      def migrations_path(repo) do
        Mix.Ecto.migrations_path(repo)
      end
    end

    embed_template(:migration, ~S|
      defmodule <%= inspect @mod %> do
        use Ecto.Migration

        def up do
          execute """
          CREATE OR REPLACE FUNCTION public.translate_field(record record, container varchar, field varchar, default_locale varchar, locales varchar[])
          RETURNS varchar
          STRICT
          STABLE
          LANGUAGE plpgsql
          AS $$
            DECLARE
              locale varchar;
              j json;
              c json;
              l varchar;
            BEGIN
              j := row_to_json(record);
              c := j->container;

              FOREACH locale IN ARRAY locales LOOP
                IF locale = default_locale THEN
                  RETURN j->>field;
                ELSEIF c->locale IS NOT NULL THEN
                  IF c->locale->>field IS NOT NULL THEN
                    RETURN c->locale->>field;
                  END IF;
                END IF;
              END LOOP;
              RETURN j->>field;
            END;
          $$;
          """

          execute("""
          CREATE OR REPLACE FUNCTION public.translate_field(record record, container varchar, default_locale varchar, locales varchar[])
          RETURNS jsonb
          STRICT
          STABLE
          LANGUAGE plpgsql
          AS $$
            DECLARE
              locale varchar;
              j json;
              c json;
            BEGIN
              j := row_to_json(record);
              c := j->container;

              FOREACH locale IN ARRAY locales LOOP
                IF c->locale IS NOT NULL THEN
                  RETURN c->locale;
                END IF;
              END LOOP;
              RETURN NULL;
            END;
          $$;
          """)
        end

        def down do
          execute "DROP FUNCTION IF EXISTS public.translate_field(container varchar, field varchar, default_locale varchar, locales varchar[])"
          execute "DROP FUNCTION IF EXISTS public.translate_field(container varchar, default_locale varchar, locales varchar[])"
        end
      end
    |)
  end
end