Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to extend submodule .Type #48

Open
willjleong opened this issue Jul 1, 2023 · 2 comments
Open

How to extend submodule .Type #48

willjleong opened this issue Jul 1, 2023 · 2 comments

Comments

@willjleong
Copy link

willjleong commented Jul 1, 2023

I've noticed .Type is generated by Waffle.Ecto.Definition's macros as a submodule of our custom uploader Definition. If I want to extend the Ecto.Type so I can add forms in Kaffy admin, I can't figure out a clean way to extend the module that is created using this code:

defmodule Module.concat(unquote(definition), "Type") do
  # After the 3.2 version Ecto has moved @behavior
  # inside the `__using__` macro
  if macro_exported?(Ecto.Type, :__using__, 1) do
    use Ecto.Type
  else
    # in order to support versions lower than 3.2
    @behaviour Ecto.Type
  end

  def type, do: Waffle.Ecto.Type.type()
  def cast(value), do: Waffle.Ecto.Type.cast(unquote(definition), value)
  def load(value), do: Waffle.Ecto.Type.load(unquote(definition), value)
  def dump(value), do: Waffle.Ecto.Type.dump(unquote(definition), value)
end

I'm also not too familiar with metaprogramming in elixir so maybe there's a trick to extend the generated .Type submodule to add the functions I want to add render_form/5 and render_index/3 to integrate a Waffle.Ecto type into Kaffy Admin.

@achempion
Copy link
Member

Hi,

Custom Ecto types are set to implement the Ecto.Type behaviour callback functions.
Are there any specific reasons why you want to extend the Waffle's Ecto type with additional functions?

I would suggest to locate render specific functions in View modules as it's in general a good idea to separate data and representation layers.

@willjleong
Copy link
Author

willjleong commented Jul 7, 2023

This is specific to using Kaffy Admin since form field behavior is attached to custom types to be useable in the admin panel - for now I just add my own TypeExt submodule that redefines the nested autogenerated Type module's inner macro in the custom Definition module and that extends the Type to add functions I needed to add based on following this to add render_form and render_index functions like this https://github.com/areski/ex-chitchat/blob/master/lib/chit_chat_web/helpers/custom_image_field.ex I wish there was a cleaner way for me to do this.

defmodule MyAppWeb.FileImage do
  use Waffle.Definition
  use Waffle.Ecto.Definition

  @versions [:original]
  @extension_whitelist ~w(.jpg .jpeg .gif .png)
  @acl :public_read

  # Whitelist file extensions:
  def validate({file, _}) do
    file_extension = file.file_name |> Path.extname() |> String.downcase()
    Enum.member?(@extension_whitelist, file_extension)
  end

  defmodule TypeExt do
    # After the 3.2 version Ecto has moved @behavior
    # inside the `__using__` macro
    if macro_exported?(Ecto.Type, :__using__, 1) do
      use Ecto.Type
    else
      # in order to support versions lower than 3.2
      @behaviour Ecto.Type
    end

    def type, do: Waffle.Ecto.Type.type()
    def cast(value), do: Waffle.Ecto.Type.cast(MyAppWeb.FileImage, value)
    def load(value), do: Waffle.Ecto.Type.load(MyAppWeb.FileImage, value)
    def dump(value), do: Waffle.Ecto.Type.dump(MyAppWeb.FileImage, value)

    # this function should return the HTML related to rendering the customized form field.
    def render_form(_conn, _changeset, form, field, _options) do
      img_div =
        if Map.get(form.data, field) do
          [
            {:safe, ~s(<p><b>Current image:</b> #{Map.get(form.data, field).file_name}</p>)},
            {:safe, ~s(<div class="form-group">)},
            Phoenix.HTML.Tag.img_tag(MyAppWeb.FileImage.url(Map.get(form.data, field)), style: "max-width: 500px;"),
            {:safe, ~s(</div>)}
          ]
        else
          {:safe, ''}
        end

      [
        img_div,
        {:safe, ~s(<div class="form-group">)},
        Phoenix.HTML.Form.label(form, field, "Image"),
        Phoenix.HTML.Form.file_input(form, field,
          class: "form-control",
          name: "#{form.name}[#{field}]"
        ),
        {:safe, ~s(</div>)}
      ]
    end

    # this is how the field should be rendered on the index page
    def render_index(_conn, resource, field, _options) do
      case Map.get(resource, field) do
        nil ->
          ""

        details ->
          filename = details

          [
            Phoenix.HTML.Tag.img_tag(MyAppWeb.FileImage.url(filename))
          ]
      end
    end
  end
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants