1
0
This repository has been archived on 2023-12-07. You can view files and clone it, but cannot push or open issues or pull requests.
body_tracking/app/models/formula.rb
cryptogopher a416e1ce9b Fixed importing Foods with QuantityValue
Fixed double flash when not followed by request
Added Food#destroy error reporting
Simplified prepare_meals with no ingredients
Renamed scope on item with subitems: subitems -> with_subitems
2020-05-20 23:33:34 +02:00

90 lines
2.4 KiB
Ruby

class Formula < ActiveRecord::Base
include BodyTracking::FormulaBuilder
attr_reader :parts, :quantity_deps, :model_deps
belongs_to :quantity, inverse_of: :formula, required: true
belongs_to :unit
validates :code, presence: true
validate do
parse.each { |message, params| errors.add(:code, message, params) }
end
after_initialize do
if new_record?
self.zero_nil = true if self.zero_nil.nil?
end
end
def calculate(inputs)
raise(InvalidInputs, 'No inputs') if inputs.empty?
deps = inputs.map { |q, v| [q.name, QuantityInput.new(q, v.transpose.first)] }.to_h
length = deps.values.first.length
raise(InvalidFormula, 'Invalid formula') unless self.valid?
raise(InvalidInputs, 'Inputs lengths differ') unless
deps.values.all? { |v| v.length == length }
args = []
@parts.each do |p|
code = p[:type] == :indexed ?
"length.times.map { |_index| #{p[:content]} }" : p[:content]
args << get_binding(deps, args, length).eval(code)
end
args.last.map { |v| [v, self.unit] }
end
def dependencies
@quantity_deps + @model_deps
end
private
class QuantityInput < Array
def initialize(q, *args)
super(*args)
@quantity = q
end
def lastBefore(timepoints)
self.map{ BigDecimal(2000) }
last_timepoint = timepoints.max
@quantity.quantity_values.includes(:registry)
end
end
def parse
d_methods = ['abs', 'nil?']
q_methods = Hash.new(['all', 'lastBefore'])
q_methods['Meal'] = Meal.attribute_names
parser = FormulaBuilder.new(self.code, d_methods: d_methods, q_methods: q_methods)
identifiers, parts = parser.parse
errors = parser.errors
project = self.quantity.project
quantities =
if project
# This is required to properly validate with in-memory records, e.g.
# during import of defaults
project.quantities.select { |q| identifiers.include?(q.name) }
else
Quantity.where(project: nil, name: identifiers.to_a)
end
identifiers -= quantities.map(&:name)
models = identifiers.map(&:safe_constantize).compact || []
(identifiers - models.map(&:class_name)).each do |i|
errors << [:unknown_dependency, {identifier: i}]
end
@parts, @quantity_deps, @model_deps = parts, quantities.to_a, models if errors.empty?
errors
end
def get_binding(quantities, parts, length)
binding
end
end