after_validation equivalent to create ancestors

[亡魂溺海] 提交于 2020-01-25 10:14:14

问题


The application stores locations which

  • can belong_to one parent
  • can have multiple ancestors

During the create_location I want to add just the parent_id. The ancestors should be created automatically (including a position field to store the hierarchy level). The tree is not endless. We are talking about 4 levels.

Example:

USA <- California <- San Francisco <- Mission St

Setup

mix phx.new example
cd example
mix ecto.create
mix phx.gen.html Maps Location locations name parent_id:references:locations
mix phx.gen.html Maps Ancestor ancestors location_id:references:locations \
    ancestor_id:references:locations position:integer
mix ecto.migrate

lib/example/maps/location.ex

defmodule Example.Maps.Location do
  use Ecto.Schema
  import Ecto.Changeset
  alias Example.Maps.Location

  schema "locations" do
    field :name, :string
    belongs_to :parent, Location
    many_to_many :ancestors, Location, join_through: "ancestors"

    timestamps()
  end

  @doc false
  def changeset(location, attrs) do
    location
    |> cast(attrs, [:name, :parent_id])
    |> validate_required([:name])
  end
end

lib/example/maps/ancestor.ex

defmodule Example.Maps.Ancestor do
  use Ecto.Schema
  import Ecto.Changeset

  schema "ancestors" do
    field :position, :integer
    belongs_to :location, Maps.Location
    belongs_to :ancestor, Maps.Location

    timestamps()
  end

  @doc false
  def changeset(ancestor, attrs) do
    ancestor
    |> cast(attrs, [:position, :location_id, :ancestor_id])
    |> validate_required([:position, :location_id, :ancestor_id])
    |> assoc_constraint(:ancestor)
    |> assoc_constraint(:location)
  end
end

Example

Assuming I want to create the above example:

alias Example.Maps
Maps.create_location(%{name: "USA"})
Maps.create_location(%{name: "California", parent_id: "1"})
Maps.create_location(%{name: "San Francisco", parent_id: "2"})
Maps.create_location(%{name: "Mission St", parent_id: "3"})

After that the table ancestors should contain the following entries:

| id | location_id | ancestor_id | position |
|----|-------------|-------------|----------|
| 1  | 2           | 1           | 1        |
| 2  | 3           | 2           | 1        |
| 3  | 3           | 1           | 2        |
| 4  | 4           | 3           | 1        |
| 5  | 4           | 2           | 2        |
| 6  | 4           | 1           | 3        |

In ActiveRecord I could handle this with an after_validation callback (not thinking about the anti never use after_validation for this war but solve it in the controller) which loops through the parents and creates the ancestors with that information (plus acts_as_list to fill the position field).

How can I solve this problem best in Ecto?

来源:https://stackoverflow.com/questions/59887353/after-validation-equivalent-to-create-ancestors

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!