Skip to content

Commit

Permalink
Merge pull request #16 from linda-seiter/main-test
Browse files Browse the repository at this point in the history
fix solution code
  • Loading branch information
linda-seiter authored Nov 15, 2023
2 parents b041b0a + fc535b6 commit e37fcb3
Showing 1 changed file with 62 additions and 59 deletions.
121 changes: 62 additions & 59 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@

## Learning Goals

- Use SQLAlchemy to join tables with one-to-one, one-to-many, and
many-to-many relationships.
- Use SQLAlchemy to join tables with one-to-one, one-to-many, and many-to-many
relationships.

***
---

## Introduction

In the previous lesson, we saw how to create a **one-to-many** association
between two models using SQLAlchemy by following certain naming conventions,
use of the `relationship()` and `backref()` methods, and using the right
foreign key on our tables when generating the migrations.
between two models using SQLAlchemy by following certain naming conventions, use
of the `relationship()` and `backref()` methods, and using the right foreign key
on our tables when generating the migrations.

In the SQL section, we learned about one other kind of relationship: the
**many-to-many**, also known as the **has many through**, relationship. For
Expand All @@ -34,14 +34,15 @@ these relationships:
- A user **has many** reviews.
- A user **has many** games, **through** reviews.

Once we're done setting up the database tables, here's what the ERD will look like:
Once we're done setting up the database tables, here's what the ERD will look
like:

![Game Reviews ERD](https://curriculum-content.s3.amazonaws.com/phase-3/active-record-associations-many-to-many/games-reviews-users-erd.png)

To get started, run `pipenv install; pipenv shell`, then follow along with
the code.
To get started, run `pipenv install; pipenv shell`, then follow along with the
code.

***
---

## Creating a User Model

Expand Down Expand Up @@ -85,14 +86,14 @@ values:
itself. Since the database is kept in one central location, assigning it the
work of creating default values means that we don't have to worry about the
quality of our developers' or users' computers.
- `onupdate` means exactly what it says: when the record is updated, the
column value is set.
- `onupdate` means exactly what it says: when the record is updated, the column
value is set.
- We saw `func` briefly in the previous module; it allows us to use SQL
operations instead of their Python counterparts. This benefits us for the same
reasons as `server_default`. `func.now()` is equivalent to the current time.

> **Note: If you add these timestamps to your `Game` and `Review` models (not
> a bad idea), you will have to clear the data in your database before running
> **Note: If you add these timestamps to your `Game` and `Review` models (not a
> bad idea), you will have to clear the data in your database before running
> your new migration. This is because SQLite can't populate existing records
> with dynamic default values, like `func.now()`.**
Expand Down Expand Up @@ -129,9 +130,9 @@ class Review(Base):

```

Now run `alembic revision --autogenerate -m'Add User model'` from the
`lib` directory to make our migration. If all goes well, run
`alembic upgrade head` to push your migrations to the database.
Now run `alembic revision --autogenerate -m'Add User model'` from the `lib`
directory to make our migration. If all goes well, run `alembic upgrade head` to
push your migrations to the database.

```console
$ alembic upgrade head
Expand All @@ -146,7 +147,7 @@ Run the first seed file as well to populate the `games` and `reviews` tables:
$ python seed.py
```

***
---

## Creating a Many-to-Many Relationship

Expand All @@ -171,8 +172,8 @@ approach, `Table` objects.
### Many-to-Many with an Association Object

An association object is really just another model, so we can create a
**`GameUser`** model using our `Base` object and simply build relationships
in either direction, as seen below:
**`GameUser`** model using our `Base` object and simply build relationships in
either direction, as seen below:

```py
# example only
Expand All @@ -193,9 +194,9 @@ class GameUser(Base):

```

An association object can use either its own primary key or a combination of
the two joined tables' primary keys as a unique identifier. Here, we use the
simpler strategy and create an `id` column.
An association object can use either its own primary key or a combination of the
two joined tables' primary keys as a unique identifier. Here, we use the simpler
strategy and create an `id` column.

Next, we use the `relationship()` method to connect to both the `Game` and
`User` models. Here, we opt for the `back_populates` argument in place of
Expand All @@ -215,9 +216,9 @@ preferred approach to define many-to-many relationships using SQLAlchemy is with
### Many-to-Many with `Table` Objects

`Table` objects are instances of the `sqlalchemy.Table` class. They function
more or less the same as data models, with the exception of being a little
more compact. This syntax visually de-emphasizes association tables in your
models and is the preferred approach for simple many-to-many relationships in
more or less the same as data models, with the exception of being a little more
compact. This syntax visually de-emphasizes association tables in your models
and is the preferred approach for simple many-to-many relationships in
SQLAlchemy.

Let's build the same association table as above with our new syntax:
Expand Down Expand Up @@ -266,8 +267,8 @@ A few more notes on this approach:
best way to leave readable code behind for other developers.

Run `alembic revision --autogenerate -m'Add game_user Association Table'`, then
`alembic upgrade head`. You can use the script in `lib/seed_2.py` to generate new
data and interact with your database through the Python shell. To create
`alembic upgrade head`. You can use the script in `lib/seed_2.py` to generate
new data and interact with your database through the Python shell. To create
relationships between `Game` records and `User` records, run the second seed
file with `python seed_2.py`.

Expand Down Expand Up @@ -390,14 +391,14 @@ building a many-to-many relationship between games and users. Run `pytest -x`
from the `lib/` directory to see if your models are working as expected- and
don't forget to use Alembic to create your database first!

***
---

## Conclusion

The power of SQLAlchemy all boils down to understanding database
relationships and making use of the correct classes and methods. By leveraging
"convention over configuration", we're able to quickly set up complex
associations between multiple models with just a few lines of code.
The power of SQLAlchemy all boils down to understanding database relationships
and making use of the correct classes and methods. By leveraging "convention
over configuration", we're able to quickly set up complex associations between
multiple models with just a few lines of code.

The **one-to-many** and **many-to-many** relationships are the most common when
working with relational databases. You can apply the same concepts and code we
Expand All @@ -412,25 +413,28 @@ Actor -< Character >- Movie
The code required to set up these relationships would look very similar to the
code we wrote in this lesson.

By understanding the conventions SQLAlchemy expects you to follow, and how
the underlying database relationships work, you have the ability to model all
kinds of complex, real-world concepts in your code!
By understanding the conventions SQLAlchemy expects you to follow, and how the
underlying database relationships work, you have the ability to model all kinds
of complex, real-world concepts in your code!

***
---

## Solution Code

```py
# many_to_many/models.py

from sqlalchemy import create_engine, func
from sqlalchemy import ForeignKey, Table, Column, Integer, String, DateTime
from sqlalchemy import ForeignKey, Table, Column, Integer, String, DateTime, MetaData
from sqlalchemy.orm import relationship, backref
from sqlalchemy.ext.declarative import declarative_base

engine = create_engine('sqlite:///many_to_many.db')
convention = {
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
}
metadata = MetaData(naming_convention=convention)

Base = declarative_base()
Base = declarative_base(metadata=metadata)

game_user = Table(
'game_users',
Expand All @@ -440,6 +444,22 @@ game_user = Table(
extend_existing=True,
)

class User(Base):
__tablename__ = 'users'

id = Column(Integer(), primary_key=True)
name = Column(String())
created_at = Column(DateTime(), server_default=func.now())
updated_at = Column(DateTime(), onupdate=func.now())

reviews = relationship('Review', backref=backref('user'))
games = relationship('Game', secondary=game_user, back_populates='users')

# don't forget your __repr__()!
def __repr__(self):
return f'User(id={self.id}, ' + \
f'name={self.name})'

class Game(Base):
__tablename__ = 'games'

Expand All @@ -449,49 +469,32 @@ class Game(Base):
platform = Column(String())
price = Column(Integer())

reviews = relationship('Review', backref=backref('game'))
users = relationship('User', secondary=game_user, back_populates='games')
reviews = relationship('Review', backref=backref('game'), cascade='all, delete-orphan')

def __repr__(self):
return f'Game(id={self.id}, ' + \
f'title={self.title}, ' + \
f'platform={self.platform})'

class User(Base):
__tablename__ = 'users'

id = Column(Integer(), primary_key=True)
name = Column(String())
created_at = Column(DateTime(), server_default=func.now())
updated_at = Column(DateTime(), onupdate=func.now())

games = relationship('Game', secondary=game_user, back_populates='users')
reviews = relationship('Review', backref=backref('user'), cascade='all, delete-orphan')

def __repr__(self):
return f'User(id={self.id}, ' + \
f'name={self.name})'

class Review(Base):
__tablename__ = 'reviews'

id = Column(Integer(), primary_key=True)

score = Column(Integer())
comment = Column(String())

game_id = Column(Integer(), ForeignKey('games.id'))
user_id = Column(Integer(), ForeignKey('users.id'))


def __repr__(self):
return f'Review(id={self.id}, ' + \
f'score={self.score}, ' + \
f'game_id={self.game_id})'

```

***
---

## Resources

Expand Down

0 comments on commit e37fcb3

Please sign in to comment.