diff --git a/app/models/measurement.rb b/app/models/measurement.rb index 36b2790..2501968 100644 --- a/app/models/measurement.rb +++ b/app/models/measurement.rb @@ -45,92 +45,4 @@ class Measurement < ActiveRecord::Base def taken_at_time=(value) self.taken_at = Time.parse(value, self.taken_at) end - - def self.filter(filters, requested_q = Quantity.none) - measurements = all.where(filters[:scope]) - - if filters[:name].present? - measurements = measurements.where("name LIKE ?", "%#{filters[:name]}%") - end - - project = proxy_association.owner - formula_q = if filters[:readouts].present? - project.quantities.new(name: '__internal_q', - formula: filters[:readouts], - domain: :measurement) - end - apply_formula = formula_q.present? && formula_q.valid? - - result = - if !requested_q.empty? || apply_formula - computed = measurements.compute_nutrients(requested_q, apply_formula && formula_q) - requested_q.present? ? computed : [computed[0]] - else - [measurements] - end - result.push(formula_q) - end - - def self.compute_nutrients(requested_q, filter_q = nil) - ingredients = all - unchecked_q = requested_q.map { |q| [q, nil] } - unchecked_q << [filter_q, nil] if filter_q - - nutrients = Hash.new { |h,k| h[k] = {} } - Nutrient.where(ingredient: ingredients).includes(:quantity, :unit) - .order('quantities.lft') - .pluck('quantities.name', :ingredient_id, :amount, 'units.shortname') - .each { |q_name, i_id, a, u_id| nutrients[q_name][i_id] = [a, u_id] } - - extra_q = nutrients.keys - requested_q.pluck(:name) - - completed_q = {} - # FIXME: loop should finish unless there is circular dependency in formulas - # for now we don't guard against that - while !unchecked_q.empty? - q, deps = unchecked_q.shift - - # quantity not computable (no formula) or not requiring calculation/computed - if q.formula.blank? || (nutrients[q.name].length == ingredients.count) - completed_q[q.name] = nutrients.delete(q.name) { {} } - next - end - - # quantity with formula requires refresh of dependencies availability - if deps.nil? || !deps.empty? - deps ||= q.formula_quantities - deps.reject! { |q| completed_q.has_key?(q.name) } - deps.each { |q| unchecked_q << [q, nil] unless unchecked_q.index { |u| u[0] == q } } - end - - # quantity with formula has all dependencies satisfied, requires calculation - if deps.empty? - input_q = q.formula_quantities - inputs = ingredients.select { |i| nutrients[q.name][i.id].nil? }.map do |i| - [ - i, - input_q.map do |i_q| - nutrient_data = completed_q[i_q.name][i.id] || [nil, nil] - [i_q.name, nutrient_data[0]] - end.to_h - ] - end - q.calculate(inputs).each { |i, result| nutrients[q.name][i.id] = result } - unchecked_q.unshift([q, deps]) - next - end - - # quantity still has unsatisfied dependencies, move to the end of queue - unchecked_q << [q, deps] - end - - all_q = nutrients.merge(completed_q) - [ - filter_q ? ingredients.to_a.keep_if { |i| all_q[filter_q.name][i.id][0] } : ingredients, - ingredients.map { |i| requested_q.map { |q| [q.name, all_q[q.name][i.id]] } }, - ingredients.map do |i| - extra_q.map { |q_name| [q_name, all_q[q_name][i.id]] if all_q[q_name][i.id] } - end - ] - end end diff --git a/lib/body_tracking/items_with_quantities.rb b/lib/body_tracking/items_with_quantities.rb new file mode 100644 index 0000000..e82a186 --- /dev/null +++ b/lib/body_tracking/items_with_quantities.rb @@ -0,0 +1,104 @@ +module BodyTracking + module ItemsWithQuantities + def filter(filters, requested_q = Quantity.none) + items = all.where(filters[:scope]) + + if filters[:name].present? + items = items.where("name LIKE ?", "%#{filters[:name]}%") + end + + if filters[:visibility].present? + items = items.where(hidden: filters[:visibility] == "1" ? false : true) + end + + project = proxy_association.owner + domain = {Measurement => :measurement, Ingredient => :diet}[proxy_association.klass] + formula_q = if filters[:formula].present? + project.quantities.new(name: '__internal_q', + formula: filters[:formula], + domain: domain) + end + apply_formula = formula_q.present? && formula_q.valid? + + result = + if !requested_q.empty? || apply_formula + computed = items.compute_quantities(requested_q, apply_formula && formula_q) + requested_q.present? ? computed : [computed[0]] + else + [items] + end + result.push(formula_q) + end + + def compute_quantities(requested_q, filter_q = nil) + items = all + unchecked_q = requested_q.map { |q| [q, nil] } + unchecked_q << [filter_q, nil] if filter_q + + subitems = Hash.new { |h,k| h[k] = {} } + subitems_scope = case proxy_association.klass + when Measurement + item_primary_key = :measurement_id + Readout.where(measurement: items) + when Ingredient + item_primary_key = :ingredient_id + Nutrient.where(ingredient: items) + end + subitems_scope.includes(:quantity, :unit) + .order('quantities.lft') + .pluck(item_primary_key, 'quantities.name', :amount, 'units.shortname') + .each { |item_id, q_name, a, u_id| subitems[q_name][item_id] = [a, u_id] } + + extra_q = subitems.keys - requested_q.pluck(:name) + + completed_q = {} + # FIXME: loop should finish unless there is circular dependency in formulas + # for now we don't guard against that + while !unchecked_q.empty? + q, deps = unchecked_q.shift + + # quantity not computable (no formula) or not requiring calculation/computed + if q.formula.blank? || (subitems[q.name].length == items.count) + completed_q[q.name] = subitems.delete(q.name) { {} } + next + end + + # quantity with formula requires refresh of dependencies availability + if deps.nil? || !deps.empty? + deps ||= q.formula_quantities + deps.reject! { |q| completed_q.has_key?(q.name) } + deps.each { |q| unchecked_q << [q, nil] unless unchecked_q.index { |u| u[0] == q } } + end + + # quantity with formula has all dependencies satisfied, requires calculation + if deps.empty? + input_q = q.formula_quantities + inputs = items.select { |i| subitems[q.name][i.id].nil? }.map do |i| + [ + i, + input_q.map do |i_q| + subitem_data = completed_q[i_q.name][i.id] || [nil, nil] + [i_q.name, subitem_data[0]] + end.to_h + ] + end + q.calculate(inputs).each { |i, result| subitems[q.name][i.id] = result } + unchecked_q.unshift([q, deps]) + next + end + + # quantity still has unsatisfied dependencies, move to the end of queue + unchecked_q << [q, deps] + end + + all_q = subitems.merge(completed_q) + [ + filter_q ? items.to_a.keep_if { |i| all_q[filter_q.name][i.id][0] } : items, + items.map { |i| requested_q.map { |q| [q.name, all_q[q.name][i.id]] } }, + items.map do |i| + extra_q.map { |q_name| [q_name, all_q[q_name][i.id]] if all_q[q_name][i.id] } + end + ] + end + end +end diff --git a/lib/body_tracking/project_patch.rb b/lib/body_tracking/project_patch.rb index 2a0aae8..bac0666 100644 --- a/lib/body_tracking/project_patch.rb +++ b/lib/body_tracking/project_patch.rb @@ -1,7 +1,7 @@ module BodyTracking module ProjectPatch Project.class_eval do - has_many :measurements, -> { order "taken_at DESC" }, dependent: :destroy + has_many :measurements, -> { order "taken_at DESC" }, dependent: :destroy, extend: ItemsWithQuantities has_many :ingredients, -> { order "name" }, dependent: :destroy has_many :sources, dependent: :destroy