Syndicate System - FreeUKGen/MyopicVicar GitHub Wiki
Syndicate System
Part of Transcriber & Coordinator Workflow.
Models: app/models/syndicate.rb, app/models/manage_syndicate.rb ยท Controllers: app/controllers/syndicates_controller.rb, app/controllers/manage_syndicates_controller.rb
A syndicate is a volunteer transcription group, usually scoped to a county/region, run by one syndicate_coordinator. There are two very different things here despite the similar names:
Syndicateis the real record โ the entity itself, plain CRUD.ManageSyndicateis not a real persisted record despite includingMongoid::Documentโ it's a throwaway form object for a syndicate picker, plus a namespace for two static query helpers. The actual class doing work isManageSyndicatesController, which is a session-driven coordinator dashboard that never CRUDs aSyndicateitself โ it just redirects into other resources' views (freereg1_csv_files,physical_files,userid_details, image-group views) scoped to whichever syndicate is insession[:syndicate].
Syndicate
Key fields: syndicate_code, syndicate_description, syndicate_notes, accepting_transcribers (Boolean โ gates whether new transcribers can join), syndicate_coordinator (+ a _lower_case twin for case-insensitive lookup), and a rename/handoff audit trail: previous_syndicate_code, previous_syndicate_coordinator, changing_name.
Changing the coordinator or code has real side effects (Syndicate#update_fields_before_applying, run on update):
- Old coordinator: the syndicate code is removed from their
syndicate_groupsarray, and if that leaves them with zero remaining groups, they're demoted fromperson_role: 'syndicate_coordinator'back to'transcriber'. - New coordinator: the code is added to their
syndicate_groups, and if they were a'transcriber'or'researcher', they're promoted toperson_role: 'syndicate_coordinator'. - None of this runs in a transaction โ it's a sequence of
update_attributes/save(validate: false)calls, so a failure partway through can leave an old coordinator demoted with no new coordinator promoted. after_save :propagate_change_in_codeโ renaming a syndicate code triggers a bulk update of everyUseridDetailwhosesyndicatefield matches the old code. This is a full-collection scan/update on every rename, not scoped to syndicate members via a join.destroyis blocked while anyUseridDetailstill has thatsyndicatecode โ must be emptied of members first.
Relationships: has_many :assignments; reads/writes UseridDetail (syndicate, syndicate_groups, person_role) directly rather than through a service object; Syndicate.syndicate_code(code) is the standard lookup used elsewhere (e.g. Contact-System's FreeCEN routing).
Worth knowing: Syndicate.productive_volunteers(syndicate, start_date, end_date) is a stats helper that queries FreecenCsvFile โ despite the generic name, it's FreeCEN-specific, not template-set-agnostic.
Routes: resources :syndicates plus syndicates/display, syndicates/select, syndicates/selection.
ManageSyndicate / ManageSyndicatesController
The coordinator's "manage my syndicate" dashboard. Flow:
newworks out which syndicate(s) the logged-in coordinator can manage, role-gated (volunteer_coordinator/data_manager/system_administrator/etc., plus FreeCEN-onlycounty_coordinator). Exactly one match โ auto-selected intosession[:syndicate], redirect straight past the picker toselect_action. Multiple matches โ shows the picker.select_actionshows a menu of dashboard options (UseridRole::SYNDICATE_MANAGEMENT_OPTIONS).- Every other action reads
session[:syndicate]and either renders or redirects into another resource's view scoped to it โ batch lists (freereg1_csv_files/freecen_csv_files/physical_files), member management (userid_details), or image-transcription review (image_server_group).
Notable footgun โ routing dispatch via one-off constraint classes. Roughly 15 of these dashboard actions all share one route path, manage_syndicates/selection, and Rails picks which controller action actually runs via custom routing constraint classes in app/constraints/ that string-match params[:option] (e.g. a constraint checks request.query_parameters['option'] == 'Review All Members'). This is the same "dispatch on a human-readable label" pattern as Message-System's MessagesController#create (which dispatches on submit-button text) โ except here it's pushed down into the routing layer as a separate class per option. Adding a new dashboard option means adding both a route line and a matching constraint class, and there's already a live typo from this fragility: the list_fully_transcribed_group route (config/routes.rb:318) is registered under manage_syndicatess (double s) โ that path silently doesn't match rather than erroring.
Relationships: reads Syndicate, UseridDetail (syndicate_groups, person_role), ImageServerGroup.group_ids_by_syndicate, and the various CSV-file/PhysicalFile models for batch listings.
Routes: resources :manage_syndicates plus the constraint-dispatched manage_syndicates/selection (~15 distinct actions behind one path), manage_syndicates/select_action, manage_syndicates/display_files_not_processed, manage_syndicates/display_files_waiting_to_be_processed, manage_syndicates/:id/selected, manage_syndicates/manage_image_group, manage_syndicates/:id/list_fully_reviewed_group, and the typo'd manage_syndicatess/:id/list_fully_transcribed_group.