UNPKG

adonis-odm

Version:

A comprehensive MongoDB ODM for AdonisJS with Lucid-style API, type-safe relationships, embedded documents, and transaction support

148 lines (147 loc) 6.08 kB
/** * AttributeManager - Handles attribute getting, setting, and management * * This class encapsulates all the logic for managing model attributes, * including getting, setting, filling, and merging attributes. */ export class AttributeManager { model; constructor(model) { this.model = model; } /** * Fill model with attributes */ fill(attributes) { for (const [key, value] of Object.entries(attributes)) { const metadata = this.model.constructor.getMetadata(); const columnOptions = metadata.columns.get(key); // Skip reference fields (virtual properties) and computed properties if (columnOptions?.isReference || columnOptions?.isComputed) { continue; } let processedValue = value; if (columnOptions?.deserialize) { processedValue = columnOptions.deserialize(value); } // If this is a column with a property descriptor, use the private key if (columnOptions && !columnOptions.isReference && !columnOptions.isComputed) { const privateKey = `_${key}`; this.model[privateKey] = processedValue; } else { ; this.model[key] = processedValue; } } return this.model; } /** * Merge new attributes into the model */ merge(attributes) { for (const [key, value] of Object.entries(attributes)) { const metadata = this.model.constructor.getMetadata(); const columnOptions = metadata.columns.get(key); // Skip reference fields (virtual properties) and computed properties if (columnOptions?.isReference || columnOptions?.isComputed) { continue; } if (this.getAttribute(key) !== value) { this.setAttribute(key, value); // setAttribute already handles dirty tracking, but we need to ensure it's set if (this.model.$isPersisted) { this.model.$dirty[key] = value; } } } return this.model; } /** * Get an attribute value */ getAttribute(key) { const metadata = this.model.constructor.getMetadata(); const columnOptions = metadata.columns.get(key); // If this is a column with metadata (and not a reference), use the property getter // which will access the private key through the property descriptor if (columnOptions && !columnOptions.isReference) { return this.model[key]; } // For properties without metadata or reference fields, access directly return this.model[key]; } /** * Set an attribute value */ setAttribute(key, value) { const metadata = this.model.constructor.getMetadata(); const columnOptions = metadata.columns.get(key); // Skip setting reference fields (virtual properties) and computed properties - they have getters only if (columnOptions?.isReference || columnOptions?.isComputed) { return; } if (columnOptions?.deserialize) { value = columnOptions.deserialize(value); } // If this is a column with a property descriptor, use the private key if (columnOptions) { const privateKey = `_${key}`; const oldValue = this.model[privateKey]; this.model[privateKey] = value; // Track dirty attributes if the model is persisted and value changed if (this.model.$isPersisted && oldValue !== value) { this.model.$dirty[key] = value; } } else { // For properties without column metadata, check if it's a getter-only property const descriptor = Object.getOwnPropertyDescriptor(this.model, key) || Object.getOwnPropertyDescriptor(Object.getPrototypeOf(this.model), key); if (descriptor && descriptor.get && !descriptor.set) { // This is a getter-only property, skip setting it return; } // Set directly for properties without metadata const oldValue = this.model[key]; this.model[key] = value; // Track dirty attributes if the model is persisted and value changed if (this.model.$isPersisted && oldValue !== value) { this.model.$dirty[key] = value; } } } /** * Get dirty attributes * Returns attributes with database column names (snake_case) for database operations */ getDirtyAttributes() { const metadata = this.model.constructor.getMetadata(); const namingStrategy = this.model.constructor.namingStrategy; const dirtyAttributes = {}; // Convert property names to database column names and apply serialization for (const [propertyName, value] of Object.entries(this.model.$dirty)) { const columnOptions = metadata.columns.get(propertyName); // Skip reference fields (virtual properties) and computed properties if (columnOptions?.isReference || columnOptions?.isComputed) { continue; } // Convert property name to database column name using naming strategy const dbColumnName = namingStrategy.columnName(this.model.constructor, propertyName); // Apply serialization if defined for this column let serializedValue = value; if (columnOptions?.serialize) { serializedValue = columnOptions.serialize(value); } dirtyAttributes[dbColumnName] = serializedValue; } return dirtyAttributes; } /** * Sync original values with current values */ syncOriginal() { this.model.$original = { ...this.model.toDocument() }; this.model.$dirty = {}; } }