I was thinking about relations spanning multiple context. Using natural example:
defmodule MyApp.Accounts.User do
use Ecto.Schema
schema "users" do
field :name, :string
field :email, :string
field :password, :string
has_many :posts, MyApp.Blog.Post
has_many :comments, MyApp.Blog.Comment
has_one :stats, MyApp.Metrics.Stats
end
end
it doesn’t look well. We create god schema, which knows about every other context and it’s schemas.
Alternatively, we can repeat schemas for different contexts:
defmodule MyApp.Accounts.User do
use Ecto.Schema
schema "accounts_users" do # or "users"
field :email, :string
field :password, :string
end
end
defmodule MyApp.Blog.User do
use Ecto.Schema
schema "blog_users" do # or "users"
field :name, :string
has_many :posts, MyApp.Blog.Post
has_many :comments, MyApp.Blog.Comment
end
end
Which provides nice separation, but doesn’t explain what to do when two contexts cross:
accounts_user = conn.assigns.current_user
# Accounts user know nothing about comments
MyApp.Blog.list_comments_of_user(accounts_user)
Now, how should list_comments_of_user be implemented?
We can read id from user, and then use it on comments:
def list_comments_of_user(%{id: user_id}) do
from c in Comments, where: [user_id: ^user_id]
end
which is fine for simple belongs_to association, but gets tricky in has_many through and many to many, or when foreign keys are not as obvious.
The best-case scenario would be to use Ecto.assoc, but Accounts.User knows nothing about posts.
The cleanest way would be to query db with user_id, load Blog.User and work with that.
I was thinking about different solution, namely:
def list_comments_of_user(user) do
cast_struct(user, to: %Blog.User{})
|> Ecto.assoc(:comments)
end
def cast_struct(data, target_struct) do
struct(target_struct, Map.from_struct(data))
end
This way we can pass user from any context, be it Blog, Accounts or whatever, and we always get the benefit of associactions (and domain-specific fields), which are defined in the same context. We can even change every Blog.User to Blog.Author, user_id to author_id, and Blog part of the app becomes blisfully unaware of such things like users, passwords or emails.
What do you think about this approach?