PERF: Eager load Theme associations in Stylesheet Manager.

Before this change, calling `StyleSheet::Manager.stylesheet_details`
for the first time resulted in multiple queries to the database. This is
because the code was modelled in a way where each `Theme` was loaded
from the database one at a time.

This PR restructures the code such that it allows us to load all the
theme records in a single query. It also allows us to eager load the
required associations upfront. In order to achieve this, I removed the
support of loading multiple themes per request. It was initially added
to support user selectable theme components but the feature was never
completed and abandoned because it wasn't a feature that we thought was
worth building.
This commit is contained in:
Alan Guo Xiang Tan
2021-06-15 14:57:17 +08:00
parent 53dab8cf1e
commit 8e3691d537
35 changed files with 983 additions and 668 deletions

View File

@ -320,6 +320,7 @@ class ColorScheme < ActiveRecord::Base
end
if theme_ids.present?
Stylesheet::Manager.cache.clear
Theme.notify_theme_change(
theme_ids,
with_scheme: true,

View File

@ -29,6 +29,9 @@ class Theme < ActiveRecord::Base
has_many :locale_fields, -> { filter_locale_fields(I18n.fallbacks[I18n.locale]) }, class_name: 'ThemeField'
has_many :upload_fields, -> { where(type_id: ThemeField.types[:theme_upload_var]).preload(:upload) }, class_name: 'ThemeField'
has_many :extra_scss_fields, -> { where(target_id: Theme.targets[:extra_scss]) }, class_name: 'ThemeField'
has_many :yaml_theme_fields, -> { where("name = 'yaml' AND type_id = ?", ThemeField.types[:yaml]) }, class_name: 'ThemeField'
has_many :var_theme_fields, -> { where("type_id IN (?)", ThemeField.theme_var_type_ids) }, class_name: 'ThemeField'
has_many :builder_theme_fields, -> { where("name IN (?)", ThemeField.scss_fields) }, class_name: 'ThemeField'
validate :component_validations
@ -164,6 +167,16 @@ class Theme < ActiveRecord::Base
end
end
def self.parent_theme_ids
get_set_cache "parent_theme_ids" do
Theme.where(component: false).pluck(:id)
end
end
def self.is_parent_theme?(id)
self.parent_theme_ids.include?(id)
end
def self.user_theme_ids
get_set_cache "user_theme_ids" do
Theme.user_selectable.pluck(:id)
@ -188,25 +201,22 @@ class Theme < ActiveRecord::Base
expire_site_cache!
end
def self.transform_ids(ids, extend: true)
return [] if ids.nil?
get_set_cache "#{extend ? "extended_" : ""}transformed_ids_#{ids.join("_")}" do
next [] if ids.blank?
def self.transform_ids(id)
return [] if id.blank?
ids = ids.dup
ids.uniq!
parent = ids.shift
components = ids
components.push(*components_for(parent)) if extend
components.sort!.uniq!
all_ids = [parent, *components]
get_set_cache "transformed_ids_#{id}" do
all_ids =
if self.is_parent_theme?(id)
components = components_for(id).tap { |c| c.sort!.uniq! }
[id, *components]
else
[id]
end
disabled_ids = Theme.where(id: all_ids)
.includes(:remote_theme)
.select { |t| !t.supported? || !t.enabled? }
.pluck(:id)
.map(&:id)
all_ids - disabled_ids
end
@ -272,11 +282,10 @@ class Theme < ActiveRecord::Base
end
end
def self.lookup_field(theme_ids, target, field, skip_transformation: false)
return if theme_ids.blank?
theme_ids = [theme_ids] unless Array === theme_ids
def self.lookup_field(theme_id, target, field, skip_transformation: false)
return "" if theme_id.blank?
theme_ids = transform_ids(theme_ids) if !skip_transformation
theme_ids = !skip_transformation ? transform_ids(theme_id) : [theme_id]
cache_key = "#{theme_ids.join(",")}:#{target}:#{field}:#{Theme.compiler_version}"
lookup = @cache[cache_key]
return lookup.html_safe if lookup
@ -289,8 +298,8 @@ class Theme < ActiveRecord::Base
def self.lookup_modifier(theme_ids, modifier_name)
theme_ids = [theme_ids] unless Array === theme_ids
theme_ids = transform_ids(theme_ids)
get_set_cache("#{theme_ids.join(",")}:modifier:#{modifier_name}:#{Theme.compiler_version}") do
ThemeModifierSet.resolve_modifier_for_themes(theme_ids, modifier_name)
end
@ -335,14 +344,18 @@ class Theme < ActiveRecord::Base
def notify_theme_change(with_scheme: false)
DB.after_commit do
theme_ids = Theme.transform_ids([id])
theme_ids = Theme.transform_ids(id)
self.class.notify_theme_change(theme_ids, with_scheme: with_scheme)
end
end
def self.refresh_message_for_targets(targets, theme_ids)
targets.map do |target|
Stylesheet::Manager.stylesheet_data(target.to_sym, theme_ids)
theme_ids = [theme_ids] unless theme_ids === Array
targets.each_with_object([]) do |target, data|
theme_ids.each do |theme_id|
data << Stylesheet::Manager.new(theme_id: theme_id).stylesheet_data(target.to_sym)
end
end
end
@ -385,7 +398,8 @@ class Theme < ActiveRecord::Base
end
def list_baked_fields(target, name)
theme_ids = Theme.transform_ids([id], extend: name == :color_definitions)
theme_ids = Theme.transform_ids(id)
theme_ids = [theme_ids.first] if name != :color_definitions
self.class.list_baked_fields(theme_ids, target, name)
end
@ -435,7 +449,7 @@ class Theme < ActiveRecord::Base
def all_theme_variables
fields = {}
ids = Theme.transform_ids([id])
ids = Theme.transform_ids(id)
ThemeField.find_by_theme_ids(ids).where(type_id: ThemeField.theme_var_type_ids).each do |field|
next if fields.key?(field.name)
fields[field.name] = field
@ -530,7 +544,7 @@ class Theme < ActiveRecord::Base
def included_settings
hash = {}
Theme.where(id: Theme.transform_ids([id])).each do |theme|
Theme.where(id: Theme.transform_ids(id)).each do |theme|
hash.merge!(theme.cached_settings)
end
@ -641,11 +655,6 @@ class Theme < ActiveRecord::Base
contents
end
def has_scss(target)
name = target == :embedded_theme ? :embedded_scss : :scss
list_baked_fields(target, name).count > 0
end
def convert_settings
settings.each do |setting|
setting_row = ThemeSetting.where(theme_id: self.id, name: setting.name.to_s).first