morfertaw
morfertaw
Explore posts from servers
AEAsh Elixir
Created by morfertaw on 9/25/2023 in #support
Attributes on `many_to_many` join/through resources.
Considering the following resources.
defmodule Formula do
use Ash.Resource,
data_layer: Ash.DataLayer.Ets

attributes do
uuid_primary_key :id

create_timestamp :created_at
update_timestamp :updated_at
end

actions do
defaults([:create, :read, :update, :destroy])

create :define do

argument :formula, {:array, :map}

change manage_relationship(:formula, :reactants,
on_lookup: :relate,
on_no_match: :create,
on_match: :update,
on_missing: :unrelate
)
end

end

relationships do
many_to_many :reactants, Reactant do
through FormulaReactant
source_attribute_on_join_resource :formula_id
destination_attribute_on_join_resource :reactant_id
end
end

end
defmodule FormulaReactant do
use Ash.Resource,
data_layer: Ash.DataLayer.Ets
attributes do
attribute :quantity, :decimal do
allow_nil? false
constraints [min: 0.0]
end

attribute :unit, :string do
allow_nil? false
constraints trim?: true, allow_empty?: false, min_length: 1
end
end

actions do
defaults [:create, :read, :update, :destroy]

end

relationships do
belongs_to :formula, Formula, primary_key?: true, allow_nil?: false
belongs_to :reactant, Reactant, primary_key?: true, allow_nil?: false
end

end
defmodule Reactant do
use Ash.Resource,
data_layer: Ash.DataLayer.Ets

attributes do
uuid_primary_key :id

attribute :name, :string

create_timestamp :created_at
update_timestamp :updated_at
end

actions do
defaults([:create, :read, :update, :destroy])
end
end
defmodule Formula do
use Ash.Resource,
data_layer: Ash.DataLayer.Ets

attributes do
uuid_primary_key :id

create_timestamp :created_at
update_timestamp :updated_at
end

actions do
defaults([:create, :read, :update, :destroy])

create :define do

argument :formula, {:array, :map}

change manage_relationship(:formula, :reactants,
on_lookup: :relate,
on_no_match: :create,
on_match: :update,
on_missing: :unrelate
)
end

end

relationships do
many_to_many :reactants, Reactant do
through FormulaReactant
source_attribute_on_join_resource :formula_id
destination_attribute_on_join_resource :reactant_id
end
end

end
defmodule FormulaReactant do
use Ash.Resource,
data_layer: Ash.DataLayer.Ets
attributes do
attribute :quantity, :decimal do
allow_nil? false
constraints [min: 0.0]
end

attribute :unit, :string do
allow_nil? false
constraints trim?: true, allow_empty?: false, min_length: 1
end
end

actions do
defaults [:create, :read, :update, :destroy]

end

relationships do
belongs_to :formula, Formula, primary_key?: true, allow_nil?: false
belongs_to :reactant, Reactant, primary_key?: true, allow_nil?: false
end

end
defmodule Reactant do
use Ash.Resource,
data_layer: Ash.DataLayer.Ets

attributes do
uuid_primary_key :id

attribute :name, :string

create_timestamp :created_at
update_timestamp :updated_at
end

actions do
defaults([:create, :read, :update, :destroy])
end
end
The following create attempt fails.
Formula
|>Ash.Changeset.for_create(:define, %{formula: [%{name: "pinata"}]})
|>Api.create()
Formula
|>Ash.Changeset.for_create(:define, %{formula: [%{name: "pinata"}]})
|>Api.create()
This is because attributes :quantity and :unit are not being set on FormulaReactant. How would a create action on Formula be setup to set both attributes?
4 replies
AEAsh Elixir
Created by morfertaw on 9/23/2023 in #support
Identity on `attribute :some_attribute, {:array , EmbeddedResource}`
So if I have the following resource.
defmodule Resource do
...
actions do
defaults [:create, :read, :update, :destroy]
end

attributes do
...
uuid_primary_key :id

attribute :some_attribute, {:array, EmbeddedResource}
...
end

identities do
...
identity :unique_some_attribute, [:some_attribute]
...
end
end
defmodule Resource do
...
actions do
defaults [:create, :read, :update, :destroy]
end

attributes do
...
uuid_primary_key :id

attribute :some_attribute, {:array, EmbeddedResource}
...
end

identities do
...
identity :unique_some_attribute, [:some_attribute]
...
end
end
And the following embedded resource.
defmodule EmbeddedResource do
use Ash.Resource,
data_layer: :embedded

attributes do
attribute :key, :string do
constraints trim?: true
allow_nil? false
end

attribute :value, :string do
constraints trim?: true
allow_nil? false
end
end

identities do
identity :unique_key, [:key]
end
end
defmodule EmbeddedResource do
use Ash.Resource,
data_layer: :embedded

attributes do
attribute :key, :string do
constraints trim?: true
allow_nil? false
end

attribute :value, :string do
constraints trim?: true
allow_nil? false
end
end

identities do
identity :unique_key, [:key]
end
end
If I do the following changeset twice.
Resource
|> Ash.Changeset.for_create(:create, %{})
|> Api.create()
Resource
|> Ash.Changeset.for_create(:create, %{})
|> Api.create()
It succeeds once and then fails a second time, which is as expected. The :unique_some_attribute identity prevents two records with some_attribute: nil. However If the next changeset is repeated twice.
Resource
|> Ash.Changeset.for_create(:create, %{some_attribute: [%{key: "color", value: "ash"}]})
|> Api.create()
Resource
|> Ash.Changeset.for_create(:create, %{some_attribute: [%{key: "color", value: "ash"}]})
|> Api.create()
Two resources with the same :some_attribute field are created. I had hoped this to be prevented by the :unique_some_attribute identity. The resulting records look something like the following.
%Resource<
...
id: "uuid_1",
some_attribute: [
%EmbeddedResource<
...
autogenerated_id: "autogenerated_uuid_1",
key: "color",
value: "ash"
>
]
>

%Resource<
...
id: "uuid_2",
some_attribute: [
%EmbeddedResource<
...
autogenerated_id: "autogenerated_uuid_2",
key: "color",
value: "ash"
>
]
>
%Resource<
...
id: "uuid_1",
some_attribute: [
%EmbeddedResource<
...
autogenerated_id: "autogenerated_uuid_1",
key: "color",
value: "ash"
>
]
>

%Resource<
...
id: "uuid_2",
some_attribute: [
%EmbeddedResource<
...
autogenerated_id: "autogenerated_uuid_2",
key: "color",
value: "ash"
>
]
>
I assume the autogenerated_id fields being unique is preventing the desired outcome. Would that work and can they be disabled?
8 replies
AEAsh Elixir
Created by morfertaw on 9/21/2023 in #support
Understanding "manage_relationship"
So I have a many_to_many relationship between the following resources. Reactant
defmodule Flame.App.Resources.Reactant do
...
attributes do
...
attribute :identity, :string do
allow_nil? false
constraints trim?: false, max_length: 20, min_length: 4, allow_empty?: false
end
...
end

actions do
defaults [:create, :read, :update, :destroy]

create :define do
upsert? true

argument :source_definitions, {:array, :map}

change manage_relationship(:source_definitions, :sources,
on_lookup: :relate,
on_no_match: :create,
on_match: :update,
on_missing: :unrelate
)
end

update :redefine do
argument :source_definitions, {:array, :map}

change manage_relationship(:source_definitions, :sources,
on_lookup: :relate,
on_match: :update,
on_no_match: :create,
on_missing: :unrelate
)
end
end

relationships do
many_to_many :sources, Flame.App.Resources.Source do
through Flame.App.Resources.ReactantSource
source_attribute_on_join_resource :reactant_id
destination_attribute_on_join_resource :source_id
end
end
...
end
defmodule Flame.App.Resources.Reactant do
...
attributes do
...
attribute :identity, :string do
allow_nil? false
constraints trim?: false, max_length: 20, min_length: 4, allow_empty?: false
end
...
end

actions do
defaults [:create, :read, :update, :destroy]

create :define do
upsert? true

argument :source_definitions, {:array, :map}

change manage_relationship(:source_definitions, :sources,
on_lookup: :relate,
on_no_match: :create,
on_match: :update,
on_missing: :unrelate
)
end

update :redefine do
argument :source_definitions, {:array, :map}

change manage_relationship(:source_definitions, :sources,
on_lookup: :relate,
on_match: :update,
on_no_match: :create,
on_missing: :unrelate
)
end
end

relationships do
many_to_many :sources, Flame.App.Resources.Source do
through Flame.App.Resources.ReactantSource
source_attribute_on_join_resource :reactant_id
destination_attribute_on_join_resource :source_id
end
end
...
end
Source
defmodule Flame.App.Resources.Source do
...
attributes do
uuid_primary_key :id

attribute :description, :string do
allow_nil? false
end

create_timestamp :created_at
update_timestamp :updated_at
end

actions do
defaults [:create, :read, :update, :destroy]
end

identities do
identity :unique_description, [:description]
end
...
end
defmodule Flame.App.Resources.Source do
...
attributes do
uuid_primary_key :id

attribute :description, :string do
allow_nil? false
end

create_timestamp :created_at
update_timestamp :updated_at
end

actions do
defaults [:create, :read, :update, :destroy]
end

identities do
identity :unique_description, [:description]
end
...
end
6 replies
AEAsh Elixir
Created by morfertaw on 9/18/2023 in #support
AshAuthenticationPhoenix controlling generated routes
I setup my phoenix app using the getting started with ash authentication guide. If I understand correctly adding routes works as follows:
# Added for macros and functions
use AshAuthentication.Phoenix.Router
pipeline :browser do
# ...
# Plug to load session from cookie
plug :load_from_session
end
scope "/", ExampleWeb do
pipe_through :browser

get "/", PageController, :home

# Adds a liveview at /sign-in based on Example.Accounts.User
sign_in_route()
# Adds a sign out get route for signing out uses logic defined in AuthController
sign_out_route AuthController
# Adds a sign in and register route that the /sign-in liveview uses to login and/or register a user, also uses logic defined in AuthController plus data from Example.Accounts.User resource?
auth_routes_for Example.Accounts.User, to: AuthController
# Unused reset route
reset_route []
end
# Added for macros and functions
use AshAuthentication.Phoenix.Router
pipeline :browser do
# ...
# Plug to load session from cookie
plug :load_from_session
end
scope "/", ExampleWeb do
pipe_through :browser

get "/", PageController, :home

# Adds a liveview at /sign-in based on Example.Accounts.User
sign_in_route()
# Adds a sign out get route for signing out uses logic defined in AuthController
sign_out_route AuthController
# Adds a sign in and register route that the /sign-in liveview uses to login and/or register a user, also uses logic defined in AuthController plus data from Example.Accounts.User resource?
auth_routes_for Example.Accounts.User, to: AuthController
# Unused reset route
reset_route []
end
Would it be possible to remove the register route from auth_routes_for generation? I would like more control over how new users can register. Can the register page also be removed from the /sign-in liveview? I want to setup registration separately
8 replies