ReStructure is a research data management platform built on Rails 7 with a flexible, configuration-driven architecture. The system is designed around five core concepts:
- App Types: Encapsulate all configurations for an end-user application (like Zeus or Athena)
- Master Records: Central participant/subject records that everything relates to
- External Identifiers: Real-world numbering systems for people or entities represented by master records
- Activity Logs: Process management and case management workflows with embedded steps
- Dynamic Models: Runtime-generated Rails models from database configurations
Everything in ReStructure relates to a Master record (participant/subject). This is enforced through:
master_idforeign key on nearly all tables (exception: external identifiers before assignment)current_userpassed through master:master.current_usernotself.current_user- Access controls verified at master level:
master.allows_user_access - Controllers set user once:
@master.current_user = current_user
- Admin Panel: Configuration management at
/admin/*routes using database-stored YAML configs - User Interface: Single-page application with custom JavaScript front-end
- Filestore: NFS-based file management with Linux group security
- Background Jobs:
delayed_jobfor file processing and notifications - User Access Controls: Granular role-based permissions system controlling table/field access
ReStructure follows a hierarchical configuration pattern:
- App Type Configuration: Define the overall application scope and user roles
- External Identifier Setup: Configure real-world ID systems (SSN, study IDs, etc.)
- Activity Log Creation: Define main workflow processes and case management
- Activity Log Types: Configure individual workflow steps/activities within processes
- Embedded Dynamic Models: Create forms and data structures for each workflow step
This approach ensures consistent user experience and proper data relationships throughout the application.
Activity Logs provide case management through extra_log_types (individual workflow steps) configured in YAML:
step_name:
label: Step Label
fields: [field1, field2]
creatable_if: # Controls when this step can be created
all:
this:
status: 'previous_step_complete'
references: # Links to other records/models
- dynamic_model__some_model:
label: Related Item
from: this
add: manyKey Workflow Concepts:
extra_log_typeattribute stores which step a record represents (e.g., 'proposal_submission', 'review')creatable_ifconditions control sequential workflow - steps only appear when prerequisites are metreferencescreate relationships to other models (dynamic models, other activity logs)statusfield typically drives workflow state transitions- Each activity log process (defined in admin) generates a runtime model class in
ActivityLog::namespace
The platform's core feature is runtime model generation from database configurations:
How It Works:
- Admin creates/updates a
DynamicModelrecord with YAMLoptionsconfiguration after_save :generate_modelcallback triggers class generation- Runtime class created in
DynamicModel::namespace (e.g.,DynamicModel::TestData) - Database migration auto-generated and run to create/update table (NOTE: auto migrations are denied for the "ml_app" schema - spec tests should use the "dynamic_test" schema)
- Routes auto-generated via
DynamicModel.routes_reload - Master association added automatically:
has_many :dynamic_model__test_datas
Key Files:
app/models/dynamic/def_generator.rb: Core generation logic, memoization, regeneration triggersapp/models/dynamic/model_generator.rb: Parses configs, creates migrationslib/active_record/migration/app_generator.rb: Migration execution- Controllers inherit from
DynamicModelControllerHandlerfor generated models
Critical Pattern: Models are NOT code files - they're runtime-generated Ruby classes stored in memory. Changes to configs trigger regeneration:
# After creating/updating dynamic model admin config
# This happens automatically, but may need manual trigger in tests
DynamicModel.routes_reload
Rails.application.routes_reloader.reload!Memoization: Generated models cached in DynamicModel.models hash and Resources::Models - cleared on regeneration
All user-facing models inherit from UserBase through HandlesUserBase concern:
- Always requires authenticated user context via
current_user - Enforces master record associations (participant linking)
- Implements granular access controls through
user_access_controls - Uses crosswalk validation for external identifiers
ReStructure implements a sophisticated role-based permission system:
- App Types: Users belong to specific app types that determine their application scope
- User Access Controls: Database-driven permissions defining who can access what resources
- Resource-Level Security: Controls access to tables, fields, and specific records
- Role Hierarchy: Permissions cascade from app type → user role → specific resources
- Master-Based Security: All access is contextual to the master records users can see
Access control checks happen at multiple levels:
# Table-level access
user.has_access_to?(:create, :table, 'player_infos')
# Record-level access through master association
record.allows_current_user_access_to?(:edit)Most functionality is configured, not coded:
- Access Controls:
user_access_controlstable defines granular permissions - Form Rules: YAML configurations define field visibility and validation
- Process Workflows: Activity log configurations manage case processes
- Data Structures: Dynamic model configurations define database schema
- External ID Systems: Configure how real-world identifiers map to master records