问题
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