Relationship
Introduction
OrionORM.jl is an ORM for Julia that simplifies defining models and managing relationships between them. With OrionORM.jl, you can define relationships such as:
- hasMany: A record in one model has many related records in another model.
- belongsTo: A record in one model belongs to a record in another model.
- hasOne: A record in one model has a single related record in another model.
In addition, OrionORM.jl allows you to enrich query results using the "include"
key in a query dictionary. This way, you can return related data as part of a Dict
(for single record queries) or a vector of Dicts
(for multiple records).
Creating Models with Relationships
To define models along with their relationships, use the @Model
macro. For example:
Model(
:User,
[
("id", INTEGER(), [PrimaryKey(), AutoIncrement()]),
("name", TEXT(), [NotNull()]),
("email", TEXT(), [Unique(), NotNull()])
],
[
("posts", Post, "authorId", :hasMany)
]
)
Model(
:Post,
[
("id", INTEGER(), [PrimaryKey(), AutoIncrement()]),
("title", TEXT(), [NotNull()]),
("authorId", INTEGER(), [NotNull()])
],
[
("User", User, "id", :belongsTo)
]
)
In this example:
- A User has many Post records through a hasMany relationship (the
authorId
field inPost
references theid
field inUser
). - A Post belongs to a User via a belongsTo relationship (the
authorId
field inPost
references theid
field inUser
).
Querying with the "include" Parameter
When using the "include"
parameter in your queries, OrionORM.jl enriches the result with related records. Depending on the relationship type, the return format will be:
- hasMany/hasOne: The result will include, under the key of the related model, either a vector of
Dicts
(for hasMany) or a singleDict
(for hasOne). - belongsTo: The result will include a single
Dict
with the parent record's data.
Example 1: Query with belongsTo
result = findFirst(Post; query=Dict("where" => Dict("id" => 1), "include" => ["User"]))
Assuming the post with id = 1
belongs to a user, the result will be a Dict
similar to:
Dict(
"id" => 1,
"title" => "Example Post",
"authorId" => 10,
"User" => Dict("id" => 10, "name" => "Thalles", "email" => "thalles@example.com")
)
Example 2: Query with hasMany
results = findMany(User; query=Dict("where" => Dict("name" => "Thiago"), "include" => ["Post"]))
Each item in results
will be a Dict
structured as follows:
Dict(
"id" => 10,
"name" => "Thiago",
"email" => "thiago@example.com",
"Post" => [
Dict("id" => 1, "title" => "First Post", "authorId" => 10),
Dict("id" => 2, "title" => "Second Post", "authorId" => 10)
]
)
Internal Workflow
When a query is executed with the "include"
parameter, the workflow is as follows:
Main Query: The
findFirst
orfindMany
function builds an SQL query to fetch records from the base model (e.g.,User
orPost
).Serialization: Each returned record is converted into a simple
Dict
(using a helper function likeserialize
orconvertRowToDict
).Enrichment: The ORM goes through the list of models specified in
"include"
. For each included model, it:- Finds the registered relationship (using
getRelationships
andresolveModel
). - Executes a separate query (via
hasMany
,hasOne
, orbelongsTo
) to fetch related records. - Adds the related data to the main record’s
Dict
under a key corresponding to the related model.
- Finds the registered relationship (using
Example Tests
Below is an excerpt of unit tests that demonstrate the use of relationships and the "include"
parameter:
@testset "SimpleORM Basic CRUD Tests" begin
# Create a user
userData = Dict("name" => "Thiago", "email" => "thiago@example.com", "cpf" => "00000000000")
user = create(User, userData)
@test user.name == "Thiago"
@test user.email == "thiago@example.com"
@test hasproperty(user, :id)
# Create a post related to the user
postData = Dict("title" => "My First Post", "authorId" => user.id)
post = create(Post, postData)
@test post.title == "My First Post"
@test post.authorId == user.id
# Query with "include" to fetch the user and their posts
result = findFirst(User; query=Dict("where" => Dict("name" => "Thiago"), "include" => ["Post"]))
@test result["id"] == user.id
@test typeof(result["Post"]) == Vector
@test result["Post"][1]["title"] == "My First Post"
# Query with belongsTo: fetch the post and include the user
result2 = findFirst(Post; query=Dict("where" => Dict("id" => post.id), "include" => ["User"]))
@test result2["id"] == post.id
@test typeof(result2["User"]) == Dict
@test result2["User"]["id"] == user.id
end
Conclusion
With OrionORM.jl, you can easily define models with relationships and perform enriched queries using the "include"
parameter. This modular approach allows you to build complex queries without manually writing SQL, returning structured results as Dicts
that simplify data consumption in your application.
Feel free to contribute suggestions or ask questions if you need further details!