From 8b17b3360304d28c51421ce8b8b5fedc189eaca7 Mon Sep 17 00:00:00 2001
From: cryptogopher
Date: Sun, 7 Feb 2021 11:02:41 +0100
Subject: [PATCH] WIP: Targets configurable with Quantities
---
app/controllers/body_trackers_controller.rb | 24 ++++-----
app/controllers/targets_controller.rb | 13 ++++-
app/helpers/body_trackers_helper.rb | 5 +-
app/helpers/targets_helper.rb | 4 --
app/models/formula.rb | 24 +++++----
app/models/goal.rb | 4 +-
app/models/quantity.rb | 5 +-
app/models/target.rb | 24 ++++-----
app/views/goals/_show_form.html.erb | 23 ++++----
app/views/quantities/_form.html.erb | 3 +-
app/views/targets/_form.html.erb | 60 ++++++++++-----------
app/views/targets/_new_form.html.erb | 6 +--
app/views/targets/subthresholds.js.erb | 5 ++
app/views/thresholds/_form.html.erb | 15 ++++++
config/locales/en.yml | 3 +-
config/routes.rb | 21 +++++---
db/migrate/001_create_schema.rb | 4 +-
db/seeds.rb | 19 +++++++
init.rb | 6 ++-
test/fixtures/formulas.yml | 14 +++++
test/fixtures/quantities.yml | 40 ++++++++++++--
test/fixtures/quantity_values.yml | 2 +-
test/fixtures/targets.yml | 2 +-
23 files changed, 218 insertions(+), 108 deletions(-)
create mode 100644 app/views/targets/subthresholds.js.erb
create mode 100644 app/views/thresholds/_form.html.erb
diff --git a/app/controllers/body_trackers_controller.rb b/app/controllers/body_trackers_controller.rb
index 3ea751b..c35fa74 100644
--- a/app/controllers/body_trackers_controller.rb
+++ b/app/controllers/body_trackers_controller.rb
@@ -28,19 +28,19 @@ class BodyTrackersController < ApplicationController
quantities_count = available_quantities.length
defaults = Quantity.defaults
Quantity.each_with_path(defaults) do |q, path|
- unless available_quantities.has_key?(path)
- attrs = q.attributes.except('id', 'project_id', 'parent_id', 'lft', 'rgt',
- 'created_at', 'updated_at')
- if q.parent
- attrs['parent'] = available_quantities[path.rpartition('::').first]
- end
- if q.formula
- attrs['formula_attributes'] = q.formula.attributes
- .except('id', 'quantity_id', 'unit_id', 'created_at', 'updated_at')
- attrs['formula_attributes']['unit_id'] = available_units[q.formula.unit.shortname]
- end
- available_quantities[path] = @project.quantities.build(attrs)
+ next if available_quantities.has_key?(path)
+
+ attrs = q.attributes.except('id', 'project_id', 'parent_id', 'lft', 'rgt', 'depth',
+ 'created_at', 'updated_at')
+ if q.parent
+ attrs['parent'] = available_quantities[path.rpartition('::').first]
end
+ if q.formula
+ attrs['formula_attributes'] = q.formula.attributes
+ .except('id', 'quantity_id', 'unit_id', 'created_at', 'updated_at')
+ attrs['formula_attributes']['unit_id'] = available_units[q.formula.unit&.shortname]
+ end
+ available_quantities[path] = @project.quantities.build(attrs)
end
Quantity.transaction do
failed_objects += available_quantities.values.reject { |o| o.persisted? || o.save }
diff --git a/app/controllers/targets_controller.rb b/app/controllers/targets_controller.rb
index 3553f77..4aa16c4 100644
--- a/app/controllers/targets_controller.rb
+++ b/app/controllers/targets_controller.rb
@@ -6,6 +6,7 @@ class TargetsController < ApplicationController
include Concerns::Finders
before_action :find_binding_goal_by_project_id, only: [:index, :new, :edit]
+ before_action :find_project, only: [:subthresholds]
before_action :find_project_by_project_id, only: [:create]
before_action :find_quantity_by_quantity_id, only: [:toggle_exposure]
#, if: ->{ params[:project_id].present? }
@@ -21,8 +22,9 @@ class TargetsController < ApplicationController
def new
target = @goal.targets.new
- target.arity.times { target.thresholds.new }
+ target.thresholds.new(quantity: Quantity.target.roots.last)
@targets = [target]
+ @effective_from = target.effective_from
end
def create
@@ -75,6 +77,15 @@ class TargetsController < ApplicationController
prepare_targets
end
+ def subthresholds
+ quantity_id = params[:goal][:targets_attributes]
+ .last[:thresholds_attributes][:quantity_id]
+ return if quantity_id.blank?
+
+ quantity = @project.quantities.find(quantity_id)
+ @threshold = Threshold.new(quantity: quantity)
+ end
+
private
def goal_params
diff --git a/app/helpers/body_trackers_helper.rb b/app/helpers/body_trackers_helper.rb
index ad5bba8..edb2b7b 100644
--- a/app/helpers/body_trackers_helper.rb
+++ b/app/helpers/body_trackers_helper.rb
@@ -36,17 +36,18 @@ module BodyTrackersHelper
options_for_select(options, disabled: 0)
end
- def quantity_options(domain = :all)
+ def quantity_options(domain = :except_targets)
Quantity.each_with_ancestors(@project.quantities.send(domain)).map do |ancestors|
quantity = ancestors.last
[
- raw("#{' ' * (ancestors.length-2)}#{quantity.name}"),
+ raw(' '*(ancestors.length-2) + quantity.name),
quantity.id,
{'data-path' => ancestors[1..-2].reduce('::') { |m, q| "#{m}#{q.try(:name)}::" }}
]
end
end
+ # TODO: replace with collection_select and remove
def unit_options
@project.units.map do |u|
[u.shortname, u.id]
diff --git a/app/helpers/targets_helper.rb b/app/helpers/targets_helper.rb
index 41669a1..93f46ee 100644
--- a/app/helpers/targets_helper.rb
+++ b/app/helpers/targets_helper.rb
@@ -1,8 +1,4 @@
module TargetsHelper
- def condition_options
- Target::CONDITIONS
- end
-
def action_links(d)
link_to(l(:button_reapply), reapply_project_targets_path(@project, d, @view_params),
{remote: true, class: "icon icon-reload"}) +
diff --git a/app/models/formula.rb b/app/models/formula.rb
index edce86b..67f4152 100644
--- a/app/models/formula.rb
+++ b/app/models/formula.rb
@@ -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
diff --git a/app/models/goal.rb b/app/models/goal.rb
index f888f3b..5c27563 100644
--- a/app/models/goal.rb
+++ b/app/models/goal.rb
@@ -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},
diff --git a/app/models/quantity.rb b/app/models/quantity.rb
index fa306d7..cd25e3a 100644
--- a/app/models/quantity.rb
+++ b/app/models/quantity.rb
@@ -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,
diff --git a/app/models/target.rb b/app/models/target.rb
index f13c1b1..0091539 100644
--- a/app/models/target.rb
+++ b/app/models/target.rb
@@ -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
diff --git a/app/views/goals/_show_form.html.erb b/app/views/goals/_show_form.html.erb
index da14743..f353963 100644
--- a/app/views/goals/_show_form.html.erb
+++ b/app/views/goals/_show_form.html.erb
@@ -1,21 +1,20 @@
-
-<%= select_tag :goal_id,
- options_from_collection_for_select(@project.goals, :id, :name, @goal.id),
- required: true, autocomplete: 'off',
- onchange: "var goal_id = $('#target_goal_attributes_id').val();
- $.ajax({
- url: '#{goal_path(id: :goal_id)}'.replace('goal_id', goal_id),
+<%= goal_f.collection_select :id, @project.goals, :id, :name,
+ {include_blank: false, required: true, label: :field_goal},
+ autocomplete: 'off',
+ onchange: "$.ajax({
+ url: '#{goal_path(id: :goal_id)}'
+ .replace('goal_id', $('#target_goal_attributes_id').val()),
dataType: 'script'
});
return false;" %>
<%= link_to l(:button_add), '#',
- onclick: "var goal_id = $('#target_goal_attributes_id').val();
- $.ajax({
- url: '#{edit_goal_path(id: :goal_id)}'.replace('goal_id', goal_id),
+ onclick: "$.ajax({
+ url: '#{edit_goal_path(id: :goal_id)}'
+ .replace('goal_id', $('#target_goal_attributes_id').val()),
dataType: 'script'
});
return false;",
class: 'icon icon-add' %>
-<% if @goal.description? %>
- <%= @goal.description %>
+<% if goal_f.object.description? %>
+ <%= goal_f.object.description %>
<% end %>
diff --git a/app/views/quantities/_form.html.erb b/app/views/quantities/_form.html.erb
index eb48e5e..6905bf4 100644
--- a/app/views/quantities/_form.html.erb
+++ b/app/views/quantities/_form.html.erb
@@ -5,8 +5,7 @@
{autocomplete: 'off',
data: {remote: true,
url: parents_project_quantities_path(@project),
- params: "form=#{f.options[:html][:id]}"}}
- %>
+ params: "form=#{f.options[:html][:id]}"}} %>
<%= f.select :parent_id, parent_options(@quantity.domain),
{required: true, label: :field_parent_quantity, include_blank: t('.null_parent')} %>
<%= f.text_field :name, size: 25, required: true %>
diff --git a/app/views/targets/_form.html.erb b/app/views/targets/_form.html.erb
index ce92e5d..d6130cc 100644
--- a/app/views/targets/_form.html.erb
+++ b/app/views/targets/_form.html.erb
@@ -2,9 +2,10 @@
- <% if @goal.persisted? %>
-
<%= render partial: 'goals/show_form' %>
-
<%= f.date_field :effective_from, disabled: !@goal.is_binding? %>
+ <% if goal_f.object.persisted? %>
+
<%= render partial: 'goals/show_form', locals: {goal_f: goal_f} %>
+
<%= goal_f.date_field :effective_from, value: @effective_from,
+ disabled: !goal_f.object.is_binding? %>
<% else %>
<%= render partial: 'goals/form' %>
<% end %>
@@ -13,34 +14,30 @@
- <% @targets.each do |target| %>
-
- <%= f.fields_for 'targets', target, index: '' do |target_f| %>
- <%= target_f.hidden_field :id %>
- <%= target_f.hidden_field :_destroy %>
- <%= t ".choose_quantity" %>
- <% target.thresholds.each_with_index do |thr, index| %>
- <%= target_f.fields_for 'thresholds_attributes', thr, index: '' do |threshold_f| %>
- <%= threshold_f.hidden_field :id %>
- <% if index == 0 %>
- <%= threshold_f.select :quantity_id, quantity_options,
- {include_blank: true, required: true, label: :field_target},
- onchange: "showQuantityPath(event);" %>
- <%= target_f.select :condition, condition_options, required: true,
- label: '' %>
- <% end %>
- <%= threshold_f.number_field :value, {size: 8, step: :any, label: ''} %>
- <% if index == 0 %>
- <%= threshold_f.select :unit_id, unit_options, {label: ''} %>
- <% end %>
- <% end %>
- <% end %>
- <%= link_to t(".button_delete_target"), '#', class: 'icon icon-del',
- style: (@targets.many? ? "" : "display:none"),
- onclick: "deleteTarget(); return false;" %>
+
+ <%= goal_f.fields_for :targets, @targets, child_index: '' do |target_f| %>
+ <%= target_f.hidden_field :_destroy %>
+ <%= t ".choose_quantity" %>
+ <%= target_f.select :quantity_id, quantity_options,
+ {include_blank: true, required: true, label: :field_target},
+ onchange: "showQuantityPath(event);" %>
+
+ <%= target_f.fields_for :thresholds do |threshold_f| %>
+ <%= render partial: 'thresholds/form', locals: {threshold_f: threshold_f} %>
<% end %>
-
+ <% last_quantity = target_f.object.thresholds.last.quantity %>
+ <% unless last_quantity.leaf? %>
+ <%= target_f.fields_for 'thresholds_attributes', Threshold.new do |threshold_f| %>
+ <%= render partial: 'thresholds/form',
+ locals: {threshold_f: threshold_f, parent: last_quantity} %>
+ <% end %>
+ <% end %>
+
+ <%= link_to t(".button_delete_target"), '#', class: 'icon icon-del',
+ style: (@targets.many? ? "" : "display:none"),
+ onclick: "deleteTarget(); return false;" %>
<% end %>
+
<%= link_to t(".button_new_target"), '#', class: 'icon icon-add',
onclick: 'newTarget(); return false;' %>
@@ -50,10 +47,11 @@
<%= javascript_tag do %>
function showQuantityPath(event) {
- $(event.target).prevAll('em').text($('option:selected', event.target).attr('data-path'));
+ $(event.target).prevAll('em').text($('option:selected', event.target)
+ .attr('data-path'));
}
$(document).ajaxComplete(function() {
- $('select[id$=__quantity_id]').trigger(jQuery.Event('change'));
+ $('p.target select:first-child[id$=__quantity_id]').trigger(jQuery.Event('change'));
})
function newTarget() {
diff --git a/app/views/targets/_new_form.html.erb b/app/views/targets/_new_form.html.erb
index 1c1658f..4bab8be 100644
--- a/app/views/targets/_new_form.html.erb
+++ b/app/views/targets/_new_form.html.erb
@@ -1,11 +1,11 @@
<%= t ".heading_new_target" %>
-<%= labelled_form_for @targets,
+<%= labelled_form_for @goal,
url: project_targets_path(@project, @view_params),
remote: true,
- html: {id: 'new-target-form', name: 'new-target-form'} do |f| %>
+ html: {id: 'new-target-form', name: 'new-target-form'} do |goal_f| %>
- <%= render partial: 'targets/form', locals: {f: f} %>
+ <%= render partial: 'targets/form', locals: {goal_f: goal_f} %>
diff --git a/app/views/targets/subthresholds.js.erb b/app/views/targets/subthresholds.js.erb
new file mode 100644
index 0000000..2700061
--- /dev/null
+++ b/app/views/targets/subthresholds.js.erb
@@ -0,0 +1,5 @@
+<% if @threshold %>
+ $('#targets').html('<%= j render partial: 'targets/index' %>');
+<% else %>
+ $(event.target).empty();
+<% end %>
diff --git a/app/views/thresholds/_form.html.erb b/app/views/thresholds/_form.html.erb
new file mode 100644
index 0000000..976889b
--- /dev/null
+++ b/app/views/thresholds/_form.html.erb
@@ -0,0 +1,15 @@
+<% threshold_q = threshold_f.object.quantity %>
+<% parent_id = defined?(parent) ? parent.id : threshold_q.parent_id %>
+<%= threshold_f.collection_select :quantity_id,
+ @project.quantities.target.children_of(parent_id),
+ :id, :name,
+ {prompt: parent_id.nil? ? false : '.', required: true, no_label: true},
+ {autocomplete: 'off', data: {remote: true,
+ url: subthresholds_quantity_path(@project)}} %>
+
+<% unless threshold_q.nil? %>
+ <%= threshold_f.hidden_field :_destroy %>
+ <%= threshold_f.number_field :value, {size: 8, step: :any, no_label: true} %>
+ <%= threshold_f.collection_select :unit_id, @project.units, :id, :shortname,
+ {no_label: true} %>
+<% end %>
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 042bd51..0560155 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -176,9 +176,10 @@ en:
link_new_quantity: 'New quantity'
form:
domains:
+ diet: 'diet'
measurement: 'measurement'
exercise: 'exercise'
- diet: 'diet'
+ target: 'target'
null_parent: '- none -'
formula_placeholder: 'provide if value of quantity has to be computed in terms of
other quantities'
diff --git a/config/routes.rb b/config/routes.rb
index 2676e03..2277b03 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -9,18 +9,20 @@ resources :projects, shallow: true do
end
resources :goals, only: [:show, :edit] do
member do
- post 'toggle_exposure', to: 'targets#toggle_exposure'
+ post 'toggle_exposure', controller: :targets
end
end
resources :targets, except: [:show, :edit, :update] do
collection do
- get 'edit/:date', to: 'targets#edit', as: :edit
+ get 'edit/:date', action: :edit, as: :edit
patch :update
- post 'reapply/:date', to: 'targets#reapply', as: :reapply
+ post 'reapply/:date', action: :reapply, as: :reapply
end
end
resources :ingredients, only: [] do
- post 'adjust/:adjustment', to: 'meals#adjust', as: :adjust, on: :member
+ member do
+ post 'adjust/:adjustment', controller: :meals, action: :adjust, as: :adjust
+ end
end
resources :meals, except: [:show] do
member do
@@ -34,8 +36,8 @@ resources :projects, shallow: true do
end
resources :measurement_routines, only: [:show, :edit] do
member do
- get 'readouts', to: 'measurements#readouts'
- post 'toggle_exposure', to: 'measurements#toggle_exposure'
+ get 'readouts', controller: :measurements
+ post 'toggle_exposure', controller: :measurements
end
end
resources :measurements, except: [:show] do
@@ -47,7 +49,9 @@ resources :projects, shallow: true do
end
end
resources :foods, except: [:show] do
- post 'toggle', on: :member
+ member do
+ post 'toggle'
+ end
collection do
get 'nutrients'
post 'toggle_exposure'
@@ -60,8 +64,9 @@ resources :projects, shallow: true do
resources :quantities, except: [:show] do
member do
get 'new_child'
+ get 'subthresholds', controller: :targets
post 'create_child'
- post 'move/:direction', to: 'quantities#move', as: :move
+ post 'move/:direction', action: :move, as: :move
end
collection do
get 'parents'
diff --git a/db/migrate/001_create_schema.rb b/db/migrate/001_create_schema.rb
index fd786e5..614ecd2 100644
--- a/db/migrate/001_create_schema.rb
+++ b/db/migrate/001_create_schema.rb
@@ -10,6 +10,8 @@ class CreateSchema <
t.references :parent
t.integer :lft, null: false, index: true
t.integer :rgt, null: false, index: true
+ # TODO: remove depth (seems to be replaceable by lft)
+ t.integer :depth, null: false, default: 0
t.timestamps null: false
end
@@ -105,8 +107,8 @@ class CreateSchema <
create_table :targets do |t|
t.references :goal
+ t.references :quantity
t.references :item, polymorphic: true
- t.string :condition
t.string :scope
t.date :effective_from
t.timestamps null: false
diff --git a/db/seeds.rb b/db/seeds.rb
index 81a67d5..81e6006 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -168,6 +168,18 @@ b_ac = Quantity.create name: "RM", domain: :measurement, p
b_ad = Quantity.create name: "VF", domain: :measurement, parent: b_a,
description: "Visceral fat"
+# -> Target conditions
+t_a = Quantity.create name: "below", domain: :target, parent: nil,
+ description: "Upper bound"
+t_b = Quantity.create name: "above", domain: :target, parent: nil,
+ description: "Lower bound"
+t_ba = Quantity.create name: "and below", domain: :target, parent: t_b,
+ description: "Range"
+t_c = Quantity.create name: "equal", domain: :target, parent: nil,
+ description: "Exact value"
+t_ca = Quantity.create name: "with accuracy of", domain: :target, parent: t_c,
+ description: "Point range"
+
# Formulas go at the and to make sure dependencies exist
e_aa.create_formula zero_nil: true, unit: u_b,
code: "4*Proteins + 9*Fats + 4*Carbs + 2*Fibre"
@@ -189,6 +201,13 @@ e_aea.create_formula zero_nil: true, unit: u_c,
b_aaa.create_formula zero_nil: true, unit: u_ac,
code: "'% fat' * Weight"
+t_a.create_formula zero_nil: false, code: "value <= below"
+t_b.create_formula zero_nil: false, code: "value >= above"
+t_ba.create_formula zero_nil: false, code: "(value >= above) && (value <= 'and below')"
+t_c.create_formula zero_nil: false, code: "value == equal"
+t_ca.create_formula zero_nil: false, code: "(value >= (equal - 'with accuracy of')) && " \
+ "(value <= (equal + 'with accuracy of'))"
+
# Sources
s_a = Source.create name: "nutrition label",
description: "nutrition facts taken from package nutrition label"
diff --git a/init.rb b/init.rb
index e5c3870..3cb4210 100644
--- a/init.rb
+++ b/init.rb
@@ -28,7 +28,8 @@ Redmine::Plugin.register :body_tracking do
permission :manage_body_trackers, {
body_trackers: [:defaults],
goals: [:edit],
- targets: [:new, :create, :edit, :update, :destroy, :reapply, :toggle_exposure],
+ targets: [:new, :create, :edit, :update, :destroy, :reapply, :toggle_exposure,
+ :subthresholds],
meals: [:new, :create, :edit, :update, :destroy, :edit_notes, :update_notes,
:toggle_eaten, :toggle_exposure, :adjust],
measurement_routines: [:edit],
@@ -36,7 +37,8 @@ Redmine::Plugin.register :body_tracking do
foods: [:new, :create, :edit, :update, :destroy, :toggle, :toggle_exposure,
:import],
sources: [:create, :destroy],
- quantities: [:new, :create, :edit, :update, :destroy, :move, :new_child, :create_child],
+ quantities: [:new, :create, :edit, :update, :destroy, :move, :new_child,
+ :create_child],
units: [:create, :destroy],
}, require: :loggedin
end
diff --git a/test/fixtures/formulas.yml b/test/fixtures/formulas.yml
index e69de29..29be63e 100644
--- a/test/fixtures/formulas.yml
+++ b/test/fixtures/formulas.yml
@@ -0,0 +1,14 @@
+formulas_01:
+ quantity: quantities_target_above
+ zero_nil: false
+ code: value >= above
+
+formulas_02:
+ quantity: quantities_target_range
+ zero_nil: false
+ code: (value >= above) && (value <= 'and below')
+
+formulas_03:
+ quantity: quantities_target_equal
+ zero_nil: false
+ code: value == equal
diff --git a/test/fixtures/quantities.yml b/test/fixtures/quantities.yml
index 39c499c..e40cb62 100644
--- a/test/fixtures/quantities.yml
+++ b/test/fixtures/quantities.yml
@@ -1,17 +1,49 @@
-quantities_energy:
+DEFAULTS: &DEFAULTS
project_id: 1
- domain: diet
parent: null
+
+quantities_energy:
+ <<: *DEFAULTS
+ domain: diet
lft: 1
rgt: 2
+ depth: 0
name: Energy
description: Total energy
quantities_proteins:
- project_id: 1
+ <<: *DEFAULTS
domain: diet
- parent: null
lft: 3
rgt: 4
+ depth: 0
name: Proteins
description: Total amount of proteins
+
+quantities_target_above:
+ <<: *DEFAULTS
+ domain: target
+ lft: 5
+ rgt: 8
+ depth: 0
+ name: above
+ description: Lower bound
+
+quantities_target_range:
+ <<: *DEFAULTS
+ parent: quantities_target_above
+ domain: target
+ lft: 6
+ rgt: 7
+ depth: 1
+ name: and below
+ description: Range
+
+quantities_target_equal:
+ <<: *DEFAULTS
+ domain: target
+ lft: 9
+ rgt: 10
+ depth: 0
+ name: equal
+ description: Exact value
diff --git a/test/fixtures/quantity_values.yml b/test/fixtures/quantity_values.yml
index cc0c2a2..8afde1c 100644
--- a/test/fixtures/quantity_values.yml
+++ b/test/fixtures/quantity_values.yml
@@ -2,6 +2,6 @@ quantity_values_001:
type: Threshold
registry_type: Target
registry: targets_01
- quantity: quantities_energy
+ quantity: quantities_target_equal
value: 2500
unit: units_kcal
diff --git a/test/fixtures/targets.yml b/test/fixtures/targets.yml
index f52a938..b79a033 100644
--- a/test/fixtures/targets.yml
+++ b/test/fixtures/targets.yml
@@ -1,4 +1,4 @@
targets_01:
goal: goals_binding
- condition: '=='
+ quantity: quantities_energy
effective_from: <%= 1.week.ago.to_s(:db) %>