UNPKG

@rikishi/watermelondb

Version:

Build powerful React Native and React web apps that scale from hundreds to tens of thousands of records and remain fast

394 lines (298 loc) 22 kB
<!DOCTYPE HTML> <html lang="en" class="sidebar-visible no-js light"> <head> <!-- Book generated using mdBook --> <meta charset="UTF-8"> <title>Defining Models - WatermelonDB documentation</title> <!-- Custom HTML head --> <meta content="text/html; charset=utf-8" http-equiv="Content-Type"> <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="theme-color" content="#ffffff" /> <link rel="icon" href="favicon.svg"> <link rel="shortcut icon" href="favicon.png"> <link rel="stylesheet" href="css/variables.css"> <link rel="stylesheet" href="css/general.css"> <link rel="stylesheet" href="css/chrome.css"> <link rel="stylesheet" href="css/print.css" media="print"> <!-- Fonts --> <link rel="stylesheet" href="FontAwesome/css/font-awesome.css"> <link rel="stylesheet" href="fonts/fonts.css"> <!-- Highlight.js Stylesheets --> <link rel="stylesheet" href="highlight.css"> <link rel="stylesheet" href="tomorrow-night.css"> <link rel="stylesheet" href="ayu-highlight.css"> <!-- Custom theme stylesheets --> </head> <body> <!-- Provide site root to javascript --> <script type="text/javascript"> var path_to_root = ""; var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light"; </script> <!-- Work around some values being stored in localStorage wrapped in quotes --> <script type="text/javascript"> try { var theme = localStorage.getItem('mdbook-theme'); var sidebar = localStorage.getItem('mdbook-sidebar'); if (theme.startsWith('"') && theme.endsWith('"')) { localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1)); } if (sidebar.startsWith('"') && sidebar.endsWith('"')) { localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1)); } } catch (e) { } </script> <!-- Set the theme before any content is loaded, prevents flash --> <script type="text/javascript"> var theme; try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { } if (theme === null || theme === undefined) { theme = default_theme; } var html = document.querySelector('html'); html.classList.remove('no-js') html.classList.remove('light') html.classList.add(theme); html.classList.add('js'); </script> <!-- Hide / unhide sidebar before it is displayed --> <script type="text/javascript"> var html = document.querySelector('html'); var sidebar = 'hidden'; if (document.body.clientWidth >= 1080) { try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { } sidebar = sidebar || 'visible'; } html.classList.remove('sidebar-visible'); html.classList.add("sidebar-" + sidebar); </script> <nav id="sidebar" class="sidebar" aria-label="Table of contents"> <div class="sidebar-scrollbox"> <ol class="chapter"><li class="chapter-item expanded "><a href="ch01-00-get-excited.html"><strong aria-hidden="true">1.</strong> Get excited</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="index.html"><strong aria-hidden="true">1.1.</strong> Check out the README</a></li><li class="chapter-item expanded "><a href="Demo.html"><strong aria-hidden="true">1.2.</strong> See the demo</a></li></ol></li><li class="chapter-item expanded "><a href="ch02-00-learn-to-use.html"><strong aria-hidden="true">2.</strong> Learn to use Watermelon</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="Installation.html"><strong aria-hidden="true">2.1.</strong> Installation</a></li><li class="chapter-item expanded "><a href="Setup.html"><strong aria-hidden="true">2.2.</strong> Setup</a></li><li class="chapter-item expanded "><a href="Schema.html"><strong aria-hidden="true">2.3.</strong> Schema</a></li><li class="chapter-item expanded "><a href="Model.html" class="active"><strong aria-hidden="true">2.4.</strong> Defining Models</a></li><li class="chapter-item expanded "><a href="CRUD.html"><strong aria-hidden="true">2.5.</strong> Create, Read, Update, Delete</a></li><li class="chapter-item expanded "><a href="Components.html"><strong aria-hidden="true">2.6.</strong> Connecting to React Components</a></li><li class="chapter-item expanded "><a href="Query.html"><strong aria-hidden="true">2.7.</strong> Querying</a></li><li class="chapter-item expanded "><a href="Relation.html"><strong aria-hidden="true">2.8.</strong> Relations</a></li><li class="chapter-item expanded "><a href="Writers.html"><strong aria-hidden="true">2.9.</strong> Writers, Readers, batching</a></li></ol></li><li class="chapter-item expanded "><a href="ch03-00-advanced.html"><strong aria-hidden="true">3.</strong> Advanced guides</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="Advanced/Migrations.html"><strong aria-hidden="true">3.1.</strong> Migrations</a></li><li class="chapter-item expanded "><a href="Advanced/Sync.html"><strong aria-hidden="true">3.2.</strong> Sync</a></li><li class="chapter-item expanded "><a href="Advanced/CreateUpdateTracking.html"><strong aria-hidden="true">3.3.</strong> Automatic create/update tracking</a></li><li class="chapter-item expanded "><a href="Advanced/AdvancedFields.html"><strong aria-hidden="true">3.4.</strong> Advanced fields</a></li><li class="chapter-item expanded "><a href="Advanced/Flow.html"><strong aria-hidden="true">3.5.</strong> Flow</a></li><li class="chapter-item expanded "><a href="Advanced/LocalStorage.html"><strong aria-hidden="true">3.6.</strong> LocalStorage</a></li><li class="chapter-item expanded "><a href="Advanced/ProTips.html"><strong aria-hidden="true">3.7.</strong> Pro tips</a></li><li class="chapter-item expanded "><a href="Advanced/Performance.html"><strong aria-hidden="true">3.8.</strong> Performance tips</a></li><li class="chapter-item expanded "><a href="Advanced/SharingDatabaseAcrossTargets.html"><strong aria-hidden="true">3.9.</strong> iOS - Sharing database across targets</a></li></ol></li><li class="chapter-item expanded "><a href="ch04-00-deeper.html"><strong aria-hidden="true">4.</strong> Dig deeper into WatermelonDB</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="Implementation/Architecture.html"><strong aria-hidden="true">4.1.</strong> Architecture</a></li><li class="chapter-item expanded "><a href="Implementation/Adapters.html"><strong aria-hidden="true">4.2.</strong> Adapters</a></li><li class="chapter-item expanded "><a href="Implementation/SyncImpl.html"><strong aria-hidden="true">4.3.</strong> Sync implementation</a></li></ol></li><li class="chapter-item expanded "><a href="ch04-00-deeper.html"><strong aria-hidden="true">5.</strong> Other</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="Roadmap.html"><strong aria-hidden="true">5.1.</strong> Roadmap</a></li><li class="chapter-item expanded "><a href="CONTRIBUTING.html"><strong aria-hidden="true">5.2.</strong> Contributing</a></li><li class="chapter-item expanded "><a href="CHANGELOG.html"><strong aria-hidden="true">5.3.</strong> Changelog</a></li></ol></li></ol> </div> <div id="sidebar-resize-handle" class="sidebar-resize-handle"></div> </nav> <div id="page-wrapper" class="page-wrapper"> <div class="page"> <div id="menu-bar-hover-placeholder"></div> <div id="menu-bar" class="menu-bar sticky bordered"> <div class="left-buttons"> <button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar"> <i class="fa fa-bars"></i> </button> <button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list"> <i class="fa fa-paint-brush"></i> </button> <ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu"> <li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li> <li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li> <li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li> <li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li> <li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li> </ul> <button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar"> <i class="fa fa-search"></i> </button> </div> <h1 class="menu-title">WatermelonDB documentation</h1> <div class="right-buttons"> <a href="print.html" title="Print this book" aria-label="Print this book"> <i id="print-button" class="fa fa-print"></i> </a> </div> </div> <div id="search-wrapper" class="hidden"> <form id="searchbar-outer" class="searchbar-outer"> <input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header"> </form> <div id="searchresults-outer" class="searchresults-outer hidden"> <div id="searchresults-header" class="searchresults-header"></div> <ul id="searchresults"> </ul> </div> </div> <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM --> <script type="text/javascript"> document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible'); document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible'); Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) { link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1); }); </script> <div id="content" class="content"> <main> <h1 id="defining-models"><a class="header" href="#defining-models">Defining Models</a></h1> <p>A <strong>Model</strong> class represents a type of thing in your app. For example, <code>Post</code>, <code>Comment</code>, <code>User</code>.</p> <p>Before defining a Model, make sure you <a href="./Schema.html">defined its schema</a>.</p> <h2 id="create-a-model"><a class="header" href="#create-a-model">Create a Model</a></h2> <p>Let's define the <code>Post</code> model:</p> <pre><code class="language-js">// model/Post.js import { Model } from '@rikishi/watermelondb' export default class Post extends Model { static table = 'posts' } </code></pre> <p>Specify the table name for this Model — the same you defined <a href="./Schema.html">in the schema</a>.</p> <p>Now add the new Model to <code>Database</code>:</p> <pre><code class="language-js">// index.js import Post from 'model/Post' const database = new Database({ // ... modelClasses: [Post], }) </code></pre> <h3 id="associations"><a class="header" href="#associations">Associations</a></h3> <p>Many models relate to one another. A <code>Post</code> has many <code>Comment</code>s. And every <code>Comment</code> belongs to a <code>Post</code>. (Every relation is double-sided). Define those associations like so:</p> <pre><code class="language-js">class Post extends Model { static table = 'posts' static associations = { comments: { type: 'has_many', foreignKey: 'post_id' }, } } class Comment extends Model { static table = 'comments' static associations = { posts: { type: 'belongs_to', key: 'post_id' }, } } </code></pre> <p>On the &quot;child&quot; side (<code>comments</code>) you define a <code>belongs_to</code> association, and pass a column name (key) that points to the parent (<code>post_id</code> is the ID of the post the comment belongs to).</p> <p>On the &quot;parent&quot; side (<code>posts</code>) you define an equivalent <code>has_many</code> association and pass the same column name (⚠️ note that the name here is <code>foreignKey</code>).</p> <h2 id="add-fields"><a class="header" href="#add-fields">Add fields</a></h2> <p>Next, define the Model's <em>fields</em> (properties). Those correspond to <a href="./Schema.html">table columns</a> defined earlier in the schema.</p> <pre><code class="language-js">import { field, text } from '@rikishi/watermelondb/decorators' class Post extends Model { static table = 'posts' static associations = { comments: { type: 'has_many', foreignKey: 'post_id' }, } @text('title') title @text('body') body @field('is_pinned') isPinned } </code></pre> <p>Fields are defined using ES6 decorators. Pass <strong>column name</strong> you defined in Schema as the argument to <code>@field</code>.</p> <p><strong>Field types</strong>. Fields are guaranteed to be the same type (string/number/boolean) as the column type defined in Schema. If column is marked <code>isOptional: true</code>, fields may also be null.</p> <p><strong>User text fields</strong>. For fields that contain arbitrary text specified by the user (e.g. names, titles, comment bodies), use <code>@text</code> - a simple extension of <code>@field</code> that also trims whitespace.</p> <p><strong>Note:</strong> Why do I have to type the field/column name twice? The database convention is to use <code>snake_case</code> for names, and the JavaScript convention is to use camelCase. So for any multi-word name, the two differ. Also, for resiliency, we believe it's better to be explicit, because over time, you might want to refactor how you name your JavaScript field names, but column names must stay the same for backward compatibility.</p> <h3 id="date-fields"><a class="header" href="#date-fields">Date fields</a></h3> <p>For date fields, use <code>@date</code> instead of <code>@field</code>. This will return a JavaScript <code>Date</code> object (instead of Unix timestamp integer).</p> <pre><code class="language-js">import { date } from '@rikishi/watermelondb/decorators' class Post extends Model { // ... @date('last_event_at') lastEventAt } </code></pre> <h3 id="derived-fields"><a class="header" href="#derived-fields">Derived fields</a></h3> <p>Use ES6 getters to define model properties that can be calculated based on database fields:</p> <pre><code class="language-js">import { field, text } from '@rikishi/watermelondb/decorators' class Post extends Model { static table = 'posts' @date('archived_at') archivedAt get isRecentlyArchived() { // in the last 7 days return this.archivedAt &amp;&amp; this.archivedAt.getTime() &gt; Date.now() - 7 * 24 * 3600 * 1000 } } </code></pre> <h3 id="to-one-relation-fields"><a class="header" href="#to-one-relation-fields">To-one relation fields</a></h3> <p>To point to a related record, e.g. <code>Post</code> a <code>Comment</code> belongs to, or author (<code>User</code>) of a <code>Comment</code>, use <code>@relation</code> or <code>@immutableRelation</code>:</p> <pre><code class="language-js">import { relation, immutableRelation } from '@rikishi/watermelondb/decorators' class Comment extends Model { // ... @relation('posts', 'post_id') post @immutableRelation('users', 'author_id') author } </code></pre> <p><strong>➡️ Learn more:</strong> <a href="./Relation.html">Relation API</a></p> <h3 id="children-to-many-relation-fields"><a class="header" href="#children-to-many-relation-fields">Children (to-many relation fields)</a></h3> <p>To point to a list of records that belong to this Model, e.g. all <code>Comment</code>s that belong to a <code>Post</code>, you can define a simple <code>Query</code> using <code>@children</code>:</p> <pre><code class="language-js">import { children } from '@rikishi/watermelondb/decorators' class Post extends Model { static table = 'posts' static associations = { comments: { type: 'has_many', foreignKey: 'post_id' }, } @children('comments') comments } </code></pre> <p>Pass the <em>table name</em> of the related records as an argument to <code>@children</code>. The resulting property will be a <code>Query</code> you can fetch, observe, or count.</p> <p><strong>Note:</strong> You must define a <code>has_many</code> association in <code>static associations</code> for this to work</p> <p><strong>➡️ Learn more:</strong> <a href="./Query.html">Queries</a></p> <h3 id="custom-queries"><a class="header" href="#custom-queries">Custom Queries</a></h3> <p>In addition to <code>@children</code>, you can define custom Queries or extend existing ones, for example:</p> <pre><code class="language-js">import { children } from '@rikishi/watermelondb/decorators' import { Q } from '@rikishi/watermelondb' class Post extends Model { static table = 'posts' static associations = { comments: { type: 'has_many', foreignKey: 'post_id' }, } @children('comments') comments @lazy verifiedComments = this.comments.extend( Q.where('is_verified', true) ) } </code></pre> <p><strong>➡️ Learn more:</strong> <a href="./Query.html">Queries</a></p> <h3 id="writer-methods"><a class="header" href="#writer-methods">Writer methods</a></h3> <p>Define <strong>writers</strong> to simplify creating and updating records, for example:</p> <pre><code class="language-js">import { writer } from '@rikishi/watermelondb/decorators' class Comment extends Model { static table = 'comments' @field('is_spam') isSpam @writer async markAsSpam() { await this.update(comment =&gt; { comment.isSpam = true }) } } </code></pre> <p>Methods must be marked as <code>@writer</code> to be able to modify the database.</p> <p><strong>➡️ Learn more:</strong> <a href="./Writers.html">Writers</a></p> <h2 id="advanced-fields"><a class="header" href="#advanced-fields">Advanced fields</a></h2> <p>You can also use these decorators:</p> <ul> <li><code>@json</code> for complex serialized data</li> <li><code>@readonly</code> to make the field read-only</li> <li><code>@nochange</code> to disallow changes to the field <em>after the first creation</em></li> </ul> <p>And you can make observable compound properties using RxJS...</p> <p><strong>➡️ Learn more:</strong> <a href="./Advanced/AdvancedFields.html">Advanced fields</a></p> <hr /> <h2 id="next-steps"><a class="header" href="#next-steps">Next steps</a></h2> <p>➡️ After you define some Models, learn the <a href="./CRUD.html"><strong>Create / Read / Update / Delete API</strong></a></p> </main> <nav class="nav-wrapper" aria-label="Page navigation"> <!-- Mobile navigation buttons --> <a rel="prev" href="Schema.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left"> <i class="fa fa-angle-left"></i> </a> <a rel="next" href="CRUD.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right"> <i class="fa fa-angle-right"></i> </a> <div style="clear: both"></div> </nav> </div> </div> <nav class="nav-wide-wrapper" aria-label="Page navigation"> <a rel="prev" href="Schema.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left"> <i class="fa fa-angle-left"></i> </a> <a rel="next" href="CRUD.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right"> <i class="fa fa-angle-right"></i> </a> </nav> </div> <script type="text/javascript"> window.playground_copyable = true; </script> <script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script> <script src="mark.min.js" type="text/javascript" charset="utf-8"></script> <script src="searcher.js" type="text/javascript" charset="utf-8"></script> <script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script> <script src="highlight.js" type="text/javascript" charset="utf-8"></script> <script src="book.js" type="text/javascript" charset="utf-8"></script> <!-- Custom JS scripts --> </body> </html>