Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ def handle_request(args = {})
collection = datasource.get_collection(collection_name)
{
name: collection.name,
fields: collection.schema[:fields].select { |_, field| field.is_a?(ColumnSchema) }.map do |name, field|
fields: collection.schema[:fields].filter_map do |name, field|
next unless field.is_a?(ColumnSchema)

{
name: name,
type: field.column_type,
operators: field.filter_operators.map { |operator| operator }
operators: field.filter_operators.to_a
}
end
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,7 @@ def id
@options[:class_name].gsub('::', '__')
)
primary_keys = ForestAdminDatasourceToolkit::Utils::Schema.primary_keys(forest_collection)
id = []
primary_keys.each { |key| id << @object[key] }

id.join('|')
primary_keys.map { |key| @object[key] }.join('|')
end

def format_name(attribute_name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ def permission_system?
def find_action_from_endpoint(collection_name, path, http_method)
endpoint = path.partition('/forest/')[1..].join
schema_file = JSON.parse(File.read(Facades::Container.config_from_cache[:schema_path]))
actions = schema_file['collections']&.select { |collection| collection['name'] == collection_name }&.first&.dig('actions')
actions = schema_file['collections']&.find { |collection| collection['name'] == collection_name }&.dig('actions')

return nil if actions.nil? || actions.empty?

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,13 @@ def get_column_type(model, column)
end

def get_enum_values(model, column)
enum_values = []
if get_column_type(model, column) == 'Enum'
if sti_column?(model, column)
model.descendants.each { |sti_model| enum_values << sti_model.name }
else
model.defined_enums[column.name].each_key { |name| enum_values << name }
end
return [] unless get_column_type(model, column) == 'Enum'

if sti_column?(model, column)
model.descendants.map(&:name)
else
model.defined_enums[column.name].keys
end
enum_values
end

def sti_column?(model, column)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ def get_validations(column)
validations = []
# NOTICE: Do not consider validations if a before_validation Active Records
# Callback is detected.
return validations if @model._validation_callbacks.map(&:kind).include? :before
return validations if @model._validation_callbacks.any? { |callback| callback.kind == :before }

if @model._validators? && @model._validators[column.name.to_sym].size.positive?
@model._validators[column.name.to_sym].each do |validator|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,9 @@ def execute_native_query(connection_name, query, context_variables = {})
end

def add_data_source(datasource)
existing_names = collections.map { |c| c.respond_to?(:name) ? c.name : c.to_s }
datasource.collections.each do |c|
new_name = c.respond_to?(:name) ? c.name : c.to_s
raise ArgumentError, "Collection '#{new_name}' already exists" if existing_names.include?(new_name)
existing_names = collections.keys
datasource.collections.each_key do |name|
raise ArgumentError, "Collection '#{name}' already exists" if existing_names.include?(name)
end

existing_charts = schema[:charts]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,22 @@ def dig(*_keys)

MARKER_NAME = '__null_marker'.freeze
def self.with_null_marker(projection)
seen = Set.new(projection)
new_projection = Projection.new(projection)

projection.each do |path|
parts = path.split(':')

parts.slice(1, parts.size).each_with_index do |_item, index|
new_projection << "#{parts.slice(0, index + 1).join(":")}:#{MARKER_NAME}"
marker = "#{parts.slice(0, index + 1).join(":")}:#{MARKER_NAME}"
next if seen.include?(marker)

seen << marker
new_projection << marker
end
end

new_projection.uniq
new_projection
end

def self.flatten(records, projection)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,31 +240,37 @@ def re_project_in_place(caller, records, projection)

def re_project_relation_in_place(caller, records, name, projection)
field_schema = schema[:fields][name]

return if field_schema.type == 'PolymorphicManyToOne'

association = datasource.get_collection(field_schema.foreign_collection)

if !@relations[name]
association = datasource.get_collection(field_schema.foreign_collection)
association.re_project_in_place(caller, records.filter_map { |r| r[name] }, projection)
elsif field_schema.type == 'ManyToOne'
ids = records.filter_map { |record| record[field_schema.foreign_key] }.uniq
sub_filter = Filter.new(condition_tree: ConditionTreeLeaf.new(field_schema.foreign_key_target, 'In', ids))
sub_records = association.list(caller, sub_filter, projection.union([field_schema.foreign_key_target]))

records.each do |record|
record[name] = sub_records.find { |sr| sr[field_schema.foreign_key_target] == record[field_schema.foreign_key] }
end
assign_many_to_one_records(caller, records, name, field_schema, projection)
elsif ['OneToOne', 'OneToMany'].include?(field_schema.type)
ids = records.filter_map { |record| record[field_schema.origin_key_target] }.uniq
sub_filter = Filter.new(condition_tree: ConditionTreeLeaf.new(field_schema.origin_key, 'In', ids))
sub_records = association.list(caller, sub_filter, projection.union([field_schema.origin_key]))

records.each do |record|
record[name] = sub_records.find { |sr| sr[field_schema.origin_key] == record[field_schema.origin_key_target] }
end
assign_to_one_or_many_records(caller, records, name, field_schema, projection)
end
end

def assign_many_to_one_records(caller, records, name, field_schema, projection)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function with many parameters (count = 5): assign_many_to_one_records [qlty:function-parameters]

association = datasource.get_collection(field_schema.foreign_collection)
ids = records.each_with_object(Set.new) { |record, set| set << record[field_schema.foreign_key] }.delete(nil).to_a
sub_filter = Filter.new(condition_tree: ConditionTreeLeaf.new(field_schema.foreign_key_target, 'In', ids))
sub_records = association.list(caller, sub_filter, projection.union([field_schema.foreign_key_target]))
sub_records_by_key = sub_records.to_h { |sr| [sr[field_schema.foreign_key_target], sr] }

records.each { |record| record[name] = sub_records_by_key[record[field_schema.foreign_key]] }
end

def assign_to_one_or_many_records(caller, records, name, field_schema, projection)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function with many parameters (count = 5): assign_to_one_or_many_records [qlty:function-parameters]

association = datasource.get_collection(field_schema.foreign_collection)
ids = records.each_with_object(Set.new) { |record, set| set << record[field_schema.origin_key_target] }.delete(nil).to_a
sub_filter = Filter.new(condition_tree: ConditionTreeLeaf.new(field_schema.origin_key, 'In', ids))
sub_records = association.list(caller, sub_filter, projection.union([field_schema.origin_key]))
sub_records_by_key = sub_records.to_h { |sr| [sr[field_schema.origin_key], sr] }

records.each { |record| record[name] = sub_records_by_key[record[field_schema.origin_key_target]] }
end
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ def schema
datasource = instance_double(ForestAdminDatasourceToolkit::Datasource,
schema: { charts: [] },
render_chart: nil,
collections: [],
collections: {},
live_query_connections: {})

customizer = described_class.new
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ module Relation
def get_polymorphic_types(relation_name)
types = {}

ObjectSpace.each_object(Class).select { |klass| klass < Mongoid::Document }.each do |model|
if model.relations.any? { |_, relation| relation.options[:as] == relation_name.to_sym }
primary_key = model.fields.keys.find { |key| model.fields[key].options[:as] == :id } || :_id
types[model.name.gsub('::', '__')] = primary_key.to_s
ObjectSpace.each_object(Class).each do |klass|
next unless klass < Mongoid::Document

if klass.relations.any? { |_, relation| relation.options[:as] == relation_name.to_sym }
primary_key = klass.fields.keys.find { |key| klass.fields[key].options[:as] == :id } || :_id
types[klass.name.gsub('::', '__')] = primary_key.to_s
end
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,35 @@ module Validation
include ForestAdminDatasourceToolkit::Components::Query::ConditionTree
def get_validations(model, column)
return [] if column.is_a?(Hash)
return [] if before_validation_callback?(model)
return [] unless model._validators? && model._validators[column.name.to_sym].size.positive?

validations = []
# NOTICE: Do not consider validations if a before_validation Active Records
# Callback is detected.
parse_column_validators(model._validators[column.name.to_sym], column)
end

def before_validation_callback?(model)
default_callback_excluded = [:normalize_changed_in_place_attributes]
return validations if model._validation_callbacks
.reject { |callback| default_callback_excluded.include?(callback.filter) }
.map(&:kind).include?(:before)
model._validation_callbacks.any? do |callback|
!default_callback_excluded.include?(callback.filter) && callback.kind == :before
end
end

if model._validators? && model._validators[column.name.to_sym].size.positive?
model._validators[column.name.to_sym].each do |validator|
# NOTICE: Do not consider conditional validations
next if validator.options[:if] || validator.options[:unless] || validator.options[:on]
def parse_column_validators(validators, column)
validations = []
validators.each do |validator|
next if validator.options[:if] || validator.options[:unless] || validator.options[:on]

case validator.class.to_s
when Mongoid::Validatable::PresenceValidator.to_s
validations << { operator: Operators::PRESENT }
when ActiveModel::Validations::NumericalityValidator.to_s
validations = parse_numericality_validator(validator, validations)
when Mongoid::Validatable::LengthValidator.to_s
validations = parse_length_validator(validator, validations, column)
when Mongoid::Validatable::FormatValidator.to_s
validations = parse_format_validator(validator, validations)
end
case validator.class.to_s
when Mongoid::Validatable::PresenceValidator.to_s
validations << { operator: Operators::PRESENT }
when ActiveModel::Validations::NumericalityValidator.to_s
validations = parse_numericality_validator(validator, validations)
when Mongoid::Validatable::LengthValidator.to_s
validations = parse_length_validator(validator, validations, column)
when Mongoid::Validatable::FormatValidator.to_s
validations = parse_format_validator(validator, validations)
end
end

validations
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function with high complexity (count = 6): parse_column_validators [qlty:function-complexity]

end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@ def add_null_values_on_record(record, projection)
result[field_prefix] ||= nil
end

nested_prefixes = projection.select { |field| field.include?(':') }.map { |field| field.split(':').first }.uniq
nested_prefixes = projection.filter_map { |field| field.split(':').first if field.include?(':') }.uniq

nested_prefixes.each do |nested_prefix|
child_paths = projection.filter { |field| field.start_with?("#{nested_prefix}:") }
.map { |field| field[(nested_prefix.size + 1)..] }
prefix_with_colon = "#{nested_prefix}:"
child_paths = projection.filter_map do |field|
field[(nested_prefix.size + 1)..] if field.start_with?(prefix_with_colon)
end

next unless result[nested_prefix] && !result[nested_prefix].nil?

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ module Helpers
# @example
# unnest(['firstname', 'book.title', 'book.author'], 'book') == ['title', 'author']
def unnest(strings, prefix)
strings.select { |field| field.start_with?("#{prefix}.") }.map { |field| field[(prefix.size + 1)..] }
prefix_with_dot = "#{prefix}."
strings.filter_map { |field| field[(prefix.size + 1)..] if field.start_with?(prefix_with_dot) }
end

def escape(str)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def self.list_fields_used_in_filter_tree(condition_tree, fields)
if condition_tree.is_a? Nodes::ConditionTreeBranch
condition_tree.conditions.each { |condition| list_fields_used_in_filter_tree(condition, fields) }
elsif condition_tree&.field&.include?(':')
list_paths(condition_tree.field).each { |field| fields << field }
fields.merge(list_paths(condition_tree.field))
end
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,25 @@ def self.add_fields(name, projection, options)
return {} if options[:include] && !options[:include].include?(name)
return {} if options[:exclude]&.include?(name)

projection.filter { |field| field.include?('@@@') }
.map { |field_name| "#{name}.#{field_name.tr(":", ".")}" }
.each_with_object({}) do |curr, acc|
acc[curr] = "$#{curr.gsub("@@@", ".")}"
end
projection.each_with_object({}) do |field_name, acc|
next unless field_name.include?('@@@')

curr = "#{name}.#{field_name.tr(":", ".")}"
acc[curr] = "$#{curr.gsub("@@@", ".")}"
end
end

def self.lookup_relation(current_path, schema_stack, name, projection, options)
models = ObjectSpace
.each_object(Class)
.select { |klass| klass < Mongoid::Document && klass.name && !klass.name.start_with?('Mongoid::') }
.to_h { |klass| [klass.name, klass] }
models = ObjectSpace.each_object(Class).with_object({}) do |klass, hash|
next unless klass < Mongoid::Document && klass.name && !klass.name.start_with?('Mongoid::')

hash[klass.name] = klass
end

as = current_path ? "#{current_path}.#{name}" : name

last_schema = schema_stack[schema_stack.length - 1]
previous_schema = schema_stack.slice(0..(schema_stack.length - 1))
last_schema = schema_stack.last
previous_schema = schema_stack

return {} if options[:include] && !options[:include].include?(as)
return {} if options[:exclude]&.include?(as)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
models_to_load = Dir.glob(File.join('spec', 'dummy', 'app', 'models', '**', '*.rb'))
.collect { |file_path| File.basename(file_path, '.rb').camelize.constantize }

allow(ObjectSpace).to receive(:each_object).and_return(models_to_load)
allow(ObjectSpace).to receive(:each_object).and_return(models_to_load.each)
end

config.before(:suite) do
Expand Down