Model
Model
classes allows you to connect your laravel models with your front end forms.
Generating Model Classes
Create a Post
model that extends the default Model
class. Note we're using the PostApi
created previously.
Example
import { Model } from '@konnec/vue-eloquent'
import PostApi from './PostApi'
import { IPost } from 'PostInterface'
import { computed, reactive } from 'vue'
export default class Post extends Model {
api = PostApi
model = reactive<IPost>({
id: undefined,
title: undefined,
description: undefined,
created_at: undefined,
deleted_at: undefined,
updated_at: undefined,
})
constructor(post?: IPost){
super()
// The factory method is only needed if you want to create a Model
// instance from an existing object, instead of from API
super.factory(post)
}
}
TIP
Notice the model
attribute is a reactive property. This allows you to maintain reactivity in your components
You model
property is where you encapsulate your model attributes. And now you can use it in our component:
<template>
<div>
<q-input v-model='post.model.id' label='ID' />
<q-input v-model='post.model.title' label='Title' />
<q-input v-model='post.model.description' label='Description' />
<q-btn label='Submit' @click='onSubmit />
</div>
</template>
<script lang="ts">
import Post from './Post'
export default defineComponent({
data() {
return {
post: new Post(),
}
},
methods: {
onSubmit() {
// save() will update or create a new post
this.post.save()
}
}
})
</script>
INFO
Note that we're linking post.model
properties to the form models
Available methods
Find
This will fetch the post
with id = 1 from the API and attach it to the post.model
property
this.post.find(1)
Create
Create a new instance of the post
this.post.create()
Update
Update the existing instance of the post
this.post.update()
Save
Alternatively, you can also use the convenient post.save()
method. If your post.model
has a defined id
attribute, it will send a PATCH
request to the API to update it. Otherwise, it will send a POST
request to create a new post
this.post.save()
Delete
You can delete the existing post by calling:
this.post.delete()
TIP
The API Class methods connect to Laravel controllers and hence use the same terminology: get
(index
), show
, store
, update
, destroy
The Model Class methods connect to Laravel Models, hence use Laravel Eloquent's terminology: create
, find
, update
, delete
, save
Default Attribute Values
You can pass default values directly to the model property:
model = reactive<IPost>({
id: undefined,
title: 'Default Title',
description: undefined,
created_at: undefined,
deleted_at: undefined,
updated_at: undefined,
})
Refreshing Models
If you already have an instance of a model that was retrieved from the API, you can "refresh" the model using the refresh
method.
// Retrieve model with id = 1
this.post.find(1)
// Updates model with id = 1 from the API
this.post.refresh()
You can also call the refresh
method to re-retrieve a new model from the API:
// Retrieve model with id = 1
this.post.find(1)
// post instance is not using model with id = 2
this.post.refresh(2)
If you want to create a fresh (empty declaration) of the model you can call the fresh
method:
// Retrieve model with id = 1
this.post.find(1)
// post.model.id = 1
this.post.fresh()
// post.model.id = undefined
Relationships
You can create hasOne
and hasMany
relationships on your model:
import { reactive } from 'vue'
import { Model } from '../../src'
import PostApi from './PostApi'
import { IPost } from './PostInterface'
import UserApi from './UserApi'
import { IUser } from './UserInterface'
import CommentApi from './CommentApi'
import { IComment } from './CommentInterface'
export default class Post extends Model {
api = PostApi
model = reactive<IPost>({
id: undefined,
created_at: undefined,
updated_at: undefined,
deleted_at: undefined,
author_id: undefined,
title: undefined,
text: undefined,
author: undefined as IUser,
comments: undefined as IComment[],
})
constructor(post?: IPost) {
super()
super.factory(post)
}
async author(): Promise<IUser>
{
return await this.hasOne(UserApi, this.model.author_id)
}
async comments(): Promise<IComment[]>
{
return await this.hasMany(CommentApi, 'id', this.model.id)
}
}
Has One Relationship
On a hasOne
relationship, the first parameter is the Api
Class of your relationship, and the second parameter is the foreign key
on your relationship model
async author(): Promise<IUser>
{
return await this.hasOne(UserApi, this.model.author_id)
}
Has Many Relationship
On a hasMany
relationship, the first parameter is the Api
Class of your relationship. The second parameter is the foreign key
on your relationship model
async comments(): Promise<IComment[]>
{
return await this.hasMany(CommentApi, this.model.id)
}
TIP
The inverse relationships methods are not available but can be abstracted using the same hasOne
and hasMany
methods.
Lazy Loading
The relationships can be 'lazy loaded' by calling the load
method after the model has been instantiated:
this.posts.load(['author', 'comments'])
Validation
Vue Eloquent
uses Vuelidate which is a great model validation library for Vue. You need to define the validation rules in your Model class:
import { required } from '@vuelidate/validators'
import { Model } from '@konnec/vue-eloquent'
import PostApi from './PostApi'
import { IPost } from 'PostInterface'
import { computed, reactive } from 'vue'
export default class Post extends Model {
api = PostApi
// MUST be a reactive property
model = reactive({
id: undefined,
title: undefined,
description: undefined,
created_at: undefined,
deleted_at: undefined,
updated_at: undefined,
} as IPost)
constructor(post?: IPost){
super()
if (post) super.factory(post)
// Create validation instance
super.initValidations()
}
// Validation rules, as per Vuelidate methods
// MUST be a computed property
protected validations = computed(() => ({
model: {
title: {
required
},
description: {
required
}
}
}))
}
WARNING
Note the validations
property is a computed property
You then need to initialize the validations in your component. From there on you can access your Vuelidate
model through this.post.$model
<template>
<div>
<q-input v-model='post.model.id' label='ID' />
<q-input v-model='post.model.title' label='Title' />
<q-input v-model='post.model.description' label='Description' />
<q-btn label='Submit' @click='onSubmit />
</div>
</template>
<script lang="ts">
import Post from './Post'
export default defineComponent({
data() {
return {
post: new Post(),
}
},
methods: {
async onSubmit() {
this.post.$validate()
if (this.post.$invalid) return
const { actioned, model } = await this.post.save()
// Do something here, e.g: emit the value to a parent component
// this.$emit(actioned, model)
// actioned = 'created' or 'updated'
}
}
})
</script>
Validation messages
<template>
<div>
<q-input v-model='post.model.id' label='ID' />
<q-input
v-model='post.model.title'
label='Title'
:error='post.$model.title.$error'
:error-message='post.$model.title.$errors[0]'
/>
<q-input
v-model='post.model.description'
label='Description'
:error='post.$model.description.$error'
:error-message='post.$model.description.$errors[0]'
/>
<q-btn label='Submit' @click='onSubmit />
</div>
</template>
Validation Rules
You can find several rules available out-of-the-box in the Vuelidate Built-in Validators documentation and also on how to create your own custom rules Vuelidate Custom Validators .
States
The Model
has 3 states which are available and updated during the API requests. You can use them to display state changes on you UI, e.g. a loading
indicator on a button.
state: {
isLoading: boolean,
isSucess: boolean,
isError: boolean
}
<template>
<div>
<q-input v-model='post.model.id' label='ID' />
<q-input
v-model='post.model.title'
label='Title'
:error='post.$model.title.$error'
:error-message='post.$model.title.$errorMessage'
/>
<q-input
v-model='post.model.description'
label='Description'
:error='post.$model.description.$error'
:error-message='post.$model.description.$errorMessage'
/>
<q-btn label='Submit' @click='onSubmit :loading='post.state.isLoading'/>
</div>
</template>
Observers
Similarly to the API class, the Model also has Observers:
Find: retriving()
, retrieved(payload)
and retrivingError(payload)
Update: updating()
, updated(payload)
and updatingError(payload)
Create: storing()
, stored(payload)
and storingError(payload)
Delete: deleting()
, deleted(payload)
and deletingError(payload)
Those are good placeholders for displaying error messages to the user, passing values to the Store, or mutating the data:
import { required } from '@vuelidate/validators'
import { computed, reactive } from 'vue'
import { Model } from '../src/index'
import PostApi from './PostApi'
import { IPost } from './PostInterface'
import UserApi from '../test/mocks/UserApi'
import { IUser } from '../test/mocks/UserInterface'
export default class Post extends Model {
api = PostApi
model = reactive({
id: undefined,
created_at: undefined,
updated_at: undefined,
deleted_at: undefined,
author_id: undefined,
title: undefined,
description: undefined,
author: undefined as IUser,
readers: undefined as IUser[],
} as IPost)
constructor(post?: IPost) {
super()
super.factory(post)
}
protected updating()
{
// strip html tags from this.model.text
// before submitting to the backend
// OR
// modifying a update_by field with the current username
}
protected updated(payload)
{
// Update a store with the returned payload
}
}
TIP
The save
method will trigger the Create
or Update
observers accordingly