Experiments >

Supabase ActiveRecord relationships

Experiment #5718th March, 2021by Joshua Nussbaum

Last month I created an ActiveRecord layer for Supabase. There were a few features missing that I’d like to add. Specifically joins and more validation features.

Thinking about joins, I want to support one-to-one and one-to-many relationships to start.

Belongs To

For the “belongs to” relationship, there would be a belongsTo() function to configure the relationship:

class Product {
  static config = {
    fields = {
      name: 'string',
      category: belongsTo(Category)
    }
  }
}

Each instance would have getters and setters for the relationship and id field:

// getter
product.category
// getter for id
product.categoryId

// setter
product.category = category
// setter for id
product.categoryId = category

Has Many

Has many relationships are configured with the hasMany() function:

class Product {
  static config = {
    fields = {
      name: 'string',
      variants: hasMany(Variant)
    }
  }
}

Each instance would have a getter and setter for the relationships:

// getter
product.variants
// setter
product.variants

A builder function is also provided to create the child record:

variant = product.variants.build()

// functionally equivalent to
variant = new Variant()
variant.productId = product.id

The list object should have query methods similar to Scope:

await product.variants.where(...)
await product.variants.findBy(...)
await product.variants.getBy(...)
await product.variants.create(...)

Loading relationships

To manually load a relationship, there would be an async load() function:

category = await product.load('category')
variants = await product.load('variants')

Joining relationships

Joins would be possible too:

await Product
        .all()
        .join('category')

Multiple relationships can be joined at the same time:

await Product
        .all()
        .join('category', 'vendor', 'variants')

It’s also possible to use an object with nested joins:

await Product
        .all()
        .join('category', {vendor: ['country', 'continent']})

Behind the scenes it will be turned into query like this.

supabase.from('category, vendor ( name, country (id, name), continent (id, name) )')

Notes

It might be a good idea to implement an identity map.

view all experiments

Stay tuned in

Learn how to add more experimentation to your workflow