Cube Views
Cube Views provide an elegant way to:
- Define a clean view on top of your data models.
- Tell Embeddable and Cube which join paths to use and when.
- Enforce that row-level security is always applied consistently.
Source: Cube's Official Docs (opens in a new tab)
Why use Cube Views?
Cube data models (known as “cubes”) allow you to define user-friendly entities, measures and dimensions and the relationships between them. This works well when you’re getting started, but as you start to build out more and more models you can quickly start to hit a number of problems:
- Clutter: Embeddable starts to show dozens of models in the builder and it can be hard for dashboard builders to know where to start from.
- Ambiguous join paths: Models are connected via multiple join paths and Cube doesn’t know which path to take.
- Enforcing row-level security: Defining row-level security at the cube level means that row-level security is only applied when queries use that cube. You therefore find yourself defining row-level security repeatedly in every model.
Cube Views solve these problems:
- Clutter: Views allow you to represent different subsets of your connected Cubes graph as individual Views (e.g. you could have just a Marketing view, a Sales View and an Admin view, each with their own individualised terminology)
- Ambiguous join paths: Views represent a subset of the connected graph and thus allow you to specify exactly which join path to use for any measure or dimension that is queried using that view.
- Enforcing row-level security: Views allow you to define view-level access policies that can significantly simplify your row-level security. You define your row-level security rules once at the view-level, and you can be confident that they will always be applied for any query.
How to define Cube Views
The simplest way to get started with Cube Views is to define a <view-name>.cube.yml
file (e.g. customer_view.cube.yml
) like so:
views:
- name: customer_view
cubes:
- join_path: customers
prefix: true
includes: "*"
- join_path: orders
prefix: true
includes: "*"
- join_path: products
prefix: true
includes: "*"
This tells Embeddable that it should present a model called “Customer View” that contains all the dimensions and measures from the customers
, orders
and products
models.

You can then mark those models as public: false
in your codebase so that the models themselves will no longer appear in Embeddable:
cubes:
- name: customers
public: false
...
cubes:
- name: orders
public: false
...
cubes:
- name: products
public: false
Meaning you end up with a much cleaner list of models in Embeddable:

Example Views
A simple Cube View might look like this:
#
# customer_view.cube.yml
#
views:
- name: customer_view
title: Customer View
cubes:
- join_path: customers
prefix: true
includes: "*"
- join_path: orders
prefix: true
includes: "*"
- join_path: products
prefix: true
includes: "*"
Things to notice:
views
Even though views have the same file structure as cubes (i.e. their file must end in.cube.yml
), the root of the file starts withviews
instead ofcubes
.name
This is the unique name used internally by Embeddable and Cube to reference this View. Updating it once it’s in use can potentially break your dashboards.title
This is an optional, human-readable name that appears in the UI. You can change it anytime without impacting the underlying logic.cubes
This lists the cubes that you want to give access to in your View.join_path
In its simplest form this is the name of the cube to include in the View, but you can use dot-notation to tell Cube which join path to use when querying members (dimensions and measures) of this model. Learn more here.prefix
Setting this totrue
will prefix thename
of all the members (dimensions and members) of this model with the modelname
(e.g. turningcreated_at
inproducts
intoproducts_created_at
). This is quite useful as it avoids conflicts where two models have members with the same name (e.g.customers
andproducts
may both have a measure namedcount
). You can also solve this conflict problem by defining analias
. Learn more here.includes
this lists the members (dimensions and measures) that you want to include in the View."*"
means include all. Below we show how to explicitly include specific members.
Includes
When describing which members (dimensions and measures) to include you can either use “*”
, as shown in the customers
section below, to include all its members. Or you can explicitly describe each member one by one, as shown in the orders
and products
sections below:
views:
- name: customer_view
cubes:
- join_path: customers
prefix: true
includes: "*"
- join_path: orders
prefix: true
includes:
- id
- created_at
- join_path: products
prefix: true
includes:
- id
- name
- size
- count
Alias and title
By default Cube will derive the name
and title
, for each member (dimension and measure) in the View, using the name
and title
as defined in the Cube it is referencing. But if you want more control over the name
and title
to use, you can define these explicitly using alias
and title
as shown in the orders
and products
sections below:
views:
- name: customer_view
cubes:
- join_path: customers
prefix: true
includes: "*"
- join_path: orders
includes:
- name: created_at
alias: orders_created_at
title: 'Order date'
- join_path: products
includes:
- name: name
alias: products_name
- name: size
alias: products_size
title: 'Size of the product'
- name: count
alias: products_count
This can be particularly useful if you want to name your members (dimensions and measures) differently for different audiences. You can define a different view for each audience and give each member different titles.
Join paths
The join_path
parameter uses dot notation (e.g. <cube1>.<cube2>.<cube3>
) to define the joins that should be used when accessing particular members (dimensions and measures) within the View.
E.g:
views:
- name: customer_view
cubes:
- join_path: customers
prefix: true
includes: "*"
- join_path: customers.orders
prefix: true
includes: "*"
- join_path: customers.orders.products
prefix: true
includes: "*"
In this example:
- All members of the
customers
cube will be accessed directly - All members of the
orders
cube must include a direct join fromcustomers
, even if no members from the customers cube are requested. - All members of the
products
cube must include a join fromcustomers
viaorders
even if only members of the products cube are requested.
E.g a query requesting just the product_count
would generate the following SQL:
SELECT
count(distinct "products".id) "customer_view__products_count"
FROM
public.customers AS "customers"
LEFT JOIN public.orders AS "orders" ON "customers".id = "orders".customer_id
LEFT JOIN public.products AS "products" ON "orders".product_id = "products".id
LIMIT
100
This can be particularly useful if:
- Your row-level security has been defined in the
customers
cube model, and you want to make sure that that row-level security is always applied. - You have defined multiple join paths from
customers
toproducts
, but you want Cube to take the path that goes throughorders
.
Excludes
If you use includes: '*'
you can also use excludes
to exclude particular members (dimensions and measures) from your View, like so:
views:
- name: customer_view
cubes:
- join_path: customers
prefix: true
includes: "*"
excludes:
- id
Access policies
Access policies allow you to apply both member_level
and row_level
security to your Views based on roles (i.e. control which columns and rows are visible to which end-users). This is a nice alternative to cube-level row-level security, as:
- It can be defined in one place on the View (rather than having to define it in each cube model)
- It can also allow you to control which members (dimensions and measures) are visible.
views:
- name: customer_view
cubes:
- join_path: customers
prefix: true
includes: "*"
access_policy:
- role: 'default'
member_level:
includes: "*"
row_level:
filters:
- member: customers_id
operator: equals
values: ["{ securityContext.user_id }"]
The above access_policy
, for example, gives every end-user with the “default” role, access to all members of the customers_view
view, but it restricts them to only access the rows whose customers_id
is equal to a given user_id
in the securityContext
.
Learn more here (TODO).