Hello all,
I’m triing to create kind of feedback system with map, skipping some details I have campaign
which has_many question_categories
which has_many questions
which has_many options
(they are optional in case of open answer). On answer side, campaign
has_many entities
(point on map) which has_many responses
, which has_many answer_categories
(I would like to get rid of this table) which has_many answers
that belnogs_to question
and option
(in case closed answer). I draw this schema here.
Now I need 4 forms,
- I need form for create entity that would contain also answers for first responses from author of entity
- then I need form to add new response to existing entity
- form to update entity (with author response)
- form to update response
I two things:
-
Create form directly without AnswerCategories, but it turn out that I cant pass string to phoenix html form functions so it is imposible to select campaign a structure form in template, so form needs to be build to desired structure in changeset (this actuali kind of work but it was a lot of incredibli ugly code)
-
Also without AnswerCategories I made separate ecto structure named response form that took to changeset function as parameter campaign and created form structure from it, it looked good for case of editing responses, but then I wanted to integrate this response form to entity, I also need to add separate update entity form and separate validation and again it ended up quite big and mostly not working
-
Now I’m triing to get work structure with answer_category table (basicali table schema is dictate by UI needs) connect it throught has_many, and use cast_assoc, forms composition was easi, I just added has many to entity -> responses and when entity was creating, I cast first response form, there I hit the block because for some reason answers are not updating, they update only if I also somehow change answer_category it is like updates don’t propagate from 3# level nested associations and only thing that is updated is update time at response
Response schema
schema "responses" do
belongs_to :entity, Entity
has_many :answer_categories, Category, on_replace: :update
timestamps()
end
Response answer category
schema "response_answer_categories" do
field :answered?, :boolean, default: false, virtual: true
belongs_to :category, Category
belongs_to :response, Response
has_many :answers, Answer, on_replace: :delete
timestamps()
end
Response answer
schema "response_answers" do
field :answer, :string
belongs_to :question, Question
belongs_to :category, Category
belongs_to :option, Option
timestamps()
end
Changeset for response form
def create_changeset(entity, attrs) do
entity.campaign
|> put_change(:entity_id, entity.id)
|> cast_assoc(:answer_categories, with: & Category.changeset(&1, entity.campaign.question_categories, &2))
|> validate_required([:entity_id])
|> prepare_changes(& filter_answered_categories/1) # I filter out categories with answer? true
end
Changeset of answer category
def changeset(category, question_categories, attrs) do
category
|> cast(attrs, [:answered?, :response_id, :category_id])
|> validate_inclusion(:category_id, Enum.map(question_categories, & &1.id))
|> cast_answers(question_categories)
|> unique_constraint([:response_id, :category_id])
end
defp cast_answers(changeset, question_categories) do
category_id = get_field(changeset, :category_id)
question_category = Enum.find(question_categories, & &1.id == category_id)
cast_assoc(
changeset,
:answers,
with: &Answer.changeset(&1, get_field(changeset, :answered?), question_category.questions, &2)
)
end
Question changeset
def changeset(answer, validate, questions, attrs) do
answer
|> cast(attrs, [:answer, :question_id, :option_id, :category_id])
|> validate_inclusion(:question_id, Enum.map(questions, & &1.id))
|> validate_question(validate, questions)
|> unique_constraint([:category_id, :question_id])
end
- I was thinking about kind of wrapping structure that would validate and then result would by saved to database using ecto multi, for instance for create entity
embeded_schema do
field :entity, Entity,
field :response, Response
end
And then in context
def create_entity(form, attrs) do
changeset = Form.changeset(form, attrs)
if changeset.valid? do
Multi.new()
|> Multi.insert(:entity, get_field(changeset, :entity))
|> Multi.insert(:response, get_field(changeset, :response))
|> Repo.transaction()
else
apply_action(changeset, :insert)
end
end
But before I create pletohra custom forms that would write some other structures I would like to know your opinion, or if anybady solved similiar situation and know some more elegant solution. Part of problem that I somehov solved was also that structure of form is saved in database, which is solved by passing campaign
to changeset and conditionali doing validation, I also tried to first create structure with all the data prefilled but cast_assoc
was throving something like structure with empty primary key
. But that is probably adept for another question (I ended up building everithing in changeset using put_change
and changes
)
Thank you for any help