2016-03-02 99 views
7

我仍然試圖如何處理在Ecto中創建/更新has_many, through:關聯。我已經重新閱讀José's發佈的社團以及the docs,但我仍在掙扎。has_many,通過Ecto中的關聯

什麼我是這樣的:

網/模型/ 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 

網/模型/ 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 

網/模型/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 

我有一個Dish,在其內部我有一個稱爲被作爲逗號分隔的字符串傳遞dietary_prefs鍵接收參數的JSON端點,所以,例如:

[info] POST /api/vendors/4/dishes 
[debug] Processing by Mp.Api.DishController.create/2 
    Parameters: %{"dish" => %{"dietary_prefs" => "2,1"}, "vendor_id" => "4"} 

(配該SO撤職的"dish"附加參數。)


如何處理這在我的控制?具體而言,我想這種行爲:

  1. 對於POST請求(創建操作),在dish_dietary_prefs創造必要的記錄,以這個新Dish與給定DietaryPref的伴隨。以逗號分隔的字符串爲id s爲DietaryPref記錄。
  2. 對於PUT/PATCH請求(更新),創建/銷燬dish_dietary_prefs中的必要記錄以更新關聯(用戶可以將菜餚重新分配給不同的飲食偏好)。
  3. 對於DELETE請求,銷燬dish_dietary_prefs。我認爲這個案例已經在模型中配置了on_delete配置。

我已經有邏輯在我的控制器創建一個給定的供應商/更新菜餚(這僅僅是一個簡單的has_many/belongs_to關係),但我仍然無法弄清楚如何創建/更新/銷燬這些關聯對於一個給定的菜。

任何幫助將不勝感激。


如果我將"need to receive the IDs and manually build the intermediate association for each"DietaryPref我聯繫到該Dish,我能得到的我會怎麼做,要在我的控制器上面的規格的例子嗎?


UPDATE:只是看到外生2.0.0 beta.1出來了,那就是supports many_to_many,它看起來像這將是我的問題的解決方案。任何人都有一個使用它的例子,就像我之前所描述的那樣?

回答

8

由於採用了獨特的絕地武士,大師何塞·Valim自己,我有這個想通了(在外生2.0.0-beta.1):

這是我的最終控制人:

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

+0

請注意,您還必須使用'many_to_many'(而不是'has_many X,through:Y')來使'put_assoc/3'正常工作。查看顯示此內容的列表的Google羣組鏈接。 – Schrockwell