Replies: 9 comments 16 replies
-
We have a bit of time before we need to conclude this by about mid-August-ish... |
Beta Was this translation helpful? Give feedback.
-
Although I was involved in writing this, I should probably give my preference. I'm leaning towards option 2 because it seems like the changes will be far less drastic, so less likely to drag on and bring up major difficulties. Option 1 is conceptually quite nice, but in practice probably doesn't give us too much of an advantage over option 2. I think option 2 will also end up being easier to understand in the code, as the polymorphism introduced by option 1 isn't completely straightforward. From what I can tell for option 2, there are very few changes that need to be made that will affect forms without question sets with the exception of a small migration for the |
Beta Was this translation helpful? Give feedback.
-
I like the abstraction of having Would implementing steps be possible in a way that retains backwards compatibility (to ease the migration)? |
Beta Was this translation helpful? Give feedback.
-
In my opinion option 1 is conceptually closer to what we are actually trying to model, which is nice. I find the structure of the JSON and session data much clearer at a glance. It does sound like the migration itself would be pretty involved though, but as others have mentioned perhaps it is worth us paying that cost now to make future features easier. |
Beta Was this translation helpful? Give feedback.
-
According to @SamJamCul there may be changes to this needed based on changes to design. We have some time as we are going to start with Add an "answer" to a single question. |
Beta Was this translation helpful? Give feedback.
-
I've been messing around with another option, which I'd like to get some feedback on! Option 3: Questions and Sets are equal tier stepsThis is some sort of hybrid option, that makes use of the step by step construction - but keeps every question on the same tier, and uses sets as positioning elements for form journeys. There will be a need to be able to identify which questions are contained within a set, and this can be done either by associating them with the set, or by adding another object that represents the end of a set Option 3a: Sets belong to questionsIn this option, steps that come after a set will be connected back to that set, which can be used to determine the set details. When reaching a set, the runner can recognise that it needs to behave differently until the user progresses past the set's last step. I think the advantage here is that the runner more closely matches our mental model of how forms work, as each step should have information about what to do next. flowchart TD
Form --- Step1(Step 1)
Form --- Step2(Step 2)
Form --- Step3(Step 3)
Form --- Step4(Step 4)
Form --- Step5(Step 5)
Step1 --- Question1(Question 1)
Step2 --- Set(Set)
Step3 --- Question2(Question 2)
Step4 --- Question3(Question 3)
Step5 --- Question4(Question 4)
Mermaid diagrams unfortunately don't keep the order of step objects, so joining the set to each contained question results in a messy diagram. Each step that falls within the set will have an attribute that keys it to that set. I've draw up some extra lines in this image to show what I mean! Option 3b: Sets have a start and finish stepIn this option, the end of the set is demarcated by another object. This makes it extremely easy for the runner to recognise when it's reached the end of a set of questions, and it decouples questions within the set from the set itself. It's not completely necessary to do this though, but I like the idea of being able to shift the start and end points of the set to add and remove questions. I think this might be more palatable if the design changes to the extent that we're using sets more as marker points in the UI. flowchart TD
Form --- Step1(Step 1)
Form --- Step2(Step 2)
Form --- Step3(Step 3)
Form --- Step4(Step 4)
Form --- Step5(Step 5)
Form --- Step6(Step 6)
Step1 --- Question1(Question 1)
Step2 --- Set(Set 1)
Step3 --- Question2(Question 2)
Step4 --- Question3(Question 3)
Step5 --- Question4(Question 4)
Step6 --- EndSet(EndSet 1)
|
Beta Was this translation helpful? Give feedback.
-
@SamJamCul what parts of option 1 depend on the design? I.e. what changes in the design could lead to a change in the data model? |
Beta Was this translation helpful? Give feedback.
-
I've spent a while playing around with these options, to try and understand the pros and cons. MethodI've coded up some example transformations in https://gist.github.com/lfdebrux/e9ff91c07935302383806f065672a2f0 to try and make things concrete. I tried converting an existing form document and a potential question set record into the different formats, and I then looked at how easy it would be to get data out of the object and/or manipulate it. Note that the code in the gist is not production ready code or even particularly good; I tried as much as possible to keep all the logic in one function for each transformation because what I wanted to measure was how complicated the operation was, and being more DRY and abstract would have obscured that. After writing this code and getting it working I analysed it using some of the metrics from RuboCop, here's the results:
Discussion
In Option 1 the information about whether a question is in a set is implicit in the structure of the object. The same is true in Option 3b, but the information is a little harder to determine computationally, so we need to rely on the extra linkage more. It is important to note though that we do not necessarily have to use the same structures in forms-admin as we use for form documents sent to forms-runner; so moving questions or question sets will probably not be done in the way I've coded here. However I think it is a good proxy for the measuring the complexity of the object structure. ConclusionI think this analysis shows the advantage of making a bigger change to the form document structure; I think we should decide on either Option 1 or Option 3b, with a preference for Option 1. |
Beta Was this translation helpful? Give feedback.
-
This is paused as the feature that requires the next stage is being deprioritised for now. We will return to this when appropriate and leave open for further comments in the time being. |
Beta Was this translation helpful? Give feedback.
-
Summary
Adding another answer is an upcoming feature for Forms. This will require backend changes in order to build, run and store questions that can have multiple answers.
Problem
Currently, we only support answering a form's questions once per submission.
We want to allow forms to include
Questions will have a minimum and maximum number of answers that can be given, which the form builder will specify for each question or set of questions.
Question sets can take place at any point within a form.
We want to support routing to the start of a question set, and skipping over a question set. Routing will not lead into the middle of a question set, or from within a question set to the outside. We will likely want to also support routing between questions within a question set.
The current data structure for a form in the API is roughly:
A
PAGE
contains question data and its position within the form.A
PAGE
can have one or moreCONDITIONs
, which are used to describe conditional routing from that page.Proposal
Options
Option 1: Separate out "steps" from "questions" and "question sets"
This option separates pages into "Steps" and "Questions", with a new "QuestionSet" type representing a set of questions. A "Step" will either point to a "Question" or "QuestionSet" using the polymorphic associations feature of Rails.
Forms-api database schema
The new
STEP
entity represents the position of a question or question set in a form.The new
QUESTION
entity represents the properties of a question for theSTEP
they belong to.The new
QUESTION_SET
entity represents a set ofSTEPs
that can be anwered multiple times. Each of theSTEPs
within aQUESTION_SET
will have aQUESTION
.We use a polymorphic association on the
STEP
entity (the 'positionable') to establish a one-to-one association with either aQUESTION
or aQUESTION_SET
.Steps have
min_answers
andmax_answers
values, which indicate how many times the question or set of questions can be answered.JSON representation of a form used by Runner
This is the JSON structure for storing a live form that runner retreives from forms-api.
In the proposed structure, the
pages
field is replaced withsteps
. Each "step" in thesteps
array has atype
field, which indicates whether it is a Question or a QuestionSet, and adata
field which contains either a Question or QuestionSet object.A QuestionSet object contains a
steps
field which contains an array of Step objects for that QuestionSet. These Step objects will always have atype: "Question"
.Runner session data structure
When a user is filling out a form, we store their completed answers in a session stored in a Redis database. The session currently has the following data structure:
For the new data model with question sets we think the data structure of the session would be something like:
Pros
Cons
Option 2: Keep pages holding their position in a form, whilst also being able to assign them to sets
This option keeps the "Pages" model largely the same, with the position remaining a property of a page. A page can have a "question set" associated with it, which holds information about the set it is in, such as the name and minimum and maximum answers.
Forms-api database schema
In this model, we add a
question_set_id
foreign key to aPAGE
. This is used to identify when a question is part of a question set.The
next_page_id
will point to the next question in the form regardless of whether it is in aQUESTION_SET
or not. For the last page in a set, thenext_page_id
will point to the next page after the set. This indicates that once a user has finished providing as many repeat answers to the question set as they need to, they will be directed to this page next. Runner will inevitably need more logic in addition to the existing routing logic to determine which page a user is shown next when sets are introduced.When the position of a
QUESTION_SET
within a form is changed, we'd have to update both thenext_page_id
of the proceeding page, and also thenext_page_id
of the final page in the set.The
postion
attribute of aPAGE
has been replaced byposition_in_form
andposition_in_set
attributes. Theposition_in_form
will be set if the question is not in a question set, or is the first page of a question set. Theposition_in_set
attribute will identifty the position of a question within a set.JSON representation of a form used by Runner
In the JSON structure, the
pages
array holds all pages in a form. All entries are at the same level, regardless of whether they belong to a set.We identify pages in a set by the
question_set_id
. There is a separatequestion_sets
attribute on the form object which is an array of all the question sets in a form.An entry in the
question_sets
array has afirst_page
, similar to thestart_page
on a form, to easily identify the first page in the set. We could also store thelast_page
if this makes the navigation logic in Runner easier.Runner session data structure
The runner session data structure should not have to change much, except the value associated with a page will be an array when the page is either a single question that can be answered multiple times, or belongs to a question set.
If we wanted to allow for questions within a set to be themselves answered multiple times per set of answers, we might have difficulties with this data structure.
Pros
Cons
Beta Was this translation helpful? Give feedback.
All reactions