Skip to content

Adding a new model

Jeff Evans edited this page Jan 19, 2021 · 4 revisions

Adding a new model

You added new functionality to Metabase which requires state to be persisted. To do so you will need to write a Toucan model, and make sure all the migrations are set up.

1. Define the new model

Note, this guide deals only with Metabase-specifics. For Toucan models basics, refer to Defining models chapter of Toucan documentation.

Models go into metabase.models.____ namespace. The convention we use is to use singular in model names, so DomainEntity, rather than DomainEntities.

To lessen boilerplate, we have metabase.models.interface/IModelDefaults, and metabase.models.interface/IObjectPermissions into which you merge customizations you might need.

(defn- perms-objects-set [{:keys [source-table]} _]
  (let [table (Table source-table)]
    #{(perms/object-path (:db_id table) (:schema table) (:id table))}))

(u/strict-extend (class DomainEntity)
  models/IModel
  (merge models/IModelDefaults
         {:hydration-keys (constantly [:domain-entity])
          :types          (constantly {:metrics             :json
                                       :segments            :json
                                       :breakout_dimensions :json
                                       :dimensions          :json
                                       :type                :keyword})
          :properties     (constantly {:timestamped? true}) ; don't forget to add created_at and updated_at fields when defining the migration (see bellow)
          :pre-update     serialize-dimensions
          :pre-insert     serialize-dimensions
          :post-select    deserialize-dimensions})

  i/IObjectPermissions
  (merge i/IObjectPermissionsDefaults
         {:can-read?         (partial i/current-user-has-full-permissions? :read)
          :can-write?        i/superuser?
          ;; here we'll just use the same permission set as the table to which this refers to, 
          ;; but you might need something more fine-grained. 
          ;; Look into `metabase.models.permissions` for what access controls you have at your disposal.
          :perms-objects-set perms-objects-set})) 

2. Write tests

Access control, pre-/post- hooks, and :types (de)serializations should be covered by tests.

3. (optional) Add the model to the list of entities we carry over when migrating from H2

Add the model to entities in metabase.cmd.load-from-h2, minding where in the list you insert it.

Update the test fixture DB. To do so, set export MB_DB_FILE=frontend/frontend/test/__runner__/test_db_fixture.db (remember: MB_DB_FILE doesn't take the trailing .mv.db in the file name) and start Metabase so that migrations get run. Include test_db_fixture.db in the PR.

4. Write the migrations

Add a new changeset to resources/migrations/000_migrations.yaml.

Use comments to explain what is being added both in terms of the whole changeset as well as documenting non-trivial fields (eg. anything that has a :types serialization).

Use delete cascades by specifying deleteCascade: true on foreign keys where possible instead of relying on manual cleanup via post-delete hook.

Don't forget to add indices that cover the common access patterns.

Avoid migrations which touch existing data (adding new fields to existing tables is fine). It's better to have a constraint less (which can always be enforced by Schema).

Clone this wiki locally