Record - GadgetRecord 

When working with an instance of a record in Gadget (either results returned from the client package or the context.record in your code effects), you are working with a GadgetRecord. GadgetRecord is a wrapper class around the properties of your object that provides additional helper functionality such as change tracking.

GadgetRecord properties 

Every GadgetRecord shares a common set of Gadget system properties such as id, updatedAt, and createdAt. In addition to these Gadget system properties, any model fields you've added to your model will also be included in the GadgetRecord. These properties can be get and set via their apiIdentifier.

JavaScript
1const user = await api.user.findOne("123");
2
3// Gadget system properties
4// "1"
5console.log(user.id);
6// Tue Jun 15 2021 10:30:59 GMT-0400
7console.log(user.createdAt);
8// Tue Jun 15 2021 12:18:01 GMT-0400
9console.log(user.updatedAt);
10
11// Model field properties
12// "Jane Doe"
13console.log(user.name);
15console.log(user.email);
1const user = await api.user.findOne("123");
2
3// Gadget system properties
4// "1"
5console.log(user.id);
6// Tue Jun 15 2021 10:30:59 GMT-0400
7console.log(user.createdAt);
8// Tue Jun 15 2021 12:18:01 GMT-0400
9console.log(user.updatedAt);
10
11// Model field properties
12// "Jane Doe"
13console.log(user.name);
15console.log(user.email);

Change tracking (dirty tracking) 

GadgetRecord instances track changes made to their local state, and provide a change tracking API for determining what properties have changed (locally) on a record.

You can use this change tracking in your own applications via the client package in the following manner:

JavaScript
1const user = await api.user.findOne("123");
2
3user.name = "A new name";
4
5const { changed, current, previous } = user.changes("name");
6
7// true
8console.log(changed);
9// "A new name";
10console.log(current);
11// "The old name";
12console.log(previous);
1const user = await api.user.findOne("123");
2
3user.name = "A new name";
4
5const { changed, current, previous } = user.changes("name");
6
7// true
8console.log(changed);
9// "A new name";
10console.log(current);
11// "The old name";
12console.log(previous);

By working with this single GadgetRecord instance user in your frontend, you could use the change tracking API to determine if there is anything that needs to be persisted via one of your actions.

Additionally, you are also working with GadgetRecord instances when using the context.record in your actions and validations.

If you wanted to determine whether or not a change was made to the record in a run effect of an update action, you could do the following:

JavaScript
1export const run: ActionRun = async ({ params, record, logger, api }) => {
2 if (record.changed("title")) {
3 const { current, previous } = record.changes("title");
4
5 // the new attribute set via params
6 console.log(current);
7 // the current value in the database
8 console.log(previous);
9 }
10};
11// ... rest of your code
1export const run: ActionRun = async ({ params, record, logger, api }) => {
2 if (record.changed("title")) {
3 const { current, previous } = record.changes("title");
4
5 // the new attribute set via params
6 console.log(current);
7 // the current value in the database
8 console.log(previous);
9 }
10};
11// ... rest of your code

GadgetRecord API 

The GadgetRecord API extends your records with helper functions to make working with records easier.

Get all changes to the record 

Get a list of all changes, keyed by field apiIdentifier since this record was instantiated.

JavaScript
1const record = new GadgetRecord({ title: "Old title", body: "Old body" });
2record.title = "New title";
3record.body = "Old body";
4record.price = 123.45;
5console.log(record.changes());
6// {
7// title: { changed: true, current: "New title", previous: "Old title" },
8// price: { changed: true, current: 123.45, previous: undefined }
9// }
1const record = new GadgetRecord({ title: "Old title", body: "Old body" });
2record.title = "New title";
3record.body = "Old body";
4record.price = 123.45;
5console.log(record.changes());
6// {
7// title: { changed: true, current: "New title", previous: "Old title" },
8// price: { changed: true, current: 123.45, previous: undefined }
9// }

Get changes to one field of the record 

Get any changes made to a specific apiIdentifier of the record since this record was instantiated.

JavaScript
const record = new GadgetRecord({ title: "Old title" });
record.title = "New title";
console.log(record.changes("title"));
// { changed: true, current: "New title", previous: "Old title" }
const record = new GadgetRecord({ title: "Old title" });
record.title = "New title";
console.log(record.changes("title"));
// { changed: true, current: "New title", previous: "Old title" }

Determine whether or not any fields changed on the record 

Determine if any changes have been made to any keys on the record.

JavaScript
const record = new GadgetRecord({ title: "Old title" });
record.title = "New title";
// true
console.log(record.changed());
const record = new GadgetRecord({ title: "Old title" });
record.title = "New title";
// true
console.log(record.changed());

Determine if one field changed on the record 

Determine if a specific apiIdentifier changed on the record.

JavaScript
const record = new GadgetRecord({ title: "Old title" });
record.title = "New title";
// true
console.log(record.changed("title"));
const record = new GadgetRecord({ title: "Old title" });
record.title = "New title";
// true
console.log(record.changed("title"));

Revert all changes to the record 

Resets the record state to the state it was instantiated with. All changes are reverted.

JavaScript
1const record = new GadgetRecord({ title: "Old title", body: "Old body" });
2record.title = "New title";
3record.body = "New body";
4record.price = 123.45;
5// {
6// title: { changed: true, current: "New title", previous: "Old title" },
7// body: { changed: true, current: "New body", previous: "Old body" },
8// price: { changed: true, current: 123.45, previous: undefined }
9// }
10console.log(record.changes());
11
12// "New title"
13console.log(record.title);
14// "New body"
15console.log(record.body);
16
17record.revertChanges();
18// {}
19console.log(record.changes());
20// false
21console.log(record.changed());
22
23// "Old title"
24console.log(record.title);
25// "Old body"
26console.log(record.body);
1const record = new GadgetRecord({ title: "Old title", body: "Old body" });
2record.title = "New title";
3record.body = "New body";
4record.price = 123.45;
5// {
6// title: { changed: true, current: "New title", previous: "Old title" },
7// body: { changed: true, current: "New body", previous: "Old body" },
8// price: { changed: true, current: 123.45, previous: undefined }
9// }
10console.log(record.changes());
11
12// "New title"
13console.log(record.title);
14// "New body"
15console.log(record.body);
16
17record.revertChanges();
18// {}
19console.log(record.changes());
20// false
21console.log(record.changed());
22
23// "Old title"
24console.log(record.title);
25// "Old body"
26console.log(record.body);

Flush changes to the record 

Flushes all changes to the record and resets the state of change tracking to the current state of the record.

JavaScript
1const record = new GadgetRecord({ title: "Old title", body: "Old body" });
2record.title = "New title";
3record.body = "New body";
4record.price = 123.45;
5// {
6// title: { changed: true, current: "New title", previous: "Old title" },
7// body: { changed: true, current: "New body", previous: "Old body" },
8// price: { changed: true, current: 123.45, previous: undefined }
9// }
10console.log(record.changes());
11
12// "New title"
13console.log(record.title);
14// "New body"
15console.log(record.body);
16
17record.flushChanges();
18// {}
19console.log(record.changes());
20// false
21console.log(record.changed());
22
23// "New title"
24console.log(record.title);
25// "New body"
26console.log(record.body);
1const record = new GadgetRecord({ title: "Old title", body: "Old body" });
2record.title = "New title";
3record.body = "New body";
4record.price = 123.45;
5// {
6// title: { changed: true, current: "New title", previous: "Old title" },
7// body: { changed: true, current: "New body", previous: "Old body" },
8// price: { changed: true, current: 123.45, previous: undefined }
9// }
10console.log(record.changes());
11
12// "New title"
13console.log(record.title);
14// "New body"
15console.log(record.body);
16
17record.flushChanges();
18// {}
19console.log(record.changes());
20// false
21console.log(record.changed());
22
23// "New title"
24console.log(record.title);
25// "New body"
26console.log(record.body);

Get a JSON representation of the record 

Return a JSON representation of the keys (by apiIdentifier) and values of your record.

JavaScript
1const record = new GadgetRecord({
2 title: "A title",
3 tags: ["Cool", "New", "Stuff"],
4 price: 123.45,
5});
6// {
7// "title": "A title",
8// "tags": ["Cool", "New", "Stuff"],
9// "price": 123.45
10// }
11console.log(record.toJSON());
1const record = new GadgetRecord({
2 title: "A title",
3 tags: ["Cool", "New", "Stuff"],
4 price: 123.45,
5});
6// {
7// "title": "A title",
8// "tags": ["Cool", "New", "Stuff"],
9// "price": 123.45
10// }
11console.log(record.toJSON());

Get and set properties that share the same name as GadgetRecord functions 

If the apiIdentifier of your model field has the same name as one of the GadgetRecord functions described above, you will need to work with it via the getField and setField functions.

JavaScript
1const record = new GadgetRecord({ title: "Something", changed: true });
2
3// false, change tracking function uses changed()
4console.log(record.changed());
5// true
6console.log(record.getField("changed"));
7record.setField("changed", false);
8// false
9console.log(record.getField("changed"));
1const record = new GadgetRecord({ title: "Something", changed: true });
2
3// false, change tracking function uses changed()
4console.log(record.changed());
5// true
6console.log(record.getField("changed"));
7record.setField("changed", false);
8// false
9console.log(record.getField("changed"));

ChangeTracking contexts 

GadgetRecord change tracking allows you to track changes on the record in different contexts. By default, all functions use the ChangeTracking.SinceLoaded context. Gadget also uses the ChangeTracking.SinceLastPersisted context to differentiate between changes that have already been persisted by the Create record and Update record effects, and changes that have been made to the record since the start of action execution.

For developers to worry less about the exact order in which their effects are run, the change tracking API assumes the ChangeTracking.SinceLoaded context by default. You may also inquire about changes since ChangeTracking.SinceLastPersisted by adding it as an option to your change tracking function calls.

JavaScript
1import {
2 GadgetRecord,
3 ChangeTracking,
4} from "@gadget-client/openai-screenwriter-tutorial-v2/src/GadgetRecord";
5
6function changedProperties(model: ModelBlob, record: GadgetRecord<BaseRecord>) {
7 const changes = record.changes();
8 const attributes = Object.keys(changes).reduce((attrs, key) => {
9 attrs[key] = record[key];
10 return attrs;
11 }, {});
12 return attributes;
13}
14
15const record = new GadgetRecord({ id: "123", title: "Something old" });
16
17record.title = "Something new";
18await api.record.update(record.id, { ...changedProperties(record) });
19// this applies the current state of the record to the ChangeTracking.SinceLastPersisted change tracking context
20record.flushChanges(ChangeTracking.SinceLastPersisted);
21
22// false since these changes have been flushed
23console.log(record.changed(ChangeTracking.SinceLastPersisted));
24
25// true, ChangeTracking.SinceLoaded context hasn't changed
26console.log(record.changed());
27// true; equivalent to above
28console.log(record.changed(ChangeTracking.SinceLoaded));
29
30// { title: { changed: true, current: "Something new", previous: "Something old" } }
31console.log(record.changes());
32// same as above
33console.log(record.changes(ChangeTracking.SinceLoaded));
34// {}, because we flushed it above
35console.log(record.changes(ChangeTracking.SinceLastPersisted));
36
37// reverts changes to the record, in accordance with the ChangeTracking.SinceLoaded context
38record.revertChanges();
39// equivalent to line above, no effect
40record.revertChanges(ChangeTracking.SinceLoaded);
41
42// "Something old"
43console.log(record.title);
44// false
45console.log(record.changed());
46// {}
47console.log(record.changes());
48
49// true; reverted since we last persisted
50console.log(record.changed(ChangeTracking.SinceLastPersisted));
51// { changed: true, current: "Something old", previous: "Something new" }
52console.log(record.changes("title", ChangeTracking.SinceLastPersisted));
1import {
2 GadgetRecord,
3 ChangeTracking,
4} from "@gadget-client/openai-screenwriter-tutorial-v2/src/GadgetRecord";
5
6function changedProperties(model: ModelBlob, record: GadgetRecord<BaseRecord>) {
7 const changes = record.changes();
8 const attributes = Object.keys(changes).reduce((attrs, key) => {
9 attrs[key] = record[key];
10 return attrs;
11 }, {});
12 return attributes;
13}
14
15const record = new GadgetRecord({ id: "123", title: "Something old" });
16
17record.title = "Something new";
18await api.record.update(record.id, { ...changedProperties(record) });
19// this applies the current state of the record to the ChangeTracking.SinceLastPersisted change tracking context
20record.flushChanges(ChangeTracking.SinceLastPersisted);
21
22// false since these changes have been flushed
23console.log(record.changed(ChangeTracking.SinceLastPersisted));
24
25// true, ChangeTracking.SinceLoaded context hasn't changed
26console.log(record.changed());
27// true; equivalent to above
28console.log(record.changed(ChangeTracking.SinceLoaded));
29
30// { title: { changed: true, current: "Something new", previous: "Something old" } }
31console.log(record.changes());
32// same as above
33console.log(record.changes(ChangeTracking.SinceLoaded));
34// {}, because we flushed it above
35console.log(record.changes(ChangeTracking.SinceLastPersisted));
36
37// reverts changes to the record, in accordance with the ChangeTracking.SinceLoaded context
38record.revertChanges();
39// equivalent to line above, no effect
40record.revertChanges(ChangeTracking.SinceLoaded);
41
42// "Something old"
43console.log(record.title);
44// false
45console.log(record.changed());
46// {}
47console.log(record.changes());
48
49// true; reverted since we last persisted
50console.log(record.changed(ChangeTracking.SinceLastPersisted));
51// { changed: true, current: "Something old", previous: "Something new" }
52console.log(record.changes("title", ChangeTracking.SinceLastPersisted));

GadgetRecord.touch() 

Each GadgetRecord also has a touch function that can be used to mark the record as changed.

When the record is saved, it's updatedAt field will be updated, even if nothing else about the record has changed.

Example of using record.touch()
JavaScript
1export const run: ActionRun = async ({ api }) => {
2 // get user record from API
3 let record = await api.user.findFirst();
4
5 // mark the record as changed
6 record.touch();
7
8 // save the record, which will change it's `updatedAt`
9 await save(record);
10};
1export const run: ActionRun = async ({ api }) => {
2 // get user record from API
3 let record = await api.user.findFirst();
4
5 // mark the record as changed
6 record.touch();
7
8 // save the record, which will change it's `updatedAt`
9 await save(record);
10};

This is useful when you are using realtime queries and want to trigger a re-fetch of the record without changing any of the other fields.

Working with GadgetRecord in actions 

When writing actions and model field validations you have access to a shared GadgetRecord in context.

Most commonly, you will be working with the context.record inside actions. Gadget will instantiate an instance of a GadgetRecord when it starts executing your action. This same instance will be shared between all validations and actions that run during that action.

The typical GadgetRecord lifecycle is as follows:

  1. Instantiate a new GadgetRecord. All fields are undefined, no changes are tracked.
  2. Call the applyParams function. This function is added to your create and update actions by default. After this function runs, your context.record will have changes() that correspond to the current state of the record.
  3. Run the save() function. save() will validate your record and then, if valid, persist the record in your database. It will also clear the changes() state of the record after persisting it.

Was this page helpful?