tommasop#2001
tommasop#2001
Explore posts from servers
AEAsh Elixir
Created by tommasop#2001 on 6/27/2023 in #support
How can I retrieve attributes from a resource record
Which is the preferred way to extract attributes from an Ash record?
record = App.Model |> Ash.Query.for_read(:last) |> App.read_one!

# how to get attributes from it?
record.attributes ????
record = App.Model |> Ash.Query.for_read(:last) |> App.read_one!

# how to get attributes from it?
record.attributes ????
Must it be done through Ash.Resource.Info or through Ash.Changeset or it should not be done alltogether 🙂 The use case is that I create a record from an external api call and in the same flow I must send a sync to another external api. I must use the record attributes instead of the received attributes because sometimes attributes coming from the first api are missing. I need something like Ash.Changeset data or attributes but for the record.
5 replies
AEAsh Elixir
Created by tommasop#2001 on 5/24/2023 in #support
BulkCreate upsert not working
No description
163 replies
AEAsh Elixir
Created by tommasop#2001 on 5/22/2023 in #support
Bulk create action Changeset Error
I'm using bulk create action with upsert and I'm tryong to test that not valid resources will not be created.
resources =
input.resources_attributes
|> MmsBiztalk.bulk_create!(
input.resource,
:create,
upsert?: true,
upsert_identity: :unique_esolver_id,
# for bulk actions upsert_fields must be specified
upsert_fields: hd(input.resources_attributes) |> Map.keys(),
return_stream?: false,
return_records?: true
)
|> Stream.map(fn
{:ok, created_resource} ->
created_resource |> input.resource.to_status!([:syncing, :synced])

_ ->
nil
end)
|> Enum.to_list()
resources =
input.resources_attributes
|> MmsBiztalk.bulk_create!(
input.resource,
:create,
upsert?: true,
upsert_identity: :unique_esolver_id,
# for bulk actions upsert_fields must be specified
upsert_fields: hd(input.resources_attributes) |> Map.keys(),
return_stream?: false,
return_records?: true
)
|> Stream.map(fn
{:ok, created_resource} ->
created_resource |> input.resource.to_status!([:syncing, :synced])

_ ->
nil
end)
|> Enum.to_list()
Using this code with a purposedly wrong changeset gives me this error:
** (FunctionClauseError) no function clause matching in anonymous fn/2 in Ash.Actions.Create.Bulk.do_run/5
(ash 2.9.10) anonymous fn(#Ash.Changeset<api: MmsBiztalk, action_type: :create, action: :create, attributes: %{...}, errors: [%Ash.Error.Changes.Required{field: :customer_id, type: :argument, resource: MmsBiztalk.InvoiceData, changeset: nil, query: nil, error_context: [], vars: [], path: [], stacktrace: #Stacktrace<>, class: :invalid}], data: #MmsBiztalk.InvoiceData<customer: #Ash.NotLoaded<:relationship>, data_in: #Ash.NotLoaded<:relationship>, __meta__: #Ecto.Schema.Metadata<:built, ...>, context: %{bulk_create: %{index: 0}}, valid?: false>, %{batch: [], count: 0, must_return_records?: false}) in Ash.Actions.Create.Bulk.do_run/5
** (FunctionClauseError) no function clause matching in anonymous fn/2 in Ash.Actions.Create.Bulk.do_run/5
(ash 2.9.10) anonymous fn(#Ash.Changeset<api: MmsBiztalk, action_type: :create, action: :create, attributes: %{...}, errors: [%Ash.Error.Changes.Required{field: :customer_id, type: :argument, resource: MmsBiztalk.InvoiceData, changeset: nil, query: nil, error_context: [], vars: [], path: [], stacktrace: #Stacktrace<>, class: :invalid}], data: #MmsBiztalk.InvoiceData<customer: #Ash.NotLoaded<:relationship>, data_in: #Ash.NotLoaded<:relationship>, __meta__: #Ecto.Schema.Metadata<:built, ...>, context: %{bulk_create: %{index: 0}}, valid?: false>, %{batch: [], count: 0, must_return_records?: false}) in Ash.Actions.Create.Bulk.do_run/5
I would expect errors to be "nilled"
59 replies
AEAsh Elixir
Created by tommasop#2001 on 4/27/2023 in #support
If I set an argument default in a Flow I have it `nil` in a custom step
defmodule MmsBiztalk.Flows.Rollback do
use Ash.Flow

flow do
api MmsBiztalk

argument :resource, :string do
allow_nil? false
end

argument :step, :integer do
default 1
end

argument :resource_id, :integer

returns :get_last_data_in_resources
end

steps do
custom :get_last_data_in_resources, MmsBiztalk.Flows.Steps.LastDataInResources do
input %{resource: arg(:resource), resource_id: arg(:resource_id)}
end

transaction :back_in_time, MmsBiztalk.DataIn do
map :cycle_resources_changes, result(:get_last_data_in_resources) do
custom :rollback_resource, MmsBiztalk.Flows.Steps.RollbackResource do
input %{
resource: arg(:resource),
change: element(:cycle_resources_changes),
step: arg(:step)
}
end
end
end
end
end
defmodule MmsBiztalk.Flows.Rollback do
use Ash.Flow

flow do
api MmsBiztalk

argument :resource, :string do
allow_nil? false
end

argument :step, :integer do
default 1
end

argument :resource_id, :integer

returns :get_last_data_in_resources
end

steps do
custom :get_last_data_in_resources, MmsBiztalk.Flows.Steps.LastDataInResources do
input %{resource: arg(:resource), resource_id: arg(:resource_id)}
end

transaction :back_in_time, MmsBiztalk.DataIn do
map :cycle_resources_changes, result(:get_last_data_in_resources) do
custom :rollback_resource, MmsBiztalk.Flows.Steps.RollbackResource do
input %{
resource: arg(:resource),
change: element(:cycle_resources_changes),
step: arg(:step)
}
end
end
end
end
end
in MmsBiztalk.Flows.Steps.RollbackResource the value of input.step is nil.
17 replies
AEAsh Elixir
Created by tommasop#2001 on 4/12/2023 in #support
Rollback record with Carbonite
I'm trying to achieve the title result. The problem is that trying to set some fields (like foreign keys and timestamps) back to their previous values will mark the changeset as invalid. I'm using force_change_attributes to try to overcome the problem but it is not woking. Is there a way to force the changeset to accept all attributes changes without validating them? Or is there a better way to achieve what I'm trying to achieve?
customer_revert =
Changeset.new(customer_update_two)
|> Changeset.force_change_attributes(data_changes)
|> MmsBiztalk.update!()
customer_revert =
Changeset.new(customer_update_two)
|> Changeset.force_change_attributes(data_changes)
|> MmsBiztalk.update!()
** (Ash.Error.Invalid) Input Invalid

* Invalid value provided for updated_at: cannot be changed.

~U[2023-04-12 07:43:32.314706Z]

(ash 2.6.29) lib/ash/changeset/changeset.ex:1037: anonymous fn/2 in Ash.Changeset.validate_attributes_accepted/2
.....
* Invalid value provided for data_in_id: cannot be changed.

"8b4951f6-055c-4d9d-a498-8e2eeeb06e74"

(ash 2.6.29) lib/ash/changeset/changeset.ex:1037: anonymous fn/2 in Ash.Changeset.validate_attributes_accepted/2
....
code: |> MmsBiztalk.update!()
** (Ash.Error.Invalid) Input Invalid

* Invalid value provided for updated_at: cannot be changed.

~U[2023-04-12 07:43:32.314706Z]

(ash 2.6.29) lib/ash/changeset/changeset.ex:1037: anonymous fn/2 in Ash.Changeset.validate_attributes_accepted/2
.....
* Invalid value provided for data_in_id: cannot be changed.

"8b4951f6-055c-4d9d-a498-8e2eeeb06e74"

(ash 2.6.29) lib/ash/changeset/changeset.ex:1037: anonymous fn/2 in Ash.Changeset.validate_attributes_accepted/2
....
code: |> MmsBiztalk.update!()
5 replies
AEAsh Elixir
Created by tommasop#2001 on 4/3/2023 in #support
Nested flows steps possibly returns differently if using `apply` ?
Sorry if I bother you again on flow issues. This step:
def run(input, _opts, _context) do
Logger.info("Creating resource #{input.resource}")

created_resource =
input.resource
|> Changeset.for_create(
:create,
input.attributes |> Map.merge(additional_attributes(input))
)
|> MmsBiztalk.create()

case created_resource do
{:ok, resource_record} ->
resource_record |> input.resource.to_syncing!() |> input.resource.to_synced()

# NOTE this skips the invalid records allowing all the valid ones to
# be saved, one error in the flow will rollback everything
{:error, error} ->
Logger.warn(error)
{:ok, nil}
end
end
def run(input, _opts, _context) do
Logger.info("Creating resource #{input.resource}")

created_resource =
input.resource
|> Changeset.for_create(
:create,
input.attributes |> Map.merge(additional_attributes(input))
)
|> MmsBiztalk.create()

case created_resource do
{:ok, resource_record} ->
resource_record |> input.resource.to_syncing!() |> input.resource.to_synced()

# NOTE this skips the invalid records allowing all the valid ones to
# be saved, one error in the flow will rollback everything
{:error, error} ->
Logger.warn(error)
{:ok, nil}
end
end
Correctly returns {:ok, result} with updated status on resource_record While this:
def run(input, _opts, _context) do
Logger.info("Updating resource #{input.resource}")

resource_to_update =
input.resource
|> Ash.Query.for_read(String.to_atom("by_#{input.update_key}"), %{
input.update_key => input.attributes[input.update_key]
})
|> MmsBiztalk.read!()
|> List.first()

updated_resource =
resource_to_update
|> Changeset.for_update(
:update,
input.attributes
)
|> MmsBiztalk.update()

case updated_resource do
{:ok, resource_record} ->
transitioned_status_name = "to_#{input.data_type}_synced" |> String.to_atom()
apply(input.resource, transitioned_status_name, [resource_record])

# NOTE this skips the invalid records allowing all the valid ones to
# be saved, one error in the flow will rollback everything
{:error, error} ->
Logger.warn(error)
{:ok, nil}
end
end
def run(input, _opts, _context) do
Logger.info("Updating resource #{input.resource}")

resource_to_update =
input.resource
|> Ash.Query.for_read(String.to_atom("by_#{input.update_key}"), %{
input.update_key => input.attributes[input.update_key]
})
|> MmsBiztalk.read!()
|> List.first()

updated_resource =
resource_to_update
|> Changeset.for_update(
:update,
input.attributes
)
|> MmsBiztalk.update()

case updated_resource do
{:ok, resource_record} ->
transitioned_status_name = "to_#{input.data_type}_synced" |> String.to_atom()
apply(input.resource, transitioned_status_name, [resource_record])

# NOTE this skips the invalid records allowing all the valid ones to
# be saved, one error in the flow will rollback everything
{:error, error} ->
Logger.warn(error)
{:ok, nil}
end
end
returns {:ok, nil} even if there is no error and the resource_record is updated with the right status
34 replies
AEAsh Elixir
Created by tommasop#2001 on 4/1/2023 in #support
Filter fields to be returned on an Ash.Query and an AshJsonApi route
I'm having a hard time finding out how can I filter returned fields from an Ash.Query this is my code:
read :quantities do
description "Product Quantities available in MMS"

prepare fn query, _ ->
query
|> Ash.Query.select([
:sku_code,
:quantity
])
end
end
read :quantities do
description "Product Quantities available in MMS"

prepare fn query, _ ->
query
|> Ash.Query.select([
:sku_code,
:quantity
])
end
end
And I'm supposing it to return only :sku_code and :quantity but it returns all the fields as nil and only the two fields with real data. Is it possible to filter the returned fields? Thanks a lot
56 replies
AEAsh Elixir
Created by tommasop#2001 on 1/22/2023 in #support
Ash Flow branch condition
I'm trying to do something like this:
check if record exists
branch on record not existing
create record with linked belongs_to record
check if record exists
branch on record not existing
create record with linked belongs_to record
the branch conditions is the following:
read :get_invoice_data, MmsBiztalk.InvoiceData, :current do
get? true
not_found_error? false

input(%{
help_desk_id: path(arg(:attributes), [:help_desk_id])
})
end

branch :invoice_data_found, expr(^result(:get_invoice_data)) do
end
read :get_invoice_data, MmsBiztalk.InvoiceData, :current do
get? true
not_found_error? false

input(%{
help_desk_id: path(arg(:attributes), [:help_desk_id])
})
end

branch :invoice_data_found, expr(^result(:get_invoice_data)) do
end
The strange thing is that the branch executes even if the record is found. I understand that condition must be a boolean value. Is there a way to achieve it with a read action or I need to use a custom :get_invoice_data step?
5 replies