Skip to content

Conversation

davidmillen50
Copy link
Contributor

@davidmillen50 davidmillen50 commented Aug 23, 2025

Add a column to member table called how_you_found_us and is populated with an array depending on options selected. How you found us is a mandatory field on the onboarding page and if not added then warning text will appear. Adding the 'how you found us' reason to a user's profile will follow in another PR.

Making the 'other' option a checkbox that reveals a text input would likely require some bespoke javascript code as simple forms don't allow that. I have therefore just left the text field visible and removed the 'other' option so the implementation is kept simple.

The options selected including the other text field are saved into the db in the members table under the how_you_found_us column as an array.

The below red message appears if you try to click next without selecting a reason

Screenshot 2025-08-26 at 14 34 05

@davidmillen50 davidmillen50 force-pushed the issue-1827-add-how-you-found-us-to-onboarding branch from 720d71f to 4af4d4f Compare August 26, 2025 13:21
@davidmillen50 davidmillen50 marked this pull request as ready for review August 26, 2025 14:02
Add spacing around how you found us section
Move validation from model to controller so validation doesn't block new user creation and is only valid for the member details page.
Fix failing tests and move other_reason into member params and give param name more description
@davidmillen50 davidmillen50 force-pushed the issue-1827-add-how-you-found-us-to-onboarding branch from 4af4d4f to 73f9c9e Compare August 27, 2025 12:12
Copy link
Collaborator

@olleolleolle olleolleolle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made invidividual comments about details, mostly about Parameters.

@@ -0,0 +1,84 @@
RSpec.describe Member::DetailsController, type: :controller do
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: type metadata for tests is derived from directory, omit it, for clarity.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Collaborator

@olleolleolle olleolleolle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is ready to go, 🐱, and when Rails 7.1 is in prod, we should merge this.

@mikej
Copy link
Contributor

mikej commented Sep 1, 2025

@olleolleolle @davidmillen50 can I give some feedback on this one before it gets merged in please? 🙏🏻

Copy link
Contributor

@mikej mikej left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi David!

Before diving into specific suggestions of code changes here the first thing is a couple of suggestion around the data model that might simplify things and make the data easier to analyse in the future.

The first is to separate the option strings displayed to the user from the values in the database. e.g. to display as "Search engine (Google etc.)" but to store as an enum value such as search_engine. This means if we want to change the text displayed to the user in the future we don't end up with slightly different strings in the database corresponding to effectively the same answer.

I would also consider storing the "other" option into its own separate database field rather than combining it into a single list with the standard options. This has the benefit from the data side in that the array contains only standard values and custom values are kept separate, and it should also simplify the controller code and remove the need for a lot of the custom logic there.

Also, although Kimberley's original issue for this mentioned check box options it could be worth asking her if we definitely want the user to be able to select multiple options, or if this should be a single choice via radio buttons/dropdown list instead? While it's technically possible for a member to have heard about codebar through multiple routes, maybe what we're really interested in is just the one that prompted them to actually sign up?

Hope this is useful. And if you decide to make some of the controller/view changes and would like a hand then just let me know.

Cheers

flash[notice] = I18n.t('notifications.signing_up')
@member.newsletter ||= true
end

def update
return render :edit unless @member.update(member_params)
how_found = Array(member_params[:how_you_found_us]).reject(&:blank?)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think all of the logic here should be moveable to a combination of the member_params method and validation on the model such that the controller action can stay as a simple @member.update(member_params)

For instance the handling of Rails' blank check box can be abstracted away into member_params.

I'll write a general comment as part of this review with a couple of thoughts and suggestions on the data model used, and if applying those suggestions it will hopefully simplify the update action.

I can see that it makes things trickier if you're wanting to require "how found" here, but not on other updates to a member (e.g. when an existing member updates their own profile). However, I think it would be good to have a look at the other suggestions I'll make and then see how this is looking. But one option would be to have an "unknown" value rather than blank for existing members and then the validation can live on the model.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've refactored the controller to move some of the logic to the concern. Moving the validation to the model will block any instances where a user is created or edited which aren't the onboarding page or the edit profile page.

.col-12.mb-3
%label{ for: 'how_you_found_us' }= t('member.details.edit.how_you_found_us')
%span *
- options = ['From a friend', 'Search engine (Google etc.)', 'Social Media', "One of Codebar's hosts or partners"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this list of standard options should be pulled out into a constant rather than living in the view here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -15,6 +15,18 @@
= f.input :about_you, as: :text, label: t('member.details.edit.coach.about_you'), input_html: { rows: 3 }, required: true
- else
= f.input :about_you, as: :text, label: t('member.details.edit.student.about_you'), input_html: { rows: 3 }, required: true
- if @member.errors[:how_you_found_us]&.any?
Copy link
Contributor

@mikej mikej Sep 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be simplifiable into two f.input lines: one for the checkboxes and one for the "other" text field.

Simple Form can take care of displaying the labels for each check box value and for displaying any validation errors for the fields.

Simple Form detects that an attribute on the model is an array so you should just need to provide the collection of allowed values and the indication of whether to use check boxes vs. radio buttons.

Something like:

f.input :how_you_found_us, collection: <a constant for the list of options>, as: :check_boxes

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Typo: inout -> input.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've implemented this for the most part.

= check_box_tag 'member[how_you_found_us][]', option, false, id: "how_you_found_us_#{option.parameterize}", class: 'form-check-input'
= label_tag "how_you_found_us_#{option.parameterize}", option, class: 'form-check-label', style: 'margin-left: 8px;'
= label_tag :how_you_found_us_other_reason, t('member.details.edit.how_you_found_us_other_reason'), class: 'my-1'
= text_field_tag 'member[how_you_found_us_other_reason]', nil, placeholder: "Please specify how you heard about us", class: 'form-control w-100'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once using Simple Form f.input for the field the placeholder text can be moved to en.yml and will be picked up automatically.

@davidmillen50
Copy link
Contributor Author

Also, although Kimberley's original issue for this mentioned check box options it could be worth asking her if we definitely want the user to be able to select multiple options, or if this should be a single choice via radio buttons/dropdown list instead? While it's technically possible for a member to have heard about codebar through multiple routes, maybe what we're really interested in is just the one that prompted them to actually sign up?

@KimberleyCook do you have a preference for radio buttons or check boxes?

Move options consolidation to member concern and refactor view to use f.input for checkboxes and 'other' text field
@KimberleyCook
Copy link
Contributor

KimberleyCook commented Sep 10, 2025

@davidmillen50 I think I want users to only be able to choose one option, as @mikej said I am really interested in the option that made them sign up

@@ -1,6 +1,13 @@
class Member < ApplicationRecord
include Permissions

HOW_YOU_FOUND_US_OPTIONS = [
'From a friend',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would still be worthwhile these being enum-style values rather than the label text. So e.g. social_media, host_or_partner etc.

When using f.input for the collection you can pass a label_method that controls how the values are turned into the actual text to display to the user. e.g. to look this up at i18n strings:

label_method: ->(option) { t("found_us.#{foption}") }

@mikej
Copy link
Contributor

mikej commented Sep 16, 2025

Hi @davidmillen50

Thanks for making some of the changes here.

I've just added a follow up comment about how we might still use enum values.

When you get the chance, would you like to take a look at switching this over to just choosing a single option and then we can take a another look at how this is looking overall?

Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants