Has_many, through associations in Ecto

I'm still trying to figure out how to deal with creating / updating has_many, through: associations in Ecto . I re-read Jos's post on associations, as well as docs , but I'm still scared.

What I have:

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 Dish , inside which I have a key called dietary_prefs , which 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 options for "dish" removed for this SO record.)


How do I handle this in my controller? In particular, I want this behavior:

  • For POST requests (creating actions), create the necessary entries in dish_dietary_prefs to associate this new Dish with DietaryPref s data. Comma delimited string id for DietaryPref entries.
  • For PUT/PATCH requests (updates), create / delete the necessary entries in dish_dietary_prefs to update associations (users can re-assign dishes to different dietary preferences).
  • For DELETE queries, destroy dish_dietary_prefs . I think this case is already being handled with the on_delete configuration in the models.

I already have logic in my controller for creating / updating dishes for this provider (this is just a simple has_many/belongs_to ), but I still can’t figure out how to create / update / destroy these associations for this dish.

Any help would be greatly appreciated.


If I "must get the identifiers and manually create an intermediate association for each" DietaryPref , I contact Dish , can I get an example of how I will do this with the above specification in the controller?


UPDATE . Just make sure that Ecto 2.0.0-beta.1 is missing and this supports many_to_many , which looks like it will be the solution to my problem. Does anyone have an example of using it in action as described above?

+7
elixir phoenix-framework ecto
source share
1 answer

Thanks to the inimitable Jedi Master Jose Valim, I realized this (in Ecto 2.0.0-beta.1 ):

Here is 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

+8
source share

All Articles