Merge pull request #3422 from okriuchykhin/ok_SCI_5847

Fix repository cell joining and preloading [SCI-5847]
This commit is contained in:
Alex Kriuchykhin 2021-07-21 13:18:21 +02:00 committed by GitHub
commit dfa2a7775d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 97 additions and 181 deletions

View file

@ -12,7 +12,7 @@ module Api
def index
cells = @inventory_item.repository_cells
.preload(@inventory.cell_preload_includes)
.preload(value: @inventory.cell_preload_includes)
.page(params.dig(:page, :number))
.per(params.dig(:page, :size))
render jsonapi: cells, each_serializer: InventoryCellSerializer

View file

@ -14,7 +14,7 @@ module Api
items = @inventory.repository_rows
.active
.preload(repository_cells: :repository_column)
.preload(repository_cells: @inventory.cell_preload_includes)
.preload(repository_cells: { value: @inventory.cell_preload_includes })
.page(params.dig(:page, :number))
.per(params.dig(:page, :size))
.order(:id)

View file

@ -12,7 +12,7 @@ module Api
items =
@task.repository_rows
.includes(repository_cells: :repository_column)
.includes(repository_cells: Extends::REPOSITORY_SEARCH_INCLUDES)
.preload(repository_cells: :value)
.page(params.dig(:page, :number))
.per(params.dig(:page, :size))
render jsonapi: items,

View file

@ -25,9 +25,10 @@ class MyModuleRepositoriesController < ApplicationController
repository_rows = datatable_service.repository_rows
rows_view = 'repository_rows/simple_view_index.json'
else
repository_rows = datatable_service.repository_rows.preload(:repository_columns,
:created_by,
repository_cells: @repository.cell_preload_includes)
repository_rows = datatable_service.repository_rows
.preload(:repository_columns,
:created_by,
repository_cells: { value: @repository.cell_preload_includes })
rows_view = 'repository_rows/index.json'
end
@repository_rows = repository_rows.page(page).per(per_page)

View file

@ -19,10 +19,11 @@ class MyModuleRepositorySnapshotsController < ApplicationController
repository_rows = datatable_service.repository_rows
rows_view = 'repository_rows/simple_view_index.json'
else
repository_rows = datatable_service.repository_rows
.preload(:repository_columns,
:created_by,
repository_cells: @repository_snapshot.cell_preload_includes)
repository_rows =
datatable_service.repository_rows
.preload(:repository_columns,
:created_by,
repository_cells: { value: @repository_snapshot.cell_preload_includes })
rows_view = 'repository_rows/snapshot_index.json'
end
@repository_rows = repository_rows.page(page).per(per_page)

View file

@ -23,7 +23,7 @@ class RepositoryRowsController < ApplicationController
@repository_rows = datatable_service.repository_rows
.preload(:repository_columns,
:created_by,
repository_cells: @repository.cell_preload_includes)
repository_cells: { value: @repository.cell_preload_includes })
.page(page)
.per(per_page)

View file

@ -101,10 +101,9 @@ module RepositoryDatatableHelper
end
def display_cell_value(cell, team)
value_name = cell.repository_column.data_type.demodulize.underscore
serializer_class = "RepositoryDatatable::#{cell.repository_column.data_type}Serializer".constantize
serializer_class.new(
cell.__send__(value_name),
cell.value,
scope: { team: team, user: current_user, column: cell.repository_column }
).serializable_hash
end

View file

@ -93,7 +93,7 @@ class Repository < RepositoryBase
repository_rows = repository_rows.or(readable_rows.where(id: matched_by_user))
Extends::REPOSITORY_EXTRA_SEARCH_ATTR.each do |_data_type, config|
custom_cell_matches = repository_rows.joins(repository_cells: config[:includes])
custom_cell_matches = repository_rows.joins(config[:includes])
.where_attributes_like(config[:field], query, options)
repository_rows = repository_rows.or(readable_rows.where(id: custom_cell_matches))
end
@ -201,14 +201,6 @@ class Repository < RepositoryBase
new_repo
end
def cell_preload_includes
cell_includes = []
repository_columns.pluck(:data_type).each do |data_type|
cell_includes << data_type.constantize::PRELOAD_INCLUDE
end
cell_includes
end
def import_records(sheet, mappings, user)
importer = RepositoryImportParser::Importer.new(sheet, mappings, user, self)
importer.run

View file

@ -18,8 +18,8 @@ class RepositoryAssetValue < ApplicationRecord
validates :asset, :repository_cell, presence: true
SORTABLE_COLUMN_NAME = 'active_storage_blobs.filename'
SORTABLE_VALUE_INCLUDE = { repository_asset_value: { asset: { file_attachment: :blob } } }.freeze
PRELOAD_INCLUDE = { repository_asset_value: { asset: { file_attachment: :blob } } }.freeze
EXTRA_SORTABLE_VALUE_INCLUDE = { asset: { file_attachment: :blob } }.freeze
EXTRA_PRELOAD_INCLUDE = { asset: { file_attachment: :blob } }.freeze
def formatted
asset.file_name

View file

@ -24,7 +24,10 @@ class RepositoryBase < ApplicationRecord
def cell_preload_includes
cell_includes = []
repository_columns.pluck(:data_type).each do |data_type|
cell_includes << data_type.constantize::PRELOAD_INCLUDE
value_class = data_type.constantize
next unless value_class.const_defined?('EXTRA_PRELOAD_INCLUDE')
cell_includes << value_class::EXTRA_PRELOAD_INCLUDE
end
cell_includes
end

View file

@ -5,106 +5,32 @@ class RepositoryCell < ApplicationRecord
belongs_to :repository_row
belongs_to :repository_column
belongs_to :value, polymorphic: true,
inverse_of: :repository_cell,
dependent: :destroy
belongs_to :repository_text_value,
(lambda do
includes(:repository_cell)
.where(repository_cells: { value_type: 'RepositoryTextValue' })
end),
optional: true, foreign_key: :value_id, inverse_of: :repository_cell
belongs_to :repository_number_value,
(lambda do
includes(:repository_cell)
.where(repository_cells: { value_type: 'RepositoryNumberValue' })
end),
optional: true, foreign_key: :value_id, inverse_of: :repository_cell
belongs_to :repository_date_value,
(lambda do
includes(:repository_cell)
.where(repository_cells: { value_type: 'RepositoryDateTimeValueBase' })
end),
optional: true, foreign_key: :value_id, inverse_of: :repository_cell
belongs_to :repository_list_value,
(lambda do
includes(:repository_cell)
.where(repository_cells: { value_type: 'RepositoryListValue' })
end),
optional: true, foreign_key: :value_id, inverse_of: :repository_cell
belongs_to :repository_asset_value,
(lambda do
includes(:repository_cell)
.where(repository_cells: { value_type: 'RepositoryAssetValue' })
end),
optional: true, foreign_key: :value_id, inverse_of: :repository_cell
belongs_to :value, polymorphic: true, inverse_of: :repository_cell, dependent: :destroy
belongs_to :repository_status_value,
(lambda do
includes(:repository_cell)
.where(repository_cells: { value_type: 'RepositoryStatusValue' })
end),
optional: true, foreign_key: :value_id, inverse_of: :repository_cell
belongs_to :repository_checklist_value,
(lambda do
includes(:repository_cell)
.where(repository_cells: { value_type: 'RepositoryChecklistValue' })
end),
optional: true, foreign_key: :value_id, inverse_of: :repository_cell
belongs_to :repository_date_time_value_base,
(lambda do
includes(:repository_cell)
.where(repository_cells: { value_type: 'RepositoryDateTimeValueBase' })
end),
optional: true, foreign_key: :value_id, inverse_of: :repository_cell
belongs_to :repository_date_time_value,
(lambda do
includes(:repository_cell)
.where(repository_cells: { value_type: 'RepositoryDateTimeValueBase' })
end),
optional: true, foreign_key: :value_id, inverse_of: :repository_cell
belongs_to :repository_time_value,
(lambda do
includes(:repository_cell)
.where(repository_cells: { value_type: 'RepositoryDateTimeValueBase' })
end),
optional: true, foreign_key: :value_id, inverse_of: :repository_cell
belongs_to :repository_date_time_range_value_base,
(lambda do
includes(:repository_cell)
.where(repository_cells: { value_type: 'RepositoryDateTimeRangeValueBase' })
end),
optional: true, foreign_key: :value_id, inverse_of: :repository_cell
belongs_to :repository_date_time_range_value,
(lambda do
includes(:repository_cell)
.where(repository_cells: { value_type: 'RepositoryDateTimeRangeValueBase' })
end),
optional: true, foreign_key: :value_id, inverse_of: :repository_cell
belongs_to :repository_date_range_value,
(lambda do
includes(:repository_cell)
.where(repository_cells: { value_type: 'RepositoryDateTimeRangeValueBase' })
end),
optional: true, foreign_key: :value_id, inverse_of: :repository_cell
belongs_to :repository_time_range_value,
(lambda do
includes(:repository_cell)
.where(repository_cells: { value_type: 'RepositoryDateTimeRangeValueBase' })
end),
optional: true, foreign_key: :value_id, inverse_of: :repository_cell
{
repository_text: 'RepositoryTextValue',
repository_number: 'RepositoryNumberValue',
repository_list: 'RepositoryListValue',
repository_asset: 'RepositoryAssetValue',
repository_status: 'RepositoryStatusValue',
repository_checklist: 'RepositoryChecklistValue',
repository_date_time: 'RepositoryDateTimeValueBase',
repository_time: 'RepositoryDateTimeValueBase',
repository_date: 'RepositoryDateTimeValueBase',
repository_date_time_range: 'RepositoryDateTimeRangeValueBase',
repository_time_range: 'RepositoryDateTimeRangeValueBase',
repository_date_range: 'RepositoryDateTimeRangeValueBase'
}.each do |relation, class_name|
belongs_to "#{relation}_value".to_sym,
(lambda do |repository_cell|
repository_cell.value_type == class_name ? self : none
end),
optional: true, foreign_key: :value_id, inverse_of: :repository_cell
end
validates :repository_column,
inclusion: { in: (lambda do |cell|
cell.repository_row&.repository&.repository_columns || []
inclusion: { in: (lambda do |repository_cell|
repository_cell.repository_row&.repository&.repository_columns || []
end) },
unless: :importing
validates :repository_column, presence: true

View file

@ -14,8 +14,8 @@ class RepositoryChecklistValue < ApplicationRecord
validates :repository_checklist_items, presence: true
SORTABLE_COLUMN_NAME = 'repository_checklist_items.data'
SORTABLE_VALUE_INCLUDE = { repository_checklist_value: :repository_checklist_items }.freeze
PRELOAD_INCLUDE = { repository_checklist_value: :repository_checklist_items }.freeze
EXTRA_SORTABLE_VALUE_INCLUDE = :repository_checklist_items
EXTRA_PRELOAD_INCLUDE = :repository_checklist_items
def formatted(separator: ' | ')
repository_checklist_items.pluck(:data).join(separator)

View file

@ -1,8 +1,6 @@
# frozen_string_literal: true
class RepositoryDateRangeValue < RepositoryDateTimeRangeValueBase
PRELOAD_INCLUDE = :repository_date_range_value
def data_changed?(new_data)
data = new_data.is_a?(String) ? JSON.parse(new_data).symbolize_keys : new_data
st = Time.zone.parse(data[:start_time])

View file

@ -1,8 +1,6 @@
# frozen_string_literal: true
class RepositoryDateTimeRangeValue < RepositoryDateTimeRangeValueBase
PRELOAD_INCLUDE = :repository_date_time_range_value
def data_changed?(new_data)
data = new_data.is_a?(String) ? JSON.parse(new_data).symbolize_keys : new_data
st = Time.zone.parse(data[:start_time])

View file

@ -13,7 +13,6 @@ class RepositoryDateTimeRangeValueBase < ApplicationRecord
validates :repository_cell, :start_time, :end_time, :type, presence: true
SORTABLE_COLUMN_NAME = 'repository_date_time_range_values.start_time'
SORTABLE_VALUE_INCLUDE = :repository_date_time_range_value_base
def data
[start_time, end_time].compact.join(' - ')

View file

@ -1,8 +1,6 @@
# frozen_string_literal: true
class RepositoryDateTimeValue < RepositoryDateTimeValueBase
PRELOAD_INCLUDE = :repository_date_time_value
def data_changed?(new_data)
new_time = Time.zone.parse(new_data)
new_time.to_i != data.to_i

View file

@ -13,7 +13,6 @@ class RepositoryDateTimeValueBase < ApplicationRecord
validates :repository_cell, :data, :type, presence: true
SORTABLE_COLUMN_NAME = 'repository_date_time_values.data'
SORTABLE_VALUE_INCLUDE = :repository_date_time_value_base
def formatted(format)
I18n.l(data, format: format)

View file

@ -1,8 +1,6 @@
# frozen_string_literal: true
class RepositoryDateValue < RepositoryDateTimeValueBase
PRELOAD_INCLUDE = :repository_date_value
def data_changed?(new_data)
new_time = Time.zone.parse(new_data)
new_time.to_date != data.to_date

View file

@ -20,8 +20,8 @@ class RepositoryListValue < ApplicationRecord
end)
SORTABLE_COLUMN_NAME = 'repository_list_items.data'
SORTABLE_VALUE_INCLUDE = { repository_list_value: :repository_list_item }.freeze
PRELOAD_INCLUDE = { repository_list_value: :repository_list_item }.freeze
EXTRA_SORTABLE_VALUE_INCLUDE = :repository_list_item
EXTRA_PRELOAD_INCLUDE = :repository_list_item
def formatted
data.to_s

View file

@ -11,8 +11,6 @@ class RepositoryNumberValue < ApplicationRecord
validates :repository_cell, :data, presence: true
SORTABLE_COLUMN_NAME = 'repository_number_values.data'
SORTABLE_VALUE_INCLUDE = :repository_number_value
PRELOAD_INCLUDE = :repository_number_value
def formatted
data.to_s

View file

@ -13,16 +13,35 @@ class RepositoryRow < ApplicationRecord
belongs_to :created_by, foreign_key: :created_by_id, class_name: 'User'
belongs_to :last_modified_by, foreign_key: :last_modified_by_id, class_name: 'User'
belongs_to :archived_by,
foreign_key: :archived_by_id,
class_name: 'User',
inverse_of: :archived_repository_rows,
optional: true
belongs_to :restored_by,
foreign_key: :restored_by_id,
class_name: 'User',
inverse_of: :restored_repository_rows,
optional: true
has_many :repository_cells, -> { order(:id) }, dependent: :destroy
has_many :repository_cells, -> { order(:id) }, inverse_of: :repository_row, dependent: :destroy
{
repository_text: 'RepositoryTextValue',
repository_number: 'RepositoryNumberValue',
repository_list: 'RepositoryListValue',
repository_asset: 'RepositoryAssetValue',
repository_status: 'RepositoryStatusValue',
repository_checklist: 'RepositoryChecklistValue',
repository_date_time: 'RepositoryDateTimeValue',
repository_time: 'RepositoryTimeValue',
repository_date: 'RepositoryDateValue',
repository_date_time_range: 'RepositoryDateTimeRangeValue',
repository_time_range: 'RepositoryTimeRangeValue',
repository_date_range: 'RepositoryDateRangeValue'
}.each do |relation, class_name|
has_many "#{relation}_cells".to_sym, -> { where(value_type: class_name) }, class_name: 'RepositoryCell',
inverse_of: :repository_row
has_many "#{relation}_values".to_sym, class_name: class_name, through: "#{relation}_cells".to_sym,
source: :value, source_type: class_name
end
has_many :repository_columns, through: :repository_cells
has_many :my_module_repository_rows,
inverse_of: :repository_row, dependent: :destroy

View file

@ -12,8 +12,8 @@ class RepositoryStatusValue < ApplicationRecord
validates :repository_cell, :repository_status_item, presence: true
SORTABLE_COLUMN_NAME = 'repository_status_items.status'
SORTABLE_VALUE_INCLUDE = { repository_status_value: :repository_status_item }.freeze
PRELOAD_INCLUDE = { repository_status_value: :repository_status_item }.freeze
EXTRA_SORTABLE_VALUE_INCLUDE = :repository_status_item
EXTRA_PRELOAD_INCLUDE = :repository_status_item
def formatted
data

View file

@ -14,8 +14,6 @@ class RepositoryTextValue < ApplicationRecord
validates :data, presence: true, length: { maximum: Constants::TEXT_MAX_LENGTH }
SORTABLE_COLUMN_NAME = 'repository_text_values.data'
SORTABLE_VALUE_INCLUDE = :repository_text_value
PRELOAD_INCLUDE = :repository_text_value
def formatted
data

View file

@ -1,8 +1,6 @@
# frozen_string_literal: true
class RepositoryTimeRangeValue < RepositoryDateTimeRangeValueBase
PRELOAD_INCLUDE = :repository_time_range_value
def data_changed?(new_data)
data = new_data.is_a?(String) ? JSON.parse(new_data).symbolize_keys : new_data

View file

@ -1,8 +1,6 @@
# frozen_string_literal: true
class RepositoryTimeValue < RepositoryDateTimeValueBase
PRELOAD_INCLUDE = :repository_time_value
def data_changed?(new_data)
new_time = Time.zone.parse(new_data)
new_time.min != data.min || new_time.hour != data.hour

View file

@ -91,7 +91,7 @@ class RepositoryDatatableService
Extends::REPOSITORY_EXTRA_SEARCH_ATTR.each do |data_type, config|
next unless data_types.include?(data_type.to_s)
custom_cell_matches = repository_rows.joins(repository_cells: config[:includes])
custom_cell_matches = repository_rows.joins(config[:includes])
.where_attributes_like(config[:field], search_value)
results = results.or(repository_rows.where(id: custom_cell_matches))
end
@ -139,37 +139,38 @@ class RepositoryDatatableService
col_order = service.load_state.state['ColReorder']
column_id = col_order[column_index].to_i
if @sortable_columns[column_id - 1] == 'assigned'
case @sortable_columns[column_id - 1]
when 'assigned'
return records if @my_module && @params[:assigned] == 'assigned'
records.order("assigned_my_modules_count #{dir}")
elsif @sortable_columns[column_id - 1] == 'repository_cell.value'
when 'repository_cell.value'
id = @mappings.key(column_id.to_s)
sorting_column = RepositoryColumn.find_by(id: id)
sorting_column = @repository.repository_columns.find_by(id: id)
return records unless sorting_column
sorting_data_type = sorting_column.data_type.constantize
cells = sorting_data_type.joins(:repository_cell)
.where('repository_cells.repository_column_id': sorting_column.id)
if sorting_data_type.const_defined?('EXTRA_SORTABLE_VALUE_INCLUDE')
cells = cells.joins(sorting_data_type::EXTRA_SORTABLE_VALUE_INCLUDE)
end
cells = if sorting_column.repository_checklist_value?
RepositoryCell.joins(sorting_data_type::SORTABLE_VALUE_INCLUDE)
.where('repository_cells.repository_column_id': sorting_column.id)
.select("repository_cells.repository_row_id,
STRING_AGG(
#{sorting_data_type::SORTABLE_COLUMN_NAME}, ' '
ORDER BY #{sorting_data_type::SORTABLE_COLUMN_NAME}) AS value")
.group('repository_cells.repository_row_id')
cells
.select("repository_cells.repository_row_id, \
STRING_AGG(#{sorting_data_type::SORTABLE_COLUMN_NAME}, ' ' \
ORDER BY #{sorting_data_type::SORTABLE_COLUMN_NAME}) AS value")
.group('repository_cells.repository_row_id')
else
RepositoryCell.joins(sorting_data_type::SORTABLE_VALUE_INCLUDE)
.where('repository_cells.repository_column_id': sorting_column.id)
.select("repository_cells.repository_row_id,
#{sorting_data_type::SORTABLE_COLUMN_NAME} AS value")
cells
.select("repository_cells.repository_row_id, #{sorting_data_type::SORTABLE_COLUMN_NAME} AS value")
end
records.joins("LEFT OUTER JOIN (#{cells.to_sql}) AS values ON values.repository_row_id = repository_rows.id")
.group('values.value')
.order("values.value #{dir}")
elsif @sortable_columns[column_id - 1] == 'users.full_name'
when 'users.full_name'
records.group('users.full_name').order("users.full_name #{dir}")
else
records.group(@sortable_columns[column_id - 1]).order("#{@sortable_columns[column_id - 1]} #{dir}")

View file

@ -41,7 +41,7 @@ class RepositorySnapshotDatatableService < RepositoryDatatableService
Extends::REPOSITORY_EXTRA_SEARCH_ATTR.each do |data_type, config|
next unless data_types.include?(data_type.to_s)
custom_cell_matches = repository_rows.joins(repository_cells: config[:includes])
custom_cell_matches = repository_rows.joins(config[:includes])
.where_attributes_like(config[:field], search_value)
results = results.or(repository_rows.where(id: custom_cell_matches))
end

View file

@ -69,32 +69,24 @@ class Extends
# Extra attributes used for search in repositories, 'filed_name' => include_hash
REPOSITORY_EXTRA_SEARCH_ATTR = {
RepositoryTextValue: {
field: 'repository_text_values.data', includes: :repository_text_value
field: 'repository_text_values.data', includes: :repository_text_values
}, RepositoryNumberValue: {
field: 'repository_number_values.data', includes: :repository_number_value
field: 'repository_number_values.data', includes: :repository_number_values
}, RepositoryListValue: {
field: 'repository_list_items.data',
includes: { repository_list_value: :repository_list_item }
includes: { repository_list_values: :repository_list_item }
}, RepositoryChecklistValue: {
field: 'repository_checklist_items.data',
includes: { repository_checklist_value: { repository_checklist_items_values: :repository_checklist_item } }
includes: { repository_checklist_values: { repository_checklist_items_values: :repository_checklist_item } }
}, RepositoryStatusValue: {
field: 'repository_status_items.status',
includes: { repository_status_value: :repository_status_item }
includes: { repository_status_values: :repository_status_item }
}, RepositoryAssetValue: {
field: 'active_storage_blobs.filename',
includes: { repository_asset_value: { asset: { file_attachment: :blob } } }
includes: { repository_asset_values: { asset: { file_attachment: :blob } } }
}
}
# Array of includes used in search query for repository rows
REPOSITORY_SEARCH_INCLUDES = [:repository_text_value,
:repository_number_value,
repository_list_value: :repository_list_item,
repository_checklist_value: :repository_checklist_items,
repository_status_value: :repository_status_item,
repository_asset_value: { asset: { file_attachment: :blob } }]
# Array of preload relations used in search query for repository rows
REPOSITORY_ROWS_PRELOAD_RELATIONS = []