I use the gem act_as_votable to allow users to select categories they like. To get the categories a user likes:
current_user.get_voted(Category)
These categories have children categories, and I need to find the posts in those children categories.
I use this line to do it:
@categories_posts = current_user.get_voted(Category).flat_map { |category| category.children.flat_map(&:posts)}.delete_if{ |post| post.status == "draft"}
But I noticed in my logs that this was doing way too much computation for this task.
Is there a way to make it lighter ?
1 Answer 1
Some notes:
- You should limit the lines to 80/100 chars.
- When a call chain seems to include too much logic, move it to methods/class-methods of models.
As a first refactor, I'd implement Category#with_children
and then write:
@categories_posts = current_user.
get_voted(Category).
flat_map(&:with_children).
flat_map(&:posts).
reject { |post| post.status == "draft" }
On a second refactor, it's nice to have relations instead of arrays as output. This way you can use scopes, paginators and such, even if you cannot directly use has_many
here (because of the recursive category requirement):
categories = current_user.get_voted(Category).flat_map(&:with_children)
@categories_posts = Post.where(category: categories).published
That's to show the implementation, on the controller you should still have less logic, something like this: @categories_posts = current_user.posts_of_voted_categories.published
.
-
\$\begingroup\$ Thanks for your input ! This indeed makes the code more readable, but does it make it more efficient ? \$\endgroup\$Graham Slick– Graham Slick2016年07月12日 20:12:15 +00:00Commented Jul 12, 2016 at 20:12
-
\$\begingroup\$ Updated, now it should be more efficient, it's using SQL from the category level. \$\endgroup\$tokland– tokland2016年07月13日 07:25:55 +00:00Commented Jul 13, 2016 at 7:25