Skip to content

Commit

Permalink
Merge pull request #226 from sgrif/update/composing_applications
Browse files Browse the repository at this point in the history
  • Loading branch information
weiznich authored Jul 26, 2024
2 parents e3b788f + 8189c2b commit 2f78d2d
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 37 deletions.
125 changes: 90 additions & 35 deletions src/guides/composing-applications.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ In this guide,
we'll look at common patterns for extracting your code into re-usable pieces.
We'll also look at best practices for how to structure your code.

All of our code examples are based on code from crates.io,
All of our code examples are based on code from [crates.io](https://github.com/rust-lang/crates.io/),
a real world application which uses Diesel extensively.
All of our examples will be focused on functions which *return*
queries or pieces of queries.
Expand All @@ -27,22 +27,21 @@ We will go into the benefits of this structure at the end of the guide.
crates.io has a `canon_crate_name` SQL function
which is always used when comparing crate names.
Rather than continuously writing
`canon_crate_name(crates::name).eq("some name")`,
`canon_crate_name(crates::name).eq(canon_crate_name("some name"))`,
we can instead pull this out into a function.

::: code-block

[src/krate/mod.rs]( https://github.com/rust-lang/crates.io/blob/b4d49ac32c5561a7a4a0948ce5ba9ada7b8924fb/src/krate/mod.rs)
[src/models/krate.rs](https://github.com/rust-lang/crates.io/blob/12e75f1a0480f86b7b2efc76e155f449b3be9287/src/models/krate.rs#L168-L170)

```rust
use diesel::dsl::Eq;
use diesel::prelude::sql_function;
use diesel::prelude::define_sql_function;
use diesel::sql_types::Text;

sql_function!(fn canon_crate_name(x: Text) -> Text);
define_sql_function!(fn canon_crate_name(x: Text) -> Text);

type WithName<'a> =
Eq<canon_crate_name::HelperType<crates::name>, canon_crate_name::HelperType<&'a str>>;
type WithName<'a> = diesel::dsl::Eq<canon_crate_name<crates::name>, canon_crate_name<&'a str>>;

fn with_name(name: &str) -> WithName {
canon_crate_name(crates::name).eq(canon_crate_name(name))
Expand All @@ -51,24 +50,29 @@ fn with_name(name: &str) -> WithName {

:::

We need to specify the return type of this function. For this we define the `WithName` type which
is composed of a helper type generated by the function macro (`canon_crate_name`) and helper types
defined in the [`diesel::dsl`](https://docs.diesel.rs/2.2.x/diesel/dsl/index.html). The later types closely mirror the functions used to construct the query.


Now when we want to find a crate by name, we can write
`crates::table.filter(with_name("foo"))` instead.
If we want to accept types other than a string,
If we want to accept types other than a `&'str`,
we can make the method generic.


::: code-block

[src/krate/mod.rs](https://github.com/rust-lang/crates.io/blob/b4d49ac32c5561a7a4a0948ce5ba9ada7b8924fb/src/krate/mod.rs):
[src/models/krate.rs](https://github.com/rust-lang/crates.io/blob/12e75f1a0480f86b7b2efc76e155f449b3be9287/src/models/krate.rs#L168-L170):

```rust
use diesel::dsl::Eq;
use diesel::prelude::sql_function;
use diesel::prelude::define_sql_function;
use diesel::sql_types::Text;

sql_function!(fn canon_crate_name(x: Text) -> Text);
define_sql_function!(fn canon_crate_name(x: Text) -> Text);

type WithName<T> = Eq<canon_crate_name::HelperType<crates::name>, canon_crate_name::HelperType<T>>;
type WithName<T> = diesel::dsl::Eq<canon_crate_name<crates::name>, canon_crate_name<T>>;

fn with_name<T>(name: T) -> WithName<T>
where
Expand All @@ -80,6 +84,10 @@ where

:::

The [`AsExpression`](https://docs.diesel.rs/2.2.x/diesel/dsl/index.html) trait describes any type that
can be converted to a expression of the SQL type `Text`. In this particular case it extends to function to
accept `String`, `Cow<str>` and any SQL side text expression (e.g. `crates::name`) as argument.

It's up to you whether you make your functions generic,
or only take a single type.
We recommend only making these functions generic if it's actually needed,
Expand All @@ -102,7 +110,7 @@ we can box the value instead.

::: code-block

[src/krate/mod.rs](https://github.com/rust-lang/crates.io/blob/b4d49ac32c5561a7a4a0948ce5ba9ada7b8924fb/src/krate/mod.rs)
[src/models/krate.rs](https://github.com/rust-lang/crates.io/blob/12e75f1a0480f86b7b2efc76e155f449b3be9287/src/models/krate.rs#L168-L170)

```rust
use diesel::pg::Pg;
Expand Down Expand Up @@ -141,13 +149,38 @@ The SQL type is needed so we know what functions this can be passed to.

Boxing an expression also implies that it has no aggregate functions.
You cannot box an aggregate expression in Diesel.
As of Diesel 1.0, a boxed expression can only be used with *exactly* the from
As of Diesel 2.0, a boxed expression can only be used with *exactly* the from
clause given.
You cannot use a boxed expression for `crates::table` with an inner join to
another table.

[`BoxableExpression`]: https://docs.diesel.rs/2.2.x/diesel/expression/trait.BoxableExpression.html

Finally it's possible to use the `#[diesel::dsl::auto_type]` attribute macro, to automatically
construct the correct return type for you.

[`#[diesel::dsl::auto_type]`]: https://docs.diesel.rs/2.2.x/diesel/dsl/attr.auto_type.html

::: code-block

[src/models/krate.rs](https://github.com/rust-lang/crates.io/blob/12e75f1a0480f86b7b2efc76e155f449b3be9287/src/models/krate.rs#L172-L177)

```rust
impl Crate {
#[diesel::dsl::auto_type(no_type_alias)]
fn with_name<'a>(name: &'a str) -> _ {
canon_crate_name(crates::name).eq(canon_crate_name.eq(name))
}
}
```

:::

For any function annotated with `#[auto_type]` the procedural macro will replace the `_` return type with
a specific type. Additionally a type definition with the same name as the function name is generated. For this specific example the generation of the type definition is suppressed via the `no_type_alias` option as stable rust does not support associated types for non-trait impls yet.

For most built-in query DSL constructs the `#[auto_type]` macro will infer the correct type and use that information to construct the return type. For custom functions it might require an explicit type annotation to correctly infer the return type. For lifetimes, as in this example, an explict type annotation is required.

In addition to extracting expressions,
you can also pull out entire queries into functions.
Going back to crates.io,
Expand All @@ -157,28 +190,32 @@ we have an `all` function which selects the columns we need.

::: code-block

[src/krate/mod.rs](https://github.com/rust-lang/crates.io/blob/b4d49ac32c5561a7a4a0948ce5ba9ada7b8924fb/src/krate/mod.rs)
[src/models/krate.rs](https://github.com/rust-lang/crates.io/blob/12e75f1a0480f86b7b2efc76e155f449b3be9287/src/models/krate.rs#L185-L187)

```rust
use diesel::backend::Backend;
use diesel::dsl::{AsSelect, Select};
use diesel::pg::Pg;

#[derive(Selectable, Queryable)]
#[diesel(table_name = crates)]
#[diesel(table_name = crates, check_for_backend(diesel::pg::Pg))]
struct Crate {
id: i32,
name: String,
updated_at: NaiveDateTime,
created_at: NaiveDateTime,
pub id: i32,
pub name: String,
pub updated_at: NaiveDateTime,
pub created_at: NaiveDateTime,
pub description: Option<String>,
pub homepage: Option<String>,
pub documentation: Option<String>,
pub repository: Option<String>,
pub max_upload_size: Option<i32>,
pub max_features: Option<i16>,
}

type All<DB> = Select<crates::table, AsSelect<Crate, DB>>;
type All = Select<crates::table, AsSelect<Crate, Pg>>;

impl Crate {
pub fn all<DB>() -> All<DB>
where
DB: Backend,
{
pub fn all() -> All {
crates::table.select(Crate::as_select())
}
}
Expand All @@ -192,15 +229,15 @@ We can pull that into a function as well.

::: code-block

[src/krate/mod.rs](https://github.com/rust-lang/crates.io/blob/b4d49ac32c5561a7a4a0948ce5ba9ada7b8924fb/src/krate/mod.rs)
[src/models/krate.rs](https://github.com/rust-lang/crates.io/blob/12e75f1a0480f86b7b2efc76e155f449b3be9287/src/models/krate.rs#L172-L177)

```rust
use diesel::dsl::Filter;

type ByName<T, DB> = Filter<All<DB>, WithName<T>>;
type ByName<'a> = Filter<All, WithName<'a>>;

impl Crate {
fn by_name<T, DB>(name: T) -> ByName<T, DB> {
fn by_name(name: &str) -> ByName<'_> {
Self::all().filter(with_name(name))
}
}
Expand All @@ -213,7 +250,7 @@ or we want to dynamically construct the query differently, we can box the whole

::: code-block

[src/krate/mod.rs](https://github.com/rust-lang/crates.io/blob/b4d49ac32c5561a7a4a0948ce5ba9ada7b8924fb/src/krate/mod.rs)
[src/models/krate.rs](https://github.com/rust-lang/crates.io/blob/12e75f1a0480f86b7b2efc76e155f449b3be9287/src/models/krate.rs#L172-L177)s)

```rust
use diesel::expression::{Expression, AsExpression};
Expand All @@ -229,11 +266,7 @@ impl Crate {
crates::table.select(Crate::as_select()).into_boxed()
}

fn by_name<'a, T>(name: T) -> BoxedQuery<'a>
where
T: AsExpression<Text>,
T::Expression: BoxableExpression<crates::table, Pg>,
{
fn by_name<'a, T>(name: &'a str) -> BoxedQuery<'a> {
Self::all().filter(with_name(name))
}
}
Expand All @@ -254,6 +287,28 @@ to future calls to `filter` and other query builder methods.
The backend is needed to ensure you don't accidentally use a
PostgreSQL function on SQLite.

Finally it's again possible to use `#[diesel::dsl::auto_type]` to let the proc-macro
infer the correct return type for you.

::: code-block

[src/models/krate.rs](https://github.com/rust-lang/crates.io/blob/12e75f1a0480f86b7b2efc76e155f449b3be9287/src/models/krate.rs#L172-L177)

```rust
impl Crate {
#[diesel::dsl::auto_type(no_type_alias)]`
fn by_name(name: &str) -> _ {
let all: All = Crate::all();
let filter: WithName<'a> = Self::with_name(name);
all.filter(filter)
}
}
```

:::

In this case the `#[auto_type]` macro needs additional type expressions to work correctly.

Note that in all of our examples,
we are writing functions which *return* queries or expressions.
None of these functions execute the query.
Expand All @@ -265,7 +320,7 @@ For example, if we had written our `by_name` function like this:

::: code-block

[src/krate/mod.rs](https://github.com/rust-lang/crates.io/blob/b4d49ac32c5561a7a4a0948ce5ba9ada7b8924fb/src/krate/mod.rs)
[src/models/krate.rs](https://github.com/rust-lang/crates.io/blob/12e75f1a0480f86b7b2efc76e155f449b3be9287/src/models/krate.rs#L172-L177)

```rust
impl Crate {
Expand Down
2 changes: 0 additions & 2 deletions src/guides/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -695,8 +695,6 @@ Diesel demo
You know, a CLI application probably isn't the best interface for a blog demo.
But really I just wanted a semi-simple example, where I could focus on Diesel.
I didn't want to get bogged down in some web framework here.
Plus I don't really like the Rust web frameworks out there. We might make a
new one, soon.
```

We've still only covered three of the four letters of CRUD though. Let's show
Expand Down

0 comments on commit 2f78d2d

Please sign in to comment.