ES6+ rest or object destructuring in FQL?

Continuing the discussion from Insert multiple entries with Map/Do/Lambda:

Hello, I’m trying to do what in ES6+ would be called “rest” or “object destructuring” within a Map / Lambda . Each object in the array of objects I’m passing into the Map already has every parameter I want to show up within the Lambda, without renaming. I’m looking at the Select function, which seems like it’ll do what I want, but I’m left wondering:

Do I really need to do a Select on each and every parameter I want to pass into the Lambda?

I have this working, but it seems way more verbose than what I’m used in modern JavaScript. Is there something ridiculously simple I’m missing about FQL, or is this as concise as it gets?

Map([{
    offsetX: 1,
    offsetY: 2,
    offsetXDistance: 2.5,
    offsetYDistance: 5,
    lat: 37.53369299218995,
    lng: -122.1521590561063
  },
  {
    offsetX: 2,
    offsetY: 2,
    offsetXDistance: 5,
    offsetYDistance: 5,
    lat: 37.53369299218995,
    lng: -122.1065751122126
  }
],
Lambda(
  "sample",
    Create(Collection("LocationSamples"), {
      data: {
        offsetX: Select("offsetX", Var("sample")),
        offsetY: Select("offsetY", Var("sample")),
        offsetXDistance: Select("offsetXDistance", Var("sample")),
        offsetYDistance: Select("offsetYDistance", Var("sample")),
        lat: Select("lat", Var("sample")),
        lng: Select("lng", Var("sample")),
      }
    })
  )
)

Hi @Samuel_Goff,

Thanks for joining our forums!

Select is used to extract values from within documents, and the Map you’re calling is essentially building two documents and passing them a Lambda. So yes, Select is the correct method to use for what you’re doing.

It’s important to keep in mind that FQL is a query language, and everything in it is designed around making queries easy, fast, and readable. And whether you’re constructing a document in a query or pulling one from the database, accessing the data within that document is handled the same way in each case. And that’s by explicitly selecting the data you want from it.

Cory

If you are not making any changes to the object, you can use it directly:

Map(
  [{/* ... */}, {/* ... */}, {/* ... */}, {/* ... */}],
  Lambda(
  "sample",
    Create(Collection("LocationSamples"), {
      data: Var("sample")
    })
  )
)

If you want to just change it a little bit, then you can use the Merge function to add or remove a few fields

Map(
  [{/* ... */}, {/* ... */}, {/* ... */}, {/* ... */}],
  Lambda(
  "sample",
    Create(Collection("LocationSamples"), {
      data: Merge(
        Var("sample"),
        {
          // add a field
          newCalculatedField: Add(
            Select("offsetX", Var("sample")),
            Select("offsetY", Var("sample"))
          ),
          // remove fields
          lat: null,
          lng: null
        }
      )
    })
  )
)

Hey @Cory_Fauna, thanks so much for getting back to me. This makes sense. Just wanted to make sure I wasn’t missing something ridiculously simple that everyone else already knows :slight_smile:

Hey @ptpaterson, thanks so much for the suggestions. This is immensely helpful. I wish it was easier to hunt down these techniques in the docs. It probably exists, but uses different terminology to talk about the same techniques I’m used to in other languages. It would be awesome if there was more metadata to ensure equivalent/similar/related ideas show up when someone performs a search of the docs.

Quite often, my goal is to take the data object directly – or with minor variation (e.g. whitelisting, blacklisting, mapping, etc.) Since Refs don’t survive the process of encoding into JSON to get to the client & back again, I find I’m often grabbing the Ref Id and mapping it into the data object, passing it to the client for rendering & interaction, then reconstructing the Id as a Ref upon hitting the endpoint for CRUD operations.

In the case of blacklisting, are you saying that if I set a field to null, Fauna will remove them? Or simply nullify their value?

Thanks for the feedback on the docs. The API reference can be better at giving you how to use FQL functions once you know they exist than they are at guiding you to them to begin with. We’re constantly iterating on the docs, and feedback is always great.

updating fields with null

Similar to the Update function, if you set a field to null with the Merge function it will be removed from the result. Any values that started as null (i.e. null values in the first argument) will stay null. This can be tested in the shell:

image

using fauna’s JSON results

This is not true. Ref’s are of course sent from Fauna as JSON. The client drivers transform that into whatever makes more sense for the driver. For the javascript driver, it uses the parseJSON function to transform into the appropriate classes.

If your end client uses the driver and receives the fauna query result directly, then the Ref’s can be used directly.

If you have a server between fauna and your end client, then you can use the parseJSON function in the end client to transform the result.

import { parseJSON } from 'faunadb'

// ...

const faunaValue = parseJSON(serverResult)

Once the result is parsed you can access the id with dot notation when it makes sense (like the key for an array of web components) or the whole ref when that makes (like when updating the document).

Of course, it is certainly a valid strategy to transform your data before going to the client. Maybe you want to the end client to be more independent from Fauna, or you may have any number of other reasons. However, know that if you want to use fauna-specific values in your application you can! :slightly_smiling_face:

Hey @ptpaterson, thanks so much for clarifying. With regard to the docs, I feel like I don’t know what I don’t know. Sometimes, I have the luxury of reading through the API end-to-end, but for some situations, you are on a targeted mission – you know the concept you are trying to find, but use different terminology than what the docs say, so they don’t show up. Even if you do read the API docs end-to-end, you often encounter a situation where the docs only provides one example, or you’re not sure it’s solving the problem you were hunting for a solution to. I often find myself asking questions like:

  • Does this solve what I am trying to solve?
  • Why would I want to do it this way?
  • What are the alternatives to doing it this way?
  • What are the advantages & disadvantages of doing it one way versus another?
  • How can I branch out to related/similar concepts after reading a particular section?
  • How does this interact with the data modeling choices you might make?

updating fields with null

So if I do an Update without Merge, but explicitly set a value to null, it gets preserved?

using fauna’s JSON results

True, but I don’t want my client to talk directly to Fauna. I’m using the FQL client on nodejs. My serverless architecture requires me to queue a bunch of long-running asynchronous API calls, so they don’t break the serverless back-end. By the time I pass a Ref to the queue & back, it comes back looking like this on the nodejs server:

ref: { '@ref': { id: '312160511242273348', collection: [Object] } }

Everything else is as I need it & expect it, so I’ve figured out how to drill down into the data structure and extract what I need, but it’s an awkward multi-step process, like so:

const { ref: locationSearchRef } = searchesAddQuery;
const {
  id: searchId,
  collection: {
  id: collectionName
}} = locationSearchRef;

If I simply pass the Ref into parseJSON, it will puke, because it’s not a valid JSON “file”, just a Ref value that’s been encoded to JSON. If there was a way to reverse that encoding – on an individual Ref value – it would be ideal.

The other alternative is to split the Ref into id & collection before queuing or sending down the wire to the client, which is something I end up doing quite often.

Update and Merge are similar in this. They remove the field. Also, Fauna doesn’t like to create and store null fields.

That is what you get when you convert a values.Ref class (which is what you get from parseJSON) back to JSON, which is not the same as the properties of the class object itself. That is, the JSON serialization is not the same shape as the original class object. 99.99% of times javascript objects are exactly the same as the JSON they are serialized to, but for the Fauna driver it is intentionally different to separate the concerns of the FQL language from the underlying over-the-wire protocol. So, the javascript objects have additional properties. The values.Ref class has an id property.

const doc = await client.query(q.Get(q.Ref(q.Collection("things"), "1234")))
cosnt ref = doc.id
const id = ref.id

console.log({ ref, id })
// output
{
  ref: Ref(Collection("things"), "1234"),
  id: '1234'
}

which means you can also do this:

const doc = await client.query(q.Get(q.Ref(q.Collection("things"), "1234")))
const result = { id: doc.ref.id, ...doc.data }

Here are some commented notes in the driver:

Hey @ptpaterson, thanks for clarifying about Update & Merge. I had it filed away in my brain that storing null in Fauna was dangerous and likely to result in it going bye-bye. Turns out, I wasn’t taking crazy pills. For object destructuring in ES6+, it’s more efficient to be able to trust that an object contains a parameter, even if it’s empty. So I had intended to use undefined instead, presuming it would be treated differently than null by Fauna, in order to ensure that the field I’ve explicitly defined continues to survive.

Also, most often I find myself trying to access both Ref id and Collection name. Is there a more efficient way to target the name of the Collection, like you’ve indicated is possible with Ref id?

Hi @Samuel_Goff. If you are able, I do encourage you to check out the documentation on the javascript driver for more specifics. It is hosted on github and linked to from the repository and the js driver page in Docs.

https://fauna.github.io/faunadb-js/module-values-Ref.html

Yes, there is a collection property for the values.Ref class.