Skip to content

Commit d472f62

Browse files
committed
Show subcategories on a category's page
1 parent ce5261a commit d472f62

File tree

4 files changed

+80
-7
lines changed

4 files changed

+80
-7
lines changed

app/models/category.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,7 @@ export default DS.Model.extend({
66
created_at: DS.attr('date'),
77
crates_cnt: DS.attr('number'),
88

9+
subcategories: DS.attr(),
10+
911
crates: DS.hasMany('crate', { async: true })
1012
});

app/templates/category/index.hbs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,25 @@
55
<h1>{{ category.category }} Crates</h1>
66
</div>
77

8+
{{#if category.subcategories }}
9+
<div id='subcategories'>
10+
<h2>Subcategories</h2>
11+
<div class='white-rows'>
12+
{{#each category.subcategories as |subcategory| }}
13+
<div class='row'>
14+
<div class='info'>
15+
{{link-to subcategory.category "category" subcategory.slug}}
16+
<span class='vers small'>
17+
{{ format-num subcategory.crates_cnt }}
18+
crates
19+
</span>
20+
</div>
21+
</div>
22+
{{/each}}
23+
</div>
24+
</div>
25+
{{/if}}
26+
827
<div id='results'>
928
<div class='nav'>
1029
<span class='amt small'>

src/category.rs

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@ pub struct EncodableCategory {
2929
pub crates_cnt: i32,
3030
}
3131

32+
#[derive(RustcEncodable, RustcDecodable)]
33+
pub struct EncodableCategoryWithSubcategories {
34+
pub id: String,
35+
pub category: String,
36+
pub slug: String,
37+
pub created_at: String,
38+
pub crates_cnt: i32,
39+
pub subcategories: Vec<EncodableCategory>,
40+
}
41+
3242
impl Category {
3343
pub fn find_by_category(conn: &GenericConnection, name: &str)
3444
-> CargoResult<Category> {
@@ -127,6 +137,14 @@ impl Category {
127137
let rows = try!(stmt.query(&[]));
128138
Ok(rows.iter().next().unwrap().get("count"))
129139
}
140+
141+
pub fn subcategories(&self, conn: &GenericConnection)
142+
-> CargoResult<Vec<Category>> {
143+
let stmt = try!(conn.prepare("SELECT * FROM categories \
144+
WHERE category ILIKE $1 || '::%'"));
145+
let rows = try!(stmt.query(&[&self.category]));
146+
Ok(rows.iter().map(|r| Model::from_row(&r)).collect())
147+
}
130148
}
131149

132150
impl Model for Category {
@@ -153,10 +171,20 @@ pub fn index(req: &mut Request) -> CargoResult<Response> {
153171
_ => "ORDER BY category ASC",
154172
};
155173

156-
// Collect all the top-level categories
174+
// Collect all the top-level categories and sum up the crates_cnt of
175+
// the crates in all subcategories
157176
let stmt = try!(conn.prepare(&format!(
158-
"SELECT * FROM categories \
159-
WHERE category NOT LIKE '%::%' {} \
177+
"SELECT c.id, c.category, c.slug, c.created_at, \
178+
counts.sum::int as crates_cnt \
179+
FROM categories as c \
180+
LEFT JOIN ( \
181+
SELECT split_part(categories.category, '::', 1), \
182+
sum(categories.crates_cnt) \
183+
FROM categories \
184+
GROUP BY split_part(categories.category, '::', 1) \
185+
) as counts \
186+
ON c.category = counts.split_part \
187+
WHERE c.category NOT LIKE '%::%' {} \
160188
LIMIT $1 OFFSET $2",
161189
sort_sql
162190
)));
@@ -188,8 +216,20 @@ pub fn show(req: &mut Request) -> CargoResult<Response> {
188216
let slug = &req.params()["category_id"];
189217
let conn = try!(req.tx());
190218
let cat = try!(Category::find_by_slug(&*conn, &slug));
219+
let subcats = try!(cat.subcategories(&*conn)).into_iter().map(|s| {
220+
s.encodable()
221+
}).collect();
222+
let cat = cat.encodable();
223+
let cat_with_subcats = EncodableCategoryWithSubcategories {
224+
id: cat.id,
225+
category: cat.category,
226+
slug: cat.slug,
227+
created_at: cat.created_at,
228+
crates_cnt: cat.crates_cnt,
229+
subcategories: subcats,
230+
};
191231

192232
#[derive(RustcEncodable)]
193-
struct R { category: EncodableCategory }
194-
Ok(req.json(&R { category: cat.encodable() }))
233+
struct R { category: EncodableCategoryWithSubcategories}
234+
Ok(req.json(&R { category: cat_with_subcats }))
195235
}

src/tests/category.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@ use conduit::{Handler, Request, Method};
33
use conduit_test::MockRequest;
44

55
use cargo_registry::db::RequestTransaction;
6-
use cargo_registry::category::{Category, EncodableCategory};
6+
use cargo_registry::category::{Category, EncodableCategory, EncodableCategoryWithSubcategories};
77

88
#[derive(RustcDecodable)]
99
struct CategoryList { categories: Vec<EncodableCategory>, meta: CategoryMeta }
1010
#[derive(RustcDecodable)]
1111
struct CategoryMeta { total: i32 }
1212
#[derive(RustcDecodable)]
1313
struct GoodCategory { category: EncodableCategory }
14+
#[derive(RustcDecodable)]
15+
struct CategoryWithSubcategories {
16+
category: EncodableCategoryWithSubcategories
17+
}
1418

1519
#[test]
1620
fn index() {
@@ -39,15 +43,23 @@ fn index() {
3943
#[test]
4044
fn show() {
4145
let (_b, app, middle) = ::app();
46+
47+
// Return not found if a category doesn't exist
4248
let mut req = ::req(app, Method::Get, "/api/v1/categories/foo-bar");
4349
let response = t_resp!(middle.call(&mut req));
4450
assert_eq!(response.status.0, 404);
4551

52+
// Create a category and a subcategory
4653
::mock_category(&mut req, "Foo Bar", "foo-bar");
54+
::mock_category(&mut req, "Foo Bar::Baz", "foo-bar::baz");
55+
56+
// The category and its subcategories should be in the json
4757
let mut response = ok_resp!(middle.call(&mut req));
48-
let json: GoodCategory = ::json(&mut response);
58+
let json: CategoryWithSubcategories = ::json(&mut response);
4959
assert_eq!(json.category.category, "Foo Bar");
5060
assert_eq!(json.category.slug, "foo-bar");
61+
assert_eq!(json.category.subcategories.len(), 1);
62+
assert_eq!(json.category.subcategories[0].category, "Foo Bar::Baz");
5163
}
5264

5365
fn tx(req: &Request) -> &GenericConnection { req.tx().unwrap() }

0 commit comments

Comments
 (0)