Skip to content

Commit 4b98fbe

Browse files
sakshamarora1ntarocco
authored andcommitted
docs: internals: update system fields relations segment
1 parent b60be24 commit 4b98fbe

File tree

1 file changed

+40
-56
lines changed

1 file changed

+40
-56
lines changed

docs/maintenance/internals/systemfield.md

Lines changed: 40 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,10 @@ Relations are specialized system fields that manage connections between records
170170

171171
Relations consist of several components:
172172

173-
- **RelationsField**: the main system field that manages multiple relations.
174-
- **Relation Classes**: define how to resolve and validate specific relation types.
173+
- **Relation Fields**: like `RelationsField`, `MultiRelationsField` manage multiple relation definitions on a record, acting as a container for individual relation configurations and providing the interface for accessing relations.
174+
- **Mapping Classes**: provides the interface for attribute access for managing relations on a record.
175+
- **Relation Classes**: like `PKRelation`, `ListRelation` define how to resolve and validate specific relation types.
175176
- **Result Classes**: handle the returned values when accessing relations.
176-
- **Mapping Class**: provides the interface for managing relations on a record.
177177

178178
### Primary key relations (PKRelation)
179179

@@ -196,6 +196,7 @@ class ArticleRecord(Record, SystemFieldsMixin):
196196

197197
# Usage
198198
article = ArticleRecord({'metadata': {'creator_id': 'user123'}})
199+
# article.relations is an instance of `RelationsMapping` class
199200
creator = article.relations.creator() # Returns User record instance
200201
article.relations.parent = parent_article # Set relation
201202
```
@@ -221,7 +222,7 @@ class ArticleRecord(Record, SystemFieldsMixin):
221222

222223
# Usage
223224
article.relations.authors = [user1, user2, user3] # Set multiple
224-
for author in article.relations.authors(): # Iterate resolved records
225+
for author in article.relations.authors(): # Iterate resolved records, an instance of `RelationListResult` class
225226
print(author.name)
226227
```
227228

@@ -443,58 +444,6 @@ class GeolocationField(SystemField):
443444
raise ValueError("Value must be GeoPoint instance")
444445
```
445446

446-
## Idempotence and data consistency
447-
448-
System fields are designed to be idempotent - running the same operation multiple times should produce the same result:
449-
450-
```python
451-
class IdempotentComputedField(SystemField):
452-
"""Field that computes values idempotently."""
453-
454-
def __get__(self, record, owner=None):
455-
if record is None:
456-
return self
457-
458-
# Check if value is already computed and valid
459-
computed_data = record.get('_computed', {})
460-
if self.attr_name in computed_data:
461-
stored_hash = computed_data[self.attr_name]['hash']
462-
current_hash = self._compute_input_hash(record)
463-
464-
if stored_hash == current_hash:
465-
# Data hasn't changed, return cached result
466-
return computed_data[self.attr_name]['value']
467-
468-
# Compute new value
469-
value = self._compute_value(record)
470-
471-
# Store with hash for future idempotence checks
472-
record.setdefault('_computed', {})[self.attr_name] = {
473-
'value': value,
474-
'hash': self._compute_input_hash(record)
475-
}
476-
477-
return value
478-
479-
def _compute_input_hash(self, record):
480-
"""Hash the input data used for computation."""
481-
482-
# Hash relevant fields that affect the computation
483-
relevant_data = {
484-
'title': record.get('metadata', {}).get('title'),
485-
'authors': record.get('metadata', {}).get('authors', [])
486-
}
487-
488-
data_str = json.dumps(relevant_data, sort_keys=True)
489-
return hashlib.md5(data_str.encode()).hexdigest()
490-
491-
def _compute_value(self, record):
492-
"""Perform the actual computation."""
493-
# Example: compute a search-friendly version of the title
494-
title = record.get('metadata', {}).get('title', '')
495-
return title.lower().replace(' ', '_')
496-
```
497-
498447
## Record lifecycle hooks deep dive
499448

500449
Understanding when each lifecycle hook is called is crucial for proper system field implementation:
@@ -703,3 +652,38 @@ When designing system fields, consider:
703652
- Testing strategies
704653

705654
By following these patterns and best practices, you can create robust, maintainable system fields that enhance InvenioRDM's capabilities while maintaining clean, readable code.
655+
656+
## Best practices
657+
658+
!!! note "Idempotence and data consistency"
659+
While system fields are intended to be idempotent, there is no strict enforcement of this principle. Developers are advised to design their implementations to respect idempotence and ensure data consistency.
660+
661+
```python
662+
class IdempotentComputedField(SystemField):
663+
"""Field that computes values idempotently."""
664+
665+
def __get__(self, record, owner=None):
666+
if record is None:
667+
return self
668+
669+
# Check if value is already computed and valid
670+
computed_data = record.get('_computed', {})
671+
if self.attr_name in computed_data:
672+
stored_hash = computed_data[self.attr_name]['hash']
673+
current_hash = self._compute_input_hash(record)
674+
675+
if stored_hash == current_hash:
676+
# Data hasn't changed, return cached result
677+
return computed_data[self.attr_name]['value']
678+
679+
# Compute new value
680+
value = self._compute_value(record)
681+
682+
# Store with hash for future idempotence checks
683+
record.setdefault('_computed', {})[self.attr_name] = {
684+
'value': value,
685+
'hash': self._compute_input_hash(record)
686+
}
687+
688+
return value
689+
```

0 commit comments

Comments
 (0)