Skip to content

Model

Model classes allows you to connect your laravel models with your front end forms.

Model Class

Generating Model Classes

Create a Post model that extends the default Model class. Note we're using the PostApi created previously.

Example

ts
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:

js
<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

js
this.post.find(1)

Create

Create a new instance of the post

js
this.post.create()

Update

Update the existing instance of the post

js
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

js
this.post.save()

Delete

You can delete the existing post by calling:

js
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:

ts
  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.

js
// 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:

js
// 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:

js
// 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:

ts
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

ts
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

ts
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:

js
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:

ts
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

js
<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

js
<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.

js
state: {
    isLoading: boolean,
    isSucess: boolean,
    isError: boolean
}
js
<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:

ts
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