Appearance
useIndexer
Composable
ts
import { useIndexer } from '@vuemodel/core'
import { Post } from '@vuemodel/sample-data'
const postsIndexer = useIndexer(Post)
await postsIndexer.index()
console.log(postsIndexer.records.value)
useIndexer
helps you "filter", "order", "paginate" and include ("with") records. Here's a basic example:
records
and request
When accessing indexer.records.value
, we're pulling it out of the store. This is good news! It means any changes to the record in other parts of our application will be reflected when using indexer.records.value
.
You can also access records via indexer.request.value.records
. These records are not pulled from the store and therefore, are not reactive. For example, if you later destroy a record with useDestroyer
, that action will not be reflected in indexer.request.value.records
.
makeQuery
ts
const usersIndexer = useIndexer(User)
const usersPopulated = computed(() => {
return usersIndexer.makeQuery()
.with('posts', query => query.with('comments'))
.get()
})
In this part of the docs, "store query" means "a query for data on the frontend".
One of the cool things about VueModel, is that the query you use to get data from the backend, is the same query used to get data on the frontend (store query). Therefore, if you fetch users with posts and comments (users.posts.comments
) the store query to fetch that data is built for you automatically (indexer.records
)!
If you want to change this store query, you can use makeQuery()
. makeQuery()
builds a query with all your filters, includes, orderBys etc. This gives you more control over the store query, because now you can continue to add to this query!
In the following example:
- all users are fetched
- all posts are fetched
- all comments are fetched
- we use
makeQuery()
on the user to "stitch" all this data together
Isn't PiniaORM wonderful? We couldn't have built VueModel without it 💚.
INFO
Take a look at PiniaORM's "Retrieving Data" docs to see other ways you can query data in the store. Or for more advanced users, jump straight to the "Query API".
immediate
ts
const postsIndexer = useIndexer(Post, { immediate: true })
Calls index()
when the composable has initialized.
Filters
Available Filters
Currently, VueModel supports the following filters:
- equals
- doesNotEqual
- lessThan
- lessThanOrEqual
- greaterThan
- greaterThanOrEqual
- in
- notIn
- contains
- doesNotContain
- between
- startsWith
- endsWith
WARNING
The driver you're using might not support all the above filters.
Usage
ts
const postsIndexer = useIndexer(Post, {
filters: {
name: { equals: 'Chelsey Dietrich' },
}
})
This example demonstrates a basic equals
filter to narrow the records to one user ("Chelsey Dietrich"):
This example uses "contains", and watchDebounced
from VueUse, for debounced search functionality:
And/Or Blocks
We can narrow our filters by using and/or blocks:
ts
{
body: {
contains: 'est',
},
or: [
{
created_at: {
greaterThan: '2023-08-02',
},
},
{
user_id: {
equals: '1',
},
},
]
}
Filtering Nested Data
Of course, we also have nested filters! Let's find users that have posted since 2023-11-11
ts
const usersIndexer = useIndexer(User, {
filters: {
posts: {
created_at: {
greaterThan: '2023-11-11',
},
},
},
})
with
(including related data)
There are three notations we can use to include data:
- object (recommended)
- string
- string array
ts
const postsIndexer = useIndexer(Post, {
with: { user: {} }
})
ts
const postsIndexer = useIndexer(Post, { with: 'user' })
ts
const postsIndexer = useIndexer(Post, { with: ['user', 'comments'] })
For more complex apps, we recommend sticking to object notation (the first example above). It provides better type completion, and allows you to add useful features like orderBy
's and filter
's when they're needed.
INFO
_limit
is also available when working with nested data.
Deeply nested with
Of course, we can also include nested data using either object notation, or dot notation:
ts
const usersIndexer = useIndexer(User, {
with: { post: { comments: {} } }
})
ts
const usersIndexer = useIndexer(User, { with: 'posts.comments' })
ts
const usersIndexer = useIndexer(User, { with: ['posts.comments'] })
Filtering Nested Data (with
)
While including nested data, we can apply filters!
ts
const postsIndexer = useIndexer(Post, {
with: {
user: { name: { equals: 'Clementine Bauch' } }
}
})
and/or blocks allow us to further narrow our result.
ts
const postsIndexer = useIndexer(Post, {
with: {
posts: {
or: [
{
created_at: { greaterThan: '2023-09-01' },
title: { equals: 'qui est esse' },
},
],
},
},
})
indexing
Add a spinner with indexing.value
:
html
<q-btn
label="Index"
@click="postsIndexer.index()"
:loading="postsIndexer.indexing.value"
/>
orderBy
ts
const postsIndexer = useIndexer(Post, {
orderBy: [
{ field: 'created_at', direction: 'descending' }
]
})
Pagination
ts
const postsIndexer = useIndexer(Post, { pagination: { recordsPerPage: 3 } })
Pagination with VueModel is easy!
- Set
recordsPerPage
- Call
next()
,previous()
ortoPage(pageNumber)
to paginate!
Here's the full pagination API:
options.pagination
page
recordsPerPage
Pagination
indexer.pagination.value.recordsCount
indexer.pagination.value.pagesCount
indexer.toFirstPage()
indexer.toLastPage()
indexer.isFirstPage.value
indexer.isLastPage.value
Paginate Immediately
ts
const postsIndexer = useIndexer(Post, {
pagination: { recordsPerPage: 3 },
paginateImmediate: true,
})
paginateImmediate
will trigger index()
when:
pagination.value.recordsPerPage
changespagination.value.page
changes
Put simply, it means we don't have to call index()
manually when we're ready to change pages.
persist
ts
const postsIndexer = useIndexer(Post, { persist: false })
After indexing data, it's automatically persisted to the store. To prevent this, we can set persist: false
.
persistBy
ts
const postsIndexer = useIndexer(Post, { persistBy: 'insert' })
There are four different strategies we can use when persisting data to the store:
- save (default): save records and related records to the store
- insert: save records without related records to the store. We might think of this as a "flat save"
- replace-save: flush all data from the store, then save
- replace-insert: flush all data from the store, then insert After indexing data, it's automatically persisted to the store. To prevent this, we can set
persist: false
Indexing By ID (whereIdIn
)
ts
const postsIndexer = useIndexer(Post, {
pagination: { recordsPerPage: 3 },
paginateImmediate: true,
})
Sometimes, we already know what id
's we need to search for. For that, we have whereIdIn
.
This is especially handy when working with composite keys. whereIdIn
is responsible for converting composite keys into a request the backend can understand.
whereIdInImmediate
Scopes
ts
import { vueModelState, useIndexer } from '@vuemodel/core'
import { Post } from 'sample-data'
vueModelState.config.scopes = {
latest: {
orderBy: [
{ field: 'created_at', direction: 'descending' }
]
},
}
ts
import { vueModelState } from '@vuemodel/core'
vueModelState.driver.local.config.scopes = {
latest: {
orderBy: [
{ field: 'created_at', direction: 'descending' }
]
},
}
Usage
ts
import { Post } from 'sample-data'
import { useIndexer } from '@vuemodel/core'
const postsIndexer = useIndexer(Post, { scopes: ['latest'] })
Applying globally
ts
import { vueModelState } from '@vuemodel/core'
vueModelState.config.scopes = {
tenant: {
filters: { tenant_id: { equals: '1' } },
},
globallyAppliedScopes: ['tenant']
}
ts
import { vueModelState } from '@vuemodel/core'
vueModelState.driver.local.config.scopes = {
tenant: {
filters: { tenant_id: { equals: '1' } },
},
globallyAppliedScopes: ['tenant']
}
WARNING
The use of a tenant_id
in the example above can be great for testing using the local driver, yet is not a secure way to filter by tenant in production.
Scopes allow you to predefine filters. For example, if all our records have a created_at
field, you may want to make a latest
scope.
Disabling Global Scopes
We can disable global scopes with the withoutGlobalScopes
option:
ts
const postsIndexer = useIndexer(Post, {
withoutGlobalScopes: ['defaultTenant']
})
Entity Scopes
Entity scopes are almost identical to scopes. The difference, is that they're only for a specific model.
For example, if you want a scope that only applies to a User model, then you want an entity scope.
ts
vueModelState.config.entityScopes = {
users: { // only applied to users
orgWebsite: {
filters: { website: { endsWith: '.org' } },
},
},
}
Disabling Global Entity Scopes
We can disable global entity scopes with the withoutEntityGlobalScopes
option:
ts
const postsIndexer = useIndexer(Post, {
withoutEntityGlobalScopes: ['orgWebsite']
})