A new analytics engineer on your team writes a dbt model that references a raw table directly with the fully qualified name (`raw.salesforce.opportunities`) instead of using `{{ source(...) }}`. Why do you ask them to change it?
Click or press Enter to reveal the answerUsing source() (and ref() for other dbt models) registers the table in dbt's dependency graph, so dbt can track lineage, run freshness checks, and build models in the correct order. Hardcoding the table name breaks dependency tracking and makes promoting the project between dev, staging, and prod environments fragile.
What ref() and source() actually do
ref('model_name') compiles to the fully-qualified table name for that model in the current target environment — so the same code resolves to dev_rmaldonado.stg_orders when you run it locally and analytics.stg_orders in production. source('salesforce', 'opportunities') does the same thing for raw tables declared in a sources.yml file.
# sources.yml
sources:
- name: salesforce
database: raw
schema: salesforce
tables:
- name: opportunities
select * from {{ source('salesforce', 'opportunities') }}
Why hardcoding breaks things
- No lineage graph.
dbt docs generateanddbt ls --select +my_modelrely onref()/source()calls to build the DAG. A hardcoded table reference is invisible to dbt, so lineage diagrams and impact analysis (“what breaks if I change this table?”) are wrong. - No environment portability. A hardcoded
raw.salesforce.opportunitiesin a model still points at the same table when you run in a dev schema, defeating the point of having separate environments — and it can’t be swapped out for a mocked/sampled table in tests. - No freshness checks. Source freshness (
dbt source freshness) is only possible on tables declared viasource(). - No build ordering guarantees. dbt uses the DAG to decide what to build before what. A hardcoded reference to another dbt model bypasses that entirely and can cause the model to run against stale or not-yet-built data.
The fix is mechanical but the payoff is real: always route raw tables through source() and other dbt models through ref(), even when it feels like unnecessary ceremony for a “quick” model.