Reflecting Models from Existing Tables
Source:vignettes/reflecting-tables.Rmd
reflecting-tables.RmdWhen a database already exists, oRm can introspect its
tables and return ready-to-use TableModel objects — no need
to re-declare every column by hand. This is called
reflection.
Single-table reflection with reflect()
engine$reflect() inspects one table and returns a
TableModel:
library(oRm)
engine <- Engine$new(
drv = RPostgres::Postgres(),
dbname = "mydb",
host = "localhost"
)
# Reflect every column of the existing "users" table
Users <- engine$reflect("users")
names(Users$fields)
#> [1] "id" "name" "email" "created_at"
# Narrow the model with include / exclude
Users <- engine$reflect("users", include = c("id", "name", "email"))
Users <- engine$reflect("users", exclude = c("password_hash", "internal_notes"))
Users$read(.mode = "data.frame")Reflection depth by dialect
| Dialect | What is reflected |
|---|---|
| Default (all) | Column names and best-effort types only |
| PostgreSQL | Canonical types, primary keys, nullability, column defaults, and foreign keys |
With PostgreSQL, reflect() captures:
-
Canonical types — e.g.
integer,text,timestamp with time zone -
Primary key flags —
update()anddelete()work without any extra arguments -
Nullability — honoured on
create() -
Column defaults — server-side defaults such as
nextval()ornow()are stored asdbplyr::sql()objects and applied by the database at insert time;flush()reads the computed value back into the record -
Foreign keys — reflected as
ForeignKeyobjects, schema-qualified when the target lives in another schema
Overriding reflected columns
Arguments passed via ... take precedence over reflected
columns, just like engine$model(). This lets you attach a
primary key on dialects that don’t reflect one, override a type, or add
a Method():
# Supply the PK explicitly on a non-PostgreSQL backend
Users <- engine$reflect(
"users",
id = Column("INTEGER", primary_key = TRUE)
)
# Attach a custom method alongside the reflected columns
Users <- engine$reflect(
"users",
display_name = Method("table", function(self) {
self$read(.mode = "data.frame")$name
})
)Schema-wide reflection with reflect_schema()
engine$reflect_schema() reflects every table in a schema
at once and automatically wires the many_to_one /
one_to_many relationships implied by the reflected foreign
keys.
# Reflect all tables in the engine's default schema
models <- engine$reflect_schema()
# Or restrict to a subset
models <- engine$reflect_schema(tables = c("users", "posts", "comments"))
# Exclude tables you don't need
models <- engine$reflect_schema(exclude = c("schema_migrations", "audit_log"))Accessing models
reflect_schema() returns a named list keyed by bare
table name:
posts <- models$posts
users <- models$usersAuto-wired relationships
Every reflected ForeignKey whose target was also
reflected is turned into a many_to_one relationship (with
the reverse one_to_many backref):
post <- posts$read(id == 1, .mode = "get")
author <- post$relationship("users") # posts.user_id -> users.id
author$data$name
#> [1] "Kent"
all_posts <- author$relationship("posts") # reverse backref
length(all_posts)
#> [1] 12Foreign keys pointing at tables outside the reflected set are skipped with a warning. You can always wire those manually afterwards:
models$posts$define_relationship(
local_key = "category_id",
type = "many_to_one",
related_model = some_other_model,
related_key = "id",
ref = "category",
backref = "posts"
)Schema-qualified foreign keys
ForeignKey() supports cross-schema references in both
shorthand and explicit forms:
# Shorthand: "schema.table.column"
fk <- ForeignKey("INTEGER", references = "audit.users.id")
# Explicit
fk <- ForeignKey(
"INTEGER",
ref_schema = "audit",
ref_table = "users",
ref_column = "id",
on_delete = "CASCADE"
)The generated SQL qualifies the target correctly:
REFERENCES "audit"."users" ("id").
PostgreSQL reflection automatically schema-qualifies foreign keys
when the target lives in a different schema, so
reflect_schema() handles cross-schema FK wiring
transparently.