I'm still trying to grok how to deal with creating/updating has_many, through:
associations in Ecto. I've re-read José's post on associations as well as the docs, but I'm still struggling.
What I have is this:
web/models/dish.ex
defmodule Mp.Dish do
use Mp.Web, :model
schema "dishes" do
# ...
has_many :dish_dietary_prefs, Mp.DishDietaryPref, on_delete: :delete_all,
on_replace: :delete
has_many :dietary_prefs, through: [:dish_dietary_prefs, :dietary_pref]
end
# ...
end
web/models/dietary_pref.ex
defmodule Mp.DietaryPref do
use Mp.Web, :model
schema "dietary_prefs" do
# ...
has_many :dish_dietary_prefs, Mp.DishDietaryPref, on_delete: :delete_all,
on_replace: :delete
has_many :dishes, through: [:dish_dietary_prefs, :dish]
end
# ...
end
web/models/dish_dietary_pref.ex
defmodule Mp.DishDietaryPref do
use Ecto.Schema
schema "dish_dietary_prefs" do
belongs_to :dish, Mp.Dish
belongs_to :dietary_pref, Mp.DietaryPref
end
end
I have a JSON endpoint that receives parameters for a Dish
, inside which I have a key called dietary_prefs
that is passed as a comma-delimited string, so, for example:
[info] POST /api/vendors/4/dishes
[debug] Processing by Mp.Api.DishController.create/2
Parameters: %{"dish" => %{"dietary_prefs" => "2,1"}, "vendor_id" => "4"}
(With additional parameters for "dish"
removed for this SO post.)
How do I handle this in my controller? Specifically, I want this behavior:
POST
requests (create actions), create the necessary records in dish_dietary_prefs
to associate this new Dish
with the given DietaryPref
s. The comma-delimited string are id
s for DietaryPref
records.PUT/PATCH
requests (updates), create/destroy the necessary records in dish_dietary_prefs
to update the associations (users can re-assign dishes to different dietary prefs).DELETE
requests, destroy dish_dietary_prefs
. I think this case is already handled with the on_delete
configuration in the models.I already have the logic in my controller to create/update dishes for a given vendor (which is just a simple has_many/belongs_to
relationship), but I still can't figure out how to create/update/destroy these associations for a given dish.
Any help would be greatly appreciated.
If I will "need to receive the IDs and manually build the intermediate association for each" DietaryPref
I am associating to the Dish
, could I get an example of how I would do that to the above specification in my controller?
UPDATE: Just seeing that Ecto 2.0.0-beta.1 is out, and that is supports many_to_many
, which looks like it would be a solution to my problem. Anyone have an example of using it in action, like I've described above?
9.0) Changesets allow filtering, casting, validation and definition of constraints when manipulating structs. There is an example of working with changesets in the introductory documentation in the Ecto module.
Unlike ActiveRecord, Ecto is not an ORM, but a library that enables the use of Elixir to write queries and interact with the database. Ecto is a domain specific language for writing queries and interacting with databases in Elixir.
Defines a repository. A repository maps to an underlying data store, controlled by the adapter. For example, Ecto ships with a Postgres adapter that stores data into a PostgreSQL database.
Schemas are maps from database tables into Elixir structs; the module provides everything you need to create them. Changeset. Changesets help validate the data that you want to insert into the database or modify.
Thanks to the inimitable Jedi-Master José Valim himself, I have got this figured out (in Ecto 2.0.0-beta.1
):
Here's my final controller:
def create(conn, %{"dish" => dish_params }, vendor) do
dietary_prefs = get_dietary_pref_changeset(dish_params["dietary_prefs"])
changeset = vendor
|> build_assoc(:dishes)
|> Repo.preload(:dietary_prefs)
|> Dish.changeset(dish_params)
|> Ecto.Changeset.put_assoc(:dietary_prefs, dietary_prefs)
case Repo.insert(changeset) do
{:ok, dish} ->
conn
|> put_status(:created)
|> render("show.json", dish: dish)
{:error, changeset} ->
conn
|> put_status(:unprocessable_entity)
|> render(ChangesetView, "error.json", changeset: changeset)
end
end
def update(conn, %{"id" => id, "dish" => dish_params}, vendor) do
dish = Repo.get!(vendor_dishes(vendor), id)
dietary_prefs = get_dietary_pref_changeset(dish_params["dietary_prefs"])
changeset = dish
|> Repo.preload(:dietary_prefs)
|> Dish.changeset(dish_params)
|> Ecto.Changeset.put_assoc(:dietary_prefs, dietary_prefs)
case Repo.update(changeset) do
{ :ok, dish } ->
render(conn, "show.json", dish: dish)
{ :error, changeset } ->
conn
|> put_status(:unprocessable_entity)
|> render(ChangesetView, "error.json", changeset: changeset)
end
end
defp vendor_dishes(vendor) do
assoc(vendor, :dishes)
end
defp parse_dietary_pref_ids(ids) do
ids
|> String.split(",")
|> Enum.map(fn(x) -> Integer.parse(x) |> Kernel.elem(0) end)
end
defp get_dietary_prefs_with_ids(ids) do
from(dp in DietaryPref, where: dp.id in ^ids) |> Repo.all
end
defp get_dietary_pref_changeset(param) do
param
|> parse_dietary_pref_ids
|> get_dietary_prefs_with_ids
|> Enum.map(&Ecto.Changeset.change/1)
end
https://groups.google.com/forum/#!topic/elixir-ecto/3cAi6nrsawk
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With