Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add one piece oban #72

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions apps/one_piece_oban/.formatter.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[
line_length: 120,
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]
26 changes: 26 additions & 0 deletions apps/one_piece_oban/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# The directory Mix will write compiled artifacts to.
/_build/

# If you run "mix test --cover", coverage assets end up here.
/cover/

# The directory Mix downloads your dependencies sources to.
/deps/

# Where third-party dependencies like ExDoc output generated docs.
/doc/

# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch

# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump

# Also ignore archive artifacts (built via "mix archive.build").
*.ez

# Ignore package tarball (built via "mix hex.build").
one_piece_commanded-*.tar

# Temporary files for e.g. tests
/tmp
7 changes: 7 additions & 0 deletions apps/one_piece_oban/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Changelog

## Unreleased

## v0.1.0 - 2023-04-07

- Initial release
21 changes: 21 additions & 0 deletions apps/one_piece_oban/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2023-Present Straw Hat, LLC

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
9 changes: 9 additions & 0 deletions apps/one_piece_oban/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# OnePiece.Clock

A Swiss Army Knife for Oban.

## Documentation

### References

- [API Reference](api-reference.html)
5 changes: 5 additions & 0 deletions apps/one_piece_oban/coveralls.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"coverage_options": {
"treat_no_relevant_lines_as_covered": true
}
}
4 changes: 4 additions & 0 deletions apps/one_piece_oban/lib/one_piece/myapp.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
defmodule MyApp.Oban do

Check warning on line 1 in apps/one_piece_oban/lib/one_piece/myapp.ex

View workflow job for this annotation

GitHub Actions / qa / Credo

Modules should have a @moduledoc tag.
use OnePiece.Oban,
otp_app: :one_piece_oban
end
218 changes: 218 additions & 0 deletions apps/one_piece_oban/lib/one_piece/oban.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
defmodule OnePiece.Oban do
@moduledoc """
Provides a simplified interface to the `Oban` library.
"""

@doc """
It creates a facade for the `Oban` functions.

It allows you to avoid having to pass the `t:Oban.name/0` value to the `Oban` functions, as it is automatically set to
the module name. As well as allowing you to configure the `Oban` instance in the application's configuration using
the module name under a given OTP application key.

## Examples

### In an application module

defmodule MyApp.Oban do
use OnePiece.Oban,
otp_app: :my_app,
repo: MyApp.Repo
end

Now you can register the `MyApp.Oban` module in the application's supervision tree:

defmodule MyApp.Application do
use Application

def start(_type, _args) do
children = [
MyApp.Repo,
MyApp.Oban
]

opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
end

### Avoiding the need of passing the `Oban` instance

Instead of calling `Oban` functions passing the `Oban` instance, you can use the `OnePiece.Oban` module directly:

OnePiece.Oban.insert(MyApp.MyJob.new(args: %{ "field" => "value" }))
"""
defmacro __using__(opts \\ []) do
{opts, child_opts} = Keyword.split(opts, [:otp_app])
otp_app = Keyword.fetch!(opts, :otp_app)

quote do

Check warning on line 49 in apps/one_piece_oban/lib/one_piece/oban.ex

View workflow job for this annotation

GitHub Actions / qa / Credo

Avoid long quote blocks.
@doc """
Returns the Oban child spec for the application. This should be added to the application's supervision tree.

The `:name` option is ignored and set to `#{inspect(__MODULE__)}`.

## Examples

defmodule MyApp.Application do
use Application

children = [
{#{inspect(__MODULE__)}, prefix: "special"}
]

opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
"""
def child_spec(opts) do
unquote(child_opts)
|> Keyword.merge(Application.get_env(unquote(otp_app), __MODULE__, []))
|> Keyword.merge(opts)
|> Keyword.put(:name, __MODULE__)
|> Oban.child_spec()
end

@doc """
A facade for `Oban.cancel_all_jobs/2` that uses the `#{inspect(__MODULE__)}` module as the `t:Oban.name/0` argument.
"""
@spec cancel_all_jobs(queryable :: Ecto.Queryable.t()) :: {:ok, non_neg_integer()}
def cancel_all_jobs(queryable) do
Oban.insert(__MODULE__, queryable)
end

@doc """
A facade for `Oban.cancel_job/2` that uses the `#{inspect(__MODULE__)}` module as the `t:Oban.name/0` argument.
"""
@spec cancel_job(job_or_id :: Oban.Job.t() | integer()) :: :ok
def cancel_job(job_or_id) do
Oban.cancel_job(__MODULE__, job_or_id)
end

@doc """
A facade for `Oban.check_queue/2` that uses the `#{inspect(__MODULE__)}` module as the `t:Oban.name/0` argument.
"""
@spec check_queue(opts :: [{:queue, Oban.queue_name()}]) :: Oban.queue_state()
def check_queue(opts) do
Oban.check_queue(__MODULE__, opts)
end

@doc """
A facade for `Oban.config/1` that uses the `#{inspect(__MODULE__)}` module as the `t:Oban.name/0` argument.
"""
@spec config :: Oban.Config.t()
def config do
Oban.config(__MODULE__)
end

@doc """
A facade for `Oban.drain_queue/2` that uses the `#{inspect(__MODULE__)}` module as the `t:Oban.name/0` argument.
"""
@spec drain_queue(opts :: [Oban.drain_option()]) :: Oban.drain_result()
def drain_queue(opts) do
Oban.drain_queue(__MODULE__, opts)
end

@doc """
A facade for `Oban.insert/3` that uses the `#{inspect(__MODULE__)}` module as the `t:Oban.name/0` argument.
"""
@spec insert(changeset :: Oban.Job.changeset(), opts :: Keyword.t()) ::
{:ok, Oban.Job.t()} | {:error, Oban.Job.changeset() | term()}
def insert(changeset, opts \\ []) do
Oban.insert(__MODULE__, changeset, opts)
end

@doc """
A facade for `Oban.insert/5` that uses the `#{inspect(__MODULE__)}` module as the `t:Oban.name/0` argument.
"""
@spec insert(Oban.multi(), Oban.multi_name(), Oban.changeset_or_fun(), Keyword.t()) :: Oban.multi()
def insert(multi, multi_name, changeset, opts \\ []) do
Oban.insert(__MODULE__, multi, multi_name, changeset, opts)
end

@doc """
A facade for `Oban.insert!/3` that uses the `#{inspect(__MODULE__)}` module as the `t:Oban.name/0` argument.
"""
@spec insert!(Job.changeset(), opts :: Keyword.t()) :: Job.t()
def insert!(changeset, opts \\ []) do
Oban.insert!(__MODULE__, changeset, opts)
end

@doc """
A facade for `Oban.insert_all/3` that uses the `#{inspect(__MODULE__)}` module as the `t:Oban.name/0` argument.
"""
@spec insert_all(
Oban.changesets_or_wrapper() | Oban.multi_name(),
Keyword.t() | Oban.changesets_or_wrapper_or_fun()
) :: [Job.t()] | Oban.multi()
def insert_all(changesets, opts) do
Oban.insert_all(__MODULE__, changesets, opts)
end

@doc """
A facade for `Oban.insert_all/5` that uses the `#{inspect(__MODULE__)}` module as the `t:Oban.name/0` argument.
"""
@spec insert_all(Oban.multi(), Oban.multi_name(), Oban.changesets_or_wrapper_or_fun(), Keyword.t()) ::
Oban.multi()
def insert_all(multi, multi_name, changesets, opts) do
Oban.insert_all(__MODULE__, multi, multi_name, changesets, opts)
end

@doc """
A facade for `Oban.start_queue/2` that uses the `#{inspect(__MODULE__)}` module as the `t:Oban.name/0` argument.
"""
@spec start_queue(opts :: Keyword.t()) :: :ok
def start_queue(opts) do
Oban.start_queue(__MODULE__, opts)
end

@doc """
A facade for `Oban.pause_queue/2` that uses the `#{inspect(__MODULE__)}` module as the `t:Oban.name/0` argument.
"""
@spec pause_queue(opts :: [Oban.queue_option()]) :: :ok
def pause_queue(opts) do
Oban.pause_queue(__MODULE__, opts)
end

@doc """
A facade for `Oban.resume_queue/2` that uses the `#{inspect(__MODULE__)}` module as the `t:Oban.name/0` argument.
"""
@spec resume_queue(opts :: [Oban.queue_option()]) :: :ok
def resume_queue(opts) do
Oban.resume_queue(__MODULE__, opts)
end

@doc """
A facade for `Oban.scale_queue/2` that uses the `#{inspect(__MODULE__)}` module as the `t:Oban.name/0` argument.
"""
@spec scale_queue(opts :: [Oban.queue_option()]) :: :ok
def scale_queue(opts) do
Oban.scale_queue(__MODULE__, opts)
end

@doc """
A facade for `Oban.stop_queue/2` that uses the `#{inspect(__MODULE__)}` module as the `t:Oban.name/0` argument.
"""
@spec stop_queue(opts :: [Oban.queue_option()]) :: :ok
def stop_queue(opts) do
Oban.stop_queue(__MODULE__, opts)
end

@doc """
A facade for `Oban.retry_job/2` that uses the `#{inspect(__MODULE__)}` module as the `t:Oban.name/0` argument.
"""
@spec retry_job(job_or_id :: Job.t() | integer()) :: :ok
def retry_job(job_or_id) do
Oban.retry_job(__MODULE__, job_or_id)
end

@doc """
A facade for `Oban.retry_all_jobs/2` that uses the `#{inspect(__MODULE__)}` module as the `t:Oban.name/0` argument.
"""
@spec retry_all_jobs(queryable :: Ecto.Queryable.t()) :: {:ok, non_neg_integer()}
def retry_all_jobs(queryable) do
Oban.retry_all_jobs(__MODULE__, queryable)
end
end
end
end
93 changes: 93 additions & 0 deletions apps/one_piece_oban/lib/one_piece/oban/query.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
defmodule OnePiece.Oban.Query do
@moduledoc """
This module provides a series of helper functions to interact with `Oban.Job` queries.

It simplifies the process of querying `Oban.Job` jobs by providing functions for common querying scenarios.
"""

import Ecto.Query, only: [from: 2]

@doc """
Creates a new Oban.Job query.
"""
@spec new() :: Ecto.Queryable.t()
def new do
Oban.Job
end

@doc """
Filters the query by the worker name.

## Examples

OnePiece.Oban.Query.new()
|> Oban.Query.where_worker("MyApp.MyWorker")
|> Oban.cancel_all_jobs()
"""
@spec where_worker(query :: Ecto.Queryable.t(), worker_name :: String.t()) :: Ecto.Query.t()
def where_worker(query, worker_name) do
from(j in query, where: j.worker == ^worker_name)
end

@doc """
Filters the query by the given queue name or job module.

When a module is given, the queue name is inferred from the module `queue` configuration.

## Examples

OnePiece.Oban.Query.new()
|> Oban.Query.where_queue(MyApp.Job)
|> Repo.all()

OnePiece.Oban.Query.new()
|> Oban.Query.where_queue("default")
|> Repo.all()
"""
@spec where_queue(query :: Ecto.Queryable.t(), queue_name_or_job_mod :: String.t() | module()) :: Ecto.Query.t()
def where_queue(query, queue_name) when is_binary(queue_name) do
from(j in query, where: j.queue == ^queue_name)
end

def where_queue(query, job_module) when is_atom(job_module) do
queue_name =
job_module
|> queue_name()
|> Kernel.to_string()

where_queue(query, queue_name)
end

@doc """
Filters the query to only include jobs whose arguments contain the given `args` map.

## Examples

OnePiece.Oban.Query.new()
|> Oban.Query.where_queue(MyApp.ConfirmationEmailJob)
|> Oban.Query.where_contains_args(%{"user_id" => "user_id"})
|> Repo.one()
"""
@spec where_contains_args(query :: Ecto.Queryable.t(), args :: map()) :: Ecto.Query.t()
def where_contains_args(query, args) do
from(j in query, where: fragment("? @> ?", j.args, ^args))
end

@doc """
Filters the query to only include jobs which are in the 'scheduled' state, i.e., jobs that are cancellable.

## Examples

OnePiece.Oban.Query.new()
|> Oban.Query.where_cancellable()
|> Repo.all()
"""
@spec where_cancellable(query :: Ecto.Queryable.t()) :: Ecto.Query.t()
def where_cancellable(query) do
from(j in query, where: j.state in ~w[scheduled])
end

defp queue_name(job_module) do
Keyword.get(job_module.__opts__(), :queue)
end
end
Loading
Loading