Skip to content

UseridDetail System

Vino-S edited this page Oct 2, 2026 · 2 revisions

UseridDetail System (Accounts & Roles)

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.

Role system

  • person_role (String, default 'researcher') is the primary role. secondary_role (Array) layers additional roles on top; get_roles/user_roles return secondary_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's OPTIONS hash (keyed by template_set) maps each role to the menu items it sees.
  • The coordinator hierarchy is geographic/organizational, not a strict tree: syndicate_coordinator (scoped via syndicate_groups), county_coordinator (county_groups), country_coordinator (country_groups), master_county_coordinator (broader), system_administrator (unrestricted). These *_groups array fields are where a coordinator's actual scope of authority lives — e.g. Contact-System's routing logic reads user.county_groups/country_groups directly.
  • Access gating repeats the same shape throughout the controller: PERMITTED_ROLES/STATS_PERMITTED_ROLES constants plus permitted_role?/permitted_secondary_roles?/permitted_users? (and the stats-specific twins stats_access_role?/stats_access_secondary?/stats_permitted_users?).

Three registration flows

Three public (no-login) entry points, each its own action + view, all funneling into the same create:

  • researcher_registration — gated by Rails.application.config.member_open; blank UseridDetail, session tagged 'New Registrant'.
  • transcriber_registration — same gate, plus a per-request honeypot (honeypot = 'agreement_' + rand.to_s[2..11], stored in session, checked by spam_check), and a syndicate picker via Syndicate.get_syndicates_open_for_transcription.
  • technical_registration — same gate, no honeypot, no syndicate picker; sets active = false on 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.

Key fields (grouped)

  • 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 via Devise::Encryptable::Encryptors::Freereg.digest — actual login happens through the mirrored Refinery User, 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 (default true), 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 of Message ids), 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.

Notable/risky methods

  • change_password doesn't set a password directly — it triggers Devise's send_reset_password_instructions on the mirrored User. If that Refinery record is missing, it just flashes a warning and logs: a silent failure mode.
  • disable toggles active; re-enabling clears the disabled-reason fields in one update_attributes and redirects immediately, while disabling itself falls through to render a form.
  • destroy hard-blocks if the user has_files? (any Freereg1CsvFile). If clear, it deletes the FreeREG userid folder then destroys the record, cascading via before_destroy :delete_refinery_user_and_userid_folder — which calls FileUtils.rmdir on the data folder. rmdir only 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 than FreeregOptionsConstants::CONFIRM_EMAIL_ADDRESS days — this is a periodic re-confirmation nag, not a one-time signup verification.

Known bugs / dead code

  • Duplicate save_to_attic method, 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_*_old controller methods (return_percentage_total_records_by_transcribers_old and 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 _old copies. Dead code, safe to delete.
  • transcription_agreement_value_change compares new_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_counties just returns a hardcoded array with no render/respond_to — looks like incomplete scaffolding rather than a real endpoint.
  • save_to_refinery vs update_refinery: save_to_refinery has commented-out add_role calls (#u.add_role('Refinery'), etc.), while the separate update_refinery method does call add_role. Role-granting on the mirrored Refinery user only actually happens on update, not initial creation — easy to misread if you only look at save_to_refinery.

Relationships

  • has_many :assignments, has_many :freereg1_csv_files, has_many :attic_files — both dependent: :restrict_with_error, i.e. deletion is blocked at the association level in addition to the controller's own has_files? check (belt and braces).
  • Syndicate — linked via field :syndicate, a plain string code rather than a real Mongoid relation, matched against Syndicate.syndicate_code. Same loose string-linking pattern as Contact#county/chapman_code (see Contact-System).
  • Message/Feedback — inbox tracking via userid_messages/userid_feedback_replies; see Message-System and Feedback-System.
  • ImageServerGroup — transcriber_statistics reads ImageServerGroup.unallocated_groups_count, cross-referencing into Image-Server-System.
  • County — coordinators_list cross-references County.distinct(:county_coordinator) and Syndicate.distinct(:syndicate_coordinator) into a combined coordinator directory.

Routes

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.

Clone this wiki locally