-
-
Notifications
You must be signed in to change notification settings - Fork 30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Optimize db queries on the front page #420
Changes from 4 commits
a05b771
ae4815f
5d16e0d
e17be3f
0709c17
7c3c69f
374782f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,11 +6,11 @@ class Game < ApplicationRecord | |
|
||
validates :name, presence: true | ||
|
||
has_many :categories | ||
has_many :categories, dependent: :destroy | ||
has_many :runs, through: :categories | ||
has_many :users, -> { distinct }, through: :runs | ||
has_many :runners, -> { distinct }, through: :runs, class_name: 'User' | ||
has_many :aliases, class_name: 'GameAlias' | ||
has_many :aliases, class_name: 'GameAlias', dependent: :destroy | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, I actually can't think of any current or future use case for destroying a game other than the existing merge flow, and in that case any aliases and categories should have already been ported over to the new game. It sounds a bit weird in theory, but realistically I'd consider the destroying of a game that still has aliases or categories to be a bug, so maybe these should be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This broke the tests, and since it was only supposed to be a rubocop fix, I'll leave this for another time. |
||
|
||
after_create :create_initial_alias | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,7 @@ class User < ApplicationRecord | |
include RivalUser | ||
include TwitchUser | ||
|
||
has_many :runs | ||
has_many :runs, dependent: :destroy | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We give users the option to keep their runs when deleting: The code that handles this does nothing to the runs if the user chooses to anonymize them, which seems wrong now that I'm looking at it. So should probably be
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll revert this one, and make an issue for this. It should probably be restricted with exception though since nullify doesn't make sense if the user wants all runs deleted, so the code that handles this should raise an error if some are left. |
||
has_many :categories, -> { distinct }, through: :runs | ||
has_many :games, -> { distinct }, through: :runs | ||
|
||
|
@@ -39,9 +39,9 @@ def self.search(term) | |
|
||
# avatar returns the user's avatar, or a default avatar if the user has not set one. It cannot return nil. | ||
def avatar | ||
return "https://splits.io/logo.svg" if read_attribute(:avatar).nil? | ||
return 'https://splits.io/logo.svg' if self[:avatar].nil? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rubocop made it sound identical and I was trying to squash a warning, so I'll just revert this. |
||
|
||
URI.parse(read_attribute(:avatar) || '').tap do |uri| | ||
URI.parse(self[:avatar] || '').tap do |uri| | ||
uri.scheme = 'https' | ||
end.to_s | ||
end | ||
|
@@ -63,9 +63,8 @@ def previous_upload_for(category) | |
end | ||
|
||
def pbs | ||
runs.where( | ||
id: categories.map { |category| pb_for(category).id10 } | runs.where(category: nil).pluck(:id) | ||
) | ||
Run.where(user: self).select('DISTINCT ON (category_id) *').order('category_id, realtime_duration_ms ASC') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any reason for using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To be honest, when I started testing stuff I was working with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doing it like you said, i was able to add an extra where clause to make it so there will not be any duplicates, and then use |
||
.union(Run.where(user: self, category: nil)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same question as above, also there's the |
||
end | ||
|
||
def runs?(category) | ||
|
@@ -76,6 +75,11 @@ def to_s | |
twitch_display_name || name || 'somebody' | ||
end | ||
|
||
def rivals | ||
User.joins('INNER JOIN rivalries ON rivalries.to_user_id = users.id AND rivalries.from_user_id = ' + id.to_s) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of string concatenation, use SQL interpolation like User.joins('INNER JOIN ... AND rivalries.from_user_id = ?', id) which will sanitize inputs to prevent SQL injection. It's just IDs in this case, but it's still a good practice (and who knows whether there might be a separate attack in the future to inject or set custom IDs). |
||
.select('users.*, rivalries.category_id') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the purpose of this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To have access to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok I'm confused. I understand the need to correlate a User with a Category, but doesn't this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It returns an array of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Whoa. I had no idea that was a thing Rails could do. It's like a... frankenstein object. Not sure how I feel about that, lol. Is it possible / still efficient to pass around There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With this pr, it never loads any of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, nevermind I'm just retarded. Adds one sql query but is much cleaner. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. lol ok nice |
||
end | ||
|
||
def twitch_followed_users | ||
User.joins(:twitch_user_followers).where(twitch_user_follows: {from_user_id: id}) | ||
end | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
- description ||= nil | ||
- cols ||= [:runner, :time, :name, :rival, :uploaded] | ||
- runs = order_runs(runs).page(params[:page]) | ||
- if runs.empty? | ||
- if runs.blank? | ||
center | ||
i No runs matched! | ||
- else | ||
|
@@ -24,7 +24,7 @@ | |
- if cols.include?(:uploaded) | ||
th.align-right = th_sorter('Uploaded', 'created_at') | ||
tbody | ||
- runs.includes(:user).each do |run| | ||
- runs.includes(:user, :game, :category).each do |run| | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When ctrl+f'ing the project, every single call to the partial had the name col, so it seemed fine to just include these always. |
||
tr id="run_#{run.id36}" | ||
- if cols.include?(:owner_controls) | ||
td.align-right | ||
|
@@ -43,8 +43,8 @@ | |
- if cols.include?(:rival) | ||
td.align-right | ||
small | ||
- rival = run.user.rival_for(run.category) | ||
- if rival.present? && rival.runs?(run.category) | ||
- rival = run.user.rivals.detect { |r| r.category_id == run.category_id } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this better because it caches the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, it caches the rivals call for every loop. I'm not sure if it caches the array that it turns it into though, so if you wanted maximum performance you could call rivals once at the top, and then use that local variable for each loop iteration. This still requires an extra call to get the rivals run, just like before, but we eliminate that first call for every run. |
||
- if rival.present? && rival.pb_for(run.category).present? | ||
- rival_run = rival.pb_for(run.category) | ||
= link_to compare_path(run, rival_run), class: 'run-link stealth-link' do | ||
= pretty_difference(run.duration_ms(Run::REAL), rival_run.duration_ms(Run::REAL)) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow nice, hadn't heard of this before. This seems sick, and can definitely clean up a lot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the other spot I noticed that had something similar was the game search, where we find game IDs from both games and game aliases. It seemed useful for the long term for some more advanced things.