waseigo
waseigo
AEAsh Elixir
Created by waseigo on 2/12/2023 in #support
How to Ash-idiomatically get all unique values of an attribute across all records?
App.Api.Item is a resource with an attribute :name. How can I go from the set of all records of Item (App.Api.Item |> App.Api.read!()) to a list of all unique values of the :name attribute in the most idiomatic way?
119 replies
AEAsh Elixir
Created by waseigo on 2/12/2023 in #support
Nested aggregates
In my Product Data Management webapp I'm trying to build with Ash, I have the following resources with attributes and relatioships in App.Api that, combined, construct/define an Item. * Category: has a :code attribute * Family: belongs_to a Category (and also has a :name, etc.) * Variant: belongs_to a Variant, has a :code attribute The idea is that an Item belongs_to a Category and a Variant, and has a calculated attribute (also called :code) resulting from concatenating the Category :code and the Variant :code. This means that Item's :code calculation must "pull" Category's :code through the Variant it belongs to, which belongs to a Family, which belongs to a Category. Item's calculated :code uniquely identifies a record. I have tried the following in a cascade, In lib/app/resources/item.ex:
aggregates do
first :category_code, :variant, :category_code
end
aggregates do
first :category_code, :variant, :category_code
end
In lib/app/resources/variant.ex:
aggregates do
first :category_code, :family, :category_code
end
aggregates do
first :category_code, :family, :category_code
end
In lib/app/resources/family.ex:
aggregates do
first :category_code, :category, :code
end
aggregates do
first :category_code, :category, :code
end
...hoping that App.Api.Item |> App.Api.read! would return records on which I can App.Api.load!(:code). Instead:
** (CaseClauseError) no case clause matching: {:error, [%Ash.Error.Unknown.UnknownError{error: "Must provide field type for first", field: nil, changeset: nil, query: nil, error_context: ["Loading aggregate: :category_code for query: #Ash.Query<resource: App.Api.Variant>"], vars: [], path: [:aggregates], stacktrace: #Stacktrace<>, class: :unknown}]}
** (CaseClauseError) no case clause matching: {:error, [%Ash.Error.Unknown.UnknownError{error: "Must provide field type for first", field: nil, changeset: nil, query: nil, error_context: ["Loading aggregate: :category_code for query: #Ash.Query<resource: App.Api.Variant>"], vars: [], path: [:aggregates], stacktrace: #Stacktrace<>, class: :unknown}]}
What is the Ash-idiomatic way of achieving the goal of being able to access Item's :code?
43 replies
AEAsh Elixir
Created by waseigo on 2/12/2023 in #support
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? 😵‍💫
38 replies
AEAsh Elixir
Created by waseigo on 2/10/2023 in #support
Example of setting a has_one relationship
I would appreciate an example of setting up a has_one relationship. I have the following resources:
App.Api.Item
App.Api.Variant
App.Api.Item
App.Api.Variant
Each resource has an attribute uuid_primary_key :id. I want to establish a 1:1 relationship between the two resources. So, I add the following to App.Api.Item:
relationships do
has_one :variant, App.Api.Variant
end
relationships do
has_one :variant, App.Api.Variant
end
I get the following errors/warnings:
warning: invalid association `variant` in schema App.Api.Item: associated schema App.Api.Variant does not have field `item_id`
lib/app/api/resources/item.ex:1: App.Api.Item (module)

** (EXIT from #PID<0.96.0>) an exception was raised:
** (Spark.Error.DslError) [App.Api.Item]
relationships -> variant:
Relationship `variant` expects destination field `item_id` to be defined on App.Api.Variant
warning: invalid association `variant` in schema App.Api.Item: associated schema App.Api.Variant does not have field `item_id`
lib/app/api/resources/item.ex:1: App.Api.Item (module)

** (EXIT from #PID<0.96.0>) an exception was raised:
** (Spark.Error.DslError) [App.Api.Item]
relationships -> variant:
Relationship `variant` expects destination field `item_id` to be defined on App.Api.Variant
Do I need to define an attribute :item_id in resource App.Api.Variant? If so, what type would it be? Or am I on the wrong path?
10 replies
AEAsh Elixir
Created by waseigo on 2/10/2023 in #support
Can a calculation use attributes of a related resource?
E.g., I have belongs_to :brand, App.Portfolio.Brand and calculate :tagline, :string, expr(brand.name <> " — " <> name) The result:
App.Portfolio.Family
|> Ash.Query.load(:brand)
|> Ash.Query.load(:tagline)
|> App.Portfolio.read!()
** (RuntimeError) Error while building reference: brand.name
App.Portfolio.Family
|> Ash.Query.load(:brand)
|> Ash.Query.load(:tagline)
|> App.Portfolio.read!()
** (RuntimeError) Error while building reference: brand.name
34 replies
AEAsh Elixir
Created by waseigo on 2/9/2023 in #support
Example of :create action with simultaneous assignment of relationship
Perhaps I'm too influenced by Django, but I've been trying to :create a new instance of a resource that belongs_to two other resource types by passing the other resources' .id values as parameters in the map, to no avail. Following the Getting Started guide, I understand how I first create the Ticket resource instance and a Representative resource instance and then use the :assign update action to assign the ticket to the representative. However, how would I go about creating a ticket with the representative already being assigned during the :create action? If this is possible, what would it look like if I were to create a ticket with more than one related resources at once? Or do I first need to create, then update/assign?
9 replies
AEAsh Elixir
Created by waseigo on 2/8/2023 in #support
"No changes detected, so no migrations or snapshots have been created."
Hi all. I am learning how to build a simple CRUD app with Ash, and for some reason mix ash_postgres.generate_migrations fails to pick up any migrations, even after I mix ash_postgres.drop and rm -f priv/repo/migrations/* to start from scratch. Any clues as to why?
21 replies