1
0

WIP: Targets configurable with Quantities

This commit is contained in:
cryptogopher
2021-02-07 11:02:41 +01:00
parent 316005bf1f
commit 8b17b33603
23 changed files with 218 additions and 108 deletions

View File

@@ -1,6 +1,7 @@
class Formula < ActiveRecord::Base
include BodyTracking::FormulaBuilder
# NOTE: check if model_deps used and merge with quantity_deps if not
attr_reader :parts, :quantity_deps, :model_deps
belongs_to :quantity, inverse_of: :formula, required: true
@@ -107,18 +108,23 @@ class Formula < ActiveRecord::Base
end
quantities = []
identifiers.reject! do |i|
if q_paths.has_key?(i)
q = q_paths[i]
q.nil? ? errors << [:ambiguous_dependency, {identifier: i}] : quantities << q
models = []
identifiers.each do |i|
case
when q_paths.has_key?(i)
if q_paths[i].nil?
errors << [:ambiguous_dependency, {identifier: i}]
else
quantities << q_paths[i]
end
when quantity.target? && (i.casecmp('Value') == 0)
when model = i.safe_constantize
models << model
else
errors << [:unknown_dependency, {identifier: i}]
end
end
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, models if errors.empty?
errors
end

View File

@@ -1,11 +1,13 @@
class Goal < ActiveRecord::Base
belongs_to :project, required: true
has_many :targets, -> { order "effective_from DESC" }, inverse_of: :goal,
has_many :targets, -> { order effective_from: :desc }, inverse_of: :goal,
dependent: :destroy, extend: BodyTracking::ItemsWithQuantities
has_many :target_exposures, as: :view, dependent: :destroy,
class_name: 'Exposure', extend: BodyTracking::TogglableExposures
has_many :quantities, -> { order "lft" }, through: :target_exposures
accepts_nested_attributes_for :targets, allow_destroy: true,
reject_if: proc { |attrs| attrs['quantity_id'].blank? }
validates :target_exposures, presence: true, unless: :is_binding?
validates :is_binding, uniqueness: {scope: :project_id}, if: :is_binding?
validates :name, presence: true, uniqueness: {scope: :project_id},

View File

@@ -2,18 +2,21 @@ class Quantity < ActiveRecord::Base
enum domain: {
diet: 0,
measurement: 1,
exercise: 2
exercise: 2,
target: 3
}
acts_as_nested_set dependent: :destroy, scope: :project
belongs_to :project, inverse_of: :quantities, required: false
has_many :nutrients, dependent: :restrict_with_error
has_many :readouts, dependent: :restrict_with_error
has_many :targets, dependent: :restrict_with_error
has_many :thresholds, dependent: :restrict_with_error
has_many :values, class_name: 'QuantityValue', dependent: :restrict_with_error
has_many :exposures, dependent: :destroy
scope :defaults, -> { where(project: nil) }
scope :except_targets, -> { where.not(domain: :target) }
has_one :formula, inverse_of: :quantity, dependent: :destroy, validate: true
accepts_nested_attributes_for :formula, allow_destroy: true,

View File

@@ -1,37 +1,37 @@
class Target < ActiveRecord::Base
CONDITIONS = ['<', '<=', '>', '>=', '==']
belongs_to :goal, inverse_of: :targets, required: true
belongs_to :quantity, inverse_of: :targets, required: true
belongs_to :item, polymorphic: true, inverse_of: :targets
has_many :thresholds, as: :registry, inverse_of: :target, dependent: :destroy,
validate: true
has_many :thresholds, -> { joins(:quantity).order(:lft) },
as: :registry, inverse_of: :target, dependent: :destroy, validate: true
validates :thresholds, presence: true
accepts_nested_attributes_for :thresholds, allow_destroy: true,
reject_if: proc { |attrs| attrs['quantity_id'].blank? && attrs['value'].blank? }
validate do
errors.add(:thresholds, :count_mismatch) unless thresholds.length == arity
errors.add(:thresholds, :quantity_mismatch) if thresholds.to_a.uniq(&:quantity).length != 1
quantities = thresholds.map(&:quantity)
ancestors = quantities.max_by(:lft).self_and_ancestors
errors.add(:thresholds, :count_mismatch) unless quantities.length == ancestors.length
errors.add(:thresholds, :quantity_mismatch) unless quantities == ancestors
end
validates :condition, inclusion: {in: CONDITIONS}
validates :scope, inclusion: {in: [:ingredient, :meal, :day],
if: -> { thresholds.first.quantity.domain == :diet }}
validates :scope, inclusion: {in: [:ingredient, :meal, :day], if: -> { quantity.diet? }}
validates :effective_from, presence: {if: :is_binding?}, absence: {unless: :is_binding?}
after_initialize do
if new_record?
self.condition ||= CONDITIONS.first
# Target should be only instantiated through Goal, so :is_binding? will be available
self.effective_from ||= Date.current if is_binding?
end
end
delegate :is_binding?, to: :goal
# NOTE: remove if not used in controller
def arity
BigDecimal.method(condition).arity
thresholds.size
end
def to_s
"#{condition} #{thresholds.first.value} [#{thresholds.first.unit.shortname}]"
thresholds.last.quantity.description %
thresholds.map { |t| [t.quantity.name, "#{t.value} [#{t.unit.shortname}]"] }.to_h
end
end