Introducing PrrrStack

  • functionalish programming — Neither Rust nor JavaScript are functional languages, but both allow for a somewhat functional style, leading to cleaner code that’s easy to write than, say, Haskell or Elm.
  • learning Rust — Maybe you’ve heard about the language that’s been Stack Overflow’s most loved language for the past three years, but you’re coming from a web background with little-to-no systems-level experience.
[dependencies]
diesel = { version = "1.2.2", features = ["postgres"] }
dotenv = "0.11.0"
r2d2 = "0.8.2"
r2d2-diesel = "1.0.0"
serde = "1.0.43"
serde_derive = "1.0.43"
serde_json = "1.0.16"
rocket = "0.3.9"
rocket_codegen = "0.3.9"
rocket_contrib = { version = "0.3.9", default-features = false, features = ["json"] }
rocket_cors = "0.2.3"
/src
|---db.rs
|---main.rs
|---models.rs
|---routes.rs
|---schema.rs
#![feature(plugin, custom_derive, const_fn, decl_macro, extern_prelude)]
#![plugin(rocket_codegen)]
extern crate rocket;
extern crate rocket_contrib;
fn rocket() -> rocket::Rocket {
rocket::ignite()
.mount("/", routes![index])
}
#[get("/")]
fn index<'a>() -> &'a str {
"Hello!"
}
fn main() {
rocket().launch();
}
🔧  Configured for development.
=> address: localhost
=> port: 8000
=> log: normal
=> workers: 8
=> secret key: generated
=> limits: forms = 32KiB
=> tls: disabled
🛰 Mounting '/':
=> GET /
🚀 Rocket has launched from http://localhost:8000
use dotenv::dotenv;
use diesel::pg::PgConnection;
use r2d2;
use r2d2_diesel::ConnectionManager;
use rocket::http::Status;
use rocket::request::{self, FromRequest};
use rocket::{Outcome, Request, State};
use std::env;
use std::ops::Deref;
pub type Pool = r2d2::Pool<ConnectionManager<PgConnection>>;
pub fn create_db_pool() -> Pool {
dotenv().ok();
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must exist");
let manager = ConnectionManager::<PgConnection>::new(database_url);
r2d2::Pool::new(manager).expect("db pool failure")
}
pub struct DbConn(pub r2d2::PooledConnection<ConnectionManager<PgConnection>>);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, ())),
}
}
}
impl Deref for DbConn {
type Target = PgConnection;
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.0
}
}
DATABASE_URL=postgres://postgres:postgres@localhost/prrr_demo
DROP table cats;
CREATE TABLE cats (
id SERIAL PRIMARY KEY,
name VARCHAR NOT NULL,
bio TEXT NOT NULL,
kills INTEGER NOT NULL,
image_url VARCHAR NOT NULL
);
table! {
cats (id) {
id -> Int4,
name -> Varchar,
bio -> Text,
kills -> Int4,
image_url -> Varchar,
}
}
use diesel;
use diesel::prelude::*;
use diesel::pg::PgConnection;
use schema::cats;
use schema::cats::dsl::cats as all_cats;
#[derive(Serialize, Queryable, Debug, Clone)]
pub struct Cat {
pub id: i32,
pub name: String,
pub bio: String,
pub kills: i32,
pub image_url: String,
}
#[derive(Serialize, Deserialize, Insertable)]
#[table_name = "cats"]
pub struct NewCat {
pub name: String,
pub bio: String,
pub kills: i32,
pub image_url: String,
}
impl Cat {
pub fn show(id: i32, conn: &PgConnection) -> Vec<Cat> {
all_cats.find(id)
.load::<Cat>(conn)
.expect("Sometimes cats don't come when you call them")
}
pub fn all(conn: &PgConnection) -> Vec<Cat> {
all_cats.order(cats::id.desc())
.load::<Cat>(conn)
.expect("Error herding cats")
}
pub fn create(cat: NewCat, conn: &PgConnection) -> bool {
diesel::insert_into(cats::table)
.values(&cat)
.execute(conn)
.is_ok()
}
pub fn update_by_id(id: i32, cat: NewCat, conn: &PgConnection) -> bool {
use schema::cats::dsl::{
name as n,
bio as b,
kills as k,
image_url as img,
};
let NewCat { name, bio, kills, image_url } = cat;
diesel::update(all_cats.find(id))
.set((n.eq(name), b.eq(bio), k.eq(kills), img.eq(image_url)))
.get_result::<Cat>(conn)
.is_ok()
}
pub fn delete_by_id(id: i32, conn: &PgConnection) -> bool {
if Cat::show(id, conn).is_empty() {
return false;
}
diesel::delete(all_cats.find(id))
.execute(conn)
.is_ok()
}
}
  • Each of our functions is using a reference to our database pool
  • show and all are returning a Vector of cats, while the other methods simply tell us whether they were successful
  • We’re using NewCat not only for adding a new cat to our database, but we’re also using it to destructure the request object in our update_by_id method
  • We make these methods public so that we can access them elsewhere.
use rocket_contrib::Json;
use serde_json::Value;
use db::DbConn;
use models::{Cat, NewCat};
#[get("/cats", format = "application/json")]
fn all_cats(conn:DbConn) -> Json<Value> {
let cats = Cat::all(&conn);
Json(json!({
"status": 200,
"result": cats,
}))
}
#[post("/cats", format = "application/json", data = "<new_cat>")]
fn new_cat(new_cat: Json<NewCat>, conn: DbConn) -> Json<Value> {
Json(json!({
"status": Cat::create(new_cat.into_inner(), &conn),
"result": Cat::all(&conn),
}))
}
#[put("/cats/<id>", format = "application/json", data = "<new_cat>")]
fn update_cat(id: i32, new_cat: Json<NewCat>, conn: DbConn) -> Json<Value> {
let status = if Cat::update_by_id(id, new_cat.into_inner(), &conn) { 200 } else { 404 };
Json(json!({
"status": status,
"result": Cat::all(&conn),
}))
}
#[delete("/cats/<id>")]
fn delete_cat(id: i32, conn: DbConn) -> Json<Value> {
let status = if Cat::delete_by_id(id, &conn) { 200 } else { 404 };
Json(json!({
"status": status,
"result": null,
}))
}
#![feature(plugin, custom_derive, const_fn, decl_macro, extern_prelude)]
#![plugin(rocket_codegen)]
#[macro_use] extern crate diesel;
extern crate dotenv;
extern crate r2d2;
extern crate r2d2_diesel;
extern crate rocket;
extern crate rocket_contrib;
extern crate rocket_cors;
#[macro_use] extern crate serde_derive;
#[macro_use] extern crate serde_json;
use rocket::http::Method;
use rocket_cors::{AllowedOrigins, AllowedHeaders};
use routes::*;mod db;
mod models;
mod routes;
mod schema;
fn rocket() -> rocket::Rocket {
let pool = db::create_db_pool();
let (allowed_origins, failed_origins) = AllowedOrigins::some(&["http://localhost:3000"]);
let options = rocket_cors::Cors {
allowed_origins: allowed_origins,
allowed_methods: vec![Method::Get, Method::Put, Method::Post, Method::Delete]
.into_iter()
.map(From::from)
.collect(),
allowed_headers: AllowedHeaders::all(),
allow_credentials: true,
..Default::default()
};
rocket::ignite()
.manage(pool)
.mount("/api", routes![all_cats, new_cat, update_cat, delete_cat])
.mount("/", routes![index])
.attach(options)
}
#[get("/")]
fn index<'a>() -> &'a str {
"Hello!"
}
fn main() {
rocket().launch();
}
{
"bio": "i was found in a trash can",
"image_url": "http://zeelifestylecebu.com/wp-content/uploads/2015/03/cat3.jpg",
"kills": 0,
"name": "Dingle Poo"
}
{
"id": 1,
"bio": "i was found in a trash can",
"image_url": "http://zeelifestylecebu.com/wp-content/uploads/2015/03/cat3.jpg",
"kills": 13,
"name": "Dingle Poo"
}

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store