Skip to content

Associate a dynamic model to master records through a masters crosswalk column #1399

Description

@philayres

Requirement

When a dynamic model definition specifies a Foreign key name that matches a crosswalk column on the masters table (msid, pro_id, pro_info_id, contact_id), generate an association with the master that allows:

  • correct scoping in conditional calculations and curly brace substitutions, without needing a masters: scope directive
  • the dynamic model resource to be added directly to a masters tab panel

Background

This appears to have been possible at some point, but is currently broken. It may only ever have worked where the crosswalk column and the dynamic model table's own primary key had the same name — see the primary_key_name conflation below, which makes that the only case where both meanings of the setting coincide.

The behaviour was investigated while documenting scoping for #1381. Findings, each verified by a spec example in spec/models/definition_scoping_spec.rb:

  1. A Foreign key name other than master_id is unusable on its own. UserHandler.assoc_rules builds belongs_to :master, foreign_key: <foreign key name>, primary_key: <primary key name>, but UserHandler#master_id calls super, which needs a real master_id attribute. Saving a record raises NoMethodError: super: no superclass method 'master_id' from HandlesUserBase#check_crosswalk. The only working non-master_id foreign key is an external ID column combined with _configurations.foreign_key_through_external_id, which defines master_id explicitly on the implementation class.

  2. primary_key_name is conflated. It is used both as the dynamic model's own ActiveRecord primary key (Dynamic::DynamicModelImplementer#primary_key) and as the masters-side join column (belongs_to :master, primary_key: and Master.has_many ..., primary_key:). Pointing the join at masters.msid would require setting it to msid, which also breaks the model's own primary key.

  3. The keys are silently overridden anyway. DynamicModel#set_keys_from_columns is a before_create that forces foreign_key_name to master_id whenever the table has a master_id column, and primary_key_name to id whenever the table has an id column — regardless of what the admin entered.

  4. check_crosswalk's lookup branch is effectively unreachable. HandlesUserBase#check_crosswalk does attempt self.master_id = found_master.id when a crosswalk attribute holds a value and master_id is not set, but the lookup needs current_user, and for a model with a master association UserHandler#current_user returns master&.current_user — nil when the master has not been resolved. Saving raises FphsException: find_with_alternative_id requires a current_user.

  5. Standalone definitions are not shown in master panels. app/views/masters/_dynamic_model_blocks.html.erb skips definitions with no Foreign key name ("Only show this if there is a master association. Otherwise we tend to load a full database of junk records.").

Current workarounds

Both are verified and documented in docs/admin_reference/general/scoping.md.

  • Condition-time only. Match the crosswalk attribute against a field on the current record. This works even from a standalone definition, and correlates any further master-associated tables named in the same block to the matched master:

    creatable_if:
      all:
        masters:
          msid:
            this: survey_id
        player_contacts:
          rec_type: email

    It gives no master association, so substitutions still return blank and the records are not shown in master panels.

  • A view that resolves the master. Backing the definition with _configurations.view_sql gives a full master association, working substitutions, and a place in the master panels:

    _configurations:
      view_sql: |
        select s.*, m.id master_id
        from survey_data s
        inner join masters m on m.msid = s.survey_id

This second workaround is what the requirement would replace with a first-class configuration option.

Acceptance criteria

  • A dynamic model definition whose Foreign key name is a masters crosswalk column resolves master and master_id for its records, without requiring a view or an external identifier.
  • set_keys_from_columns no longer overrides a deliberately configured crosswalk foreign key.
  • Table conditions on such a definition use the default master join, with no masters: or no_masters: directive needed.
  • {{association_name.field_name}} substitutions resolve through the master.
  • The definition's records appear in master record panels in the same way as a master_id-based definition.
  • The masters-side join column is configurable independently of the dynamic model's own primary key, or is derived from the foreign key name, so that primary_key_name no longer has to serve both purposes.
  • Existing master_id and foreign_key_through_external_id definitions are unaffected.

Notes

  • spec/models/definition_scoping_spec.rb already asserts the current (broken) behaviour, including can not use a crosswalk column such as msid to drive the master association and forces the foreign key name to master_id when the definition is created. Those examples will need updating as part of the fix.
  • The Foreign key name and Primary key name admin field descriptions in app/models/admin/defs/dynamic_model_field_defs.yaml, and docs/admin_reference/general/scoping.md, currently document the crosswalk column as unsupported. They will need revising.

Related to #1381.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions