Skip to content

Commit 7c23270

Browse files
committed
Allow tuples to be used with grouped_by
The current implementation of associations basically ignores multiple level nesting. It lets us get `Vec<(User, Vec<Post>)>`, but not `Vec<(User, Vec<(Post, Vec<Comment>)>)>`. This allows a tuple of arbitrary size to be used with `grouped_by`, as long as the first element in the tuple is the actual child used in the association. This means that `Vec<(Post, Vec<Comment>)>` implements `GroupedBy<User>`. I've also moved the impl off of `Vec` and onto `T: IntoIterator`, to get rid of some unneccesary `collect` calls in tests.
1 parent 133895e commit 7c23270

4 files changed

Lines changed: 55 additions & 1 deletion

File tree

diesel/src/associations/belongs_to.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ pub trait GroupedBy<Parent>: IntoIterator + Sized {
1616
fn grouped_by(self, parents: &[Parent]) -> Vec<Vec<Self::Item>>;
1717
}
1818

19-
impl<Parent, Child> GroupedBy<Parent> for Vec<Child> where
19+
impl<Parent, Child, Iter> GroupedBy<Parent> for Iter where
20+
Iter: IntoIterator<Item=Child>,
2021
Child: BelongsTo<Parent>,
2122
Parent: Identifiable,
2223
{

diesel/src/types/impls/tuples.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use associations::{BelongsTo, Identifiable};
12
use backend::{Backend, SupportsDefaultKeyword};
23
use expression::{Expression, SelectableExpression, NonAggregate};
34
use persistable::{ColumnInsertValue, InsertValues};
@@ -278,6 +279,21 @@ macro_rules! tuple_impls {
278279
Ok(())
279280
}
280281
}
282+
283+
impl<$($T,)+ Parent> BelongsTo<Parent> for ($($T,)+) where
284+
A: BelongsTo<Parent>,
285+
Parent: Identifiable,
286+
{
287+
type ForeignKeyColumn = A::ForeignKeyColumn;
288+
289+
fn foreign_key(&self) -> Parent::Id {
290+
self.0.foreign_key()
291+
}
292+
293+
fn foreign_key_column() -> Self::ForeignKeyColumn {
294+
A::foreign_key_column()
295+
}
296+
}
281297
)+
282298
}
283299
}

diesel_tests/tests/associations.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,32 @@ fn grouping_associations_maintains_ordering() {
6161
assert_eq!(expected_data, users_and_posts);
6262
}
6363

64+
fn associations_can_be_grouped_multiple_levels_deep() {
65+
// I'm manually defining the data rather than loding from the database here,
66+
// as it makes the tests *way* more readable if I omit setup here. This is
67+
// the equivalent to.
68+
//
69+
// ```rust
70+
// let users = users.load::<User>().unwrap();
71+
// let posts = Post::belonging_to(&users).load::<Post>().unwrap();
72+
// let comments = Comment::belonging_to(&users).load::<Comment>().unwrap();
73+
// ```
74+
let users = vec![User::new(1, "Sean"), User::new(2, "Tess")];
75+
let posts = vec![Post::new(1, 1, "Hello", None), Post::new(2, 2, "World", None), Post::new(3, 1, "Hello 2", None)];
76+
let comments = vec![Comment::new(1, 3, "LOL"), Comment::new(2, 1, "UR dumb"), Comment::new(3, 3, "Funny")]; // Never read the comments
77+
78+
let expected_data = vec![
79+
(users[0].clone(), vec![(posts[0].clone(), vec![comments[1].clone()]), (posts[2].clone(), vec![comments[0].clone(), comments[2].clone()])]),
80+
(users[1].clone(), vec![(posts[1].clone(), vec![])]),
81+
];
82+
83+
let comments = comments.grouped_by(&posts);
84+
let posts_and_comments = posts.into_iter().zip(comments).grouped_by(&users);
85+
let data = users.into_iter().zip(posts_and_comments).collect::<Vec<_>>();
86+
87+
assert_eq!(expected_data, data);
88+
}
89+
6490
fn conn_with_test_data() -> (TestConnection, User, User, User) {
6591
let connection = connection_with_sean_and_tess_in_users_table();
6692
insert(&NewUser::new("Jim", None)).into(users::table).execute(&connection).unwrap();

diesel_tests/tests/schema.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,23 @@ impl User {
3131
}
3232

3333
#[derive(PartialEq, Eq, Debug, Clone, Queryable, Identifiable)]
34+
#[belongs_to(Post)]
3435
pub struct Comment {
3536
id: i32,
3637
post_id: i32,
3738
text: String,
3839
}
3940

41+
impl Comment {
42+
pub fn new(id: i32, post_id: i32, text: &str) -> Self {
43+
Comment {
44+
id: id,
45+
post_id: post_id,
46+
text: text.into(),
47+
}
48+
}
49+
}
50+
4051
#[cfg(feature = "postgres")]
4152
#[path="postgres_specific_schema.rs"]
4253
mod backend_specifics;

0 commit comments

Comments
 (0)