mirror of
https://github.com/ash-project/ash_oban.git
synced 2024-09-20 05:13:15 +12:00
improvement: initial feature set
This commit is contained in:
parent
316a77a7f2
commit
cad7a65fae
13 changed files with 718 additions and 56 deletions
12
README.md
12
README.md
|
@ -1,12 +1,9 @@
|
||||||
# AshOban
|
# AshStateMachine
|
||||||
|
|
||||||
**TODO: Add description**
|
An oban extension for `Ash.Resource`
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
|
|
||||||
by adding `ash_oban` to your list of dependencies in `mix.exs`:
|
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
def deps do
|
def deps do
|
||||||
[
|
[
|
||||||
|
@ -15,7 +12,6 @@ def deps do
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
|
## Get Started
|
||||||
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
|
|
||||||
be found at <https://hexdocs.pm/ash_oban>.
|
|
||||||
|
|
||||||
|
Check out the [getting started guide](/documentation/tutorials/get-started-with-ash-oban.md).
|
||||||
|
|
62
documentation/get-started-with-ash-oban.md
Normal file
62
documentation/get-started-with-ash-oban.md
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
# Get Started With Ash Oban
|
||||||
|
|
||||||
|
AshOban will likely grow to provide many more oban-related features, but for now the primary focus is on "triggers".
|
||||||
|
|
||||||
|
A trigger describes an action that is run periodically.
|
||||||
|
|
||||||
|
This guide will need to be expanded, this is primarily a placeholder with an example.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
First, follow the oban setup guide.
|
||||||
|
|
||||||
|
If you are using Oban Pro, set the following configuration:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
config :ash_oban, :pro?, true
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, allow AshOban to alter your configuration
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
# in your application
|
||||||
|
{Oban, AshOban.config([YourApi, YourOtherApi], your_oban_config)}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Finally, configure your triggers in your resources.
|
||||||
|
|
||||||
|
Add the `AshOban` extension:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
use Ash.Resource, extensions: [AshOban]
|
||||||
|
```
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
oban do
|
||||||
|
triggers do
|
||||||
|
api YourApi
|
||||||
|
|
||||||
|
# add a triggere called `:process`
|
||||||
|
trigger :process do
|
||||||
|
# this trigger calls the `process` action
|
||||||
|
action :process
|
||||||
|
# for any record that has `processed != true`
|
||||||
|
where expr(processed != true)
|
||||||
|
# checking for matches every minute
|
||||||
|
scheduler_cron "* * * * *"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
See the DSL documentation for more: `AshOban`
|
||||||
|
|
||||||
|
## Changing Triggers
|
||||||
|
|
||||||
|
To remove or disable triggers, *do not just remove them from your resource*. Due to the way that oban implements cron jobs, if you just remove them from your resource, the cron will attempt to continue scheduling jobs. Instead, set `paused true` or `delete true` on the trigger. See the oban docs for more: https://getoban.pro/docs/pro/0.14.1/Oban.Pro.Plugins.DynamicCron.html#module-using-and-configuring
|
||||||
|
|
||||||
|
|
203
lib/ash_oban.ex
203
lib/ash_oban.ex
|
@ -1,26 +1,96 @@
|
||||||
defmodule AshOban do
|
defmodule AshOban do
|
||||||
@moduledoc """
|
|
||||||
Documentation for `AshOban`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
defmodule Trigger do
|
defmodule Trigger do
|
||||||
@type t :: %__MODULE__{
|
@type t :: %__MODULE__{
|
||||||
|
name: atom,
|
||||||
action: atom,
|
action: atom,
|
||||||
where: Ash.Expr.t()
|
read_action: atom,
|
||||||
|
queue: atom,
|
||||||
|
scheduler_cron: String.t(),
|
||||||
|
scheduler_queue: atom,
|
||||||
|
max_attempts: pos_integer(),
|
||||||
|
max_scheduler_attempts: pos_integer(),
|
||||||
|
where: Ash.Expr.t(),
|
||||||
|
scheduler: module,
|
||||||
|
worker: module,
|
||||||
|
__identifier__: atom
|
||||||
}
|
}
|
||||||
|
|
||||||
defstruct [:action, :where]
|
defstruct [
|
||||||
|
:name,
|
||||||
|
:action,
|
||||||
|
:read_action,
|
||||||
|
:queue,
|
||||||
|
:scheduler_cron,
|
||||||
|
:scheduler_queue,
|
||||||
|
:max_attempts,
|
||||||
|
:max_scheduler_attempts,
|
||||||
|
:where,
|
||||||
|
:scheduler,
|
||||||
|
:worker,
|
||||||
|
:__identifier__
|
||||||
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
@trigger %Spark.Dsl.Entity{
|
@trigger %Spark.Dsl.Entity{
|
||||||
name: :trigger,
|
name: :trigger,
|
||||||
target: Trigger,
|
target: Trigger,
|
||||||
args: [:action],
|
args: [:name],
|
||||||
|
identifier: :name,
|
||||||
imports: [Ash.Filter.TemplateHelpers],
|
imports: [Ash.Filter.TemplateHelpers],
|
||||||
schema: [
|
schema: [
|
||||||
|
name: [
|
||||||
|
type: :atom,
|
||||||
|
doc: "A unique identifier for this trigger."
|
||||||
|
],
|
||||||
|
scheduler_queue: [
|
||||||
|
type: :atom,
|
||||||
|
doc:
|
||||||
|
"The queue to place the scheduler job in. The trigger name plus \"_scheduler\" is used by default."
|
||||||
|
],
|
||||||
|
scheduler_cron: [
|
||||||
|
type: :string,
|
||||||
|
default: "* * * * *",
|
||||||
|
doc:
|
||||||
|
"A crontab configuration for when the job should run. Defaults to once per minute (\"* * * * *\")."
|
||||||
|
],
|
||||||
|
queue: [
|
||||||
|
type: :atom,
|
||||||
|
doc: "The queue to place the worker job in. The trigger name is used by default."
|
||||||
|
],
|
||||||
|
max_scheduler_attempts: [
|
||||||
|
type: :pos_integer,
|
||||||
|
default: 1,
|
||||||
|
doc: "How many times to attempt scheduling of the triggered action."
|
||||||
|
],
|
||||||
|
max_attempts: [
|
||||||
|
type: :pos_integer,
|
||||||
|
default: 1,
|
||||||
|
doc: "How many times to attempt the job."
|
||||||
|
],
|
||||||
|
state: [
|
||||||
|
type: {:one_of, [:active, :paused, :deleted]},
|
||||||
|
doc: """
|
||||||
|
Describes the state of the cron job.
|
||||||
|
|
||||||
|
See the getting started guide for an explanation on the need for this field.
|
||||||
|
The most important thing is that you *do not remove a trigger from a resource*.
|
||||||
|
Oban's cron jobs are persisted, meaning you will get repeated errors whenever the cron
|
||||||
|
job tries to fire.
|
||||||
|
"""
|
||||||
|
],
|
||||||
|
read_action: [
|
||||||
|
type: :atom,
|
||||||
|
required: true,
|
||||||
|
doc: """
|
||||||
|
The read action to use when querying records. Defaults to the primary read.
|
||||||
|
|
||||||
|
This action *must* support keyset pagination.
|
||||||
|
"""
|
||||||
|
],
|
||||||
action: [
|
action: [
|
||||||
type: :atom,
|
type: :atom,
|
||||||
doc: "The action to be triggered"
|
doc:
|
||||||
|
"The action to be triggered. Defaults to the identifier of the resource plus the name of the trigger"
|
||||||
],
|
],
|
||||||
where: [
|
where: [
|
||||||
type: :any,
|
type: :any,
|
||||||
|
@ -36,13 +106,124 @@ defmodule AshOban do
|
||||||
|
|
||||||
@oban %Spark.Dsl.Section{
|
@oban %Spark.Dsl.Section{
|
||||||
name: :oban,
|
name: :oban,
|
||||||
|
schema: [
|
||||||
|
api: [
|
||||||
|
type: {:behaviour, Ash.Api},
|
||||||
|
doc: "The Api module to use when calling actions on this resource",
|
||||||
|
required: true
|
||||||
|
]
|
||||||
|
],
|
||||||
sections: [@triggers]
|
sections: [@triggers]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@sections [@oban]
|
||||||
|
|
||||||
|
@moduledoc """
|
||||||
|
Dsl documentation for AshOban
|
||||||
|
<!--- ash-hq-hide-start --> <!--- -->
|
||||||
|
|
||||||
|
## DSL Documentation
|
||||||
|
|
||||||
|
### Index
|
||||||
|
|
||||||
|
#{Spark.Dsl.Extension.doc_index(@sections)}
|
||||||
|
|
||||||
|
### Docs
|
||||||
|
|
||||||
|
#{Spark.Dsl.Extension.doc(@sections)}
|
||||||
|
<!--- ash-hq-hide-stop --> <!--- -->
|
||||||
|
"""
|
||||||
|
|
||||||
use Spark.Dsl.Extension,
|
use Spark.Dsl.Extension,
|
||||||
sections: [@oban],
|
sections: [@oban],
|
||||||
verifiers: [
|
imports: [AshOban.Changes.BuiltinChanges],
|
||||||
# This is a bit dumb, a verifier probably shouldn't have side effects
|
transformers: [
|
||||||
AshOban.Verifiers.DefineSchedulers
|
AshOban.Transformers.SetDefaults,
|
||||||
|
AshOban.Transformers.DefineSchedulers
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def config(apis, base \\ %{}) do
|
||||||
|
pro? = AshOban.Info.pro?()
|
||||||
|
|
||||||
|
cron_plugin =
|
||||||
|
if pro? do
|
||||||
|
Oban.Pro.Plugins.DynamicCron
|
||||||
|
else
|
||||||
|
Oban.Pro.Plugins.Cron
|
||||||
|
end
|
||||||
|
|
||||||
|
if pro? && base[:engine] != Oban.Pro.Queue.SmartEngine do
|
||||||
|
raise """
|
||||||
|
Expected oban engine to be Oban.Pro.Queue.SmartEngine, but got #{inspect(base[:engine])}.
|
||||||
|
This expectation is because you've set `config :ash_oban, pro?: true`.
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
require_cron!(base, cron_plugin)
|
||||||
|
|
||||||
|
apis
|
||||||
|
|> Enum.flat_map(fn api ->
|
||||||
|
api
|
||||||
|
|> Ash.Api.Info.resources()
|
||||||
|
end)
|
||||||
|
|> Enum.uniq()
|
||||||
|
|> Enum.flat_map(fn resource ->
|
||||||
|
resource
|
||||||
|
|> AshOban.Info.oban_triggers()
|
||||||
|
|> Enum.map(&{resource, &1})
|
||||||
|
end)
|
||||||
|
|> Enum.reduce(base, fn {resource, trigger}, config ->
|
||||||
|
require_queues!(config, resource, trigger)
|
||||||
|
add_job(config, cron_plugin, resource, trigger)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp add_job(config, cron_plugin, _resource, trigger) do
|
||||||
|
Keyword.update!(config, :plugins, fn plugins ->
|
||||||
|
Keyword.update!(plugins, cron_plugin, fn plugins ->
|
||||||
|
opts =
|
||||||
|
case trigger.state do
|
||||||
|
:paused ->
|
||||||
|
[paused: true]
|
||||||
|
|
||||||
|
:deleted ->
|
||||||
|
[delete: true]
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
|
||||||
|
if(trigger.state = cron = {trigger.scheduler_cron, trigger.scheduler, []})
|
||||||
|
Keyword.update(plugins, :crontab, [cron], &[cron | &1])
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp require_queues!(config, resource, trigger) do
|
||||||
|
unless config[:queues][trigger.queue] do
|
||||||
|
raise """
|
||||||
|
Must configure the queue `:#{trigger.queue}`, requied for
|
||||||
|
the trigger `:#{trigger.name}` on #{inspect(resource)}
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
unless config[:queues][trigger.scheduler_queue] do
|
||||||
|
raise """
|
||||||
|
Must configure the queue `:#{trigger.queue}`, required for
|
||||||
|
the scheduler of the trigger `:#{trigger.name}` on #{inspect(resource)}
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp require_cron!(config, name) do
|
||||||
|
unless config[:plugins][name] do
|
||||||
|
raise """
|
||||||
|
Must configure cron plugin #{name}.
|
||||||
|
|
||||||
|
See oban's documentation for more. AshOban will
|
||||||
|
add cron jobs to the configuration, but will not
|
||||||
|
add the basic configuration for you.
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
7
lib/changes/builtin_changes.ex
Normal file
7
lib/changes/builtin_changes.ex
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
defmodule AshOban.Changes.BuiltinChanges do
|
||||||
|
@moduledoc "Builtin changes for `AshOban`"
|
||||||
|
|
||||||
|
def run_oban_trigger(trigger_name) do
|
||||||
|
{AshOban.Changes.RunObanTrigger, trigger: trigger_name}
|
||||||
|
end
|
||||||
|
end
|
20
lib/changes/run_oban_trigger.ex
Normal file
20
lib/changes/run_oban_trigger.ex
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
defmodule AshOban.Changes.RunObanTrigger do
|
||||||
|
use Ash.Resource.Change
|
||||||
|
|
||||||
|
def change(changeset, opts, _context) do
|
||||||
|
trigger = AshOban.Info.oban_trigger(changeset.resource, opts[:trigger])
|
||||||
|
primary_key = Ash.Resource.Info.primary_key(changeset.resource)
|
||||||
|
|
||||||
|
if !trigger do
|
||||||
|
raise "No such trigger #{opts[:trigger]} for resource #{inspect(changeset.resource)}"
|
||||||
|
end
|
||||||
|
|
||||||
|
Ash.Changeset.after_action(changeset, fn _changeset, result ->
|
||||||
|
%{primary_key: Map.take(result, primary_key)}
|
||||||
|
|> trigger.worker.new()
|
||||||
|
|> Oban.insert!()
|
||||||
|
|
||||||
|
{:ok, result}
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
28
lib/checks/ash_oban_interaction.ex
Normal file
28
lib/checks/ash_oban_interaction.ex
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
defmodule AshOban.Checks.AshObanInteraction do
|
||||||
|
@moduledoc """
|
||||||
|
This check is true if the context `private.ash_oban?` is set to true.
|
||||||
|
|
||||||
|
This context will only ever be set in code that is called internally by
|
||||||
|
`ash_oban`, allowing you to create a bypass in your policies on your
|
||||||
|
user/user_token resources.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
policies do
|
||||||
|
bypass AshObanInteraction do
|
||||||
|
authorize_if always()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
use Ash.Policy.SimpleCheck
|
||||||
|
|
||||||
|
@impl Ash.Policy.Check
|
||||||
|
def describe(_) do
|
||||||
|
"AshOban is performing this interaction"
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl Ash.Policy.SimpleCheck
|
||||||
|
def match?(_, %{query: %{context: %{private: %{ash_oban?: true}}}}, _), do: true
|
||||||
|
def match?(_, %{changeset: %{context: %{private: %{ash_oban?: true}}}}, _), do: true
|
||||||
|
def match?(_, _, _), do: false
|
||||||
|
end
|
10
lib/info.ex
10
lib/info.ex
|
@ -1,3 +1,13 @@
|
||||||
defmodule AshOban.Info do
|
defmodule AshOban.Info do
|
||||||
use Spark.InfoGenerator, extension: AshOban, sections: [:oban]
|
use Spark.InfoGenerator, extension: AshOban, sections: [:oban]
|
||||||
|
@pro Application.compile_env(:ash_oban, :pro?) || false
|
||||||
|
|
||||||
|
def pro?, do: @pro
|
||||||
|
|
||||||
|
@spec oban_trigger(Ash.Resource.t() | Spark.Dsl.t(), atom) :: nil | AshOban.Trigger.t()
|
||||||
|
def oban_trigger(resource, name) do
|
||||||
|
resource
|
||||||
|
|> oban_triggers()
|
||||||
|
|> Enum.find(&(&1.name == name))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
289
lib/transformers/define_schedulers.ex
Normal file
289
lib/transformers/define_schedulers.ex
Normal file
|
@ -0,0 +1,289 @@
|
||||||
|
defmodule AshOban.Transformers.DefineSchedulers do
|
||||||
|
use Spark.Dsl.Transformer
|
||||||
|
alias Spark.Dsl.Transformer
|
||||||
|
|
||||||
|
def after?(AshOban.Transformers.SetDefaults), do: true
|
||||||
|
def after?(_), do: false
|
||||||
|
|
||||||
|
def transform(dsl) do
|
||||||
|
module = Transformer.get_persisted(dsl, :module)
|
||||||
|
|
||||||
|
dsl
|
||||||
|
|> AshOban.Info.oban_triggers()
|
||||||
|
|> Enum.reduce(dsl, fn trigger, dsl ->
|
||||||
|
scheduler_module_name = module_name(module, trigger, "Scheduler")
|
||||||
|
worker_module_name = module_name(module, trigger, "Worker")
|
||||||
|
|
||||||
|
dsl
|
||||||
|
|> Transformer.replace_entity([:oban, :triggers], %{
|
||||||
|
trigger
|
||||||
|
| scheduler: scheduler_module_name,
|
||||||
|
worker: worker_module_name
|
||||||
|
})
|
||||||
|
|> Transformer.async_compile(fn ->
|
||||||
|
define_worker(module, worker_module_name, trigger, dsl)
|
||||||
|
end)
|
||||||
|
|> Transformer.async_compile(fn ->
|
||||||
|
define_scheduler(module, scheduler_module_name, worker_module_name, trigger, dsl)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|> then(&{:ok, &1})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp module_name(module, trigger, type) do
|
||||||
|
module
|
||||||
|
|> List.wrap()
|
||||||
|
|> Enum.concat(["AshOban", type])
|
||||||
|
|> Enum.concat([Macro.camelize(to_string(trigger.name))])
|
||||||
|
|> Module.concat()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp define_scheduler(resource, scheduler_module_name, worker_module_name, trigger, dsl) do
|
||||||
|
api = AshOban.Info.oban_api!(dsl)
|
||||||
|
primary_key = Ash.Resource.Info.primary_key(dsl)
|
||||||
|
pro? = AshOban.Info.pro?()
|
||||||
|
|
||||||
|
filter =
|
||||||
|
if not is_nil(trigger.where) do
|
||||||
|
quote do
|
||||||
|
def filter(query) do
|
||||||
|
Ash.Query.do_filter(query, unquote(Macro.escape(trigger.where)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
stream =
|
||||||
|
if is_nil(trigger.where) do
|
||||||
|
quote do
|
||||||
|
def stream(resource) do
|
||||||
|
resource
|
||||||
|
|> Ash.Query.set_context(%{private: %{ash_oban?: true}})
|
||||||
|
|> Ash.Query.select(unquote(primary_key))
|
||||||
|
|> Ash.Query.for_read(unquote(trigger.read_action))
|
||||||
|
|> unquote(api).stream!()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
quote do
|
||||||
|
def stream(resource) do
|
||||||
|
resource
|
||||||
|
|> Ash.Query.set_context(%{private: %{ash_oban?: true}})
|
||||||
|
|> Ash.Query.select(unquote(primary_key))
|
||||||
|
|> Ash.Query.for_read(unquote(trigger.read_action))
|
||||||
|
|> filter()
|
||||||
|
|> unquote(api).stream!()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
insert =
|
||||||
|
if pro? do
|
||||||
|
quote do
|
||||||
|
def insert(stream) do
|
||||||
|
stream
|
||||||
|
|> Stream.chunk_every(100)
|
||||||
|
|> Stream.each(&Oban.insert_all!/1)
|
||||||
|
|> Stream.run()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
quote do
|
||||||
|
def insert(stream) do
|
||||||
|
stream
|
||||||
|
|> Stream.each(&Oban.insert!())
|
||||||
|
|> Stream.run()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
worker =
|
||||||
|
if pro? do
|
||||||
|
Oban.Pro.Worker
|
||||||
|
else
|
||||||
|
Oban.Worker
|
||||||
|
end
|
||||||
|
|
||||||
|
function_name =
|
||||||
|
if pro? do
|
||||||
|
:process
|
||||||
|
else
|
||||||
|
:perform
|
||||||
|
end
|
||||||
|
|
||||||
|
quoted =
|
||||||
|
quote do
|
||||||
|
use unquote(worker),
|
||||||
|
queue: unquote(trigger.scheduler_queue),
|
||||||
|
unique: [
|
||||||
|
period: :infinity,
|
||||||
|
states: [
|
||||||
|
:available,
|
||||||
|
:retryable,
|
||||||
|
:scheduled
|
||||||
|
]
|
||||||
|
],
|
||||||
|
max_attempts: unquote(trigger.max_scheduler_attempts)
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
@impl unquote(worker)
|
||||||
|
if unquote(trigger.state != :active) do
|
||||||
|
def unquote(function_name)(%Oban.Job{}) do
|
||||||
|
{:discard, unquote(trigger.state)}
|
||||||
|
end
|
||||||
|
else
|
||||||
|
def unquote(function_name)(%Oban.Job{}) do
|
||||||
|
unquote(resource)
|
||||||
|
|> stream()
|
||||||
|
|> Stream.map(fn record ->
|
||||||
|
unquote(worker_module_name).new(%{
|
||||||
|
primary_key: Map.take(record, unquote(primary_key))
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|> insert()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
unquote(stream)
|
||||||
|
unquote(filter)
|
||||||
|
unquote(insert)
|
||||||
|
end
|
||||||
|
|
||||||
|
Module.create(scheduler_module_name, quoted, Macro.Env.location(__ENV__))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp define_worker(resource, worker_module_name, trigger, dsl) do
|
||||||
|
api = AshOban.Info.oban_api!(dsl)
|
||||||
|
pro? = AshOban.Info.pro?()
|
||||||
|
|
||||||
|
worker =
|
||||||
|
if pro? do
|
||||||
|
Oban.Pro.Worker
|
||||||
|
else
|
||||||
|
Oban.Worker
|
||||||
|
end
|
||||||
|
|
||||||
|
function_name =
|
||||||
|
if pro? do
|
||||||
|
:process
|
||||||
|
else
|
||||||
|
:perform
|
||||||
|
end
|
||||||
|
|
||||||
|
query =
|
||||||
|
if is_nil(trigger.where) do
|
||||||
|
quote do
|
||||||
|
def query do
|
||||||
|
unquote(resource)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
quote do
|
||||||
|
def query do
|
||||||
|
Ash.Query.do_filter(unquote(resource), unquote(Macro.escape(trigger.where)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Module.create(
|
||||||
|
worker_module_name,
|
||||||
|
quote do
|
||||||
|
use unquote(worker),
|
||||||
|
max_attempts: 3,
|
||||||
|
queue: unquote(trigger.queue),
|
||||||
|
unique: [
|
||||||
|
period: :infinity,
|
||||||
|
states: [
|
||||||
|
:available,
|
||||||
|
:retryable,
|
||||||
|
:scheduled
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
@impl unquote(worker)
|
||||||
|
if unquote(trigger.state != :active) do
|
||||||
|
def unquote(function_name)(_) do
|
||||||
|
{:discard, unquote(trigger.state)}
|
||||||
|
end
|
||||||
|
else
|
||||||
|
def unquote(function_name)(%Oban.Job{args: %{"primary_key" => primary_key}}) do
|
||||||
|
if Ash.DataLayer.data_layer_can?(unquote(resource), :transact) do
|
||||||
|
Ash.DataLayer.transaction(
|
||||||
|
unquote(resource),
|
||||||
|
fn ->
|
||||||
|
opts = [
|
||||||
|
action: unquote(trigger.read_action),
|
||||||
|
context: %{private: %{ash_oban?: true}}
|
||||||
|
]
|
||||||
|
|
||||||
|
opts =
|
||||||
|
if Ash.DataLayer.data_layer_can?(unquote(resource), {:lock, :for_update}) do
|
||||||
|
opts
|
||||||
|
else
|
||||||
|
Keyword.put(opts, :lock, :for_update)
|
||||||
|
end
|
||||||
|
|
||||||
|
query()
|
||||||
|
|> unquote(api).read_one(primary_key, opts)
|
||||||
|
|> case do
|
||||||
|
{:ok, nil} ->
|
||||||
|
{:discard, :trigger_no_longer_applies}
|
||||||
|
|
||||||
|
{:ok, record} ->
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|> Ash.Changeset.new()
|
||||||
|
|> Ash.Changeset.set_context(%{private: %{ash_oban?: true}})
|
||||||
|
|> Ash.Changeset.for_update(unquote(trigger.action), %{})
|
||||||
|
|> unquote(api).update!()
|
||||||
|
end,
|
||||||
|
nil,
|
||||||
|
%{
|
||||||
|
type: :ash_oban_trigger,
|
||||||
|
metadata: %{
|
||||||
|
resource: unquote(resource),
|
||||||
|
trigger: unquote(trigger.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|> case do
|
||||||
|
{:ok, {:discard, reason}} ->
|
||||||
|
{:discard, reason}
|
||||||
|
|
||||||
|
{:ok, _} ->
|
||||||
|
:ok
|
||||||
|
|
||||||
|
other ->
|
||||||
|
other
|
||||||
|
end
|
||||||
|
else
|
||||||
|
opts = [
|
||||||
|
action: unquote(trigger.read_action),
|
||||||
|
context: %{private: %{ash_oban?: true}}
|
||||||
|
]
|
||||||
|
|
||||||
|
query()
|
||||||
|
|> unquote(api).read_one(primary_key, opts)
|
||||||
|
|> case do
|
||||||
|
{:ok, nil} ->
|
||||||
|
{:discard, :trigger_no_longer_applies}
|
||||||
|
|
||||||
|
{:ok, record} ->
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|> Ash.Changeset.new()
|
||||||
|
|> Ash.Changeset.set_context(%{private: %{ash_oban?: true}})
|
||||||
|
|> Ash.Changeset.for_update(unquote(trigger.action), %{})
|
||||||
|
|> unquote(api).update()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
unquote(query)
|
||||||
|
end,
|
||||||
|
Macro.Env.location(__ENV__)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
62
lib/transformers/set_defaults.ex
Normal file
62
lib/transformers/set_defaults.ex
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
defmodule AshOban.Transformers.SetDefaults do
|
||||||
|
use Spark.Dsl.Transformer
|
||||||
|
alias Spark.Dsl.Transformer
|
||||||
|
|
||||||
|
def after?(AshOban.Transformers.DefineSchedulers), do: false
|
||||||
|
def after?(_), do: true
|
||||||
|
def before?(AshOban.Transformers.DefineSchedulers), do: true
|
||||||
|
def before?(_), do: false
|
||||||
|
|
||||||
|
def transform(dsl) do
|
||||||
|
module = Transformer.get_persisted(dsl, :module)
|
||||||
|
|
||||||
|
{:ok,
|
||||||
|
dsl
|
||||||
|
|> Transformer.get_entities([:oban, :triggers])
|
||||||
|
|> Enum.reduce(dsl, fn trigger, dsl ->
|
||||||
|
read_action =
|
||||||
|
case trigger.read_action do
|
||||||
|
nil ->
|
||||||
|
Ash.Resource.Info.primary_action(dsl, :read) ||
|
||||||
|
raise Spark.Error.DslError,
|
||||||
|
path: [
|
||||||
|
:oban,
|
||||||
|
:triggers,
|
||||||
|
trigger.name,
|
||||||
|
:read_action
|
||||||
|
],
|
||||||
|
module: module,
|
||||||
|
message: """
|
||||||
|
No read action was configured for this trigger, and no primary read action exists
|
||||||
|
"""
|
||||||
|
|
||||||
|
read_action ->
|
||||||
|
Ash.Resource.Info.action(dsl, read_action)
|
||||||
|
end
|
||||||
|
|
||||||
|
unless read_action.pagination && read_action.pagination.keyset? do
|
||||||
|
raise Spark.Error.DslError,
|
||||||
|
path: [:oban, :triggers, trigger.name, :read_action],
|
||||||
|
module: module,
|
||||||
|
message: """
|
||||||
|
The read action `:#{read_action.name}` must support keyset pagination in order to be
|
||||||
|
used by an AshOban trigger.
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
queue = trigger.queue || default_queue_name(dsl, trigger)
|
||||||
|
|
||||||
|
Transformer.replace_entity(dsl, [:oban, :triggers], %{
|
||||||
|
trigger
|
||||||
|
| read_action: read_action.name,
|
||||||
|
queue: queue,
|
||||||
|
scheduler_queue: trigger.scheduler_queue || :"#{queue}_scheduler",
|
||||||
|
action: trigger.action || trigger.name
|
||||||
|
})
|
||||||
|
end)}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp default_queue_name(dsl, trigger) do
|
||||||
|
:"#{Ash.Resource.Info.short_name(dsl)}_#{trigger.name}"
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,14 +0,0 @@
|
||||||
defmodule AshOban.Verifiers.DefineSchedulers do
|
|
||||||
use Spark.Dsl.Verifier
|
|
||||||
|
|
||||||
def verify(dsl_state) do
|
|
||||||
# TODO
|
|
||||||
# dsl_state
|
|
||||||
# |> AshOban.Info.oban_triggers()
|
|
||||||
# |> Enum.each(fn trigger ->
|
|
||||||
# IO.inspect(trigger)
|
|
||||||
# end)
|
|
||||||
|
|
||||||
:ok
|
|
||||||
end
|
|
||||||
end
|
|
26
mix.exs
26
mix.exs
|
@ -97,29 +97,13 @@ defmodule AshOban.MixProject do
|
||||||
module: AshOban,
|
module: AshOban,
|
||||||
name: "AshOban",
|
name: "AshOban",
|
||||||
target: "Ash.Resource",
|
target: "Ash.Resource",
|
||||||
type: "StateMachine Resource"
|
type: "AshOban Resource"
|
||||||
},
|
|
||||||
%{
|
|
||||||
module: AshGraphql.Api,
|
|
||||||
name: "AshGraphql Api",
|
|
||||||
target: "Ash.Api",
|
|
||||||
type: "GraphQL Api"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
extras: extras(),
|
extras: extras(),
|
||||||
groups_for_extras: groups_for_extras(),
|
groups_for_extras: groups_for_extras(),
|
||||||
groups_for_modules: [
|
groups_for_modules: [
|
||||||
AshGraphql: [
|
|
||||||
AshGraphql
|
|
||||||
],
|
|
||||||
Introspection: [
|
|
||||||
AshGraphql.Resource.Info,
|
|
||||||
AshGraphql.Api.Info
|
|
||||||
],
|
|
||||||
Miscellaneous: [
|
|
||||||
AshGraphql.Resource.Helpers
|
|
||||||
],
|
|
||||||
Internals: ~r/.*/
|
Internals: ~r/.*/
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
@ -135,8 +119,12 @@ defmodule AshOban.MixProject do
|
||||||
# Run "mix help deps" to learn about dependencies.
|
# Run "mix help deps" to learn about dependencies.
|
||||||
defp deps do
|
defp deps do
|
||||||
[
|
[
|
||||||
{:ash, "~> 2.7"},
|
{:ash, github: "ash-project/ash"},
|
||||||
{:spark, ">= 1.0.9"}
|
{:spark, ">= 1.1.3"},
|
||||||
|
{:oban, "~> 2.15"},
|
||||||
|
{:oban_pro, "~> 0.14", repo: "oban"},
|
||||||
|
{:oban_web, "~> 2.9", repo: "oban"}
|
||||||
|
# {:oban, path: "../oban"}
|
||||||
# {:dep_from_hexpm, "~> 0.3.0"},
|
# {:dep_from_hexpm, "~> 0.3.0"},
|
||||||
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
|
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
|
||||||
]
|
]
|
||||||
|
|
23
mix.lock
23
mix.lock
|
@ -1,16 +1,33 @@
|
||||||
%{
|
%{
|
||||||
"ash": {:hex, :ash, "2.7.1", "e8915f4ebb4dbf8e8f689020430adf30f68ac2f4bd2613e416e6f69fd8f4dc69", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, "~> 1.0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.5.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b6e708fac305147f99d292823782bf8f7d2897efae7fe4fdf688e3c7cad777f"},
|
"ash": {:git, "https://github.com/ash-project/ash.git", "fbc341b3a00d1d0798a8966d831443f88f412390", []},
|
||||||
|
"castore": {:hex, :castore, "1.0.1", "240b9edb4e9e94f8f56ab39d8d2d0a57f49e46c56aced8f873892df8ff64ff5a", [:mix], [], "hexpm", "b4951de93c224d44fac71614beabd88b71932d0b1dea80d2f80fb9044e01bbb3"},
|
||||||
"comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"},
|
"comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"},
|
||||||
"decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},
|
"db_connection": {:hex, :db_connection, "2.5.0", "bb6d4f30d35ded97b29fe80d8bd6f928a1912ca1ff110831edcd238a1973652c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c92d5ba26cd69ead1ff7582dbb860adeedfff39774105a4f1c92cbb654b55aa2"},
|
||||||
|
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
|
||||||
"ecto": {:hex, :ecto, "3.10.1", "c6757101880e90acc6125b095853176a02da8f1afe056f91f1f90b80c9389822", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d2ac4255f1601bdf7ac74c0ed971102c6829dc158719b94bd30041bbad77f87a"},
|
"ecto": {:hex, :ecto, "3.10.1", "c6757101880e90acc6125b095853176a02da8f1afe056f91f1f90b80c9389822", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d2ac4255f1601bdf7ac74c0ed971102c6829dc158719b94bd30041bbad77f87a"},
|
||||||
|
"ecto_sql": {:hex, :ecto_sql, "3.10.1", "6ea6b3036a0b0ca94c2a02613fd9f742614b5cfe494c41af2e6571bb034dd94c", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f6a25bdbbd695f12c8171eaff0851fa4c8e72eec1e98c7364402dda9ce11c56b"},
|
||||||
"elixir_make": {:hex, :elixir_make, "0.7.6", "67716309dc5d43e16b5abbd00c01b8df6a0c2ab54a8f595468035a50189f9169", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5a0569756b0f7873a77687800c164cca6dfc03a09418e6fcf853d78991f49940"},
|
"elixir_make": {:hex, :elixir_make, "0.7.6", "67716309dc5d43e16b5abbd00c01b8df6a0c2ab54a8f595468035a50189f9169", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5a0569756b0f7873a77687800c164cca6dfc03a09418e6fcf853d78991f49940"},
|
||||||
"ets": {:hex, :ets, "0.8.1", "8ff9bcda5682b98493f8878fc9dbd990e48d566cba8cce59f7c2a78130da29ea", [:mix], [], "hexpm", "6be41b50adb5bc5c43626f25ea2d0af1f4a242fb3fad8d53f0c67c20b78915cc"},
|
"ets": {:hex, :ets, "0.8.1", "8ff9bcda5682b98493f8878fc9dbd990e48d566cba8cce59f7c2a78130da29ea", [:mix], [], "hexpm", "6be41b50adb5bc5c43626f25ea2d0af1f4a242fb3fad8d53f0c67c20b78915cc"},
|
||||||
"jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
|
"jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
|
||||||
|
"mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"},
|
||||||
"nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"},
|
"nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"},
|
||||||
|
"oban": {:hex, :oban, "2.15.0", "27b9c2845cdff30c98c8060b11a64318e79bbc1bd32b8dc95fa59a1580a8d90c", [:mix], [{:ecto_sql, "~> 3.6", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "22e181c540335d1dd5c995be00435927075519207d62b3de32477d95dbf9dfd3"},
|
||||||
|
"oban_pro": {:hex, :oban_pro, "0.14.1", "729394796e1e79633bb1083633cb9db5c5ddf2a13ab1cbc081593978348f285f", [:mix], [{:ecto_sql, "~> 3.8", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.13", [hex: :libgraph, repo: "hexpm", optional: true]}, {:oban, "~> 2.15.0", [hex: :oban, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}], "oban", "ef5e448ec98faa7f07924548904718d3b7a5c1e3eddd9a5a0c11bf90b9af75c2"},
|
||||||
|
"oban_web": {:hex, :oban_web, "2.9.6", "40a77a41f13e9a1a712101eec0972616dc2a82ae5befe4020592e885977d41da", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:oban, "~> 2.11", [hex: :oban, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, ">= 0.17.4 and < 0.19.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}], "oban", "be7917e91555050ff82f29921168ff7f4dd5d80692daaa987d78286de4b8b297"},
|
||||||
|
"phoenix": {:hex, :phoenix, "1.7.2", "c375ffb482beb4e3d20894f84dd7920442884f5f5b70b9f4528cbe0cedefec63", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.4", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "1ebca94b32b4d0e097ab2444a9742ed8ff3361acad17365e4e6b2e79b4792159"},
|
||||||
|
"phoenix_html": {:hex, :phoenix_html, "3.3.1", "4788757e804a30baac6b3fc9695bf5562465dd3f1da8eb8460ad5b404d9a2178", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bed1906edd4906a15fd7b412b85b05e521e1f67c9a85418c55999277e553d0d3"},
|
||||||
|
"phoenix_live_view": {:hex, :phoenix_live_view, "0.18.18", "1f38fbd7c363723f19aad1a04b5490ff3a178e37daaf6999594d5f34796c47fc", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a5810d0472f3189ede6d2a95bda7f31c6113156b91784a3426cb0ab6a6d85214"},
|
||||||
|
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"},
|
||||||
|
"phoenix_template": {:hex, :phoenix_template, "1.0.1", "85f79e3ad1b0180abb43f9725973e3b8c2c3354a87245f91431eec60553ed3ef", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "157dc078f6226334c91cb32c1865bf3911686f8bcd6bcff86736f6253e6993ee"},
|
||||||
|
"phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"},
|
||||||
"picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"},
|
"picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"},
|
||||||
|
"plug": {:hex, :plug, "1.14.2", "cff7d4ec45b4ae176a227acd94a7ab536d9b37b942c8e8fa6dfc0fff98ff4d80", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "842fc50187e13cf4ac3b253d47d9474ed6c296a8732752835ce4a86acdf68d13"},
|
||||||
|
"plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"},
|
||||||
"sourceror": {:hex, :sourceror, "0.12.2", "2ae55efd149193572e0eb723df7c7a1bda9ab33c43373c82642931dbb2f4e428", [:mix], [], "hexpm", "7ad74ade6fb079c71f29fae10c34bcf2323542d8c51ee1bcd77a546cfa89d59c"},
|
"sourceror": {:hex, :sourceror, "0.12.2", "2ae55efd149193572e0eb723df7c7a1bda9ab33c43373c82642931dbb2f4e428", [:mix], [], "hexpm", "7ad74ade6fb079c71f29fae10c34bcf2323542d8c51ee1bcd77a546cfa89d59c"},
|
||||||
"spark": {:hex, :spark, "1.0.9", "6a98f4d0183a30a241de06d27604d5baa48a99b0fe99afc8a5bd82d4067a501e", [:mix], [{:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "4e64cc73fb43d7d64e985e80b5e6300e94114782c59464c4476d272b0bdb9df1"},
|
"spark": {:hex, :spark, "1.1.3", "5577146f14f7af85c9c56da283d633377428377952cdafae862e55aabdbd5133", [:mix], [{:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "4512bd2b9b9b20cc47450c83420d7e7e223fd08c4ac75ce0846d0f522d82e50b"},
|
||||||
"stream_data": {:hex, :stream_data, "0.5.0", "b27641e58941685c75b353577dc602c9d2c12292dd84babf506c2033cd97893e", [:mix], [], "hexpm", "012bd2eec069ada4db3411f9115ccafa38540a3c78c4c0349f151fc761b9e271"},
|
"stream_data": {:hex, :stream_data, "0.5.0", "b27641e58941685c75b353577dc602c9d2c12292dd84babf506c2033cd97893e", [:mix], [], "hexpm", "012bd2eec069ada4db3411f9115ccafa38540a3c78c4c0349f151fc761b9e271"},
|
||||||
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
|
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
|
||||||
"typable": {:hex, :typable, "0.3.0", "0431e121d124cd26f312123e313d2689b9a5322b15add65d424c07779eaa3ca1", [:mix], [], "hexpm", "880a0797752da1a4c508ac48f94711e04c86156f498065a83d160eef945858f8"},
|
"typable": {:hex, :typable, "0.3.0", "0431e121d124cd26f312123e313d2689b9a5322b15add65d424c07779eaa3ca1", [:mix], [], "hexpm", "880a0797752da1a4c508ac48f94711e04c86156f498065a83d160eef945858f8"},
|
||||||
|
"websock": {:hex, :websock, "0.5.1", "c496036ce95bc26d08ba086b2a827b212c67e7cabaa1c06473cd26b40ed8cf10", [:mix], [], "hexpm", "b9f785108b81cd457b06e5f5dabe5f65453d86a99118b2c0a515e1e296dc2d2c"},
|
||||||
|
"websock_adapter": {:hex, :websock_adapter, "0.5.1", "292e6c56724e3457e808e525af0e9bcfa088cc7b9c798218e78658c7f9b85066", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "8e2e1544bfde5f9d0442f9cec2f5235398b224f75c9e06b60557debf64248ec1"},
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,14 @@ defmodule AshObanTest do
|
||||||
use ExUnit.Case
|
use ExUnit.Case
|
||||||
doctest AshOban
|
doctest AshOban
|
||||||
|
|
||||||
|
defmodule Api do
|
||||||
|
use Ash.Api
|
||||||
|
|
||||||
|
resources do
|
||||||
|
allow_unregistered? true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defmodule Triggered do
|
defmodule Triggered do
|
||||||
use Ash.Resource,
|
use Ash.Resource,
|
||||||
data_layer: Ash.DataLayer.Ets,
|
data_layer: Ash.DataLayer.Ets,
|
||||||
|
@ -9,14 +17,22 @@ defmodule AshObanTest do
|
||||||
|
|
||||||
oban do
|
oban do
|
||||||
triggers do
|
triggers do
|
||||||
|
api Api
|
||||||
|
|
||||||
trigger :process do
|
trigger :process do
|
||||||
|
action :process
|
||||||
where expr(processed != true)
|
where expr(processed != true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
actions do
|
actions do
|
||||||
defaults [:read, :create]
|
defaults [:create]
|
||||||
|
|
||||||
|
read :read do
|
||||||
|
primary? true
|
||||||
|
pagination keyset?: true
|
||||||
|
end
|
||||||
|
|
||||||
update :process do
|
update :process do
|
||||||
change set_attribute(:processed, true)
|
change set_attribute(:processed, true)
|
||||||
|
|
Loading…
Reference in a new issue