rubysg / rubysg-reboot

Website for http://ruby.sg
33 stars 65 forks source link

A basic form creation and submission framework #338

Closed TayKangSheng closed 3 years ago

TayKangSheng commented 3 years ago

I want to create forms on RubySG so that I can collect feedbacks from meetup participants.

The requirements are:

  1. No admin page to CRUD forms. Ruby.sg currently does not have admin pages and I do not want to set a precedence for it.
  2. I want to be able to have multiple forms and collect feedbacks for each. This is so that I can create one feedback form for each event (eg. meetup)

Approach

  1. I want to be able to create forms with code.

    # this is pseudo code
    class ThisIsAForm
      title "Meetup - Jan 2021"
    
      questions [{
        question: "How would you rate today's meetup?"
        ...
      }, {
        question: "Any suggestions to make this better?"
        ...
      }]
    end
  2. A url should be immediately available for the form so that I can answer the question
    https://ruby.sg/forms/this_is_a_form

How did I do it?

The implementation consists of 4 parts:

1. FeedbackForm objects

The implementation of FeedbackForm objects is heavily influenced by Form Object Pattern and Meta-Programming style in RoR. Every form object inherits the FeedbackForm class and is namespace with FeedbackForm so as not to confuse with general usage Form Object Pattern in the future. feedback_form.rb provides the template for all feedback forms so that they all can share the same view code.

Every feedback form just needs to inherit FeedbackForm and provide the title and questions. With the use of meta programming, I was able to create a simple and clean interface for creation of feedback forms which automatically set up validations and attr_accessors for use when building the forms. See app/forms/feedback/behind_the_gems2.rb for an example of a feedback form.

2. Controller

The controller relies on #constantize by combining the :form_type param in the URL and the FeedbackForm namespace convention to find the form class. I did also consider using Service Locator Pattern to look up classes but I wanted even lesser code so I decided to just set a convention for the FormController to look up classes. Therefore only form objects created in app/forms/feedback/ will work for this set up.

3. View

Since all form objects inherits FeedbackForm, the view code can be used for all Feedback form objects. We just need to loop through all the questions and create the input accordingly. This view only supports 2 types of input fields which from UX perspective we can consider to add more input types only if its really necessary.

The view uses ConversationalForms, a messenger style of form filling.

Screenshot 2021-09-13 at 1 07 53 AM

4. FormResponse model

This is a basic model to collect all type of form response. It only includes a :form_type key to identify which form does this record belongs to and also a json dump of all the responses provided by the user. I chose to use a json column type because the list of questions can be different for each form so this is a simple way to handle the response for all the different forms.

How does it look like?

https://user-images.githubusercontent.com/10722197/132996973-40450a44-1a78-4119-8db3-4c7328492902.mov

How to test?

  1. $ bin/rails db:migrate
  2. $ bin/rails s
  3. go to http://localhost:3000/forms/behind_the_gems2
  4. Try and answer the questions and submit the form
weimeng commented 3 years ago

Having forms as code is cool, and avoiding proprietary tools/services is admirable.

How are you going to handle the form data collected? I see you're storing it in the database -- will you plan to also make the data available to the public?

TayKangSheng commented 3 years ago

How are you going to handle the form data collected? I see you're storing it in the database -- will you plan to also make the data available to the public?

@weimeng At the moment, I don't have any plans to use the data collected other than to gauge response for the meetup and collect feedback on how to make it better. Open to suggestions on ideas for how to use the information. 😀

TayKangSheng commented 3 years ago

@adriangohjw

Feel like code base might bloat up overtime?

Yea. But I actually kind of want to keep the feedback forms over time. I feel that 1/ we likely won't have a lot anyway, 2/ the feedback form will sort of be an artifact that can provide more context of how rubysg evolved.

Also, might be confusing for subsequent maintainers if they don't have any context if the code is still used

Given the current set up, I don't think that it will be confusing if the code is still used. Since a creation of a form class will lead to a new url for the form. Removing the class will just remove the url and the ability for people to give more feedback.

will a in-between solution be to store the form and questions in the DB and instantiate the FeedbackForm object from the data instead?

But this will mean that we will have to somehow get a record in the db, and we will need to have a "forms" table with associations with "form_responses" table which I am trying to avoid. Even if without the questions in readable text, i'm hoping that the key of the question will provide enough context about the question asked if the the form class is eventually deleted. For example

{
  rating: 4,
  feedback: "The event was great, just that I think it can be better!"
}
TayKangSheng commented 3 years ago

The reviews were dismissed because of a setting for this project. Will see if I can get a ✅ tomorrow, if not I'll merge it anyway.