jeffdeville
Situation
I’m working on a multi-tenant library for Ecto. Ecto structs and queries let you set a prefix in the meta area, which is awesome. But it’s getting really verbose to send every query and changeset through a set_prefix() method before forwarding it along to the Repo.
Thoughts / Ideas
-
This may simply be the elixir/functional way. After all, less magic is good, and not hiding your function inputs is good too.

-
Currying - Currying isn’t quite what I want because I’m currying a module, not a function. (Also, I’m not changing the number of parameters required)
-
Wrapping - I have a repo wrapper that verifies that a prefix was set on relevant Ecto structs. This is easy because I’m just verifying that SOMETHING was set, which means I can build it at compile time with macros. But what I’d really like to be able to do is:
repo = Repo.prefixify(123)
user = User.changeset(%User{}, %{name: “joe”})
repo.insert(user)
and have it sent to the 123 prefix. Automatically. Then later,
loaded_user = repo.get!(User, user.id)
and have it know to pull from prefix 123.
So repo itself is a module.
Agents seem like the closest strategy here. However, it seems like with an agent, you have to keep track of your agent’s pid. Then you call your regular module, just including the pid as an argument. But at this point, all I’ve done is traded passing in a prefix for a pid.
Is there any way to pass this in once, and be done with it? If not, no worries. I just don’t want to let my unfamiliarity with the language limit the easy of use of my library.
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixirconf-us
- #ai
- #blog-post
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #hex
- #security










Showing Posts 1 to 9- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
jeffdeville
Nov 2, 2016 Update
So indeed my tests were where I was feeling the most pain.
I worked out a solution in 2 parts for that.
When I get back to this project, I’ll look into @josevalim’s insight about Ecto 2.1, and its ability to accept the prefix as a keyword to Repo operations. That is probably the ‘good enough’ solution right there!
Oct 31, 2016 Update
Update for future readers. I realized that the place I was noticing all of this painful duplication was in my specs. But my specs are usually only using a single prefix, and part of the struggle was with ex_machina needing a new (prefix) parameter that hosed its lovely strategy pattern. So the compromise solution I’m going with now is to create a test-only import file that will wrap ex_machina, letting me pass in a tenant that is defaulted to a ‘test’ tenant. And then also wrappers around the Repo that do the same. So far (this is a work in process), I’m not planning to use the wrappers in my production code, because my methods are short, and so I’m comfortable seeing the specification of the tenant. Hopefully, once this is is all complete, I’ll bake it in to the library docs.
That said, if there’s a solution to my original question that I’ve missed, I’d still really like to know!
pba
You could use the pipe operqator
|>and instead of:do
Also if the module where you to this is
Userspecific you might considerimport UserEDIT: Expanded original answer
Regarding the prefix: How about using something like this:
and then
josevalim
One option is to use the process dictionary along side custom functions in the repository. So you would do:
And then:
Keep in mind I am using Ecto 2.1 (currently out as a release candidate) ability to pass the prefix as an option to all Repo operations.
jeffdeville
Thank you for the detailed writeup, @pba. I wasn’t as clear as I should have been on what I was trying to achieve. The problem I’m struggling with was how to write the function Repo.prefixify(123), such that it returned a version of the Repo that would apply the given prefix to either the query or changeset appropriately. The problem is that the prefix will change based on each request. I’m using postgresql’s schemas to isolate client data. As a result, I can’t set the prefix in a config. Poor explanation on my part. Thank you for the input!
jeffdeville
@josevalim Great to know about 2.1! To be honest, that might just represent a solid solution right there.
The process solution seems ideal, depending on how processes work in Phoenix. Does each request get its own process? (ie: Could I write a plug that would call MyApp.Repo.put_prefix, and not have to worry that that setting could impact concurrent processes? I would assume there’s a pool of these. Is there a way to ensure I clear that setting before checking it back into the pool? (In case of an exception)
OvermindDL1
Each request gets its own process. The Erlang scheduler ensures nothing shared between processes. There is no pool of processes (rather a pool of memory, it is low level stuff, just assume erlang does things right, because it does ^.^), no need to clear it unless you want it cleared for your current request for some reason.
michalmuskala
I’d say we missed the obvious solution here - anonymous functions.
If we were talking about a single function instead of whole repo - this would be obvious with using partial application, wouldn’t it?
So can we do something similar for a module? We need to do some changes, mainly because now we need to decide at runtime which function to call, fortunately we can use
apply/3.This allows us to call:
It’s a bit different than the original, but achieves the goal. Is it worth it and should be done? That’s a completely different question
jeffdeville
Awesome! Great to know more about the innards there. Thank you, @OvermindDL1!
jeffdeville
prefixed_insert = &Repo.insert(&1, prefix: "foo")←I didn’t know that was possible. Very slick. I think the drawback here (using apply on the entire module) is that you lose the use of the |> operator.
I think Ecto 2.1’s new method signature is likely to be a good enough solution here. Unfortunately, it looks like my ex_machina PR (Allow passing a func to customize structs by jeffdeville · Pull Request #176 · beam-community/ex_machina · GitHub) is going to be rejected. Not quite sure how I’ll work around that.