Skip to content
This repository has been archived by the owner on Dec 8, 2017. It is now read-only.

Commit

Permalink
Merge pull request discourse#4569 from tgxworld/fix_n+1_queries
Browse files Browse the repository at this point in the history
PERF: Fix N+1 queries when loading groups.
  • Loading branch information
tgxworld authored Nov 25, 2016
2 parents d8a69e6 + 559918c commit 8f70829
Show file tree
Hide file tree
Showing 18 changed files with 123 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import NotificationsButton from 'discourse/components/notifications-button';

export default NotificationsButton.extend({
classNames: ['notification-options', 'group-notification-menu'],
notificationLevel: Em.computed.alias('group.notification_level'),
notificationLevel: Em.computed.alias('group.group_user.notification_level'),
i18nPrefix: 'groups.notifications',

clicked(id) {
Expand Down
14 changes: 11 additions & 3 deletions app/assets/javascripts/discourse/controllers/group.js.es6
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,16 @@ export default Ember.Controller.extend({
});
},

@computed('model.is_member')
getTabs(isMember) {
return this.get('tabs').filter(t => isMember || !t.get('requiresMembership'));
@computed('model.is_group_user')
getTabs(isGroupUser) {
return this.get('tabs').filter(t => {
let isMember = false;

if (this.currentUser) {
isMember = this.currentUser.admin || isGroupUser;
}

return isMember || !t.get('requiresMembership');
});
}
});
8 changes: 6 additions & 2 deletions app/assets/javascripts/discourse/models/group.js.es6
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ const Group = Discourse.Model.extend({
},

setNotification(notification_level) {
this.set("notification_level", notification_level);
this.set("group_user.notification_level", notification_level);
return ajax(`/groups/${this.get("name")}/notifications`, {
data: { notification_level },
type: "POST"
Expand Down Expand Up @@ -181,7 +181,11 @@ Group.reopenClass({
offset: offset || 0
}
});
}
},

mentionable(name) {
return ajax(`/groups/${name}/mentionable`, { data: { name } });
},
});

export default Group;
10 changes: 9 additions & 1 deletion app/assets/javascripts/discourse/models/user.js.es6
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,15 @@ const User = RestModel.extend({
}

if (!Em.isEmpty(json.user.groups)) {
json.user.groups = json.user.groups.map(g => Group.create(g));
const groups = [];

for(let i = 0; i < json.user.groups.length; i++) {
const group = Group.create(json.user.groups[i]);
group.group_user = json.user.group_users[i];
groups.push(group);
}

json.user.groups = groups;
}

if (json.user.invited_by) {
Expand Down
8 changes: 4 additions & 4 deletions app/assets/javascripts/discourse/routes/new-message.js.es6
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ export default Discourse.Route.extend({
});
} else if (params.groupname) {
// send a message to a group
Group.find(params.groupname).then(group => {
if (group.mentionable) {
Ember.run.next(() => e.send("createNewMessageViaParams", group.name, params.title, params.body));
Group.mentionable(params.groupname).then(result => {
if (result.mentionable) {
Ember.run.next(() => e.send("createNewMessageViaParams", params.groupname, params.title, params.body));
} else {
bootbox.alert(I18n.t("composer.cant_send_pm", { username: group.name }));
bootbox.alert(I18n.t("composer.cant_send_pm", { username: params.groupname }));
}
}).catch(function() {
bootbox.alert(I18n.t("generic_error"));
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/admin/groups_controller.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
class Admin::GroupsController < Admin::AdminController

def index
groups = Group.order(:name).where("id <> ?", Group::AUTO_GROUPS[:everyone])
groups = Group.order(:name).where("groups.id <> ?", Group::AUTO_GROUPS[:everyone])

if search = params[:search].to_s
groups = groups.where("name ILIKE ?", "%#{search}%")
Expand Down
14 changes: 12 additions & 2 deletions app/controllers/groups_controller.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
class GroupsController < ApplicationController

before_filter :ensure_logged_in, only: [:set_notifications]
before_filter :ensure_logged_in, only: [:set_notifications, :mentionable]
skip_before_filter :preload_json, :check_xhr, only: [:posts_feed, :mentions_feed]

def show
render_serialized(find_group(:id), BasicGroupSerializer)
render_serialized(find_group(:id), GroupShowSerializer, root: 'basic_group')
end

def counts
Expand Down Expand Up @@ -120,6 +120,16 @@ def add_members
end
end

def mentionable
group = find_group(:name)

if group
render json: { mentionable: Group.mentionable(current_user).where(id: group.id).present? }
else
raise Discourse::InvalidAccess.new
end
end

def remove_member
group = Group.find(params[:id])
guardian.ensure_can_edit!(group)
Expand Down
1 change: 0 additions & 1 deletion app/controllers/session_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,6 @@ def create
login = params[:login].strip
login = login[1..-1] if login[0] == "@"


if user = User.find_by_username_or_email(login)

# If their password is correct
Expand Down
4 changes: 0 additions & 4 deletions app/models/group.rb
Original file line number Diff line number Diff line change
Expand Up @@ -381,10 +381,6 @@ def bulk_add(user_ids)
true
end

def mentionable?(user, group_id)
Group.mentionable(user).where(id: group_id).exists?
end

def staff?
STAFF_GROUPS.include?(self.name.to_sym)
end
Expand Down
29 changes: 0 additions & 29 deletions app/serializers/basic_group_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,41 +11,12 @@ class BasicGroupSerializer < ApplicationSerializer
:title,
:grant_trust_level,
:incoming_email,
:notification_level,
:has_messages,
:is_member,
:mentionable,
:flair_url,
:flair_bg_color,
:flair_color

def include_incoming_email?
scope.is_staff?
end

def notification_level
fetch_group_user&.notification_level
end

def include_notification_level?
scope.authenticated?
end

def mentionable
object.mentionable?(scope.user, object.id)
end

def is_member
scope.is_admin? || fetch_group_user.present?
end

def include_is_member?
scope.authenticated?
end

private

def fetch_group_user
@group_user ||= object.group_users.find_by(user_id: scope.user.id)
end
end
3 changes: 3 additions & 0 deletions app/serializers/basic_group_user_serializer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class BasicGroupUserSerializer < ApplicationSerializer
attributes :group_id, :user_id, :notification_level
end
11 changes: 11 additions & 0 deletions app/serializers/group_show_serializer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class GroupShowSerializer < BasicGroupSerializer
attributes :is_group_user

def include_is_group_user?
scope.authenticated?
end

def is_group_user
object.users.include?(scope.user)
end
end
11 changes: 9 additions & 2 deletions app/serializers/user_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ def self.untrusted_attributes(*attrs)

has_one :invited_by, embed: :object, serializer: BasicUserSerializer
has_many :groups, embed: :object, serializer: BasicGroupSerializer
has_many :group_users, embed: :object, serializer: BasicGroupUserSerializer
has_many :featured_user_badges, embed: :ids, serializer: UserBadgeSerializer, root: :user_badges
has_one :card_badge, embed: :object, serializer: BadgeSerializer
has_one :user_option, embed: :object, serializer: UserOptionSerializer
Expand Down Expand Up @@ -127,13 +128,19 @@ def mailing_list_posts_per_day
end

def groups
groups = object.groups.order(:id)

if scope.is_admin? || object.id == scope.user.try(:id)
object.groups
groups
else
object.groups.where(visible: true)
groups.where(visible: true)
end
end

def group_users
object.group_users.order(:group_id)
end

def include_email?
object.id && object.id == scope.user.try(:id)
end
Expand Down
3 changes: 2 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@
get "posts/:username/flagged" => "posts#flagged_posts", constraints: {username: USERNAME_ROUTE_FORMAT}

get "groups/:id.json" => 'groups#show', constraints: {id: USERNAME_ROUTE_FORMAT}, defaults: {format: 'json'}

resources :groups, id: USERNAME_ROUTE_FORMAT do
get "posts.rss" => "groups#posts_feed", format: :rss
get "mentions.rss" => "groups#mentions_feed", format: :rss
Expand All @@ -409,6 +409,7 @@
get 'mentions'
get 'messages'
get 'counts'
get 'mentionable'

member do
put "members" => "groups#add_members"
Expand Down
3 changes: 0 additions & 3 deletions spec/controllers/admin/groups_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,7 @@
"primary_group"=>false,
"grant_trust_level"=>nil,
"incoming_email"=>nil,
"notification_level"=>2,
"has_messages"=>false,
"is_member"=>true,
"mentionable"=>false,
"flair_url"=>nil,
"flair_bg_color"=>nil,
"flair_color"=>nil
Expand Down
4 changes: 4 additions & 0 deletions spec/fabricators/email_token_fabricator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fabricator(:email_token) do
user
email { |attrs| attrs[:user].email }
end
36 changes: 36 additions & 0 deletions spec/integration/groups_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
require 'rails_helper'

describe "Groups" do
describe "checking if a group can be mentioned" do
let(:password) { 'somecomplicatedpassword' }
let(:email_token) { Fabricate(:email_token, confirmed: true) }
let(:user) { email_token.user }
let(:group) { Fabricate(:group, name: 'test', users: [user]) }

before do
user.update_attributes!(password: password)
end

it "should return the right response" do
group

post "/session.json", { login: user.username, password: password }
expect(response).to be_success

get "/groups/test/mentionable.json", { name: group.name }

expect(response).to be_success

response_body = JSON.parse(response.body)
expect(response_body["mentionable"]).to eq(false)

group.update_attributes!(alias_level: Group::ALIAS_LEVELS[:everyone])

get "/groups/test/mentionable.json", { name: group.name }
expect(response).to be_success

response_body = JSON.parse(response.body)
expect(response_body["mentionable"]).to eq(true)
end
end
end
15 changes: 14 additions & 1 deletion test/javascripts/acceptance/groups-test.js.es6
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { acceptance } from "helpers/qunit-helpers";
import { acceptance, logIn } from "helpers/qunit-helpers";

acceptance("Groups");

test("Browsing Groups", () => {
Expand All @@ -24,6 +25,18 @@ test("Browsing Groups", () => {

visit("/groups/discourse/messages");
andThen(() => {
ok($('.action-list li').length === 4, 'it should not show messages tab');
ok(count('.user-stream .item') > 0, "it lists stream items");
});
});

test("Messages tab", () => {
logIn();
Discourse.reset();

visit("/groups/discourse");

andThen(() => {
ok($('.action-list li').length === 5, 'it should show messages tab if user is admin');
});
});

0 comments on commit 8f70829

Please sign in to comment.