Update state guide for 0.3. Add databases section.

This commit is contained in:
Sergio Benitez 2017-07-06 01:58:57 -07:00
parent ef2739ee50
commit 67fe89dcc7
1 changed files with 175 additions and 24 deletions

View File

@ -22,15 +22,17 @@ The process for using managed state is simple:
### Adding State ### Adding State
To instruct Rocket to manage state for your application, call the To instruct Rocket to manage state for your application, call the
[manage](https://api.rocket.rs/rocket/struct.Rocket.html#method.manage) method [`manage`](https://api.rocket.rs/rocket/struct.Rocket.html#method.manage) method
on a `Rocket` instance. For example, to ask Rocket to manage a `HitCount` on an instance of `Rocket`. For example, to ask Rocket to manage a `HitCount`
structure with an internal `AtomicUsize` with an initial value of `0`, we can structure with an internal `AtomicUsize` with an initial value of `0`, we can
write the following: write the following:
```rust ```rust
struct HitCount(AtomicUsize); struct HitCount {
count: AtomicUsize
}
rocket::ignite().manage(HitCount(AtomicUsize::new(0))); rocket::ignite().manage(HitCount { count: AtomicUsize::new(0) });
``` ```
The `manage` method can be called any number of times as long as each call The `manage` method can be called any number of times as long as each call
@ -39,23 +41,23 @@ a `HitCount` value and a `Config` value, we can write:
```rust ```rust
rocket::ignite() rocket::ignite()
.manage(HitCount(AtomicUsize::new(0))) .manage(HitCount { count: AtomicUsize::new(0) })
.manage(Config::from(user_input)); .manage(Config::from(user_input));
``` ```
### Retrieving State ### Retrieving State
State that is being managed by Rocket can be retrieved via the State that is being managed by Rocket can be retrieved via the
[State](https://api.rocket.rs/rocket/struct.State.html) type: a [request [`State`](https://api.rocket.rs/rocket/struct.State.html) type: a [request
guard](/guide/requests/#request-guards) for managed state. To use the request guard](/guide/requests/#request-guards) for managed state. To use the request
guard, add a `State<T>` type to any request handler, where `T` is the guard, add a `State<T>` type to any request handler, where `T` is the type of
type of the managed state. For example, we can retrieve and respond with the the managed state. For example, we can retrieve and respond with the current
current `HitCount` in a `count` route as follows: `HitCount` in a `count` route as follows:
```rust ```rust
#[get("/count")] #[get("/count")]
fn count(hit_count: State<HitCount>) -> String { fn count(hit_count: State<HitCount>) -> String {
let current_count = hit_count.0.load(Ordering::Relaxed); let current_count = hit_count.count.load(Ordering::Relaxed);
format!("Number of visits: {}", current_count) format!("Number of visits: {}", current_count)
} }
``` ```
@ -67,20 +69,22 @@ You can retrieve more than one `State` type in a single route as well:
fn state(hit_count: State<HitCount>, config: State<Config>) -> T { ... } fn state(hit_count: State<HitCount>, config: State<Config>) -> T { ... }
``` ```
### Within a Request Guard
It can also be useful to retrieve managed state from a `FromRequest` It can also be useful to retrieve managed state from a `FromRequest`
implementation. To do so, invoke the `from_request` method of a `State<T>` type implementation. To do so, simple invoke `State<T>` as a guard using the
directly, passing in the `req` parameter of `from_request`: [`Request::guard()`] method.
```rust ```rust
fn from_request(req: &'a Request<'r>) -> request::Outcome<T, ()> { fn from_request(req: &'a Request<'r>) -> request::Outcome<T, ()> {
let count = match <State<HitCount> as FromRequest>::from_request(req) { let hit_count_state = req.guard::<State<HitCount>>()?;
Outcome::Success(count) => count, let current_count = hit_count_state.count.load(Ordering::Relaxed);
...
};
... ...
} }
``` ```
[`Request::guard()`]: https://api.rocket.rs/rocket/struct.Request.html#method.guard
### Unmanaged State ### Unmanaged State
If you request a `State<T>` for a `T` that is not `managed`, Rocket won't call If you request a `State<T>` for a `T` that is not `managed`, Rocket won't call
@ -100,7 +104,7 @@ type from previous examples:
```rust ```rust
#[get("/count")] #[get("/count")]
fn count(hit_count: State<HitCount>) -> String { fn count(hit_count: State<HitCount>) -> String {
let current_count = hit_count.0.load(Ordering::Relaxed); let current_count = hit_count.count.load(Ordering::Relaxed);
format!("Number of visits: {}", current_count) format!("Number of visits: {}", current_count)
} }
@ -120,7 +124,7 @@ warning: HitCount is not currently being managed by Rocket
--> src/main.rs:2:17 --> src/main.rs:2:17
| |
2 | fn count(hit_count: State<HitCount>) -> String { 2 | fn count(hit_count: State<HitCount>) -> String {
| ^^^^^^^^^^^^^^^ | ^^^^^^^^^
| |
= note: this State request guard will always fail = note: this State request guard will always fail
help: maybe add a call to 'manage' here? help: maybe add a call to 'manage' here?
@ -131,14 +135,161 @@ help: maybe add a call to 'manage' here?
``` ```
The `unmanaged_state` lint isn't perfect. In particular, it cannot track calls The `unmanaged_state` lint isn't perfect. In particular, it cannot track calls
to `manage` across function boundaries. You can disable the lint on a per-route to `manage` across function boundaries. Because of this, you may find yourself
basis by adding `#[allow(unmanaged_state)]` to a route handler. If you wish to with incorrect warnings. You can disable the lint on a per-route basis by adding
disable the lint globally, add `#![allow(unmanaged_state)]` to your crate `#[allow(unmanaged_state)]` to a route handler. If you wish to disable the lint
attributes. globally, add `#![allow(unmanaged_state)]` to your crate attributes.
You can find a complete example using the `HitCounter` structure in the [state You can find a complete example using the `HitCount` structure in the [state
example on example on
GitHub](https://github.com/SergioBenitez/Rocket/tree/v0.2.8/examples/state) and GitHub](https://github.com/SergioBenitez/Rocket/tree/v0.2.8/examples/state) and
learn more about the [manage learn more about the [manage
method](https://api.rocket.rs/rocket/struct.Rocket.html#method.manage) and method](https://api.rocket.rs/rocket/struct.Rocket.html#method.manage) and
[State type](https://api.rocket.rs/rocket/struct.State.html) in the API docs. [State type](https://api.rocket.rs/rocket/struct.State.html) in the API docs.
## Databases
While Rocket doesn't have built-in support for databases yet, you can combine a
few external libraries to get native-feeling access to databases in a Rocket
application. Let's take a look at how we might integrate Rocket with two common
database libraries: [`diesel`], a type-safe ORM and query builder, and [`r2d2`],
a library for connection pooling.
Our approach will be to have Rocket manage a pool of database connections using
managed state and then implement a request guard that retrieves one connection.
This will allow us to get access to the database in a handler by simply adding a
`DbConn` argument:
```rust
#[get("/users")]
fn handler(conn: DbConn) { ... }
```
[`diesel`]: http://diesel.rs/
[`r2d2`]: https://docs.rs/r2d2/0.7.2/r2d2/
### Dependencies
To get started, we need to depend on the `diesel` and `r2d2` crates. For
detailed information on how to use Diesel, please see the [Diesel getting
started guide](http://diesel.rs/guides/getting-started/). For this example, we
use the following dependencies:
```toml
[dependencies]
rocket = "0.2.8"
diesel = { version = "*", features = ["sqlite"] }
diesel_codegen = { version = "*", features = ["sqlite"] }
r2d2-diesel = "*"
r2d2 = "*"
```
Your `diesel` dependency information will differ. In particular, you should
specify the latest versions of these libraries as opposed to using a `*`. The
crates are imported as well:
```rust
extern crate rocket;
#[macro_use] extern crate diesel;
#[macro_use] extern crate diesel_codegen;
extern crate r2d2_diesel;
extern crate r2d2;
```
### Managed Pool
The first step is to initialize a pool of database connections. The `init_pool`
function below uses `r2d2` to create a new pool of database connections. Diesel
advocates for using a `DATABASE_URL` environment variable to set the database
URL, and we use the same convention here. Excepting the long-winded types, the
code is fairly straightforward: the `DATABASE_URL` environment variable is
stored in the `DATABASE_URL` static, and an `r2d2::Pool` is created using the
default configuration parameter and a Diesel `SqliteConnection`
`ConnectionManager`.
```rust
use diesel::sqlite::SqliteConnection;
use r2d2_diesel::ConnectionManager;
// An alias to the type for a pool of Diesel SQLite connections.
type Pool = r2d2::Pool<ConnectionManager<SqliteConnection>>;
// The URL to the database, set via the `DATABASE_URL` environment variable.
static DATABASE_URL: &'static str = env!("DATABASE_URL");
/// Initializes a database pool.
fn init_pool() -> Pool {
let config = r2d2::Config::default();
let manager = ConnectionManager::<SqliteConnection>::new(DATABASE_URL);
r2d2::Pool::new(config, manager).expect("db pool")
}
```
We then use managed state to have Rocket manage the pool for us:
```rust
fn main() {
rocket::ignite()
.manage(init_pool())
.launch();
}
```
### Connection Request Guard
The second and final step is to implement a request guard that retrieves a
single connection from the managed connection pool. We create a new type,
`DbConn`, that wraps an `r2d2` pooled connection. We then implement
`FromRequest` for `DbConn` so that we can use it as a request guard. Finally, we
implement `Deref` with a target of `SqliteConnection` so that we can
transparently use an `&DbConn` as an `&SqliteConnection`.
```rust
use std::ops::Deref;
use rocket::http::Status;
use rocket::request::{self, FromRequest};
use rocket::{Request, State, Outcome};
// Connection request guard type: a wrapper around an r2d2 pooled connection.
pub struct DbConn(pub r2d2::PooledConnection<ConnectionManager<SqliteConnection>>);
/// Attempts to retrieve a single connection from the managed database pool. If
/// no pool is currently managed, fails with an `InternalServerError` status. If
/// no connections are available, fails with a `ServiceUnavailable` status.
impl<'a, 'r> FromRequest<'a, 'r> for DbConn {
type Error = ();
fn from_request(request: &'a Request<'r>) -> request::Outcome<DbConn, ()> {
let pool = request.guard::<State<Pool>>()?;
match pool.get() {
Ok(conn) => Outcome::Success(DbConn(conn)),
Err(_) => Outcome::Failure((Status::ServiceUnavailable, ()))
}
}
}
// For the convenience of using an &DbConn as an &SqliteConnection.
impl Deref for DbConn {
type Target = SqliteConnection;
fn deref(&self) -> &Self::Target {
&self.0
}
}
```
### Usage
With these two pieces in place, we can use `DbConn` as a request guard in any
handler or other request guard implementation, giving our application access to
a database. As a simple example, we might write a route that returns a JSON
array of some `Task` structures that are fetched from a database:
```rust
#[get("/tasks")]
fn get_tasks(conn: DbConn) -> QueryResult<JSON<Vec<Task>>> {
all_tasks.order(tasks::id.desc())
.load::<Task>(&conn)
.map(|tasks| JSON(tasks))
}
```