igorbenav / fastcrud

FastCRUD is a Python package for FastAPI, offering robust async CRUD operations and flexible endpoint creation utilities.
MIT License
530 stars 32 forks source link

One-to-many support in joins #93

Closed igorbenav closed 1 month ago

igorbenav commented 1 month ago

One-to-many support in joins

This fixes #91. Btw I'm not completely satisfied with the implementation, but it's enough for now.


Documentation

Handling One-to-One and One-to-Many Joins in FastCRUD

FastCRUD provides flexibility in handling one-to-one and one-to-many relationships through get_joined and get_multi_joined methods, along with the ability to specify how joined data should be structured using both the relationship_type (default one-to-one) and the nest_joins (default False) parameters.

One-to-One Relationships

Example

Let's define two tables:

class User(Base):
    __tablename__ = "user"
    id = Column(Integer, primary_key=True)
    name = Column(String)
    tier_id = Column(Integer, ForeignKey("tier.id"))

class Tier(Base):
    __tablename__ = "tier"
    id = Column(Integer, primary_key=True)
    name = Column(String, unique=True)

Fetch a user and their tier:

user_tier = await user_crud.get_joined(
    db=db,
    join_model=Tier,
    join_on=User.tier_id == Tier.id,
    join_type="left",
    join_prefix="tier_",
    id=1
)

The result will be:

{
    "id": 1,
    "name": "Example",
    "tier_id": 1,
    "tier_name": "Free"
}
One-to-One Relationship with Nested Joins

To get the joined data in a nested dictionary:

user_tier = await user_crud.get_joined(
    db=db,
    join_model=Tier,
    join_on=User.tier_id == Tier.id,
    join_type="left",
    join_prefix="tier_",
    nest_joins=True,
    id=1
)

The result will be:

{
    "id": 1,
    "name": "Example",
    "tier": {
        "id": 1,
        "name": "Free"
    }
}

One-to-Many Relationships

[!WARNING] When using nest_joins=True, the performance will always be a bit worse than when using nest_joins=False. For cases where more performance is necessary, consider using nest_joins=False and remodeling your database.

Example

To demonstrate a one-to-many relationship, let's assume User and Post tables:

class User(Base):
    __tablename__ = "user"
    id = Column(Integer, primary key=True)
    name = Column(String)

class Post(Base):
    __tablename__ = "post"
    id = Column(Integer, primary key=True)
    user_id = Column(Integer, ForeignKey("user.id"))
    content = Column(String)

Fetch a user and all their posts:

user_posts = await user_crud.get_joined(
    db=db,
    join_model=Post,
    join_on=User.id == Post.user_id,
    join_type="left",
    join_prefix="post_",
    nest_joins=True,
    id=1
)

The result will be:

{
    "id": 1,
    "name": "Example User",
    "posts": [
        {
            "id": 101,
            "user_id": 1,
            "content": "First post content"
        },
        {
            "id": 102,
            "user_id": 1,
            "content": "Second post content"
        }
    ]
}
codecov[bot] commented 1 month ago

Codecov Report

All modified and coverable lines are covered by tests :white_check_mark:

Project coverage is 100.00%. Comparing base (03d4a2d) to head (8191543).

Additional details and impacted files ```diff @@ Coverage Diff @@ ## main #93 +/- ## ========================================== Coverage 100.00% 100.00% ========================================== Files 68 70 +2 Lines 4819 5429 +610 ========================================== + Hits 4819 5429 +610 ```

:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.