316 lines
8.1 KiB
Ruby
316 lines
8.1 KiB
Ruby
|
require 'active_record'
|
||
|
|
||
|
require_relative '../../models/carto/shared_entity'
|
||
|
require_dependency 'carto/bounding_box_utils'
|
||
|
require_dependency 'carto/uuidhelper'
|
||
|
|
||
|
# TODO: consider moving some of this to model scopes if convenient
|
||
|
class Carto::VisualizationQueryBuilder
|
||
|
include Carto::UUIDHelper
|
||
|
|
||
|
def self.user_public_tables(user)
|
||
|
user_public(user).with_type(Carto::Visualization::TYPE_CANONICAL)
|
||
|
end
|
||
|
|
||
|
def self.user_public_visualizations(user)
|
||
|
user_public_privacy_visualizations(user).with_published
|
||
|
end
|
||
|
|
||
|
def self.user_public_privacy_visualizations(user)
|
||
|
user_public(user).with_types(Carto::Visualization::MAP_TYPES)
|
||
|
end
|
||
|
|
||
|
def self.user_public_privacy_datasets(user)
|
||
|
user_public(user).with_types(Carto::Visualization::TYPE_CANONICAL)
|
||
|
end
|
||
|
|
||
|
def self.user_link_privacy_visualizations(user)
|
||
|
new.with_user_id(user.id)
|
||
|
.with_types(Carto::Visualization::MAP_TYPES)
|
||
|
.with_privacy(Carto::Visualization::PRIVACY_LINK)
|
||
|
end
|
||
|
|
||
|
def self.user_password_privacy_visualizations(user)
|
||
|
new.with_user_id(user.id)
|
||
|
.with_types(Carto::Visualization::MAP_TYPES)
|
||
|
.with_privacy(Carto::Visualization::PRIVACY_PROTECTED)
|
||
|
end
|
||
|
|
||
|
def self.user_private_privacy_visualizations(user)
|
||
|
new.with_user_id(user.id)
|
||
|
.with_types(Carto::Visualization::MAP_TYPES)
|
||
|
.with_privacy(Carto::Visualization::PRIVACY_PRIVATE)
|
||
|
end
|
||
|
|
||
|
def self.user_all_visualizations(user)
|
||
|
new.with_user_id(user ? user.id : nil).with_types(Carto::Visualization::MAP_TYPES)
|
||
|
end
|
||
|
|
||
|
def self.user_public(user)
|
||
|
new.with_user_id(user ? user.id : nil).with_privacy(Carto::Visualization::PRIVACY_PUBLIC)
|
||
|
end
|
||
|
|
||
|
def initialize
|
||
|
@include_associations = []
|
||
|
@eager_load_associations = []
|
||
|
@filtering_params = {}
|
||
|
end
|
||
|
|
||
|
def with_id_or_name(id_or_name)
|
||
|
raise 'VisualizationQueryBuilder: id or name supplied is nil' if id_or_name.nil?
|
||
|
|
||
|
if uuid?(id_or_name)
|
||
|
with_id(id_or_name)
|
||
|
else
|
||
|
with_name(id_or_name)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def with_id(id)
|
||
|
@filtering_params[:id] = id
|
||
|
self
|
||
|
end
|
||
|
|
||
|
def with_excluded_ids(ids)
|
||
|
@filtering_params[:excluded_ids] = ids
|
||
|
self
|
||
|
end
|
||
|
|
||
|
def without_synced_external_sources
|
||
|
@filtering_params[:exclude_synced_external_sources] = true
|
||
|
self
|
||
|
end
|
||
|
|
||
|
def without_imported_remote_visualizations
|
||
|
@filtering_params[:exclude_imported_remote_visualizations] = true
|
||
|
self
|
||
|
end
|
||
|
|
||
|
def without_raster
|
||
|
@filtering_params[:excluded_kinds] ||= []
|
||
|
@filtering_params[:excluded_kinds] << Carto::Visualization::KIND_RASTER
|
||
|
self
|
||
|
end
|
||
|
|
||
|
def with_name(name)
|
||
|
@filtering_params[:name] = name
|
||
|
self
|
||
|
end
|
||
|
|
||
|
def with_user_id(user_id)
|
||
|
@filtering_params[:user_id] = user_id
|
||
|
self
|
||
|
end
|
||
|
|
||
|
def with_user_id_not(user_id)
|
||
|
@filtering_params[:user_id_not] = user_id
|
||
|
self
|
||
|
end
|
||
|
|
||
|
def with_privacy(privacy)
|
||
|
@filtering_params[:privacy] = privacy
|
||
|
self
|
||
|
end
|
||
|
|
||
|
def with_liked_by_user_id(user_id)
|
||
|
@filtering_params[:liked_by_user_id] = user_id
|
||
|
self
|
||
|
end
|
||
|
|
||
|
def with_shared_with_user_id(user_id)
|
||
|
@filtering_params[:shared_with_user_id] = user_id
|
||
|
self
|
||
|
end
|
||
|
|
||
|
def with_owned_by_or_shared_with_user_id(user_id)
|
||
|
@filtering_params[:owned_by_or_shared_with_user_id] = user_id
|
||
|
self
|
||
|
end
|
||
|
|
||
|
def with_prefetch_user(force_join = false)
|
||
|
if force_join
|
||
|
with_eager_load_of(:user)
|
||
|
else
|
||
|
with_include_of(:user)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def with_prefetch_table
|
||
|
nested_association = { map: :user_table }
|
||
|
with_eager_load_of(nested_association)
|
||
|
end
|
||
|
|
||
|
def with_prefetch_dependent_visualizations
|
||
|
inner_visualization = { visualization: { map: { layers: :layers_user_tables }, permission: :owner } }
|
||
|
nested_association = { map: { user_table: { layers: { maps: inner_visualization } } } }
|
||
|
with_eager_load_of(nested_association)
|
||
|
end
|
||
|
|
||
|
def with_prefetch_permission
|
||
|
nested_association = { permission: :owner }
|
||
|
with_eager_load_of(nested_association)
|
||
|
end
|
||
|
|
||
|
def with_prefetch_external_source
|
||
|
with_eager_load_of(:external_source)
|
||
|
end
|
||
|
|
||
|
def with_prefetch_synchronization
|
||
|
with_eager_load_of(:synchronization)
|
||
|
self
|
||
|
end
|
||
|
|
||
|
def with_type(type)
|
||
|
@filtering_params[:type] = type
|
||
|
self
|
||
|
end
|
||
|
|
||
|
alias with_types with_type
|
||
|
|
||
|
def with_locked(locked)
|
||
|
@filtering_params[:locked] = locked
|
||
|
self
|
||
|
end
|
||
|
|
||
|
def with_current_user_id(user_id)
|
||
|
@current_user_id = user_id
|
||
|
end
|
||
|
|
||
|
def with_order(order, direction = 'asc')
|
||
|
@order = order.to_s
|
||
|
@direction = direction.to_s
|
||
|
self
|
||
|
end
|
||
|
|
||
|
def with_partial_match(tainted_search_pattern)
|
||
|
@filtering_params[:tainted_search_pattern] = tainted_search_pattern
|
||
|
self
|
||
|
end
|
||
|
|
||
|
def with_tags(tags)
|
||
|
@filtering_params[:tags] = tags
|
||
|
self
|
||
|
end
|
||
|
|
||
|
def with_bounding_box(bounding_box)
|
||
|
@filtering_params[:bounding_box] = bounding_box
|
||
|
self
|
||
|
end
|
||
|
|
||
|
def with_display_name
|
||
|
@filtering_params[:only_with_display_name] = true
|
||
|
self
|
||
|
end
|
||
|
|
||
|
def with_organization_id(organization_id)
|
||
|
@filtering_params[:organization_id] = organization_id
|
||
|
self
|
||
|
end
|
||
|
|
||
|
# Published: see `Carto::Visualization#published?`
|
||
|
def with_published
|
||
|
@filtering_params[:only_published] = true
|
||
|
self
|
||
|
end
|
||
|
|
||
|
def with_version(version)
|
||
|
@filtering_params[:version] = version
|
||
|
self
|
||
|
end
|
||
|
|
||
|
def build
|
||
|
offdatabase_order? ? build_regular : build_subquery
|
||
|
end
|
||
|
|
||
|
def count
|
||
|
filtered_query.count
|
||
|
end
|
||
|
|
||
|
def build_paged(page = 1, per_page = 20)
|
||
|
offdatabase_order? ? build_regular(page, per_page) : build_subquery(page, per_page)
|
||
|
end
|
||
|
|
||
|
def filtered_query
|
||
|
query = Carto::Visualization.all
|
||
|
Carto::VisualizationQueryFilterer.new(query).filter(@filtering_params)
|
||
|
end
|
||
|
|
||
|
private
|
||
|
|
||
|
def build_regular(page = nil, per_page = nil)
|
||
|
query = filtered_query
|
||
|
query = with_associations(query)
|
||
|
query = order_query(query)
|
||
|
query = query.offset((page.to_i - 1) * per_page.to_i).limit(per_page.to_i) if page && per_page
|
||
|
query
|
||
|
end
|
||
|
|
||
|
def build_subquery(page = nil, per_page = nil)
|
||
|
subquery = with_ordering_associations(filtered_query)
|
||
|
subquery = order_query(subquery)
|
||
|
subquery = subquery.offset((page.to_i - 1) * per_page.to_i).limit(per_page.to_i) if page && per_page
|
||
|
|
||
|
# Fetching related tables after filtering the results for better performance
|
||
|
query = Carto::Visualization.from(subquery, 'visualizations')
|
||
|
with_associations(query)
|
||
|
end
|
||
|
|
||
|
def order_query(query)
|
||
|
# Search has its own ordering criteria
|
||
|
return query if @tainted_search_pattern
|
||
|
|
||
|
orderer = Carto::VisualizationQueryOrderer.new(query)
|
||
|
orderer.order(@order, @direction)
|
||
|
end
|
||
|
|
||
|
def with_include_of(association)
|
||
|
@include_associations << association
|
||
|
self
|
||
|
end
|
||
|
|
||
|
def with_eager_load_of(association)
|
||
|
@eager_load_associations << association
|
||
|
self
|
||
|
end
|
||
|
|
||
|
def with_associations(query)
|
||
|
query = query.includes(@include_associations) unless @include_associations.empty?
|
||
|
query = query.eager_load(@eager_load_associations) unless @eager_load_associations.empty?
|
||
|
query
|
||
|
end
|
||
|
|
||
|
def with_ordering_associations(query)
|
||
|
query = with_favorited(query) unless @filtering_params[:liked_by_user_id]
|
||
|
query = with_dependent_visualization_count(query)
|
||
|
query
|
||
|
end
|
||
|
|
||
|
def with_favorited(query)
|
||
|
# We have to include favorites if we're not filtering by them
|
||
|
# Why? Both of them include a join with the likes table: favorited uses
|
||
|
# a left-join one and the filter will use an inner-join.
|
||
|
# So what is the problem? It'll fail because is not possible to include two
|
||
|
# joins for the same table
|
||
|
# And what is the difference?
|
||
|
# - Filtering leaves only the favorited/liked visualizations by the user
|
||
|
# - With favorited we add the like/favorite data to the visualization information
|
||
|
return query unless @order&.include?('favorited') && @current_user_id
|
||
|
|
||
|
Carto::VisualizationQueryIncluder.new(query).include_favorited(@current_user_id)
|
||
|
end
|
||
|
|
||
|
def with_dependent_visualization_count(query)
|
||
|
return query unless @order&.include?('dependent_visualizations')
|
||
|
|
||
|
with_prefetch_dependent_visualizations
|
||
|
Carto::VisualizationQueryIncluder.new(query).include_dependent_visualization_count(@filtering_params)
|
||
|
end
|
||
|
|
||
|
def offdatabase_order?
|
||
|
Carto::VisualizationQueryOrderer::SUPPORTED_OFFDATABASE_ORDERS.any? do |offdatabase_order|
|
||
|
@order&.include?(offdatabase_order)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
end
|