2016-03-02 90 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