-
Notifications
You must be signed in to change notification settings - Fork 1
Description
Related to #16
In order to keep the code simpler and more readable, in some cases I personally prefer creating no-ops (or is it null-ops? -- in a reducer, it means producing an operation like an update that will do no side effect to the db). This avoids conditional creation of ops and overkill extractions with conditions and/or pattern matching.
The most common case, for me, is having a pattern match that might return null instead of an op. But there has been one case case where I used and updated that changed no fields (issue #16) -- this one was in Hatch, don't remember if there were any in TurtleOS.
Of course we could just wrap the returned list of ops with a function that filters no-ops (like reject null values or even filtering out Derive operations' structs based on some logic), therefore this isn't a blocker. But, I think this is something we could do on Derive's side the keep userland-code cleaner.
The sudo-code for what I'm suggesting would look like:
def foo (a, b, c) do
apply(reducer_mod, :handle_event, [event])
|> List.wrap() # in case a single op (not a list) is returned
|> List.flatten() # in case there are nested lists, useful when you extract
# the conditional logic for generating subsets of events.
|> Enum.reject(&no_op?/1)
|> invoke_commit(reducer_mod)
end
defp no_op?(null), do: true
defp no_op?(%Update{fields: fields}) when map_size(fields) == 0, do: true
# ...
def no_op?(_), do: falseA use case example:
def handle_event(%Foo{} = event) do
[
maybe_delete_bar(event.baz)
]
end
defp maybe_delete_bar([]), do: nil
defp maybe_delete_bar([_ | _] = bar) do
[bla: bar]
|> Query.bars()
|> delete()
endAnother one:
def handle_event(%Foo{} = event) do
[
maybe_update_bar(event)
]
end
defp maybe_update_bar(event) do
transaction(fn repo ->
current_bar = load_bar(repo, event.bar_id)
new_bar = Map.merge(current_bar, %{a: event.bar_a, b: event.bar_b})
changed_fields = diff(current_bar, new_bar)
update({Bar, event.bar_id}, changed_fields) # there might be no fields changed and we actually have
# a use case like that. In fact, the query fails at
# runtime if no fields were changed (that's issue 16).
end)
endIt also makes it easier to do this:
def handle_event(%Foo{} = event) do
[
if (not is_nil(event.bar)) do
insert(%Bar{id: event.bar_id, a: event.a, b: event.b})
end
# the `if` block will return null when the condition evaluates to false.
]
endEdit: updated sudo-code for filtering out no-ops, now allows for this use case:
def handle_event(%Foo{} = event) do
[
maybe_delete_x(event.foo),
maybe_create_y(event.bar),
maybe_create_z(event.baz)
# Each `maybe_*` function returns a list of 0 or more events.
# That's why that `List.flatten/1` I added is important.
]
end