Module Calculations: Concat example leads to Enum.map_join/3 error (separator argument missing)

I have the following 3 resources:
App.Api.Brand
App.Api.Category
App.Api.Family
App.Api.Brand
App.Api.Category
App.Api.Family
Family belongs_to a Brand and a Category. Brand and Category each have a :name string attribute. Within Family I have two aggregates:
aggregates do
first :brand_name, :brand, :name
first :category_name, :category, :name
end
aggregates do
first :brand_name, :brand, :name
first :category_name, :category, :name
end
...and one calculation for :tagline that should concatenate the two aggregates above.
calculations do
calculate :tagline, :string, {Concat, keys: [:brand_name, :category_name]} do
argument :separator, :string, constraints: [allow_empty?: true, trim?: false]
end
end
calculations do
calculate :tagline, :string, {Concat, keys: [:brand_name, :category_name]} do
argument :separator, :string, constraints: [allow_empty?: true, trim?: false]
end
end
The Concat code is in lib/api/calculations.ex. I have a record f of type App.Api.Family, associated with a Brand and a Category record. In IEx:
iex(36)> f |> App.Api.load(:tagline)
** (FunctionClauseError) no function clause matching in Enum.map_join/3

The following arguments were given to Enum.map_join/3:

# 1
[:brand_name, :name, :category_name]

# 2
nil

# 3
#Function<1.6122725/1 in Concat.calculate/3>

Attempted function clauses (showing 1 out of 1):

def map_join(enumerable, joiner, mapper) when is_binary(joiner)
iex(36)> f |> App.Api.load(:tagline)
** (FunctionClauseError) no function clause matching in Enum.map_join/3

The following arguments were given to Enum.map_join/3:

# 1
[:brand_name, :name, :category_name]

# 2
nil

# 3
#Function<1.6122725/1 in Concat.calculate/3>

Attempted function clauses (showing 1 out of 1):

def map_join(enumerable, joiner, mapper) when is_binary(joiner)
Alternatively:
iex(36)> f |> App.Api.load(tagline: [separator: " - "])
** (Protocol.UndefinedError) protocol String.Chars not implemented for #Ash.NotLoaded<:aggregate> of type Ash.NotLoaded (a struct). This protocol is implemented for the following type(s): Ash.CiString, Atom, BitString, Date, DateTime, Decimal, Float, Hex.Solver.Assignment, Hex.Solver.Constraints.Empty, Hex.Solver.Constraints.Range, Hex.Solver.Constraints.Union, Hex.Solver.Incompatibility, Hex.Solver.PackageRange, Hex.Solver.Term, Integer, List, NaiveDateTime, Phoenix.LiveComponent.CID, Postgrex.Copy, Postgrex.Query, Time, URI, Version, Version.Requirement
iex(36)> f |> App.Api.load(tagline: [separator: " - "])
** (Protocol.UndefinedError) protocol String.Chars not implemented for #Ash.NotLoaded<:aggregate> of type Ash.NotLoaded (a struct). This protocol is implemented for the following type(s): Ash.CiString, Atom, BitString, Date, DateTime, Decimal, Float, Hex.Solver.Assignment, Hex.Solver.Constraints.Empty, Hex.Solver.Constraints.Range, Hex.Solver.Constraints.Union, Hex.Solver.Incompatibility, Hex.Solver.PackageRange, Hex.Solver.Term, Integer, List, NaiveDateTime, Phoenix.LiveComponent.CID, Postgrex.Copy, Postgrex.Query, Time, URI, Version, Version.Requirement
What am I doing wrong? šŸ˜µā€šŸ’«
21 Replies
waseigo
waseigoOP•3y ago
@Zach Daniel, any ideas?
ZachDaniel
ZachDaniel•3y ago
Sorry, forgot to come back to this šŸ¤” lemme take a look at the builtin calculation fixed in main
waseigo
waseigoOP•3y ago
Do you mean it was a bug that is now fixed in the trunk?
ZachDaniel
ZachDaniel•3y ago
Yeah I just pushed a fix
waseigo
waseigoOP•3y ago
aha, thank you!
ZachDaniel
ZachDaniel•3y ago
You can use main, or you can make the dependencies explicit via:
calculate :tagline, :string, {Concat, keys: [:brand_name, :category_name]} do
argument :separator, :string, constraints: [allow_empty?: true, trim?: false]
load [:brand_name, :category_name] # <- normally you want the calculation to do this, but this should fix your bug w/o needing to use the main branch if you don't want to
end
calculate :tagline, :string, {Concat, keys: [:brand_name, :category_name]} do
argument :separator, :string, constraints: [allow_empty?: true, trim?: false]
load [:brand_name, :category_name] # <- normally you want the calculation to do this, but this should fix your bug w/o needing to use the main branch if you don't want to
end
waseigo
waseigoOP•3y ago
I looked at github and saw that the fix is for lib/ash/resource/calculation/concat.ex -- does this mean that Ash already provides the Concat module, and that my lib/api/calculations.ex is not required?
waseigo
waseigoOP•3y ago
GitHub
ash/builtins.ex at main Ā· ash-project/ash
A declarative and extensible framework for building Elixir applications. - ash/builtins.ex at main Ā· ash-project/ash
ZachDaniel
ZachDaniel•3y ago
oh I thought you were using it šŸ˜†
waseigo
waseigoOP•3y ago
I guess my calculations.ex was never loaded because anyway I never imported it No, I just copied the Concat module from the Getting Started guide
ZachDaniel
ZachDaniel•3y ago
Ohhh okay well I should fix that too then But yes, we do have a built in concat that I just fixed Just pushed up a fix for that guide, the docs will be fixed on the next release
waseigo
waseigoOP•3y ago
thx! Indeed, I just saw that actually I was using the guide's Concat
ZachDaniel
ZachDaniel•3y ago
In your custom concat you can add def load/3 and return the fields required if you'd rather do that than use ash's main branch, your call šŸ˜„
waseigo
waseigoOP•3y ago
I'm trying this out. My calculations.ex file contains defmodule Concat ... from the latest commit https://github.com/ash-project/ash/commit/2c8b07fd0b9a7b3b0bb8a5886f1255c6ef91ba33 (so it now includes the load/3 part). My resource contains a calculation:
calculate :tagline, :string, {Concat, keys: [:brand_name, :category_name]} do
argument :separator, :string, constraints: [allow_empty?: true, trim?: false]
load [:brand_name, :category_name]
end
calculate :tagline, :string, {Concat, keys: [:brand_name, :category_name]} do
argument :separator, :string, constraints: [allow_empty?: true, trim?: false]
load [:brand_name, :category_name]
end
In IEx (where f is a record of type App.Api.Family:
iex(18)> f |> Api.App.load!(:tagline)
** (FunctionClauseError) no function clause matching in Enum.map_join/3

The following arguments were given to Enum.map_join/3:

# 1
[:brand_name, :category_name]

# 2
nil

# 3
#Function<1.65930104/1 in Concat.calculate/3>

Attempted function clauses (showing 1 out of 1):

def map_join(enumerable, joiner, mapper) when is_binary(joiner)

(elixir 1.14.0) lib/enum.ex:1753: Enum.map_join/3
(elixir 1.14.0) lib/enum.ex:1658: Enum."-map/2-lists^map/1-0-"/2
(ash 2.6.0) lib/ash/actions/read.ex:1663: anonymous fn/7 in Ash.Actions.Read.calculation_request/11
(ash 2.6.0) lib/ash/engine/engine.ex:468: anonymous fn/2 in Ash.Engine.run_iteration/1
(ash 2.6.0) lib/ash/engine/engine.ex:623: Ash.Engine.start_pending_tasks/1
(ash 2.6.0) lib/ash/engine/engine.ex:273: Ash.Engine.run_to_completion/1
(ash 2.6.0) lib/ash/engine/engine.ex:202: Ash.Engine.do_run/2
(ash 2.6.0) lib/ash/engine/engine.ex:141: Ash.Engine.run/2
(ash 2.6.0) lib/ash/actions/read.ex:170: Ash.Actions.Read.do_run/3
(ash 2.6.0) lib/ash/actions/read.ex:90: Ash.Actions.Read.run/3
(ash 2.6.0) lib/ash/api/api.ex:947: Ash.Api.load/4
(ash 2.6.0) lib/ash/api/api.ex:921: Ash.Api.load!/4
iex(18)> f |> Api.App.load!(:tagline)
** (FunctionClauseError) no function clause matching in Enum.map_join/3

The following arguments were given to Enum.map_join/3:

# 1
[:brand_name, :category_name]

# 2
nil

# 3
#Function<1.65930104/1 in Concat.calculate/3>

Attempted function clauses (showing 1 out of 1):

def map_join(enumerable, joiner, mapper) when is_binary(joiner)

(elixir 1.14.0) lib/enum.ex:1753: Enum.map_join/3
(elixir 1.14.0) lib/enum.ex:1658: Enum."-map/2-lists^map/1-0-"/2
(ash 2.6.0) lib/ash/actions/read.ex:1663: anonymous fn/7 in Ash.Actions.Read.calculation_request/11
(ash 2.6.0) lib/ash/engine/engine.ex:468: anonymous fn/2 in Ash.Engine.run_iteration/1
(ash 2.6.0) lib/ash/engine/engine.ex:623: Ash.Engine.start_pending_tasks/1
(ash 2.6.0) lib/ash/engine/engine.ex:273: Ash.Engine.run_to_completion/1
(ash 2.6.0) lib/ash/engine/engine.ex:202: Ash.Engine.do_run/2
(ash 2.6.0) lib/ash/engine/engine.ex:141: Ash.Engine.run/2
(ash 2.6.0) lib/ash/actions/read.ex:170: Ash.Actions.Read.do_run/3
(ash 2.6.0) lib/ash/actions/read.ex:90: Ash.Actions.Read.run/3
(ash 2.6.0) lib/ash/api/api.ex:947: Ash.Api.load/4
(ash 2.6.0) lib/ash/api/api.ex:921: Ash.Api.load!/4
ZachDaniel
ZachDaniel•3y ago
oh šŸ¤” I think your separator is nil actually
argument :separator, :string, constraints: [allow_empty?: true, trim?: false], default: ""
argument :separator, :string, constraints: [allow_empty?: true, trim?: false], default: ""
waseigo
waseigoOP•3y ago
I thought this was allowed due to allow_empty?: true
ZachDaniel
ZachDaniel•3y ago
allow_empty? prevents the type from turning "" into nil but if no separator is supplied, it will be nil
waseigo
waseigoOP•3y ago
Ah so I should always pass a separator?
ZachDaniel
ZachDaniel•3y ago
Or set default: "" on the separator argument and allow_nil?: false
argument :separator, :string do
constraints [allow_empty?: true, trim?: false]
allow_nil? false
default ""
end
argument :separator, :string do
constraints [allow_empty?: true, trim?: false]
allow_nil? false
default ""
end
is probably what you want I'll update the example
waseigo
waseigoOP•3y ago
Excellent, works now! 🄳
ZachDaniel
ZachDaniel•3y ago
🄳

Did you find this page helpful?