Reusing Code with Extends

LookML
Version

On this Page
Docs Menu

Please note: this is an advanced topic that assumes a good, pre-existing knowledge of LookML

As your LookML model expands in size and complexity it becomes increasingly useful to re-use various bits of LookML in multiple places. Looker has the option to "extend" explores, views, and dashboards so that you can do just that.

In essence, when you extend something, you combine its contents with the contents of another LookML object. Then, if there are any conflicts, one of the objects "wins" based on how you've written your LookML. Finally, the new, combined LookML is interpreted as normal.

There are lots of reasons you might want to work with extends. Here are just a few:

  • Writing DRY (don't repeat yourself) code, so that you can define things in just one place, making your code more consistent and faster to edit
  • Easy management of different field sets for different users
  • Sharing design patterns in different parts of your project
  • Re-using sets of joins, dimensions, or measures across a project

Example

To help make this more clear, let's look at a simple example.

- explore: transaction extends: customer persist_for: 5 minutes   - explore: customer persist_for: 12 hours
explore: transaction { extends: [customer] persist_for: "5 minutes" }   explore: customer { persist_for: "12 hours" }

In this situation you're creating an explore called transaction, which is based on the explore called customer. You can see that the code kind of reads this way, like "transaction extends what's already in customer". Everything that is in customer - like its joins - will be included in transaction. And of course, anything that is in transaction will remain in transaction.

However, you'll notice there is a conflict. The customer explore says the persist_for setting should be 12 hours, but the transaction explore says it should be 5 minutes. In this situation the transaction explore "wins", because it is the view that is doing the extending.

Basic Usage

The extends parameter only works with LookML dashboards, views, and explores. Although you can't extend a model file with the extends parameter, there is a workaround using include described in this Discourse post.

Extending a LookML Dashboard

Extending a dashboard is the most straightforward extension, and works like you'd expect:

The new, extended dashboard looks like:

- dashboard: name_of_the_new_dashboard extends: name_of_the_dashboard_being_extended # The additional things you want to add or change # on the new dashboard are added as normal

The existing dashboard that is being extended looks like:

- dashboard: name_of_the_dashboard_being_extended # The normal contents of the dashboard follow

Extending a View

When you extend a view it's a good idea to use the sql_table_name parameter on the view that is going to be extended, if you haven't done so already. For example:

The new, extended view looks like:

- view: name_of_the_new_view extends: name_of_the_view_being_extended # The additional things you want to add or change # on the new view are added as normal
include: "file_that_contains_the_view_being_extended" view: name_of_the_new_view { extends: [name_of_the_view_being_extended] # The additional things you want to add or change # on the new view are added as normal }

The existing view that is being extended looks like:

- view: name_of_the_view_being_extended sql_table_name: appropriate_setting_as_explained_next # The normal contents of the view follow
view: name_of_the_view_being_extended { sql_table_name: appropriate_setting_as_explained_next ;; # The normal contents of the view follow }

Remember that sql_table_name defines the table in your database that will be queried by a view, and that its default value is the name of the view. So, if you aren't already using sql_table_name, simply give it the same value as your view name. The reason that adding sql_table_name is a good idea is explained in more detail below.

You will also notice the use of the include parameter. This may initially seem unecessary, especially if the file has already been included in your model file. However, explicitly declaring the files on which the extended view depends allows Looker to complete more robust error checking. It also prevents future problems if someone were to change the include settings in your model file.

Extending an Explore

When you extend an explore it's a good idea to use both the view and from parameters on the explore that is going to be extended, if you haven't done so already. For example:

The new, extended explore looks like:

- explore: name_of_the_new_explore extends: name_of_the_explore_being_extended # The additional things you want to add or change # on the new view are added as normal   # IMPORTANT: Relies on the from and view parameters # in the explore that is being extended
explore: name_of_the_new_explore { extends: [name_of_the_explore_being_extended] # The additional things you want to add or change # on the new view are added as normal   # IMPORTANT: Relies on the from and view_name parameters # in the explore that is being extended }

The existing view that is being extended looks like:

- explore: name_of_the_explore_being_extended view: appropriate_setting_as_explained_next from: appropriate_setting_as_explained_next # The normal contents of the explore follow
explore: name_of_the_explore_being_extended { view_name: appropriate_setting_as_explained_next from: appropriate_setting_as_explained_next # The normal contents of the explore follow }

Outside of extends, it would be pretty unusual to use view_name and from at the same time. However, in this context, this is how you can think about their purposes:

  • explore is the title of the explore, and how it will appear to users in the Explore menu. Because you're also going to use the from parameter, it can be anything you want; it does not have to be the name of an existing view.
  • view_name is how the fields of the explore will show up in the field picker, and how you'll reference the fields in LookML. You can enter anything you want; it does not have to be the name of an existing view. If you aren't already using view_name, or aren't sure what value to give it, keep in mind that its default value is the name of the explore. Therefore, you can usually just give it the same name as the explore if you aren't sure what to do.
  • from is the view where the explore's dimensions and measures come from. This does need to be the name of an existing view. If you aren't already using from, or aren't sure what value to give it, keep in mind that its default value is the name of the view_name ... which in turn, get its default value from explore. Therefore, you can usually just give it the same name as the explore if you aren't sure what to do.

The reason that adding these parameters is a good idea is explained in more detail below.

Requiring Extension

For explores, views, and LookML dashboards, there is also an option to use the extension: required parameter if you want to create a starting point that must always be extended by something else in order to be visible to users. For example, maybe you have an explore called Basic Order Fields that should be hidden to users, except when it is extended by other useful information:

# This will make sure that basic_order_fields # doesn’t show up to users all by itself - explore: basic_order_fields extension: required   # This will appear to users because # basic_order_fields is being extended - explore: marketing_order_fields extends: basic_order_fields
# This will make sure that basic_order_fields # doesn’t show up to users all by itself explore: basic_order_fields { extension: required }   # This will appear to users because # basic_order_fields is being extended explore: marketing_order_fields { extends: [basic_order_fields] }

It's tempting to turn on hidden to hide Basic Order Fields, but remember that such a setting is going to be inherited by any explore that is based on Basic Order Fields. In this particular example, Marketing Order Fields would get hidden as well if you turned on hidden.

Details of Extend Functionality

If you'd like to understand better why we suggest the extra parameters in the Basic Usage of Extends, or start to use extends in more complex or unusual ways, it's important to know exactly how extends are executed. The four steps are:

  1. A copy is made of the explicitly defined LookML objects inside the dashboard/view/explore that is being extended
  2. The copies are merged with the explicitly defined LookML objects inside the dashboard/view/explore that is doing the extending
  3. If a LookML object is explicitly defined in both places, the object from the extending dashboard/view/explore gets used
  4. The resulting LookML is interpreted as normal

Let's look at an example, step-by-step, to see the implications of this approach:

Starting LookML

This is the LookML that we're going to use in our example:

User With Age Extensions View File

- view: user_with_age_extensions extends: user suggestions: false fields: - dimension: age type: number sql: ${TABLE}.age
view: user_with_age_extensions { extends: [user] suggestions: no dimension: age { type: number sql: ${TABLE}.age ;; } }

User View File

- view: user suggestions: true fields: - dimension: name sql: ${TABLE}.name
view: user { suggestions: yes dimension: name { sql: ${TABLE}.name ;; } }

Step 1: Copy

In this case the User view is being extended into the User With Age Extensions view. Since User is the view that is being extended, a copy of it is made before merging. Understanding that a copy is made is not particularly important, other than to realize that the original User view is left unchanged, and usable as normal.

Step 2: Merge

The next step is for all of LookML from the extended view (User) to be merged into the extending view (User With Age Extensions). It's important to understand the nature of this merge, which is simply a merging of YAML hashes (YAML is the language on which LookML is based). In practical terms this means that any explicitly written LookML gets merged, but the default LookML values that you haven't written down don't get merged. In a sense, it's really just the text of the LookML that is getting put together, and none of the meaning of that text.

Step 3: Resolve Conflicts

The third step that occurs is a resolution of any conflicts between the merged views. The extending view is the view that will "win", which in this case is User With Age Extensions. The fact that default LookML values aren't being considered yet is important, because you don't want to make the mistake of thinking that conflicts between default values are getting resolved. In actuality, they're just being ignored at this step. This is why we suggest in the Basic Usage of Extends that you explicitly add some additional parameters, so that the conflict resolution will occur. In this particular example we have not done that (i.e. we never added sql_table_name to the User view), which is going to cause some problems in the next step.

Step 4: Interpret the LookML

In the final step, the resulting LookML is interpreted as normal, including all the default values. In this particular example we've ended up with LookML that includes view: user_with_age_extensions, but no sql_table_name parameter. As a result, Looker is going to assume that the value of sql_table_name is equal to the view name:

The problem is that there is probably no table in our database called user_with_age_extensions. This is why we suggest that you add a sql_table_name parameter to any view that is going to be extended. Adding view and from to explores that will be extended avoids similar problems.

Advanced Usage

Extending More Than One Object At the Same Time

It is possible to extend more than one dashboard, view, or explore at the same time. For example:

- explore: orders extends: [user_info, marketing_info] # Also works for dashboards and views
explore: orders { extends: [user_info, marketing_info] } # Also works for dashboards and views

The extension process works exactly as described above, but you'll need to keep one extra rule in mind about how conflicts are resolved. If there are any conflicts between the multiple items listed in the extends parameter, priority is given to the items listed last. So in the example above, if there were conflicts between User Info and Marketing Info, the Marketing Info explore would win.

Chaining Together Multiple Extends

You can also chain together as many extends as you'd like. For example:

- explore: orders extends: user_info ...   - explore: user_info extends: marketing_info ...
explore: orders { extends: [user_info] ... } explore: user_info { extends: [marketing_info] ... }

Again, the extension process works exactly as described above, with one extra rule about conflict resolution. If there are any conflicts, priority is given to the last item in the chain of extends. In this example:

  • Orders would have priority over both User Info and Marketing Info
  • User Info would have priority over Marketing Info

Additional Options with Lists

When working with lists you may choose to combine them, instead of having the extending object's list be the winner. Consider this simple extension with a conflicting list called animals:

- view: pets extends: fish sets: animals: [dog, cat]   - view: fish sets: animals: [goldfish, guppy]
view: pets { extends: fish set: animals { fields: [dog, cat] } } view: fish { set: animals { fields: [goldfish, guppy] } }

In this case the Pets view is doing the extending, and will therefore win, making animals contain [dog, cat]. However, by making use of the special EXTENDED* set, you can combine the lists instead:

- view: pets extends: fish sets: animals: [dog, cat, EXTENDED*]   - view: fish sets: animals: [goldfish, guppy]
view: pets { extends: fish set: animals { fields: [dog, cat, EXTENDED*] } } view: fish { set: animals { fields: [goldfish, guppy] } }

Now the animals list will contain [dog, cat, goldfish, guppy].

Combining Instead of Replacing During Conflict Resolution

In the basic behavior described above we mentioned that, if there are any conflicts during extension, the extending object wins. For example, take this simple extension:

- view: product_short_description extends: product fields: - dimension: description sql: ${TABLE}.short_description   - view: product fields: - dimension: description sql: ${TABLE}.full_description
view: product_short_description { extends: product dimension: description { sql: ${TABLE}.short_description ;; } } view: product { dimension: description { sql: ${TABLE}.full_description ;; } }

You can see there is a conflict of the sql parameter within the Description dimension. Typically, the defintion from Product Short Description will simply overwrite the definition from Product because it is doing the extending.

However, you can also choose to combine the definitions if you like. To do so, you'll use the ${EXTENDED} keyword like this:

- view: product_short_description extends: product fields: - dimension: description sql: LEFT(${EXTENDED}, 50)   - view: product fields: - dimension: description sql: ${TABLE}.full_description
view: product_short_description { extends: product dimension: description { sql: LEFT(${EXTENDED}, 50) ;; } } view: product { dimension: description { sql: ${TABLE}.full_description ;; } }

Now the conflict of the sql parameter will be addressed differently. Instead of the Product Short Description definition winning, it will take the definition from Product and insert it where ${EXTENDED} is used. The resulting definition for Description in this case will be: LEFT(${TABLE}.full_description, 50).

Still have questions?
Go to Discourse - or - Email Support
Top