WIP: Targets configurable with Quantities
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user