foam3

Updating Objects in a DAO

Table of Contents

  1. Quick Start
  2. Overview
  3. Architecture
  4. Key Concepts
  5. How It Works
  6. Usage
  7. Gotchas

Quick Start

To change a stored object, read it, copy it, edit the copy, then write the copy back:

var obj   = await dao.find(id);   // 1. read  (the returned object is shared; do not mutate it)
var clone = obj.clone();          // 2. copy  (a mutable duplicate)
clone.status = 'DONE';            // 3. edit  (set the exact property)
await dao.put(clone);             // 4. write (this is the save)

The put is the step people miss. A DAO does not watch your object for changes. It only records what you hand back to it. No put, no update.


Overview

What It Does

A DAO (Data Access Object) is the read/write interface to a collection of objects. Updating works the same way as creating: you call put with an object that carries the id you want to replace. The DAO overwrites the stored value with the one you pass.

The twist is that objects coming out of a DAO are shared with the DAO, and in Java they are frozen. You must not edit them in place. You clone first, edit the clone, and put the clone back.

Key Files

File Purpose
foam/lang/FObject.js Defines clone and copyFrom for the JavaScript side
foam/lang/FObject.java Defines fclone; throws Object is frozen. from assertNotFrozen
foam/dao/MDAO.java In-memory DAO; clones and freezes objects on store
foam/dao/MapDAO.java Map-backed DAO; calls obj.freeze() on store
foam/dao/FreezingDAO.js Decorator that freezes objects passing through

Architecture

A DAO is rarely a single object. It is a stack of decorators wrapping a storage core. A put travels down the stack, so validation, authorization, logging, and journaling all run on the object you hand in:

+-------------------------------------------------------------+
|                          DAO Stack                          |
|                                                             |
|   put(clone)                                                |
|      |                                                      |
|      v                                                      |
|  +-------------+   +-------------+   +-------------------+  |
|  | Validating  |-->|   Auth /    |-->|   Logging /       |  |
|  | Decorator   |   |  Permission |   |   Journaling      |  |
|  +-------------+   +-------------+   +-------------------+  |
|                                            |                |
|                                            v                |
|                                   +-----------------+       |
|                                   |  Storage Core   |       |
|                                   |  (MDAO / MapDAO)|       |
|                                   |  clone + freeze |       |
|                                   +-----------------+       |
+-------------------------------------------------------------+

The storage core freezes what it keeps. MDAO.java clones and freezes on the way in; MapDAO.java calls obj.freeze(). That frozen instance is what later find and select calls return, and it may be shared across threads, which is why mutating it is unsafe.


Key Concepts

Concept Description
frozen In Java, a stored object is read-only. Calling a setter on it fails. The DAO caches and shares these instances, so editing one would corrupt shared state.
clone / fclone Produces a mutable, independent copy. clone is the JavaScript method; fclone is the Java method.
copyFrom Bulk-applies property values from a map or another object onto a target. Useful for refreshing a live object after a write returns.
put / put_ Writes an object into the DAO, keyed by id. put uses the DAO’s own context; put_(x, obj) takes an explicit context.
id The key that decides insert versus update. Same id replaces the existing row; new id inserts.
put return value put returns the stored object (often re-frozen, sometimes enriched by decorators). Use it, do not assume your input is now the source of truth.

How It Works

Flow for updating an Object

+----------+    +----------+    +-----------+    +---------+    +---------+
|  find /  |--->|  clone   |--->|  set the  |--->|  put    |--->| stored  |
|  select  |    | (mutable |    |  property |    | (clone) |    | & frozen|
| (frozen) |    |  copy)   |    |  you want |    |         |    |  again  |
+----------+    +----------+    +-----------+    +---------+    +---------+
     ^                                                              |
     |                       returns the saved object               |
     +--------------------------------------------------------------+

Read returns a shared object (frozen in Java). Clone makes it editable. You set the exact properties. Put hands the clone to the DAO stack, which validates, authorizes, journals, and stores it, re-freezing the result. The value put returns is the canonical stored object.

Why the clone is required

The storage core deliberately freezes objects so they can be cached and shared safely. A getter on a frozen object works; a setter does not. On the Java side, the setter path runs assertNotFrozen, which throws Object is frozen when the object is frozen. On the JavaScript side, you would be mutating an instance the DAO still holds and shares, which silently corrupts cached state. Cloning sidesteps both problems: you own the copy outright.

Why the put is required

Cloning and editing change nothing in storage. The clone is a private copy in your code. The DAO has no reference to it and no change listener on it. The update becomes real only when you call put, which sends the clone through the decorator stack down to the storage core. Treat find as read, clone/edit as staging, and put as commit.


Usage

JavaScript: read, clone, edit, put

var fresh = await dao.find(id);   // shared with the DAO
var clone = fresh.clone();        // mutable copy
clone.status        = 'APPROVED';
clone.reviewedBy    = currentUser;
await dao.put(clone);             // committed

Re-reading with find right before the clone is a good habit. It reduces the window in which another writer changed the record between your read and your write.

JavaScript: refresh a live object from the write result

When the same object instance must reflect what was stored (for example, a view model bound to the UI), copy the returned value back onto it:

var saved = await dao.put(clone);
this.copyFrom(saved);   // pull stored values, including any set by decorators

copyFrom accepts either a plain map or another object. From a map it copies every key that names a property; from another object it copies the properties the two share, skipping values that are still at their default.

Java: read, fclone, edit, put_

MyModel frozen = (MyModel) dao.find(id);
MyModel clone  = (MyModel) frozen.fclone();   // mutable copy
clone.setStatus("APPROVED");
dao.put_(x, clone);                           // committed with explicit context

Use put_(x, obj) when you need to pass a specific context, such as inside a service, decorator, or rule. Use put(obj) when the DAO’s own context is correct.

Java: update many in a loop

Each object from a select is frozen, so clone inside the loop before editing:

List<MyModel> rows = ((ArraySink) dao.select(new ArraySink())).getArray();
for ( MyModel row : rows ) {
  MyModel clone = (MyModel) row.fclone();
  clone.setProcessed(true);
  dao.put_(x, clone);
}

JavaScript: update many in a loop

select with no sink resolves to an ArraySink; read its results from .array. Each object in that array is shared with the DAO, so clone inside the loop before editing, and await each put:

var sink = await dao.where(M.EQ(MyModel.PROCESSED, false)).select();
for ( var row of sink.array ) {
  var clone = row.clone();   // each row from select() is shared with the DAO
  clone.processed = true;
  await dao.put(clone);
}

The same rule as the single-object case applies per row: clone, edit the clone, put the clone. Editing row directly would either fail on a frozen instance (Java) or change an object the DAO still shares (JavaScript).

Choosing find versus select

Need Use
One object by id dao.find(id)
One object by predicate dao.find(predicate)
A list to iterate dao.select(new ArraySink())