Skip to content

Commit

Permalink
Refactor defining groups
Browse files Browse the repository at this point in the history
  • Loading branch information
samnang committed Jan 13, 2025
1 parent 1901e4e commit e9e29cb
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 34 deletions.
8 changes: 3 additions & 5 deletions app/controllers/api/v1/beneficiaries/stats_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@ def index
serializer_class: StatSerializer,
**serializer_options
) do |permitted_params|
joins_with = permitted_params[:groups].pluck(:relation).compact
scope = beneficiaries_scope
scope = scope.joins(*joins_with) if joins_with.any?

AggregateDataQuery.new(permitted_params).apply(scope)
AggregateDataQuery.new(permitted_params).apply(
beneficiaries_scope
)
end
end

Expand Down
21 changes: 16 additions & 5 deletions app/models/aggregate_data_query.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,41 @@ class AggregateDataQuery

class TooManyResultsError < StandardError; end

attr_reader :filters, :groups
attr_reader :filters, :group_by_fields

def initialize(options)
@filters = options.fetch(:filters, {})
@groups = options.fetch(:groups)
@group_by_fields = options.fetch(:group_by_fields)
end

def apply(scope)
query = scope.where(filters).group(group_by)
query = query_scope(scope).where(filters).group(group_by)
raise TooManyResultsError if total_count(query) > MAX_RESULTS

result = query.count
result.map.with_index do |(key, value), index|
StatResult.new(groups:, key: Array(key), value:, sequence_number: index + 1)
StatResult.new(
groups: group_by_fields.map(&:name),
key: Array(key),
value:,
sequence_number: index + 1
)
end
end

private

def query_scope(scope)
joins_with = group_by_fields.pluck(:relation).compact_blank
scope = scope.joins(*joins_with) if joins_with.any?
scope
end

def total_count(query)
ApplicationRecord.from(query.select("1")).count
end

def group_by
groups.map(&:column)
group_by_fields.map(&:column)
end
end
53 changes: 30 additions & 23 deletions app/request_schemas/v1/beneficiary_stats_request_schema.rb
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
module V1
class BeneficiaryStatsRequestSchema < ApplicationRequestSchema
Group = Struct.new(:name, :column, :relation, keyword_init: true)
Field = Struct.new(:name, :column, :relation, keyword_init: true)
FIELDS = {
"gender" => Field.new(name: "gender", column: "gender"),
"language_code" => Field.new(name: "language_code", column: "language_code"),
"iso_country_code" => Field.new(name: "iso_country_code", column: "iso_country_code"),
"address.iso_region_code" => Field.new(name: "address.iso_region_code", column: "beneficiary_addresses.iso_region_code", relation: :addresses),
"address.administrative_division_level_2_code" => Field.new(name: "address.administrative_division_level_2_code", column: "beneficiary_addresses.administrative_division_level_2_code", relation: :addresses),
"address.administrative_division_level_2_name" => Field.new(name: "address.administrative_division_level_2_name", column: "beneficiary_addresses.administrative_division_level_2_name", relation: :addresses),
"address.administrative_division_level_3_code" => Field.new(name: "address.administrative_division_level_3_code", column: "beneficiary_addresses.administrative_division_level_3_code", relation: :addresses),
"address.administrative_division_level_3_name" => Field.new(name: "address.administrative_division_level_3_name", column: "beneficiary_addresses.administrative_division_level_3_name", relation: :addresses),
"address.administrative_division_level_4_code" => Field.new(name: "address.administrative_division_level_4_code", column: "beneficiary_addresses.administrative_division_level_4_code", relation: :addresses),
"address.administrative_division_level_4_name" => Field.new(name: "address.administrative_division_level_4_name", column: "beneficiary_addresses.administrative_division_level_4_name", relation: :addresses)
}.freeze

GROUPS = [
Group.new(name: "gender", column: "gender"),
Group.new(name: "language_code", column: "language_code"),
Group.new(name: "iso_country_code", column: "iso_country_code"),
Group.new(name: "address.iso_region_code", column: "beneficiary_addresses.iso_region_code", relation: :addresses),
Group.new(name: "address.administrative_division_level_2_code", column: "beneficiary_addresses.administrative_division_level_2_code", relation: :addresses),
Group.new(name: "address.administrative_division_level_2_name", column: "beneficiary_addresses.administrative_division_level_2_name", relation: :addresses),
Group.new(name: "address.administrative_division_level_3_code", column: "beneficiary_addresses.administrative_division_level_3_code", relation: :addresses),
Group.new(name: "address.administrative_division_level_3_name", column: "beneficiary_addresses.administrative_division_level_3_name", relation: :addresses),
Group.new(name: "address.administrative_division_level_4_code", column: "beneficiary_addresses.administrative_division_level_4_code", relation: :addresses),
Group.new(name: "address.administrative_division_level_4_name", column: "beneficiary_addresses.administrative_division_level_4_name", relation: :addresses)
"gender",
"language_code",
"iso_country_code",
"address.iso_region_code",
"address.administrative_division_level_2_code",
"address.administrative_division_level_2_name",
"address.administrative_division_level_3_code",
"address.administrative_division_level_3_name",
"address.administrative_division_level_4_code",
"address.administrative_division_level_4_name"
].freeze

params do
Expand All @@ -21,35 +34,29 @@ class BeneficiaryStatsRequestSchema < ApplicationRequestSchema
required(:group_by).value(array[:string])
end

rule(:group_by) do |context:|
next key.failure("is invalid") unless value.all? { |group| group.in?(GROUPS.map(&:name)) }
rule(:group_by) do
next key.failure("is invalid") unless value.all? { |group| group.in?(GROUPS) }

address_groups = value.select { |group| group.start_with?("address.") }
next if address_groups.empty?
next key.failure("address.iso_region_code is required") unless value.include?("address.iso_region_code")

address_attributes = address_groups.each_with_object({}) do |group, result|
result[group.delete_prefix("address.")] = true
_prefix, column = group.split(".")
result[column] = true
end

validator = BeneficiaryAddressValidator.new(address_attributes)
next if validator.valid?

key.failure("address.#{validator.errors.first.key} is required")
end

def output
result = super

result[:groups] = Array(result[:group_by]).map do |group|
GROUPS.find { |g| g.name == group }
result[:group_by_fields] = result[:group_by].map do |group|
FIELDS.fetch(group)
end

# filter = params.fetch(:filter)
# conditions = filter.slice(:type, :locality)
# conditions[:iso_country_code] = filter.fetch(:country) if filter.key?(:country)
# conditions[:iso_region_code] = filter.fetch(:region) if filter.key?(:region)
#
result
end
end
Expand Down
2 changes: 1 addition & 1 deletion app/serailizers/stat_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ class StatSerializer < JSONAPISerializer
result = {}

object.groups.each_with_index do |group, index|
result[group.name] = object.key[index]
result[group] = object.key[index]
end

result[:value] = object.value
Expand Down

0 comments on commit e9e29cb

Please sign in to comment.