Search code examples
elixirphoenix-framework

Calculating the value of virtual fields with DecorateX


I am trying to use the Decoratex library to populate some virtual fields in my User model but I am unable to avoid the following error:

no function clause matching in Decoratex.decorate/1
  This functions just call the configured function to each field passing
  the model structure it self and it store the result in the virtual field.
  """
  @spec decorate(nil) :: nil
  def decorate(nil), do: nil
  @spec decorate(struct) :: struct
  def decorate(%module{} = element) do
    module.__decorations__ |> Enum.reduce(element, &do_decorate/2)
  end

As an example I am trying to add a user_age key value to the model struct. The model is:

defmodule InformAPI.UTL.User_take_list do
  use Ecto.Schema
  use Decoratex.Schema
  # alias InformAPI.UTL.UTLHelpers


  import Ecto.Changeset

  decorations do
    decorate_field :user_age, :integer,  &UTLHelpers.calculate_user_age/1

  end


  schema "user_take_lists" do
    field :date_of_birth, :date
    field :first_name, :string
    field :last_name, :string
    field :user_id, :string
    field :needs_senior_review, :boolean
    field :stable, :boolean
    field :list_date, :date
    belongs_to :consultant, InformAPI.UTL.User_take_list_consultant

timestamps()
decorations()
end


  def changeset(user_take_list, attrs) do
    user_take_list
    |> cast(attrs, [:user_id, :first_name, :date_of_birth, :last_name, :needs_senior_review, :list_date, :stable, :consultant_id])
    |> validate_required([:user_id, :first_name, :date_of_birth, :last_name, :needs_senior_review, :consultant_id, :stable])
  end
end

The calculate_user_age function is currently configured to just return a result:

defmodule InformAPI.UTL.UTLHelpers do
  alias InformAPI.UTL.User_take_list

  def calculate_user_age(struct) do
    x = [1,2,3,4,5,6]
    Enum.count(x)
  end
end

And the required Decoratex.decorate is called on the Index function of my controller:

defmodule InformAPIWeb.User_take_listController do
  import Ecto.Query, warn: false
  use InformAPIWeb, :controller
  use Filterable.Phoenix.Controller

  alias InformAPI.UTL
  alias InformAPI.UTL.User_take_list
  alias InformAPI.Repo

  action_fallback InformAPIWeb.FallbackController

  filterable do
    filter review(query, value, _conn) do
      IO.inspect(query)
      query |> where(needs_senior_review: ^value)
    end

    @options param: :dateFrom, cast: :date 
      filter dateFrom(query, value, _conn) do
        query |> where([d], d.list_date >= ^value)
    end

    @options param: :dateTo, cast: :date 
      filter dateTo(query, value, _conn) do
        query |> where([d], d.list_date <= ^value)
      end

    @options param: :conid, cast: :integer
        filter conID(query, value, _conn) do
          query  |> where([d], d.consultant_id <= ^value)         
    end

  end



 def index(conn, params) do
    with {:ok, query, filter_values} <- apply_filters(User_take_list, conn),
         user_take_lists                       <- Repo.all(query) |>Decoratex.decorate,

     do: render(conn, "index.json", user_take_lists: user_take_lists, meta: filter_values)
  end


  def show(conn, %{"id" => id}) do
    user_take_list = UTL.get_user_take_list!(id)
    render(conn, "show.json", user_take_list: user_take_list)
  end

end

I am very new to Elixir and I cannot hunt down the nature of this error. If I inspect the struct I can see a user_age field with a nil value but I don't know if the problem is caused by my own helper function or by the way I am using DecorateX.decorate.

As requested in the comments, the complete error with stacktrace:

[info] GET /api/user_take_list
[debug] Processing with InformAPIWeb.User_take_listController.index/2
  Parameters: %{}
  Pipelines: [:api]
[debug] QUERY OK source="user_take_lists" db=0.0ms decode=16.0ms
SELECT m0."id", m0."date_of_birth", m0."first_name", m0."last_name", m0."user_id", m0."needs_senior_review", m0."stable", m0."list_date", m0."consultant_id", m0."inserted_at", m0."updated_at" FROM "user_take_lists" AS m0 []
[info] Sent 500 in 141ms
[error] #PID<0.390.0> running InformAPIWeb.Endpoint (cowboy_protocol) terminated
Server: localhost:4000 (http)
Request: GET /api/user_take_list
** (exit) an exception was raised:
    ** (FunctionClauseError) no function clause matching in Decoratex.decorate/1
        (decoratex) lib/decoratex.ex:145: Decoratex.decorate([%InformAPI.UTL.User_take_list{__meta__: #Ecto.Schema.Metadata<:loaded, "user_take_lists">, consultant: #Ecto.Association.NotLoaded<association :consultant is not loaded>, consultant_id: 3, date_of_birth: ~D[1987-02-13], first_name: "Hal", id: 1, inserted_at: ~N[2018-09-19 10:58:49.194000], last_name: "Bradley", list_date: ~D[2018-09-13], needs_senior_review: true, user_age: nil, user_id: "D00001", stable: true, updated_at: ~N[2018-09-19 10:58:49.194000]}, %InformAPI.UTL.User_take_list{__meta__: #Ecto.Schema.Metadata<:loaded, "user_take_lists">, consultant: #Ecto.Association.NotLoaded<association :consultant is not loaded>, consultant_id: 3, date_of_birth: ~D[1987-02-13], first_name: "Isobel", id: 2, inserted_at: ~N[2018-09-19 10:58:49.211000], last_name: "Rogers", list_date: ~D[2018-09-13], needs_senior_review: false, user_age: nil, user_id: "D00001", stable: true, updated_at: ~N[2018-09-19 10:58:49.211000]}, %InformAPI.UTL.User_take_list{__meta__: #Ecto.Schema.Metadata<:loaded, "user_take_lists">, consultant: #Ecto.Association.NotLoaded<association :consultant is not loaded>, consultant_id: 2, date_of_birth: ~D[1987-02-13], first_name: "Oliver", id: 3, inserted_at: ~N[2018-09-19 10:58:49.220000], last_name: "Barton", list_date: ~D[2018-09-13], needs_senior_review: true, user_age: nil, user_id: "D00001", stable: true, updated_at: ~N[2018-09-19 10:58:49.220000]}])
        (inform_api) lib/inform_api_web/controllers/user_take_list_controller.ex:37: InformAPIWeb.User_take_listController.index/2
        (inform_api) lib/inform_api_web/controllers/user_take_list_controller.ex:1: InformAPIWeb.User_take_listController.action/2
        (inform_api) lib/inform_api_web/controllers/user_take_list_controller.ex:1: InformAPIWeb.User_take_listController.phoenix_controller_pipeline/2
        (inform_api) lib/inform_api_web/endpoint.ex:1: InformAPIWeb.Endpoint.instrument/4
        (phoenix) lib/phoenix/router.ex:278: Phoenix.Router.__call__/1
        (inform_api) lib/inform_api_web/endpoint.ex:1: InformAPIWeb.Endpoint.plug_builder_call/2
        (inform_api) lib/plug/debugger.ex:122: InformAPIWeb.Endpoint."call (overridable 3)"/2
        (inform_api) lib/inform_api_web/endpoint.ex:1: InformAPIWeb.Endpoint.call/2
        (plug) lib/plug/adapters/cowboy/handler.ex:16: Plug.Adapters.Cowboy.Handler.upgrade/4
        (cowboy) c:/Users/dbradley/Projects/inform-api/deps/cowboy/src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4

Solution

  • According to the documentation of decoratex, Decoratex.decorate/1 accepts a single item, not a list.

    Changing

    Repo.all(query) |> Decoratex.decorate
    

    to

    Repo.all(query) |> Enum.map(&Decoratex.decorate/1)
    

    should work.