WIP: Targets configurable with Quantities
This commit is contained in:
parent
316005bf1f
commit
8b17b33603
@ -28,8 +28,9 @@ class BodyTrackersController < ApplicationController
|
|||||||
quantities_count = available_quantities.length
|
quantities_count = available_quantities.length
|
||||||
defaults = Quantity.defaults
|
defaults = Quantity.defaults
|
||||||
Quantity.each_with_path(defaults) do |q, path|
|
Quantity.each_with_path(defaults) do |q, path|
|
||||||
unless available_quantities.has_key?(path)
|
next if available_quantities.has_key?(path)
|
||||||
attrs = q.attributes.except('id', 'project_id', 'parent_id', 'lft', 'rgt',
|
|
||||||
|
attrs = q.attributes.except('id', 'project_id', 'parent_id', 'lft', 'rgt', 'depth',
|
||||||
'created_at', 'updated_at')
|
'created_at', 'updated_at')
|
||||||
if q.parent
|
if q.parent
|
||||||
attrs['parent'] = available_quantities[path.rpartition('::').first]
|
attrs['parent'] = available_quantities[path.rpartition('::').first]
|
||||||
@ -37,11 +38,10 @@ class BodyTrackersController < ApplicationController
|
|||||||
if q.formula
|
if q.formula
|
||||||
attrs['formula_attributes'] = q.formula.attributes
|
attrs['formula_attributes'] = q.formula.attributes
|
||||||
.except('id', 'quantity_id', 'unit_id', 'created_at', 'updated_at')
|
.except('id', 'quantity_id', 'unit_id', 'created_at', 'updated_at')
|
||||||
attrs['formula_attributes']['unit_id'] = available_units[q.formula.unit.shortname]
|
attrs['formula_attributes']['unit_id'] = available_units[q.formula.unit&.shortname]
|
||||||
end
|
end
|
||||||
available_quantities[path] = @project.quantities.build(attrs)
|
available_quantities[path] = @project.quantities.build(attrs)
|
||||||
end
|
end
|
||||||
end
|
|
||||||
Quantity.transaction do
|
Quantity.transaction do
|
||||||
failed_objects += available_quantities.values.reject { |o| o.persisted? || o.save }
|
failed_objects += available_quantities.values.reject { |o| o.persisted? || o.save }
|
||||||
end
|
end
|
||||||
|
@ -6,6 +6,7 @@ class TargetsController < ApplicationController
|
|||||||
include Concerns::Finders
|
include Concerns::Finders
|
||||||
|
|
||||||
before_action :find_binding_goal_by_project_id, only: [:index, :new, :edit]
|
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_project_by_project_id, only: [:create]
|
||||||
before_action :find_quantity_by_quantity_id, only: [:toggle_exposure]
|
before_action :find_quantity_by_quantity_id, only: [:toggle_exposure]
|
||||||
#, if: ->{ params[:project_id].present? }
|
#, if: ->{ params[:project_id].present? }
|
||||||
@ -21,8 +22,9 @@ class TargetsController < ApplicationController
|
|||||||
|
|
||||||
def new
|
def new
|
||||||
target = @goal.targets.new
|
target = @goal.targets.new
|
||||||
target.arity.times { target.thresholds.new }
|
target.thresholds.new(quantity: Quantity.target.roots.last)
|
||||||
@targets = [target]
|
@targets = [target]
|
||||||
|
@effective_from = target.effective_from
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@ -75,6 +77,15 @@ class TargetsController < ApplicationController
|
|||||||
prepare_targets
|
prepare_targets
|
||||||
end
|
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
|
private
|
||||||
|
|
||||||
def goal_params
|
def goal_params
|
||||||
|
@ -36,17 +36,18 @@ module BodyTrackersHelper
|
|||||||
options_for_select(options, disabled: 0)
|
options_for_select(options, disabled: 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
def quantity_options(domain = :all)
|
def quantity_options(domain = :except_targets)
|
||||||
Quantity.each_with_ancestors(@project.quantities.send(domain)).map do |ancestors|
|
Quantity.each_with_ancestors(@project.quantities.send(domain)).map do |ancestors|
|
||||||
quantity = ancestors.last
|
quantity = ancestors.last
|
||||||
[
|
[
|
||||||
raw("#{' ' * (ancestors.length-2)}#{quantity.name}"),
|
raw(' '*(ancestors.length-2) + quantity.name),
|
||||||
quantity.id,
|
quantity.id,
|
||||||
{'data-path' => ancestors[1..-2].reduce('::') { |m, q| "#{m}#{q.try(:name)}::" }}
|
{'data-path' => ancestors[1..-2].reduce('::') { |m, q| "#{m}#{q.try(:name)}::" }}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# TODO: replace with collection_select and remove
|
||||||
def unit_options
|
def unit_options
|
||||||
@project.units.map do |u|
|
@project.units.map do |u|
|
||||||
[u.shortname, u.id]
|
[u.shortname, u.id]
|
||||||
|
@ -1,8 +1,4 @@
|
|||||||
module TargetsHelper
|
module TargetsHelper
|
||||||
def condition_options
|
|
||||||
Target::CONDITIONS
|
|
||||||
end
|
|
||||||
|
|
||||||
def action_links(d)
|
def action_links(d)
|
||||||
link_to(l(:button_reapply), reapply_project_targets_path(@project, d, @view_params),
|
link_to(l(:button_reapply), reapply_project_targets_path(@project, d, @view_params),
|
||||||
{remote: true, class: "icon icon-reload"}) +
|
{remote: true, class: "icon icon-reload"}) +
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
class Formula < ActiveRecord::Base
|
class Formula < ActiveRecord::Base
|
||||||
include BodyTracking::FormulaBuilder
|
include BodyTracking::FormulaBuilder
|
||||||
|
|
||||||
|
# NOTE: check if model_deps used and merge with quantity_deps if not
|
||||||
attr_reader :parts, :quantity_deps, :model_deps
|
attr_reader :parts, :quantity_deps, :model_deps
|
||||||
|
|
||||||
belongs_to :quantity, inverse_of: :formula, required: true
|
belongs_to :quantity, inverse_of: :formula, required: true
|
||||||
@ -107,17 +108,22 @@ class Formula < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
quantities = []
|
quantities = []
|
||||||
identifiers.reject! do |i|
|
models = []
|
||||||
if q_paths.has_key?(i)
|
identifiers.each do |i|
|
||||||
q = q_paths[i]
|
case
|
||||||
q.nil? ? errors << [:ambiguous_dependency, {identifier: i}] : quantities << q
|
when q_paths.has_key?(i)
|
||||||
|
if q_paths[i].nil?
|
||||||
|
errors << [:ambiguous_dependency, {identifier: i}]
|
||||||
|
else
|
||||||
|
quantities << q_paths[i]
|
||||||
end
|
end
|
||||||
end
|
when quantity.target? && (i.casecmp('Value') == 0)
|
||||||
|
when model = i.safe_constantize
|
||||||
models = identifiers.map(&:safe_constantize).compact || []
|
models << model
|
||||||
(identifiers - models.map(&:class_name)).each do |i|
|
else
|
||||||
errors << [:unknown_dependency, {identifier: i}]
|
errors << [:unknown_dependency, {identifier: i}]
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@parts, @quantity_deps, @model_deps = parts, quantities, models if errors.empty?
|
@parts, @quantity_deps, @model_deps = parts, quantities, models if errors.empty?
|
||||||
errors
|
errors
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
class Goal < ActiveRecord::Base
|
class Goal < ActiveRecord::Base
|
||||||
belongs_to :project, required: true
|
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
|
dependent: :destroy, extend: BodyTracking::ItemsWithQuantities
|
||||||
has_many :target_exposures, as: :view, dependent: :destroy,
|
has_many :target_exposures, as: :view, dependent: :destroy,
|
||||||
class_name: 'Exposure', extend: BodyTracking::TogglableExposures
|
class_name: 'Exposure', extend: BodyTracking::TogglableExposures
|
||||||
has_many :quantities, -> { order "lft" }, through: :target_exposures
|
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 :target_exposures, presence: true, unless: :is_binding?
|
||||||
validates :is_binding, uniqueness: {scope: :project_id}, if: :is_binding?
|
validates :is_binding, uniqueness: {scope: :project_id}, if: :is_binding?
|
||||||
validates :name, presence: true, uniqueness: {scope: :project_id},
|
validates :name, presence: true, uniqueness: {scope: :project_id},
|
||||||
|
@ -2,18 +2,21 @@ class Quantity < ActiveRecord::Base
|
|||||||
enum domain: {
|
enum domain: {
|
||||||
diet: 0,
|
diet: 0,
|
||||||
measurement: 1,
|
measurement: 1,
|
||||||
exercise: 2
|
exercise: 2,
|
||||||
|
target: 3
|
||||||
}
|
}
|
||||||
|
|
||||||
acts_as_nested_set dependent: :destroy, scope: :project
|
acts_as_nested_set dependent: :destroy, scope: :project
|
||||||
belongs_to :project, inverse_of: :quantities, required: false
|
belongs_to :project, inverse_of: :quantities, required: false
|
||||||
has_many :nutrients, dependent: :restrict_with_error
|
has_many :nutrients, dependent: :restrict_with_error
|
||||||
has_many :readouts, 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 :thresholds, dependent: :restrict_with_error
|
||||||
has_many :values, class_name: 'QuantityValue', dependent: :restrict_with_error
|
has_many :values, class_name: 'QuantityValue', dependent: :restrict_with_error
|
||||||
has_many :exposures, dependent: :destroy
|
has_many :exposures, dependent: :destroy
|
||||||
|
|
||||||
scope :defaults, -> { where(project: nil) }
|
scope :defaults, -> { where(project: nil) }
|
||||||
|
scope :except_targets, -> { where.not(domain: :target) }
|
||||||
|
|
||||||
has_one :formula, inverse_of: :quantity, dependent: :destroy, validate: true
|
has_one :formula, inverse_of: :quantity, dependent: :destroy, validate: true
|
||||||
accepts_nested_attributes_for :formula, allow_destroy: true,
|
accepts_nested_attributes_for :formula, allow_destroy: true,
|
||||||
|
@ -1,37 +1,37 @@
|
|||||||
class Target < ActiveRecord::Base
|
class Target < ActiveRecord::Base
|
||||||
CONDITIONS = ['<', '<=', '>', '>=', '==']
|
|
||||||
|
|
||||||
belongs_to :goal, inverse_of: :targets, required: true
|
belongs_to :goal, inverse_of: :targets, required: true
|
||||||
|
belongs_to :quantity, inverse_of: :targets, required: true
|
||||||
belongs_to :item, polymorphic: true, inverse_of: :targets
|
belongs_to :item, polymorphic: true, inverse_of: :targets
|
||||||
has_many :thresholds, as: :registry, inverse_of: :target, dependent: :destroy,
|
has_many :thresholds, -> { joins(:quantity).order(:lft) },
|
||||||
validate: true
|
as: :registry, inverse_of: :target, dependent: :destroy, validate: true
|
||||||
|
|
||||||
validates :thresholds, presence: true
|
validates :thresholds, presence: true
|
||||||
accepts_nested_attributes_for :thresholds, allow_destroy: true,
|
accepts_nested_attributes_for :thresholds, allow_destroy: true,
|
||||||
reject_if: proc { |attrs| attrs['quantity_id'].blank? && attrs['value'].blank? }
|
reject_if: proc { |attrs| attrs['quantity_id'].blank? && attrs['value'].blank? }
|
||||||
validate do
|
validate do
|
||||||
errors.add(:thresholds, :count_mismatch) unless thresholds.length == arity
|
quantities = thresholds.map(&:quantity)
|
||||||
errors.add(:thresholds, :quantity_mismatch) if thresholds.to_a.uniq(&:quantity).length != 1
|
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
|
end
|
||||||
validates :condition, inclusion: {in: CONDITIONS}
|
validates :scope, inclusion: {in: [:ingredient, :meal, :day], if: -> { quantity.diet? }}
|
||||||
validates :scope, inclusion: {in: [:ingredient, :meal, :day],
|
|
||||||
if: -> { thresholds.first.quantity.domain == :diet }}
|
|
||||||
validates :effective_from, presence: {if: :is_binding?}, absence: {unless: :is_binding?}
|
validates :effective_from, presence: {if: :is_binding?}, absence: {unless: :is_binding?}
|
||||||
|
|
||||||
after_initialize do
|
after_initialize do
|
||||||
if new_record?
|
if new_record?
|
||||||
self.condition ||= CONDITIONS.first
|
|
||||||
# Target should be only instantiated through Goal, so :is_binding? will be available
|
# Target should be only instantiated through Goal, so :is_binding? will be available
|
||||||
self.effective_from ||= Date.current if is_binding?
|
self.effective_from ||= Date.current if is_binding?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
delegate :is_binding?, to: :goal
|
delegate :is_binding?, to: :goal
|
||||||
|
# NOTE: remove if not used in controller
|
||||||
def arity
|
def arity
|
||||||
BigDecimal.method(condition).arity
|
thresholds.size
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_s
|
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
|
||||||
end
|
end
|
||||||
|
@ -1,21 +1,20 @@
|
|||||||
<label for="goal_id"><%= l(:field_goal) %><span class="required"> *</span></label>
|
<%= goal_f.collection_select :id, @project.goals, :id, :name,
|
||||||
<%= select_tag :goal_id,
|
{include_blank: false, required: true, label: :field_goal},
|
||||||
options_from_collection_for_select(@project.goals, :id, :name, @goal.id),
|
autocomplete: 'off',
|
||||||
required: true, autocomplete: 'off',
|
onchange: "$.ajax({
|
||||||
onchange: "var goal_id = $('#target_goal_attributes_id').val();
|
url: '#{goal_path(id: :goal_id)}'
|
||||||
$.ajax({
|
.replace('goal_id', $('#target_goal_attributes_id').val()),
|
||||||
url: '#{goal_path(id: :goal_id)}'.replace('goal_id', goal_id),
|
|
||||||
dataType: 'script'
|
dataType: 'script'
|
||||||
});
|
});
|
||||||
return false;" %>
|
return false;" %>
|
||||||
<%= link_to l(:button_add), '#',
|
<%= link_to l(:button_add), '#',
|
||||||
onclick: "var goal_id = $('#target_goal_attributes_id').val();
|
onclick: "$.ajax({
|
||||||
$.ajax({
|
url: '#{edit_goal_path(id: :goal_id)}'
|
||||||
url: '#{edit_goal_path(id: :goal_id)}'.replace('goal_id', goal_id),
|
.replace('goal_id', $('#target_goal_attributes_id').val()),
|
||||||
dataType: 'script'
|
dataType: 'script'
|
||||||
});
|
});
|
||||||
return false;",
|
return false;",
|
||||||
class: 'icon icon-add' %>
|
class: 'icon icon-add' %>
|
||||||
<% if @goal.description? %>
|
<% if goal_f.object.description? %>
|
||||||
<p style='white-space: pre-wrap;' ><%= @goal.description %></p>
|
<p style='white-space: pre-wrap;' ><%= goal_f.object.description %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
@ -5,8 +5,7 @@
|
|||||||
{autocomplete: 'off',
|
{autocomplete: 'off',
|
||||||
data: {remote: true,
|
data: {remote: true,
|
||||||
url: parents_project_quantities_path(@project),
|
url: parents_project_quantities_path(@project),
|
||||||
params: "form=#{f.options[:html][:id]}"}}
|
params: "form=#{f.options[:html][:id]}"}} %></p>
|
||||||
%></p>
|
|
||||||
<p><%= f.select :parent_id, parent_options(@quantity.domain),
|
<p><%= f.select :parent_id, parent_options(@quantity.domain),
|
||||||
{required: true, label: :field_parent_quantity, include_blank: t('.null_parent')} %></p>
|
{required: true, label: :field_parent_quantity, include_blank: t('.null_parent')} %></p>
|
||||||
<p><%= f.text_field :name, size: 25, required: true %></p>
|
<p><%= f.text_field :name, size: 25, required: true %></p>
|
||||||
|
@ -2,9 +2,10 @@
|
|||||||
|
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<div id='goal-form' class="tabular">
|
<div id='goal-form' class="tabular">
|
||||||
<% if @goal.persisted? %>
|
<% if goal_f.object.persisted? %>
|
||||||
<p><%= render partial: 'goals/show_form' %></p>
|
<p><%= render partial: 'goals/show_form', locals: {goal_f: goal_f} %></p>
|
||||||
<p><%= f.date_field :effective_from, disabled: !@goal.is_binding? %></p>
|
<p><%= goal_f.date_field :effective_from, value: @effective_from,
|
||||||
|
disabled: !goal_f.object.is_binding? %></p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= render partial: 'goals/form' %>
|
<%= render partial: 'goals/form' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
@ -13,34 +14,30 @@
|
|||||||
<hr style="width: 95%;">
|
<hr style="width: 95%;">
|
||||||
|
|
||||||
<div class="tabular">
|
<div class="tabular">
|
||||||
<% @targets.each do |target| %>
|
|
||||||
<p class="target">
|
<p class="target">
|
||||||
<%= f.fields_for 'targets', target, index: '' do |target_f| %>
|
<%= goal_f.fields_for :targets, @targets, child_index: '' do |target_f| %>
|
||||||
<%= target_f.hidden_field :id %>
|
|
||||||
<%= target_f.hidden_field :_destroy %>
|
<%= target_f.hidden_field :_destroy %>
|
||||||
<em class="info"><%= t ".choose_quantity" %></em>
|
<em class="info"><%= t ".choose_quantity" %></em>
|
||||||
<% target.thresholds.each_with_index do |thr, index| %>
|
<%= target_f.select :quantity_id, quantity_options,
|
||||||
<%= 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},
|
{include_blank: true, required: true, label: :field_target},
|
||||||
onchange: "showQuantityPath(event);" %>
|
onchange: "showQuantityPath(event);" %>
|
||||||
<%= target_f.select :condition, condition_options, required: true,
|
|
||||||
label: '' %>
|
<%= target_f.fields_for :thresholds do |threshold_f| %>
|
||||||
<% end %>
|
<%= render partial: 'thresholds/form', locals: {threshold_f: threshold_f} %>
|
||||||
<%= threshold_f.number_field :value, {size: 8, step: :any, label: ''} %>
|
|
||||||
<% if index == 0 %>
|
|
||||||
<%= threshold_f.select :unit_id, unit_options, {label: ''} %>
|
|
||||||
<% end %>
|
<% 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 %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= link_to t(".button_delete_target"), '#', class: 'icon icon-del',
|
<%= link_to t(".button_delete_target"), '#', class: 'icon icon-del',
|
||||||
style: (@targets.many? ? "" : "display:none"),
|
style: (@targets.many? ? "" : "display:none"),
|
||||||
onclick: "deleteTarget(); return false;" %>
|
onclick: "deleteTarget(); return false;" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
|
||||||
<p>
|
<p>
|
||||||
<%= link_to t(".button_new_target"), '#', class: 'icon icon-add',
|
<%= link_to t(".button_new_target"), '#', class: 'icon icon-add',
|
||||||
onclick: 'newTarget(); return false;' %>
|
onclick: 'newTarget(); return false;' %>
|
||||||
@ -50,10 +47,11 @@
|
|||||||
|
|
||||||
<%= javascript_tag do %>
|
<%= javascript_tag do %>
|
||||||
function showQuantityPath(event) {
|
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() {
|
$(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() {
|
function newTarget() {
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
<h2><%= t ".heading_new_target" %></h2>
|
<h2><%= t ".heading_new_target" %></h2>
|
||||||
|
|
||||||
<%= labelled_form_for @targets,
|
<%= labelled_form_for @goal,
|
||||||
url: project_targets_path(@project, @view_params),
|
url: project_targets_path(@project, @view_params),
|
||||||
remote: true,
|
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} %>
|
||||||
|
|
||||||
<div class="tabular">
|
<div class="tabular">
|
||||||
<p>
|
<p>
|
||||||
|
5
app/views/targets/subthresholds.js.erb
Normal file
5
app/views/targets/subthresholds.js.erb
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<% if @threshold %>
|
||||||
|
$('#targets').html('<%= j render partial: 'targets/index' %>');
|
||||||
|
<% else %>
|
||||||
|
$(event.target).empty();
|
||||||
|
<% end %>
|
15
app/views/thresholds/_form.html.erb
Normal file
15
app/views/thresholds/_form.html.erb
Normal file
@ -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 %>
|
@ -176,9 +176,10 @@ en:
|
|||||||
link_new_quantity: 'New quantity'
|
link_new_quantity: 'New quantity'
|
||||||
form:
|
form:
|
||||||
domains:
|
domains:
|
||||||
|
diet: 'diet'
|
||||||
measurement: 'measurement'
|
measurement: 'measurement'
|
||||||
exercise: 'exercise'
|
exercise: 'exercise'
|
||||||
diet: 'diet'
|
target: 'target'
|
||||||
null_parent: '- none -'
|
null_parent: '- none -'
|
||||||
formula_placeholder: 'provide if value of quantity has to be computed in terms of
|
formula_placeholder: 'provide if value of quantity has to be computed in terms of
|
||||||
other quantities'
|
other quantities'
|
||||||
|
@ -9,18 +9,20 @@ resources :projects, shallow: true do
|
|||||||
end
|
end
|
||||||
resources :goals, only: [:show, :edit] do
|
resources :goals, only: [:show, :edit] do
|
||||||
member do
|
member do
|
||||||
post 'toggle_exposure', to: 'targets#toggle_exposure'
|
post 'toggle_exposure', controller: :targets
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
resources :targets, except: [:show, :edit, :update] do
|
resources :targets, except: [:show, :edit, :update] do
|
||||||
collection do
|
collection do
|
||||||
get 'edit/:date', to: 'targets#edit', as: :edit
|
get 'edit/:date', action: :edit, as: :edit
|
||||||
patch :update
|
patch :update
|
||||||
post 'reapply/:date', to: 'targets#reapply', as: :reapply
|
post 'reapply/:date', action: :reapply, as: :reapply
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
resources :ingredients, only: [] do
|
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
|
end
|
||||||
resources :meals, except: [:show] do
|
resources :meals, except: [:show] do
|
||||||
member do
|
member do
|
||||||
@ -34,8 +36,8 @@ resources :projects, shallow: true do
|
|||||||
end
|
end
|
||||||
resources :measurement_routines, only: [:show, :edit] do
|
resources :measurement_routines, only: [:show, :edit] do
|
||||||
member do
|
member do
|
||||||
get 'readouts', to: 'measurements#readouts'
|
get 'readouts', controller: :measurements
|
||||||
post 'toggle_exposure', to: 'measurements#toggle_exposure'
|
post 'toggle_exposure', controller: :measurements
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
resources :measurements, except: [:show] do
|
resources :measurements, except: [:show] do
|
||||||
@ -47,7 +49,9 @@ resources :projects, shallow: true do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
resources :foods, except: [:show] do
|
resources :foods, except: [:show] do
|
||||||
post 'toggle', on: :member
|
member do
|
||||||
|
post 'toggle'
|
||||||
|
end
|
||||||
collection do
|
collection do
|
||||||
get 'nutrients'
|
get 'nutrients'
|
||||||
post 'toggle_exposure'
|
post 'toggle_exposure'
|
||||||
@ -60,8 +64,9 @@ resources :projects, shallow: true do
|
|||||||
resources :quantities, except: [:show] do
|
resources :quantities, except: [:show] do
|
||||||
member do
|
member do
|
||||||
get 'new_child'
|
get 'new_child'
|
||||||
|
get 'subthresholds', controller: :targets
|
||||||
post 'create_child'
|
post 'create_child'
|
||||||
post 'move/:direction', to: 'quantities#move', as: :move
|
post 'move/:direction', action: :move, as: :move
|
||||||
end
|
end
|
||||||
collection do
|
collection do
|
||||||
get 'parents'
|
get 'parents'
|
||||||
|
@ -10,6 +10,8 @@ class CreateSchema <
|
|||||||
t.references :parent
|
t.references :parent
|
||||||
t.integer :lft, null: false, index: true
|
t.integer :lft, null: false, index: true
|
||||||
t.integer :rgt, 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
|
t.timestamps null: false
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -105,8 +107,8 @@ class CreateSchema <
|
|||||||
|
|
||||||
create_table :targets do |t|
|
create_table :targets do |t|
|
||||||
t.references :goal
|
t.references :goal
|
||||||
|
t.references :quantity
|
||||||
t.references :item, polymorphic: true
|
t.references :item, polymorphic: true
|
||||||
t.string :condition
|
|
||||||
t.string :scope
|
t.string :scope
|
||||||
t.date :effective_from
|
t.date :effective_from
|
||||||
t.timestamps null: false
|
t.timestamps null: false
|
||||||
|
19
db/seeds.rb
19
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,
|
b_ad = Quantity.create name: "VF", domain: :measurement, parent: b_a,
|
||||||
description: "Visceral fat"
|
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
|
# Formulas go at the and to make sure dependencies exist
|
||||||
e_aa.create_formula zero_nil: true, unit: u_b,
|
e_aa.create_formula zero_nil: true, unit: u_b,
|
||||||
code: "4*Proteins + 9*Fats + 4*Carbs + 2*Fibre"
|
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,
|
b_aaa.create_formula zero_nil: true, unit: u_ac,
|
||||||
code: "'% fat' * Weight"
|
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
|
# Sources
|
||||||
s_a = Source.create name: "nutrition label",
|
s_a = Source.create name: "nutrition label",
|
||||||
description: "nutrition facts taken from package nutrition label"
|
description: "nutrition facts taken from package nutrition label"
|
||||||
|
6
init.rb
6
init.rb
@ -28,7 +28,8 @@ Redmine::Plugin.register :body_tracking do
|
|||||||
permission :manage_body_trackers, {
|
permission :manage_body_trackers, {
|
||||||
body_trackers: [:defaults],
|
body_trackers: [:defaults],
|
||||||
goals: [:edit],
|
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,
|
meals: [:new, :create, :edit, :update, :destroy, :edit_notes, :update_notes,
|
||||||
:toggle_eaten, :toggle_exposure, :adjust],
|
:toggle_eaten, :toggle_exposure, :adjust],
|
||||||
measurement_routines: [:edit],
|
measurement_routines: [:edit],
|
||||||
@ -36,7 +37,8 @@ Redmine::Plugin.register :body_tracking do
|
|||||||
foods: [:new, :create, :edit, :update, :destroy, :toggle, :toggle_exposure,
|
foods: [:new, :create, :edit, :update, :destroy, :toggle, :toggle_exposure,
|
||||||
:import],
|
:import],
|
||||||
sources: [:create, :destroy],
|
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],
|
units: [:create, :destroy],
|
||||||
}, require: :loggedin
|
}, require: :loggedin
|
||||||
end
|
end
|
||||||
|
14
test/fixtures/formulas.yml
vendored
14
test/fixtures/formulas.yml
vendored
@ -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
|
40
test/fixtures/quantities.yml
vendored
40
test/fixtures/quantities.yml
vendored
@ -1,17 +1,49 @@
|
|||||||
quantities_energy:
|
DEFAULTS: &DEFAULTS
|
||||||
project_id: 1
|
project_id: 1
|
||||||
domain: diet
|
|
||||||
parent: null
|
parent: null
|
||||||
|
|
||||||
|
quantities_energy:
|
||||||
|
<<: *DEFAULTS
|
||||||
|
domain: diet
|
||||||
lft: 1
|
lft: 1
|
||||||
rgt: 2
|
rgt: 2
|
||||||
|
depth: 0
|
||||||
name: Energy
|
name: Energy
|
||||||
description: Total energy
|
description: Total energy
|
||||||
|
|
||||||
quantities_proteins:
|
quantities_proteins:
|
||||||
project_id: 1
|
<<: *DEFAULTS
|
||||||
domain: diet
|
domain: diet
|
||||||
parent: null
|
|
||||||
lft: 3
|
lft: 3
|
||||||
rgt: 4
|
rgt: 4
|
||||||
|
depth: 0
|
||||||
name: Proteins
|
name: Proteins
|
||||||
description: Total amount of 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
|
||||||
|
2
test/fixtures/quantity_values.yml
vendored
2
test/fixtures/quantity_values.yml
vendored
@ -2,6 +2,6 @@ quantity_values_001:
|
|||||||
type: Threshold
|
type: Threshold
|
||||||
registry_type: Target
|
registry_type: Target
|
||||||
registry: targets_01
|
registry: targets_01
|
||||||
quantity: quantities_energy
|
quantity: quantities_target_equal
|
||||||
value: 2500
|
value: 2500
|
||||||
unit: units_kcal
|
unit: units_kcal
|
||||||
|
2
test/fixtures/targets.yml
vendored
2
test/fixtures/targets.yml
vendored
@ -1,4 +1,4 @@
|
|||||||
targets_01:
|
targets_01:
|
||||||
goal: goals_binding
|
goal: goals_binding
|
||||||
condition: '=='
|
quantity: quantities_energy
|
||||||
effective_from: <%= 1.week.ago.to_s(:db) %>
|
effective_from: <%= 1.week.ago.to_s(:db) %>
|
||||||
|
Reference in New Issue
Block a user