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:
-
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.
-
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.
-
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.
-
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.
-
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.
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:masters:scope directiveBackground
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_nameconflation 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:A Foreign key name other than
master_idis unusable on its own.UserHandler.assoc_rulesbuildsbelongs_to :master, foreign_key: <foreign key name>, primary_key: <primary key name>, butUserHandler#master_idcallssuper, which needs a realmaster_idattribute. Saving a record raisesNoMethodError: super: no superclass method 'master_id'fromHandlesUserBase#check_crosswalk. The only working non-master_idforeign key is an external ID column combined with_configurations.foreign_key_through_external_id, which definesmaster_idexplicitly on the implementation class.primary_key_nameis 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:andMaster.has_many ..., primary_key:). Pointing the join atmasters.msidwould require setting it tomsid, which also breaks the model's own primary key.The keys are silently overridden anyway.
DynamicModel#set_keys_from_columnsis abefore_createthat forcesforeign_key_nametomaster_idwhenever the table has amaster_idcolumn, andprimary_key_nametoidwhenever the table has anidcolumn — regardless of what the admin entered.check_crosswalk's lookup branch is effectively unreachable.HandlesUserBase#check_crosswalkdoes attemptself.master_id = found_master.idwhen a crosswalk attribute holds a value andmaster_idis not set, but the lookup needscurrent_user, and for a model with a master associationUserHandler#current_userreturnsmaster&.current_user— nil when the master has not been resolved. Saving raisesFphsException: find_with_alternative_id requires a current_user.Standalone definitions are not shown in master panels.
app/views/masters/_dynamic_model_blocks.html.erbskips 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:
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_sqlgives a full master association, working substitutions, and a place in the master panels:This second workaround is what the requirement would replace with a first-class configuration option.
Acceptance criteria
masterandmaster_idfor its records, without requiring a view or an external identifier.set_keys_from_columnsno longer overrides a deliberately configured crosswalk foreign key.masters:orno_masters:directive needed.{{association_name.field_name}}substitutions resolve through the master.master_id-based definition.primary_key_nameno longer has to serve both purposes.master_idandforeign_key_through_external_iddefinitions are unaffected.Notes
spec/models/definition_scoping_spec.rbalready asserts the current (broken) behaviour, includingcan not use a crosswalk column such as msid to drive the master associationandforces the foreign key name to master_id when the definition is created. Those examples will need updating as part of the fix.app/models/admin/defs/dynamic_model_field_defs.yaml, anddocs/admin_reference/general/scoping.md, currently document the crosswalk column as unsupported. They will need revising.Related to #1381.