Skip to content

Commit 5919ad7

Browse files
committed
Switch several category routes over to diesel
1 parent ffd15b9 commit 5919ad7

File tree

3 files changed

+129
-72
lines changed

3 files changed

+129
-72
lines changed

src/category.rs

Lines changed: 45 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -193,17 +193,18 @@ impl Category {
193193
Ok(invalid_categories)
194194
}
195195

196-
pub fn count_toplevel(conn: &GenericConnection) -> CargoResult<i64> {
197-
let sql = format!(
196+
pub fn count_toplevel(conn: &PgConnection) -> CargoResult<i64> {
197+
use diesel::expression::*;
198+
use diesel::types::*;
199+
200+
let query = sql::<BigInt>(
198201
"\
199202
SELECT COUNT(*) \
200-
FROM {} \
201-
WHERE category NOT LIKE '%::%'",
202-
Model::table_name(None::<Self>)
203+
FROM categories \
204+
WHERE category NOT LIKE '%::%'"
203205
);
204-
let stmt = conn.prepare(&sql)?;
205-
let rows = stmt.query(&[])?;
206-
Ok(rows.iter().next().unwrap().get("count"))
206+
let count = query.get_result(&*conn)?;
207+
Ok(count)
207208
}
208209

209210
pub fn toplevel(
@@ -269,27 +270,31 @@ impl Category {
269270
Ok(categories)
270271
}
271272

272-
pub fn subcategories(&self, conn: &GenericConnection) -> CargoResult<Vec<Category>> {
273-
let stmt = conn.prepare(
273+
pub fn subcategories(&self, conn: &PgConnection) -> CargoResult<Vec<Category>> {
274+
use diesel::expression::*;
275+
use diesel::types::*;
276+
277+
let query = sql::<(Integer, Text, Text, Text, Integer, Timestamp)>(
274278
"\
275-
SELECT c.id, c.category, c.slug, c.description, c.created_at, \
279+
SELECT c.id, c.category, c.slug, c.description, \
276280
COALESCE (( \
277281
SELECT sum(c2.crates_cnt)::int \
278282
FROM categories as c2 \
279283
WHERE c2.slug = c.slug \
280284
OR c2.slug LIKE c.slug || '::%' \
281-
), 0) as crates_cnt \
285+
), 0) as crates_cnt, \
286+
c.created_at \
282287
FROM categories as c \
283288
WHERE c.category ILIKE $1 || '::%' \
284289
AND c.category NOT ILIKE $1 || '::%::%'",
285-
)?;
290+
).bind::<Text, _>(&self.category);
286291

287-
let rows = stmt.query(&[&self.category])?;
288-
Ok(rows.iter().map(|r| Model::from_row(&r)).collect())
292+
let rows = query.get_results(conn)?;
293+
Ok(rows)
289294
}
290295
}
291296

292-
#[derive(Insertable, Default)]
297+
#[derive(Insertable, AsChangeset, Default)]
293298
#[table_name = "categories"]
294299
pub struct NewCategory<'a> {
295300
pub category: &'a str,
@@ -312,6 +317,18 @@ impl<'a> NewCategory<'a> {
312317

313318
categories.filter(slug.eq(self.slug)).first(conn)
314319
}
320+
321+
/// Inserts the user into the database, or updates an existing one.
322+
pub fn create_or_update(&self, conn: &PgConnection) -> CargoResult<Category> {
323+
use diesel::insert;
324+
use diesel::pg::upsert::*;
325+
326+
insert(&self.on_conflict(categories::slug, do_update().set(self)))
327+
.into(categories::table)
328+
.get_result(conn)
329+
.map_err(Into::into)
330+
}
331+
315332
}
316333

317334
impl Model for Category {
@@ -332,16 +349,16 @@ impl Model for Category {
332349

333350
/// Handles the `GET /categories` route.
334351
pub fn index(req: &mut Request) -> CargoResult<Response> {
335-
let conn = req.tx()?;
352+
let conn = req.db_conn()?;
336353
let (offset, limit) = req.pagination(10, 100)?;
337354
let query = req.query();
338355
let sort = query.get("sort").map_or("alpha", String::as_str);
339356

340-
let categories = Category::toplevel_old(conn, sort, limit, offset)?;
357+
let categories = Category::toplevel(&conn, sort, limit, offset)?;
341358
let categories = categories.into_iter().map(Category::encodable).collect();
342359

343360
// Query for the total count of categories
344-
let total = Category::count_toplevel(conn)?;
361+
let total = Category::count_toplevel(&conn)?;
345362

346363
#[derive(RustcEncodable)]
347364
struct R {
@@ -361,13 +378,15 @@ pub fn index(req: &mut Request) -> CargoResult<Response> {
361378

362379
/// Handles the `GET /categories/:category_id` route.
363380
pub fn show(req: &mut Request) -> CargoResult<Response> {
364-
let slug = &req.params()["category_id"];
365-
let conn = req.tx()?;
366-
let cat = Category::find_by_slug(conn, slug)?;
367-
let subcats = cat.subcategories(conn)?
368-
.into_iter()
369-
.map(|s| s.encodable())
370-
.collect();
381+
use self::categories::dsl::{categories, slug};
382+
383+
let id = &req.params()["category_id"];
384+
let conn = req.db_conn()?;
385+
let cat = categories.filter(slug.eq(id)).first::<Category>(&*conn)?;
386+
let subcats = cat.subcategories(&*conn)?.into_iter().map(|s| {
387+
s.encodable()
388+
}).collect();
389+
371390
let cat = cat.encodable();
372391
let cat_with_subcats = EncodableCategoryWithSubcategories {
373392
id: cat.id,

src/tests/all.rs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use cargo_registry::krate::NewCrate;
3232
use cargo_registry::upload as u;
3333
use cargo_registry::user::NewUser;
3434
use cargo_registry::version::NewVersion;
35-
use cargo_registry::{User, Crate, Version, Dependency, Category, Model, Replica};
35+
use cargo_registry::{User, Crate, Version, Dependency, Replica};
3636
use conduit::{Request, Method};
3737
use conduit_test::MockRequest;
3838
use diesel::pg::PgConnection;
@@ -436,16 +436,17 @@ fn new_category<'a>(category: &'a str, slug: &'a str) -> NewCategory<'a> {
436436
}
437437
}
438438

439-
fn mock_category(req: &mut Request, name: &str, slug: &str) -> Category {
440-
let conn = req.tx().unwrap();
441-
let stmt = conn.prepare(
442-
" \
443-
INSERT INTO categories (category, slug) \
444-
VALUES ($1, $2) \
445-
RETURNING *",
446-
).unwrap();
447-
let rows = stmt.query(&[&name, &slug]).unwrap();
448-
Model::from_row(&rows.iter().next().unwrap())
439+
fn new_crate(name: &str) -> NewCrate {
440+
NewCrate {
441+
name: name,
442+
description: Some("desc"),
443+
homepage: None,
444+
documentation: None,
445+
readme: None,
446+
repository: None,
447+
license: Some("MIT"),
448+
max_upload_size: None,
449+
}
449450
}
450451

451452
fn logout(req: &mut Request) {

src/tests/category.rs

Lines changed: 72 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use conduit::{Handler, Method};
22
use conduit_test::MockRequest;
33

4-
use cargo_registry::db::RequestTransaction;
54
use cargo_registry::category::{Category, EncodableCategory, EncodableCategoryWithSubcategories};
65

76
#[derive(RustcDecodable)]
@@ -25,7 +24,7 @@ struct CategoryWithSubcategories {
2524
#[test]
2625
fn index() {
2726
let (_b, app, middle) = ::app();
28-
let mut req = ::req(app, Method::Get, "/api/v1/categories");
27+
let mut req = ::req(app.clone(), Method::Get, "/api/v1/categories");
2928

3029
// List 0 categories if none exist
3130
let mut response = ok_resp!(middle.call(&mut req));
@@ -34,9 +33,11 @@ fn index() {
3433
assert_eq!(json.meta.total, 0);
3534

3635
// Create a category and a subcategory
37-
::mock_category(&mut req, "foo", "foo");
38-
::mock_category(&mut req, "foo::bar", "foo::bar");
39-
36+
{
37+
let conn = t!(app.diesel_database.get());
38+
::new_category("foo", "foo").create_or_update(&conn).unwrap();
39+
::new_category("foo::bar", "foo::bar").create_or_update(&conn).unwrap();
40+
}
4041
let mut response = ok_resp!(middle.call(&mut req));
4142
let json: CategoryList = ::json(&mut response);
4243

@@ -51,13 +52,18 @@ fn show() {
5152
let (_b, app, middle) = ::app();
5253

5354
// Return not found if a category doesn't exist
54-
let mut req = ::req(app, Method::Get, "/api/v1/categories/foo-bar");
55+
let mut req = ::req(app.clone(), Method::Get, "/api/v1/categories/foo-bar");
5556
let response = t_resp!(middle.call(&mut req));
5657
assert_eq!(response.status.0, 404);
5758

5859
// Create a category and a subcategory
59-
::mock_category(&mut req, "Foo Bar", "foo-bar");
60-
::mock_category(&mut req, "Foo Bar::Baz", "foo-bar::baz");
60+
{
61+
let conn = t!(app.diesel_database.get());
62+
63+
// Create a category and a subcategory
64+
t!(::new_category("Foo Bar", "foo-bar").create_or_update(&conn));
65+
t!(::new_category("Foo Bar::Baz", "foo-bar::baz").create_or_update(&conn));
66+
}
6167

6268
// The category and its subcategories should be in the json
6369
let mut response = ok_resp!(middle.call(&mut req));
@@ -71,57 +77,82 @@ fn show() {
7177
#[test]
7278
fn update_crate() {
7379
let (_b, app, middle) = ::app();
74-
let mut req = ::req(app, Method::Get, "/api/v1/categories/foo");
80+
let mut req = ::req(app.clone(), Method::Get, "/api/v1/categories/foo");
7581
let cnt = |req: &mut MockRequest, cat: &str| {
7682
req.with_path(&format!("/api/v1/categories/{}", cat));
7783
let mut response = ok_resp!(middle.call(req));
7884
::json::<GoodCategory>(&mut response).category.crates_cnt as usize
7985
};
80-
::mock_user(&mut req, ::user("foo"));
81-
let (krate, _) = ::mock_crate(&mut req, ::krate("foocat"));
82-
::mock_category(&mut req, "cat1", "cat1");
83-
::mock_category(&mut req, "Category 2", "category-2");
86+
87+
let krate = {
88+
let conn = t!(app.diesel_database.get());
89+
let user = t!(::new_user("foo").create_or_update(&conn));
90+
t!(::new_category("cat1", "cat1").create_or_update(&conn));
91+
t!(::new_category("Category 2", "category-2").create_or_update(&conn));
92+
t!(::new_crate("foo_crate").create_or_update(&conn, None, user.id))
93+
};
8494

8595
// Updating with no categories has no effect
86-
Category::update_crate_old(req.tx().unwrap(), &krate, &[]).unwrap();
96+
{
97+
let conn = t!(app.diesel_database.get());
98+
Category::update_crate(&conn, &krate, &[]).unwrap();
99+
}
87100
assert_eq!(cnt(&mut req, "cat1"), 0);
88101
assert_eq!(cnt(&mut req, "category-2"), 0);
89102

90103
// Happy path adding one category
91-
Category::update_crate_old(req.tx().unwrap(), &krate, &["cat1".to_string()]).unwrap();
104+
{
105+
let conn = t!(app.diesel_database.get());
106+
Category::update_crate(&conn, &krate, &["cat1"]).unwrap();
107+
}
92108
assert_eq!(cnt(&mut req, "cat1"), 1);
93109
assert_eq!(cnt(&mut req, "category-2"), 0);
94110

95111
// Replacing one category with another
96-
Category::update_crate_old(req.tx().unwrap(), &krate, &["category-2".to_string()]).unwrap();
112+
{
113+
let conn = t!(app.diesel_database.get());
114+
Category::update_crate(&conn, &krate, &["category-2"]).unwrap();
115+
}
97116
assert_eq!(cnt(&mut req, "cat1"), 0);
98117
assert_eq!(cnt(&mut req, "category-2"), 1);
99118

100119
// Removing one category
101-
Category::update_crate_old(req.tx().unwrap(), &krate, &[]).unwrap();
120+
{
121+
let conn = t!(app.diesel_database.get());
122+
Category::update_crate(&conn, &krate, &[]).unwrap();
123+
}
102124
assert_eq!(cnt(&mut req, "cat1"), 0);
103125
assert_eq!(cnt(&mut req, "category-2"), 0);
104126

105127
// Adding 2 categories
106-
Category::update_crate_old(
107-
req.tx().unwrap(),
108-
&krate,
109-
&["cat1".to_string(), "category-2".to_string()],
110-
).unwrap();
128+
{
129+
let conn = t!(app.diesel_database.get());
130+
Category::update_crate(
131+
&conn,
132+
&krate,
133+
&["cat1", "category-2"],
134+
).unwrap();
135+
}
111136
assert_eq!(cnt(&mut req, "cat1"), 1);
112137
assert_eq!(cnt(&mut req, "category-2"), 1);
113138

114139
// Removing all categories
115-
Category::update_crate_old(req.tx().unwrap(), &krate, &[]).unwrap();
140+
{
141+
let conn = t!(app.diesel_database.get());
142+
Category::update_crate(&conn, &krate, &[]).unwrap();
143+
}
116144
assert_eq!(cnt(&mut req, "cat1"), 0);
117145
assert_eq!(cnt(&mut req, "category-2"), 0);
118146

119147
// Attempting to add one valid category and one invalid category
120-
let invalid_categories = Category::update_crate_old(
121-
req.tx().unwrap(),
122-
&krate,
123-
&["cat1".to_string(), "catnope".to_string()],
124-
).unwrap();
148+
let invalid_categories = {
149+
let conn = t!(app.diesel_database.get());
150+
Category::update_crate(
151+
&conn,
152+
&krate,
153+
&["cat1", "catnope"],
154+
).unwrap()
155+
};
125156
assert_eq!(invalid_categories, vec!["catnope".to_string()]);
126157
assert_eq!(cnt(&mut req, "cat1"), 1);
127158
assert_eq!(cnt(&mut req, "category-2"), 0);
@@ -135,17 +166,23 @@ fn update_crate() {
135166
assert_eq!(json.meta.total, 2);
136167

137168
// Attempting to add a category by display text; must use slug
138-
Category::update_crate_old(req.tx().unwrap(), &krate, &["Category 2".to_string()]).unwrap();
169+
{
170+
let conn = t!(app.diesel_database.get());
171+
Category::update_crate(&conn, &krate, &["Category 2"]).unwrap();
172+
}
139173
assert_eq!(cnt(&mut req, "cat1"), 0);
140174
assert_eq!(cnt(&mut req, "category-2"), 0);
141175

142176
// Add a category and its subcategory
143-
::mock_category(&mut req, "cat1::bar", "cat1::bar");
144-
Category::update_crate_old(
145-
req.tx().unwrap(),
146-
&krate,
147-
&["cat1".to_string(), "cat1::bar".to_string()],
148-
).unwrap();
177+
{
178+
let conn = t!(app.diesel_database.get());
179+
t!(::new_category("cat1::bar", "cat1::bar").create_or_update(&conn));
180+
Category::update_crate(
181+
&conn,
182+
&krate,
183+
&["cat1", "cat1::bar"],
184+
).unwrap();
185+
}
149186
assert_eq!(cnt(&mut req, "cat1"), 1);
150187
assert_eq!(cnt(&mut req, "cat1::bar"), 1);
151188
assert_eq!(cnt(&mut req, "category-2"), 0);

0 commit comments

Comments
 (0)