miguelcoba

miguelcoba

Oban on phoenix apps with scope contexts

Hi there, I would like to know your thoughts about how to use Oban workers in a Phoenix app with Scopes.

When using scopes the context functions receive as first parameter the scope, f.e.

# lib/my_app/blog.ex
def list_posts(%Scope{} = scope) do
  Repo.all(from post in Post, where: post.user_id == ^scope.user.id)
end

If I needed to use the list_posts inside a worker, I’ll need somehow to build a scope in order to use those scoped functions:

defmodule MyApp.MyWorker do
  use Oban.Worker

  @impl Oban.Worker
  def perform(%Oban.Job{args: %{"id" => id}}) do
    scope = # ? how to get the scope at the time the worker runs

    list = Blog.list_posts(scope)
    # Do something with list
    :ok
  end
end

Option 1.

Pass the scope (or enought data to rebuild the scope) as params to `Worker.new()`

%{user_id: scope.user.id, other: "data"}
|> MyApp.Worker.new()
|> Oban.insert()

and in the worker use user_id to build a `scope` and continue from that.

Option 2.

Add additional heads in the Context that don’t require scope and are only used by trusted callers (in this case Oban)

# lib/my_app/blog.ex
# additional function head that does not use scope
def list_posts(user_id) do
  Repo.all(from post in Post, where: post.user_id == ^user_id)
end

defmodule MyApp.MyWorker do
  use Oban.Worker

  @impl Oban.Worker
  def perform(%Oban.Job{args: %{"id" => user_id}}) do
    # use the non-scoped functions
    list = Blog.list_posts(user_id)
    # Do something with list
    :ok
  end
end

None of the options is optimal, but from those two, the option 2 is the worst as it totally breaks the idea of scopes in the context modules by allowing anyone to bypass the scoped functions and use the non-scoped ones.

So I’m leaning on passing enough info to the worker params and use that to build a scope inside it and then use it to do whatever thing the worker needs to do using the scoped context functions.

Is there another way?

Do you have suggestions/ideas/experience implementing something like this?

Thanks in advance,

Miguel Cobá

Most Liked

tfwright

tfwright

My point was that this is all just business logic, so yeah. There is no “idea of scopes” that can be violated in this way. Usually validating at the edges is fine but depending on the specific requirements of the app/business it might not be. Even if you need to care about permission changes (it might even be the case roles/access are immutable!), it can also go the other way, that you need to respect what they were at the time of the request, or it might be necessary to raise a more specific error, etc etc. Checking on mount in a LV may very well be acceptable or exactly what’s required. I don’t think there is any general rule for any of this, and so there is no utility that Phoenix can/does provide that you can use to ensure you are using scopes “the correct way,” it’s just a nudge to start with a basic pattern in place.

pjode

pjode

I think it depends on the type of worker. If the job being run is a deferred action a user is making that should be run in the scope of a user, putting the data necessary to reconstruct a user scope in the job is the right choice IMO. If the job is something that executes in a more privileged way, I actually define a new scope for that. For example, I have a scope defined for ingest and use that in function heads instead of a user scope. So you could imagine something like

defmodule MyApp.Accounts do
  def create_user(%Accounts.Scope{admin: true} = scope, attrs) do
    # ...
  end

  def create_user(%Ingest.Scope{} = scope, attrs) do
    # ...
  end
end

and again you’d store what you need in the job to construct such a scope. This way functions still expect some form of scope regardless.

miguelcoba

miguelcoba

Interesting. I was trying to follow the docs and use the scope to harden/validate access to data. I didn’t even consider to break that “contract” as it seemed to me like diverging of the “good practices”. Maybe I should relax my view and view unscoped functions that are called from places where validation has already happened and it is safe to do so, is ok.

Yeah, maybe I’m reading too much and assigning too much weight to them and that introduces unflexibility

I’ll take a good thinking on this.

Thank you!

Where Next?

Popular in Questions Top

9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
hariharasudhan94
lets say i have a sample like a = 20; b = 10; if (a > b) do {:ok, "a"} end if (a < b) do {:ok, b} end if (a == b) do {:ok, "equa...
New
Emily
I have VueJS GUIs with the project generated using Webpack. I have Elixir modules that will need to be used by the VueJS GUIs. I forese...
New
RisingFromAshes
I’ve read in another post that it may be possible with a router helper - but I couldn’t find an appropriate one, and tbh, I’m still just ...
New

Other popular topics Top

aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
Nvim
Anybody knows a comprehensive comparison of Django and Phoenix, thanks for the help. Where are they similar? Where do they differ the m...
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
komlanvi
Hi everyone, I was playing with phoenix liveView but I run into an issue. I have a form and want to validate each input text when the te...
New
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New

We're in Beta

About us Mission Statement