Repository navigation
UseridDetail System
Part of Transcriber & Coordinator Workflow.
Model: app/models/userid_detail.rb · Controller: app/controllers/userid_details_controller.rb
The single account/profile/role model for every human actor in the app — researchers, trainees, transcribers, and every flavor of coordinator/admin, across both FreeREG and FreeCEN. It's Mongoid, not Devise directly — it's mirrored into a parallel Devise User record for actual login (save_to_refinery/update_refinery — the "refinery" names are historical), with callbacks trying to keep the two in sync. See Authentication-System. This is the biggest, most heavily-used model in this domain; if you're new to the codebase, start here.
-
person_role(String, default'researcher') is the primary role.secondary_role(Array) layers additional roles on top;get_roles/user_rolesreturnsecondary_role + [person_role], uniqued. - The full 25-role vocabulary lives in
lib/userid_role.rb::UseridRole::VALUES—researcher,trainee,transcriber,syndicate_coordinator,county_coordinator,country_coordinator,master_county_coordinator,system_administrator,technical,contacts_coordinator,volunteer_coordinator,website_coordinator,general_communication_coordinator,genealogy_coordinator,publicity_coordinator,project_manager,executive_director,engagement_coordinator,documentation_coordinator,data_manager,pieces_coordinator,image_server_coord,validator,checker,computer,pending. The same file'sOPTIONShash (keyed bytemplate_set) maps each role to the menu items it sees. - The coordinator hierarchy is geographic/organizational, not a strict tree:
syndicate_coordinator(scoped viasyndicate_groups),county_coordinator(county_groups),country_coordinator(country_groups),master_county_coordinator(broader),system_administrator(unrestricted). These*_groupsarray fields are where a coordinator's actual scope of authority lives — e.g. Contact-System's routing logic readsuser.county_groups/country_groupsdirectly. - Access gating repeats the same shape throughout the controller:
PERMITTED_ROLES/STATS_PERMITTED_ROLESconstants pluspermitted_role?/permitted_secondary_roles?/permitted_users?(and the stats-specific twinsstats_access_role?/stats_access_secondary?/stats_permitted_users?).
Three public (no-login) entry points, each its own action + view, all funneling into the same create:
-
researcher_registration— gated byRails.application.config.member_open; blankUseridDetail, session tagged'New Registrant'. -
transcriber_registration— same gate, plus a per-request honeypot (honeypot = 'agreement_' + rand.to_s[2..11], stored in session, checked byspam_check), and a syndicate picker viaSyndicate.get_syndicates_open_for_transcription. -
technical_registration— same gate, no honeypot, no syndicate picker; setsactive = falseon the resulting account, i.e. technical accounts start disabled pending manual approval.
spam_check requires the honeypot field to arrive blank and the submission to take more than 5 seconds (diff <= 5 is flagged as spam) — and it logs raw PII (email, userid, forename/surname, IP, host) to a plaintext log/spam_check_error_messages.log on every suspected hit. Worth knowing from a data-handling standpoint.
Post-create routing (next_place_to_go_successful_create/_unsuccessful_create) branches on the literal params[:commit] button value — the same "dispatch on button label" pattern documented on Message-System.
-
Identity/profile:
userid,userid_lower_case(auto-set),person_forename/person_surname(auto-titleized),email_address(+alternate_email_address),address,telephone_number. -
Role/scope:
person_role,secondary_role,syndicate,syndicate_coordinator,syndicate_groups/county_groups/country_groups. -
Account lifecycle:
active,sign_up_date,disabled_date,disabled_reason_standard/disabled_reason,previous_syndicate. -
Auth:
password/password_confirmation/digest, hashed viaDevise::Encryptable::Encryptors::Freereg.digest— actual login happens through the mirrored RefineryUser, not directly on this model. -
Agreements:
transcription_agreement/technical_agreement/research_agreement(Booleans),new_transcription_agreement(String:"Unknown"/"Accepted"/"Declined"/"Requested"). -
Email validity workflow:
email_address_valid(defaulttrue),email_address_last_confirmned(typo baked into the field name — it's spelled that way in the schema, not a doc typo),email_address_validity_change_message(append-only audit array). -
Messaging inbox:
userid_messages(Array ofMessageids),userid_feedback_replies(Hash: feedback id → reply message id) — ties into Message-System and Feedback-System. -
Misc UX:
favorite_actions(see Favorite-Actions-System),do_not_acknowledge_me/acknowledge_with_pseudo_name/pseudo_name(opt out of public credit for transcription work),skill_level/fiche_reader/skill_notes.
-
change_passworddoesn't set a password directly — it triggers Devise'ssend_reset_password_instructionson the mirroredUser. If that Refinery record is missing, it just flashes a warning and logs: a silent failure mode. -
disabletogglesactive; re-enabling clears the disabled-reason fields in oneupdate_attributesand redirects immediately, while disabling itself falls through to render a form. -
destroyhard-blocks if the userhas_files?(anyFreereg1CsvFile). If clear, it deletes the FreeREG userid folder then destroys the record, cascading viabefore_destroy :delete_refinery_user_and_userid_folder— which callsFileUtils.rmdiron the data folder.rmdironly removes empty directories, so if anything's left in there, this silently no-ops instead of raising or warning. -
need_to_confirm_email_address?is true if the address is already invalid, or if the last confirmation is older thanFreeregOptionsConstants::CONFIRM_EMAIL_ADDRESSdays — this is a periodic re-confirmation nag, not a one-time signup verification.
-
Duplicate
save_to_atticmethod, defined twice verbatim in the model (once as one of the first methods, again later in the file) — same name, same body. Ruby just silently uses whichever is defined last; not broken today, but a copy-paste trap for anyone editing one copy without noticing the other. -
return_percentage_*_oldcontroller methods (return_percentage_total_records_by_transcribers_oldand three siblings) duplicate near-identical class methods already on the model (without_old).transcriber_statistics— the only action that computes these stats — calls the model versions; nothing calls the controller_oldcopies. Dead code, safe to delete. -
transcription_agreement_value_changecomparesnew_transcription_agreement == 0(Integer) in one branch and== "1"(String) in another. Since values arrive from params as strings, the integer-comparison branch likely never matches in practice. -
scotland_countiesjust returns a hardcoded array with no render/respond_to— looks like incomplete scaffolding rather than a real endpoint. -
save_to_refineryvsupdate_refinery:save_to_refineryhas commented-outadd_rolecalls (#u.add_role('Refinery'), etc.), while the separateupdate_refinerymethod does calladd_role. Role-granting on the mirrored Refinery user only actually happens on update, not initial creation — easy to misread if you only look atsave_to_refinery.
-
has_many :assignments,has_many :freereg1_csv_files,has_many :attic_files— bothdependent: :restrict_with_error, i.e. deletion is blocked at the association level in addition to the controller's ownhas_files?check (belt and braces). -
Syndicate— linked viafield :syndicate, a plain string code rather than a real Mongoid relation, matched againstSyndicate.syndicate_code. Same loose string-linking pattern asContact#county/chapman_code(see Contact-System). -
Message/Feedback— inbox tracking viauserid_messages/userid_feedback_replies; see Message-System and Feedback-System. -
ImageServerGroup—transcriber_statisticsreadsImageServerGroup.unallocated_groups_count, cross-referencing into Image-Server-System. -
County—coordinators_listcross-referencesCounty.distinct(:county_coordinator)andSyndicate.distinct(:syndicate_coordinator)into a combined coordinator directory.
Custom, non-resourceful (config/routes.rb, roughly lines 419-450): confirm_email_address, role, secondary, person_roles, secondary_roles, :id/change_password, researcher_registration, transcriber_registration, technical_registration, general, :id/disable, :id/move, :id/syndicate, my_own, all, select, selection, options, display, incomplete_registrations, transcriber_statistics, list_users_handle_communications, list_roles_and_assignees, plus resources :userid_details.
© 2025 FreeUKGen. Licensed under Apache License 2.0