Hello,
Here’s a playground project where I’m setting a project and its associations upon creation :
schema "projects" do
field :date_end, :date
field :date_start, :date
field :description, :string
field :display_date, :string
field :draft, :boolean, default: false
field :place, :string
field :price, :integer
field :text, :string
field :title, :string
field :url, :string
many_to_many :tags, Docs.Contents.Tag, join_through: "projects_tags"
many_to_many :images, Docs.Contents.Photo, join_through: "projects_photos"
has_many :translations, Docs.Contents.Translation, foreign_key: :target_id, where: [table: "projects"]
belongs_to :commanditaire, Docs.Contents.Commanditaire
belongs_to :main_image, Docs.Contents.Photo
belongs_to :category, Docs.Contents.Category
has_many :metadata, Docs.Contents.Metadata
many_to_many :blogs, Docs.Contents.Blog, join_through: "blogs_projects"
timestamps()
end
def changeset(
project,
%{
"tags" => t,
"commanditaire" => co,
"category" => ca,
"metadata" => m,
"images" => i,
"main_image" => mi,
"translations" => tr
} = attrs
) do
tags = Docs.Contents.DataUtilitites.get_or_create_tags(t)
commanditaire = Docs.Contents.DataUtilitites.get_or_create_commanditaire(co)
category = Docs.Contents.DataUtilitites.get_or_create_category(ca)
metadata = to_metadata(m)
translations = Docs.Contents.DataUtilitites.create_or_update_translations(tr, "projects", project)
images = to_images(i)
main_image = to_main_image(mi)
project
|> cast(attrs, [
:title,
:draft,
:url,
:description,
:date_start,
:date_end,
:display_date,
:text,
:price,
:place,
])
|> put_assoc(:tags, tags)
|> put_assoc(:main_image, main_image)
|> put_assoc(:category, category)
|> put_assoc(:commanditaire, commanditaire)
|> put_assoc(:images, images)
|> put_assoc(:metadata, metadata)
|> put_assoc(:translations, translations)
|> validate_required([
:title,
:draft,
:url,
:description,
:date_start,
:date_end,
:display_date,
:price,
:place,
:text
])
end
And the assoc fetch-or-create part :
defmodule Docs.Contents.DataUtilitites do
@moduledoc """
Contains helpers that are generic enough to serve
all of our CRUD models.
"""
def get_or_create_category(category) do
if is_binary(category) do
{:ok, c} = Docs.Contents.create_category(%{"name" => category, "slug" => Slug.slugify(category)})
c
else
Docs.Contents.get_category!(Map.get(category, "id"))
end
end
def get_or_create_tags(tags) do
{existing_tags, new_tags} = tags
|> Enum.reduce({[],[]}, fn tag, {ex, new} ->
if is_binary(tag) do
{:ok, new_tag} = Docs.Contents.create_tag(%{"name" => tag, "slug" => Slug.slugify(tag)})
{ex, [new_tag | new]}
else
db_tag = Docs.Contents.get_tag!(Map.get(tag, "id"))
{[db_tag | ex], new}
end
end)
existing_tags ++ new_tags
end
def create_or_update_translations(translations, table, obj \\ nil) do
Enum.map(translations, fn trans ->
if Map.has_key?(trans, "id") do
db_trans = Docs.Contents.get_translation!(Map.get(trans, "id"))
{:ok, db_trans} = db_trans |> Docs.Contents.update_translation(%{
"content" => Map.get(trans, "content")
})
db_trans
else
{:ok, db_trans} = Docs.Contents.create_translation(%{
"content" => Map.get(trans, "content"),
"table" => table,
"lang" => Map.get(trans, "lang"),
"target_id" => obj.id
})
db_trans
end
end)
end
def get_or_create_commanditaire(commanditaire) do
if is_binary(commanditaire) do
{:ok, c} = Docs.Contents.create_commanditaire(%{"name" => commanditaire, "slug" => Slug.slugify(commanditaire)})
c
else
Docs.Contents.get_commanditaire!(Map.get(commanditaire, "id"))
end
end
Some parts are redundant with what Ecto is capable of doing for you, or not-too-clean. It’s a test. You can find the whole thing here if you’d like : https://github.com/Lucassifoni/documents-next/tree/master/lib/docs/contents
Hoping it would be useful to you. Don’t pay too much attention to translation/form generation, it’s tangent to your question…






















