So I'm currently learning Phoenix 1.3 and i want to learn ecto associations. I'm Using Angularjs for the frontend
I have two schema user.ex and profile.ex
Both Generated by mix phx.gen.json
user.ex
schema "users" do
field(:password_hash, :string)
field(:username, :string)
# Virtual fields:
field(:password, :string, virtual: true)
field(:password_confirmation, :string, virtual: true)
# this was added
has_one(:profiles, AuthApp.Accounts.Profile)
timestamps()end
profile.ex
schema "profiles" do
field :firstname, :string
field :lastname, :string
field :photo, :string
field :user_id, :id
belongs_to :user, AuthApp.Accounts.User # this was added
timestamps()end
profile controller
def create(conn, %{"profile" => profile_params}) do
with {:ok, %Profile{} = profile} <- Accounts.create_profile(profile_params) do
conn
|> put_status(:created)
|> put_resp_header("location", profile_path(conn, :show, profile))
|> render("show.json", profile: profile)
endend
My problem is I don't know what controller should I add the associations and how will it look like.
This is really up to you, you can do it both ways! However, some guidance / best practice style tips for this specific scenario.
If your app feature / page / etc is working primarily with one of those domain objects in mind, use that controller.
For example, if you are rendering a profile page, and offering various routes within it, or loading various pieces of data to populate the profile and enable features related to it, than returning a nested API response representing the data's relationship makes sense with the profile at the top level.
I would typically do something like this (starting where you've left off with the defined schemas and relationships):
Pick up the related data I want for my profile API request in the ProfileController
. I usually do this via a Context function that handles the preloading of the relationship. In this case that combination might look something like:
def show(conn, %{"id" => id}) do
profile = Accounts.get_profile!(id)
render(conn, "show.html", profile: profile)
end
Assuming something like an Account
context to hold the Profile
schema,
where Account.get_profile!/1 looks something like:
def get_profile!(id) do
query =
from(p in Account.Profile,
where: p.user_id == ^id,
select: p,
preload: [:user]
)
YourApp.Repo.get(query)
end
This ultimately results in an API response looking something like:
profile : {
photo: {},
user: {
id: "1"
}
}
Which is intuitive to work with when approaching from the perspective of enabling profile features.
However, a couple caveats:
I usually handle loading basic user information into the app via conn.assigns, session, and plug or some cache layer if using token based auth. If you get a chance, the Programming Phoenix book has great entry level examples of this approach, and the Guardian library has good info on the token based approach.
If you're conforming to a particular API standard, there will be guidelines there and best practices, so googling around for "REST" or "GraphQL" when X will yield helpful insight.
I ignored all the stuff about authentication/authorization etc, but a benefit of doing (1) is that you can safely scope API requests so a user couldn't just request a different profile ID from their own (which happens when making the basic profile request and preloading all of a users information afterwards).