PageSourceSearch

https://docs.flarum.org/assets/js/57a6cc7b.5bbff416.js

js flarum.org collected 2026-10-02 03:58:10 UTC 25,913 bytes, 1 lines download raw bytes

1"use strict";(globalThis.webpackChunkflarum_docs=globalThis.webpackChunkflarum_docs||[]).push([[6923],{8949(e,n,s){s.r(n),s.d(n,{assets:()=>d,contentTitle:()=>r,default:()=>h,frontMatter:()=>o,metadata:()=>t,toc:()=>l});const t=JSON.parse('{"id":"extend/models","title":"Models and Migrations","description":"At the foundation, any forum revolves around data: users provide discussions, posts, profile information, etc. Our job as forum developers is to provide a great experience for creating, reading, updating, and deleting this data. This article will discuss how Flarum stores and access data. In the next article, we\'ll follow up on this by explaining how data flows through the API.","source":"@site/versioned_docs/version-1.x/extend/models.md","sourceDirName":"extend","slug":"/extend/models","permalink":"/1.x/extend/models","draft":false,"unlisted":false,"editUrl":"https://github.com/flarum/docs/tree/main/versioned_docs/version-1.x/extend/models.md","tags":[],"version":"1.x","frontMatter":{},"sidebar":"extendSidebar","previous":{"title":"Routes and Content","permalink":"/1.x/extend/routes"},"next":{"title":"API and Data Flow","permalink":"/1.x/extend/api"}}');var a=s(4848),i=s(8453);const o={},r="Models and Migrations",d={},l=[{value:"The Big Picture",id:"the-big-picture",level:2},{value:"Migrations",id:"migrations",level:2},{value:"Migration Structure",id:"migration-structure",level:3},{value:"Migration Lifecycle",id:"migration-lifecycle",level:3},{value:"Creating Tables",id:"creating-tables",level:3},{value:"Renaming Tables",id:"renaming-tables",level:3},{value:"Creating/Dropping Columns",id:"creatingdropping-columns",level:3},{value:"Renaming Columns",id:"renaming-columns",level:3},{value:"Default Settings and Permissions",id:"default-settings-and-permissions",level:3},{value:"Data Migrations (Advanced)",id:"data-migrations-advanced",level:3},{value:"Backend Models",id:"backend-models",level:2},{value:"Adding New Models",id:"adding-new-models",level:3},{value:"Extending Models",id:"extending-models",level:3},{value:"Relationships",id:"relationships",level:3},{value:"Frontend Models",id:"frontend-models",level:2},{value:"Fetching Data",id:"fetching-data",level:3},{value:"Adding New Models",id:"adding-new-models-1",level:3},{value:"Extending Models",id:"extending-models-1",level:3},{value:"Saving Resources",id:"saving-resources",level:3},{value:"Creating New Resources",id:"creating-new-resources",level:3},{value:"Deleting Resources",id:"deleting-resources",level:3},{value:"Backend Models vs Frontend Models",id:"backend-models-vs-frontend-models",level:2}];function c(e){const n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",mdxAdmonitionTitle:"mdxAdmonitionTitle",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,i.R)(),...e.components};return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(n.header,{children:(0,a.jsx)(n.h1,{id:"models-and-migrations",children:"Models and Migrations"})}),"\n",(0,a.jsxs)(n.p,{children:["At the foundation, any forum revolves around data: users provide discussions, posts, profile information, etc. Our job as forum developers is to provide a great experience for creating, reading, updating, and deleting this data. This article will discuss how Flarum stores and access data. In the ",(0,a.jsx)(n.a,{href:"/1.x/extend/api",children:"next article"}),", we'll follow up on this by explaining how data flows through the API."]}),"\n",(0,a.jsxs)(n.p,{children:["Flarum makes use of ",(0,a.jsx)(n.a,{href:"https://laravel.com/docs/database",children:"Laravel's Database component"}),". You should familiarize yourself with it before proceeding, as it is assumed as prior knowledge in the following documentation."]}),"\n",(0,a.jsx)(n.h2,{id:"the-big-picture",children:"The Big Picture"}),"\n",(0,a.jsx)(n.p,{children:"Before we delve into implementation details, let's define some key concepts."}),"\n",(0,a.jsxs)(n.p,{children:[(0,a.jsx)(n.strong,{children:"Migrations"})," allow you to modify the database. If you're adding a new table, defining a new relationship, adding a new column to a table, or making some other DB structural change, you'll need to use a migration."]}),"\n",(0,a.jsxs)(n.p,{children:[(0,a.jsx)(n.strong,{children:"Models"})," provide a convenient, code-based API for creating, reading, updating, and deleting data. On the backend, they are represented by PHP classes, and are used to interact with the MySQL database. On the frontend, they are represented by JS classes, and are used to interact with the ",(0,a.jsxs)(n.a,{href:"/1.x/extend/api",children:["JSON",":API"]}),", which we'll discuss in the next article."]}),"\n",(0,a.jsxs)(n.admonition,{type:"info",children:[(0,a.jsx)(n.mdxAdmonitionTitle,{children:(0,a.jsx)(n.a,{href:"https://github.com/flarum/cli",children:"Flarum CLI"})}),(0,a.jsx)(n.p,{children:"You can use the CLI to automatically create your model:"}),(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-bash",children:"$ flarum-cli make backend model\n$ flarum-cli make frontend model\n"})})]}),"\n",(0,a.jsx)(n.h2,{id:"migrations",children:"Migrations"}),"\n",(0,a.jsx)(n.p,{children:"If we want to use a custom model, or add attributes to an existing one, we will need to modify the database to add tables / columns. We do this via migrations."}),"\n",(0,a.jsxs)(n.p,{children:["Migrations are like version control for your database, allowing you to easily modify Flarum's database schema in a safe way. Flarum's migrations are very similar to ",(0,a.jsx)(n.a,{href:"https://laravel.com/docs/migrations",children:"Laravel's"}),", although there are some differences."]}),"\n",(0,a.jsxs)(n.p,{children:["Migrations live inside a folder suitably named ",(0,a.jsx)(n.code,{children:"migrations"})," in your extension's  directory. Migrations should be named in the format ",(0,a.jsx)(n.code,{children:"YYYY_MM_DD_HHMMSS_snake_case_description"})," so that they are listed and run in order of creation."]}),"\n",(0,a.jsx)(n.h3,{id:"migration-structure",children:"Migration Structure"}),"\n",(0,a.jsxs)(n.p,{children:["In Flarum, migration files should ",(0,a.jsx)(n.strong,{children:"return an array"})," with two functions: ",(0,a.jsx)(n.code,{children:"up"})," and ",(0,a.jsx)(n.code,{children:"down"}),". The ",(0,a.jsx)(n.code,{children:"up"})," function is used to add new tables, columns, or indexes to your database, while the ",(0,a.jsx)(n.code,{children:"down"})," function should reverse these operations. These functions receive an instance of the ",(0,a.jsx)(n.a,{href:"https://laravel.com/docs/8.x/migrations#creating-tables",children:"Laravel schema builder"})," which you can use to alter the database schema:"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-php",children:"<?php\n\nuse Illuminate\\Database\\Schema\\Builder;\n\nreturn [\n    'up' => function (Builder $schema) {\n        // up migration\n    },\n    'down' => function (Builder $schema) {\n        // down migration\n    }\n];\n"})}),"\n",(0,a.jsxs)(n.p,{children:["For common tasks like creating a table, or adding columns to an existing table, Flarum provides some helpers which construct this array for you, and take care of writing the ",(0,a.jsx)(n.code,{children:"down"})," migration logic while they're at it. These are available as static methods on the ",(0,a.jsx)(n.a,{href:"https://api.docs.flarum.org/php/master/flarum/database/migration",children:(0,a.jsx)(n.code,{children:"Flarum\\Database\\Migration"})})," class."]}),"\n",(0,a.jsx)(n.h3,{id:"migration-lifecycle",children:"Migration Lifecycle"}),"\n",(0,a.jsx)(n.p,{children:"Migrations are applied when the extension is enabled for the first time or when it's enabled and there are some outstanding migrations. The executed migrations are logged in the database, and when some are found in the migrations folder of an extension that aren't logged as completed yet, they will be executed."}),"\n",(0,a.jsxs)(n.p,{children:["Migrations can also be manually applied with ",(0,a.jsx)(n.code,{children:"php flarum migrate"}),' which is also needed to update the migrations of an already enabled extension. To undo the changes applied by migrations, you need to click "Purge" next to an extension in the Admin UI, or you need to use the ',(0,a.jsx)(n.code,{children:"php flarum migrate:reset"})," command. Nothing can break by running ",(0,a.jsx)(n.code,{children:"php flarum migrate"})," again if you've already migrated - executed migrations will not run again."]}),"\n",(0,a.jsxs)(n.p,{children:["There are currently no composer-level hooks for managing migrations at all (i.e. updating an extension with ",(0,a.jsx)(n.code,{children:"composer update"})," will not run its outstanding migrations)."]}),"\n",(0,a.jsx)(n.h3,{id:"creating-tables",children:"Creating Tables"}),"\n",(0,a.jsxs)(n.p,{children:["To create a table, use the ",(0,a.jsx)(n.code,{children:"Migration::createTable"})," helper. The ",(0,a.jsx)(n.code,{children:"createTable"})," helper accepts two arguments. The first is the name of the table, while the second is a ",(0,a.jsx)(n.code,{children:"Closure"})," which receives a ",(0,a.jsx)(n.code,{children:"Blueprint"})," object that may be used to define the new table:"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-php",children:"use Flarum\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\n\nreturn Migration::createTable('users', function (Blueprint $table) {\n    $table->increments('id');\n});\n"})}),"\n",(0,a.jsxs)(n.p,{children:["When creating the table, you may use any of the schema builder's ",(0,a.jsx)(n.a,{href:"https://laravel.com/docs/8.x/migrations#creating-columns",children:"column methods"})," to define the table's columns."]}),"\n",(0,a.jsx)(n.h3,{id:"renaming-tables",children:"Renaming Tables"}),"\n",(0,a.jsxs)(n.p,{children:["To rename an existing database table, use the ",(0,a.jsx)(n.code,{children:"Migration::renameTable"})," helper:"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-php",children:"return Migration::renameTable($from, $to);\n"})}),"\n",(0,a.jsx)(n.h3,{id:"creatingdropping-columns",children:"Creating/Dropping Columns"}),"\n",(0,a.jsxs)(n.p,{children:["To add columns to an existing table, use the ",(0,a.jsx)(n.code,{children:"Migration::addColumns"})," helper. The ",(0,a.jsx)(n.code,{children:"addColumns"})," helper accepts two arguments. The first is the name of the table. The second is an array of column definitions, with the key being the column name. The value of each item is an array with the column definitions, as understood by Laravel's ",(0,a.jsx)(n.code,{children:"Illuminate\\Database\\Schema\\Blueprint::addColumn()"})," method. The first value is the column type, and any other keyed values are passed through to ",(0,a.jsx)(n.code,{children:"addColumn"}),"."]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-php",children:"return Migration::addColumns('users', [\n    'email' => ['string', 'length' => 255, 'nullable' => true],\n    'discussion_count' => ['integer', 'unsigned' => true]\n]);\n"})}),"\n",(0,a.jsxs)(n.p,{children:["To drop columns from an existing table, use the ",(0,a.jsx)(n.code,{children:"Migration::dropColumns"})," helper, which accepts the same arguments as the ",(0,a.jsx)(n.code,{children:"addColumns"})," helper. Just like when dropping tables, you should specify the full column definitions so that the migration can be rolled back cleanly."]}),"\n",(0,a.jsx)(n.h3,{id:"renaming-columns",children:"Renaming Columns"}),"\n",(0,a.jsxs)(n.p,{children:["To rename columns, use the ",(0,a.jsx)(n.code,{children:"Migration::renameColumns"})," helper. The ",(0,a.jsx)(n.code,{children:"renameColumns"})," helper accepts two arguments. The first is the name of the table, while the second is an array of column names to rename:"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-php",children:"return Migration::renameColumns('users', ['from' => 'to']);\n"})}),"\n",(0,a.jsx)(n.h3,{id:"default-settings-and-permissions",children:"Default Settings and Permissions"}),"\n",(0,a.jsx)(n.p,{children:"Data migrations are the recommended way to specify default settings and permissions:"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-php",children:"return Migration::addSettings([\n    'foo' => 'bar',\n]);\n"})}),"\n",(0,a.jsx)(n.p,{children:"and"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-php",children:"use Flarum\\Group\\Group;\n\nreturn Migration::addPermissions([\n    'some.permission' => Group::MODERATOR_ID\n]);\n"})}),"\n",(0,a.jsxs)(n.p,{children:["Note that this should only be used then adding ",(0,a.jsx)(n.strong,{children:"new"})," permissions or settings. If you use these helpers, and the settings/permissions already exist, you'll end up overriding those settings on all sites where they have been manually configured."]}),"\n",(0,a.jsx)(n.h3,{id:"data-migrations-advanced",children:"Data Migrations (Advanced)"}),"\n",(0,a.jsxs)(n.p,{children:["A migration doesn't have to change database structure: you could use a migration to insert, update, or delete rows in a table. The migration helpers that add ",(0,a.jsx)(n.a,{href:"#default-settings-and-permissions",children:"defaults for settings/permissions"})," are just one case of this. For instance, you could use migrations to create default instances of a new model your extension adds. Since you have access to the ",(0,a.jsx)(n.a,{href:"https://laravel.com/docs/8.x/migrations#creating-tables",children:"Eloquent Schema Builder"}),", anything is possible (although of course, you should be extremely cautious and test your extension extensively)."]}),"\n",(0,a.jsx)(n.h2,{id:"backend-models",children:"Backend Models"}),"\n",(0,a.jsxs)(n.p,{children:["With all your snazzy new database tables and columns, you're going to want a way to access the data in both the backend and the frontend. On the backend it's pretty straightforward \u2013\xa0you just need to be familiar with ",(0,a.jsx)(n.a,{href:"https://laravel.com/docs/8.x/eloquent",children:"Eloquent"}),"."]}),"\n",(0,a.jsx)(n.h3,{id:"adding-new-models",children:"Adding New Models"}),"\n",(0,a.jsxs)(n.p,{children:["If you've added a new table, you'll need to set up a new model for it. Rather than extending the Eloquent ",(0,a.jsx)(n.code,{children:"Model"})," class directly, you should extend ",(0,a.jsx)(n.code,{children:"Flarum\\Database\\AbstractModel"})," which provides a bit of extra functionality to allow your models to be extended by other extensions. See the Eloquent docs linked above for examples of what your model class should look like."]}),"\n",(0,a.jsx)(n.h3,{id:"extending-models",children:"Extending Models"}),"\n",(0,a.jsxs)(n.p,{children:["If you've added columns to existing tables, they will be accessible on existing models. For example, you can grab data from the ",(0,a.jsx)(n.code,{children:"users"})," table via the ",(0,a.jsx)(n.code,{children:"Flarum\\User\\User"})," model."]}),"\n",(0,a.jsxs)(n.p,{children:["If you need to define any attribute ",(0,a.jsx)(n.a,{href:"https://laravel.com/docs/8.x/eloquent-mutators#attribute-casting",children:"casts"}),", or ",(0,a.jsx)(n.a,{href:"https://laravel.com/docs/8.x/eloquent#default-attribute-values",children:"default values"})," on an existing model, you can use the ",(0,a.jsx)(n.code,{children:"Model"})," extender:"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-php",children:"use Flarum\\Extend;\nuse Flarum\\User\\User;\n\nreturn [\n    (new Extend\\Model(User::class))\n        ->default('is_alive', true)\n        ->cast('suspended_until', 'datetime')\n        ->cast('is_admin', 'boolean')\n];\n"})}),"\n",(0,a.jsx)(n.h3,{id:"relationships",children:"Relationships"}),"\n",(0,a.jsxs)(n.p,{children:["You can also add ",(0,a.jsx)(n.a,{href:"https://laravel.com/docs/8.x/eloquent-relationships",children:"relationships"})," to existing models using the ",(0,a.jsx)(n.code,{children:"hasOne"}),", ",(0,a.jsx)(n.code,{children:"belongsTo"}),", ",(0,a.jsx)(n.code,{children:"hasMany"}),",  ",(0,a.jsx)(n.code,{children:"belongsToMany"}),"and ",(0,a.jsx)(n.code,{children:"relationship"})," methods on the ",(0,a.jsx)(n.code,{children:"Model"})," extender. The first argument is the relationship name; the rest of the arg
1uments are passed into the equivalent method on the model, so you can specify the related model name and optionally override table and key names:"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-php",children:"    new Extend\\Model(User::class)\n        ->hasOne('phone', 'App\\Phone', 'foreign_key', 'local_key')\n        ->belongsTo('country', 'App\\Country', 'foreign_key', 'other_key')\n        ->hasMany('comment', 'App\\Comment', 'foreign_key', 'local_key')\n        ->belongsToMany('role', 'App\\Role', 'role_user', 'user_id', 'role_id')\n"})}),"\n",(0,a.jsxs)(n.p,{children:["Those 4 should cover the majority of relations, but sometimes, finer-grained customization is needed (e.g. ",(0,a.jsx)(n.code,{children:"morphMany"}),", ",(0,a.jsx)(n.code,{children:"morphToMany"}),", and ",(0,a.jsx)(n.code,{children:"morphedByMany"}),"). ANY valid Eloquent relationship is supported by the ",(0,a.jsx)(n.code,{children:"relationship"})," method:"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-php",children:"    new Extend\\Model(User::class)\n        ->relationship('mobile', 'App\\Phone', function ($user) {\n            // Return any Eloquent relationship here.\n            return $user->belongsToMany(Discussion::class, 'recipients')\n                ->withTimestamps()\n                ->wherePivot('removed_at', null);\n        })\n"})}),"\n",(0,a.jsx)(n.h2,{id:"frontend-models",children:"Frontend Models"}),"\n",(0,a.jsx)(n.p,{children:"Flarum provides a simple toolset for working with data in the frontend in the form of frontend models. There's 2 main concepts to be aware of:"}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:"Model instances are objects that represent a record from the database. You can use their methods to get attributes and relationships of that record, save changes to the record, or delete the record."}),"\n",(0,a.jsx)(n.li,{children:"The Store is a util class that caches all the models we've fetched from the API, links related models together, and provides methods for getting model instances from both the API and the local cache."}),"\n"]}),"\n",(0,a.jsx)(n.h3,{id:"fetching-data",children:"Fetching Data"}),"\n",(0,a.jsxs)(n.p,{children:["Flarum's frontend contains a local data ",(0,a.jsx)(n.code,{children:"store"})," which provides an interface to interact with the JSON",":API",". You can retrieve resource(s) from the API using the ",(0,a.jsx)(n.code,{children:"find"})," method, which always returns a promise:"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-js",children:"// GET /api/discussions?sort=createdAt\napp.store.find('discussions', {sort: 'createdAt'}).then(console.log);\n\n// GET /api/discussions/123\napp.store.find('discussions', 123).then(console.log);\n"})}),"\n",(0,a.jsxs)(n.p,{children:["Once resources have been loaded, they will be cached in the store so you can access them again without hitting the API using the ",(0,a.jsx)(n.code,{children:"all"})," and ",(0,a.jsx)(n.code,{children:"getById"})," methods:"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-js",children:"const discussions = app.store.all('discussions');\nconst discussion = app.store.getById('discussions', 123);\n"})}),"\n",(0,a.jsx)(n.p,{children:"The store wraps the raw API resource data in model objects which make it a bit easier to work with. Attributes and relationships can be accessed via pre-defined instance methods:"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-js",children:"const id = discussion.id();\nconst title = discussion.title();\nconst posts = discussion.posts(); // array of Post models\n"})}),"\n",(0,a.jsxs)(n.p,{children:["You can learn more about the store in our ",(0,a.jsx)(n.a,{href:"https://api.docs.flarum.org/js/1.x/classes/flarum.common_store.store",children:"API documentation"}),"."]}),"\n",(0,a.jsx)(n.h3,{id:"adding-new-models-1",children:"Adding New Models"}),"\n",(0,a.jsxs)(n.p,{children:["If you have added a new resource type, you will need to define a new model for it. Models must extend the ",(0,a.jsx)(n.code,{children:"Model"})," class and re-define the resource attributes and relationships:"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-js",children:"import Model from 'flarum/common/Model';
1\n\nexport default class Tag extends Model {\n  title = Model.attribute('title');\n  createdAt = Model.attribute('createdAt', Model.transformDate);\n  parent = Model.hasOne('parent');\n  discussions = Model.hasMany('discussions');\n}\n"})}),"\n",(0,a.jsxs)(n.p,{children:["You must then register your new model with the store using the frontend ",(0,a.jsx)(n.code,{children:"Store"})," extender in a new ",(0,a.jsx)(n.code,{children:"extend.js"})," module:"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-js",children:"import Extend from 'flarum/common/extenders';\n\nexport default [\n  new Extend.Store()\n    .add('tags', Tag),\n];\n"})}),"\n",(0,a.jsxs)(n.admonition,{type:"info",children:[(0,a.jsxs)(n.p,{children:["Remember to export the ",(0,a.jsx)(n.code,{children:"extend"})," module from your entry ",(0,a.jsx)(n.code,{children:"index.js"})," file:"]}),(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-js",children:"export { default as extend } from './extend';\n"})})]}),"\n",(0,a.jsx)(n.h3,{id:"extending-models-1",children:"Extending Models"}),"\n",(0,a.jsxs)(n.p,{children:["To add attributes and relationships to existing models, use the ",(0,a.jsx)(n.code,{children:"Model"})," extender:"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-ts",children:"  new Extend.Model(Discussion)\n    .attribute<string>('slug')\n    .hasOne<User>('user')\n    .hasMany<Post>('posts')\n"})}),"\n",(0,a.jsx)(n.h3,{id:"saving-resources",children:"Saving Resources"}),"\n",(0,a.jsxs)(n.p,{children:["To send data back through the API, call the ",(0,a.jsx)(n.code,{children:"save"})," method on a model instance. This method returns a Promise which resolves with the same model instance:"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-js",children:"discussion.save({ title: 'Hello, world!' }).then(console.log);\n"})}),"\n",(0,a.jsxs)(n.p,{children:["You can also save relationships by passing them in a ",(0,a.jsx)(n.code,{children:"relationships"})," key. For has-one relationships, pass a single model instance. For has-many relationships, pass an array of model instances."]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-js",children:"user.save({\n  relationships: {\n    groups: [\n      store.getById('groups', 1),\n      store.getById('groups', 2)\n    ]\n  }\n})\n"})}),"\n",(0,a.jsx)(n.h3,{id:"creating-new-resources",children:"Creating New Resources"}),"\n",(0,a.jsxs)(n.p,{children:["To create a new resource, create a new model instance for the resource type using the store's ",(0,a.jsx)(n.code,{children:"createRecord"})," method, then ",(0,a.jsx)(n.code,{children:"save"})," it:"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-js",children:"const discussion = app.store.createRecord('discussions');\n\ndiscussion.save({ title: 'Hello, world!' }).then(console.log);\n"})}),"\n",(0,a.jsx)(n.h3,{id:"deleting-resources",children:"Deleting Resources"}),"\n",(0,a.jsxs)(n.p,{children:["To delete a resource, call the ",(0,a.jsx)(n.code,{children:"delete"})," method on a model instance. This method returns a Promise:"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-js",children:"discussion.delete().then(done);\n"})}),"\n",(0,a.jsx)(n.h2,{id:"backend-models-vs-frontend-models",children:"Backend Models vs Frontend Models"}),"\n",(0,a.jsx)(n.p,{children:"Often, backend and frontend models will have similar attributes and relationships. This is a good pattern to follow, but isn't always true."}),"\n",(0,a.jsxs)(n.p,{children:["The attributes and relationships of backend models are based on the ",(0,a.jsx)(n.strong,{children:"database"}),". Each column in the model's table will map to an attribute on the backend model."]}),"\n",(0,a.jsxs)(n.p,{children:["The attributes and relationships of frontend models are based on the output of ",(0,a.jsx)(n.a,{href:"/1.x/extend/api",children:"API Serializers"}),". These will be covered more in depth in the next article, but it's worth that a serializer could output all, any, or none of the backend model's attributes, and the names under which they're accessed might be different in the backend and frontend."]}),"\n",(0,a.jsxs)(n.p,{children:["Furthermore, when you save a backend model, that data is being written directly to the database. But when you save a frontend model, all you're doing is triggering a request to the API. In the ",(0,a.jsx)(n.a,{href:"/1.x/extend/api",children:"next article"}),", we'll learn how to handle these requests in the backend, so your requested changes are actually reflected in the database."]})]})}function h(e={}){const{wrapper:n}={...(0,i.R)(),...e.components};return n?(0,a.jsx)(n,{...e,children:(0,a.jsx)(c,{...e})}):c(e)}},8453(e,n,s){s.d(n,{R:()=>o,x:()=>r});var t=s(6540);const a={},i=t.createContext(a);function o(e){const n=t.useContext(i);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(a):e.components||a:o(e.components),t.createElement(i.Provider,{value:n},e.children)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.