Added Goal model and preliminary Targets index
This commit is contained in:
parent
8240e5e868
commit
1dd2e2b596
24
app/controllers/targets_controller.rb
Normal file
24
app/controllers/targets_controller.rb
Normal file
@ -0,0 +1,24 @@
|
||||
class TargetsController < ApplicationController
|
||||
layout 'body_tracking'
|
||||
menu_item :body_trackers
|
||||
helper :body_trackers
|
||||
|
||||
include Concerns::Finders
|
||||
|
||||
before_action :find_project_by_project_id, only: [:index]
|
||||
|
||||
def index
|
||||
prepare_targets
|
||||
end
|
||||
|
||||
def new
|
||||
@target = @project.targets.new
|
||||
@target.arity.times { @target.thresholds.new }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def prepare_targets
|
||||
@targets = @project.targets.includes(:item, :thresholds)
|
||||
end
|
||||
end
|
6
app/models/goal.rb
Normal file
6
app/models/goal.rb
Normal file
@ -0,0 +1,6 @@
|
||||
class Goal < ActiveRecord::Base
|
||||
belongs_to :project, required: true
|
||||
has_many :targets, inverse_of: :goal, dependent: :destroy
|
||||
|
||||
validates :name, presence: true, uniqueness: {scope: :project_id}
|
||||
end
|
@ -2,7 +2,7 @@ class Ingredient < ActiveRecord::Base
|
||||
belongs_to :composition, inverse_of: :ingredients, polymorphic: true, required: true
|
||||
belongs_to :food, required: true
|
||||
belongs_to :part_of, required: false
|
||||
has_many :nutrients, through: :food, source: :nutrients
|
||||
has_many :nutrients, through: :food
|
||||
|
||||
DOMAIN = :diet
|
||||
alias_attribute :subitems, :nutrients
|
||||
|
@ -1,6 +1,8 @@
|
||||
class Nutrient < QuantityValue
|
||||
belongs_to :food, foreign_key: 'registry_id', foreign_type: 'registry_type',
|
||||
inverse_of: :nutrients, polymorphic: true, required: true
|
||||
# Need to specify polymorphic association so :registry_type gets written (see
|
||||
# QuantityValue for explanation why it's needed)
|
||||
belongs_to :food, inverse_of: :nutrients, polymorphic: true, required: true,
|
||||
foreign_key: 'registry_id', foreign_type: 'registry_type'
|
||||
|
||||
# Uniqueness NOT validated here, see Value for explanation
|
||||
#validates :quantity, uniqueness: {scope: :food_id}
|
||||
|
@ -9,6 +9,7 @@ class Quantity < ActiveRecord::Base
|
||||
belongs_to :project, inverse_of: :quantities, required: false
|
||||
has_many :nutrients, dependent: :restrict_with_error
|
||||
has_many :readouts, 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
|
||||
|
||||
|
@ -1,5 +1,9 @@
|
||||
class QuantityValue < ActiveRecord::Base
|
||||
# Requirement validation for :registry left to subclasses
|
||||
# Polymorphic registry (including :registry_type) is required - despite 1:1
|
||||
# mapping between Nutrient:Food, Readout:Measurement, Threshold:Target ... -
|
||||
# to allow for accessing registry item without knowing QuantityValue (subitem)
|
||||
# type, e.g. qv.registry.completed_at
|
||||
belongs_to :registry, polymorphic: true
|
||||
belongs_to :quantity, required: true
|
||||
belongs_to :unit, required: true
|
||||
|
@ -1,6 +1,8 @@
|
||||
class Readout < QuantityValue
|
||||
belongs_to :measurement, foreign_key: 'registry_id', foreign_type: 'registry_type',
|
||||
inverse_of: :readouts, polymorphic: true, required: true
|
||||
# Need to specify polymorphic association so :registry_type gets written (see
|
||||
# QuantityValue for explanation why it's needed)
|
||||
belongs_to :measurement, inverse_of: :readouts, polymorphic: true, required: true,
|
||||
foreign_key: 'registry_id', foreign_type: 'registry_type'
|
||||
|
||||
# Uniqueness NOT validated here, see Value for explanation
|
||||
#validates :quantity, uniqueness: {scope: [:measurement_id, :unit_id]}
|
||||
|
@ -10,5 +10,16 @@ class Target < ActiveRecord::Base
|
||||
# TODO: validate thresholds count according to condition type
|
||||
validates :condition, inclusion: {in: [:<, :<=, :>, :>=, :==]}
|
||||
validates :scope, inclusion: {in: [:day], if: -> { thresholds.first.domain == :diet }}
|
||||
validates :effective_from, presence: {unless: goal?}, absence: {if: goal?}
|
||||
validates :effective_from, presence: {unless: -> { goal.present? }},
|
||||
absence: {if: -> { goal.present? }}
|
||||
|
||||
after_initialize do
|
||||
if new_record?
|
||||
self.condition = :<
|
||||
end
|
||||
end
|
||||
|
||||
def arity
|
||||
BigDecimal.method(condition).arity
|
||||
end
|
||||
end
|
||||
|
@ -1,6 +1,8 @@
|
||||
class Threshold < QuantityValue
|
||||
belongs_to :target, foreign_key: 'registry_id', foreign_type: 'foreign_type',
|
||||
inverse_of: :thresholds, polymorphic: true, required: true
|
||||
# Need to specify polymorphic association so :registry_type gets written (see
|
||||
# QuantityValue for explanation why it's needed)
|
||||
belongs_to :target, inverse_of: :thresholds, polymorphic: true, required: true,
|
||||
foreign_key: 'registry_id', foreign_type: 'registry_type'
|
||||
|
||||
validates :value, numericality: true
|
||||
end
|
||||
|
@ -1,6 +1,12 @@
|
||||
<h3><%= t ".heading_body_trackers" %></h3>
|
||||
<ul>
|
||||
<li><%= link_to t(".link_summary"), project_body_trackers_path(@project) %></li>
|
||||
<li>
|
||||
<%= link_to t(".link_targets"), project_targets_path(@project) %>
|
||||
/
|
||||
<%#= link_to t(".link_goals"), nutrients_project_foods_path(@project) %>
|
||||
Goals
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3><%= t ".heading_measurements" %></h3>
|
||||
|
5
app/views/targets/_contextual.html.erb
Normal file
5
app/views/targets/_contextual.html.erb
Normal file
@ -0,0 +1,5 @@
|
||||
<% if User.current.allowed_to?(:manage_common, @project) %>
|
||||
<%= link_to t(".link_new_target"),
|
||||
new_project_target_path(@project, @view_params),
|
||||
{remote: true, class: 'icon icon-add'} %>
|
||||
<% end %>
|
34
app/views/targets/_index.html.erb
Normal file
34
app/views/targets/_index.html.erb
Normal file
@ -0,0 +1,34 @@
|
||||
<% if @targets.any? { |t| t.persisted? } %>
|
||||
<table class="list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:5%"><%= l(:field_taken_at_date) %></th>
|
||||
<th><%= l(:field_name) %></th>
|
||||
<th><%= l(:field_notes) %></th>
|
||||
<th><%= l(:field_source) %></th>
|
||||
<th style="width:5%"><%= l(:field_action) %></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @measurements.each do |m| %>
|
||||
<% next if m.new_record? %>
|
||||
<tr id="measurement-<%= m.id %>" class="primary measurement">
|
||||
<td class="date unwrappable"><%= format_datetime(m) %></td>
|
||||
<td class="name">
|
||||
<div style="float:left;">
|
||||
<%= link_to m.routine.name, readouts_measurement_routine_path(m.routine) %>
|
||||
</div>
|
||||
<div style="float:right;">
|
||||
<small><%= " (#{pluralize(m.readouts.size, 'readout')})" %></small>
|
||||
</div>
|
||||
</td>
|
||||
<td class="notes ellipsible"><%= m.notes %></td>
|
||||
<td class="source"><%= m.source.name if m.source.present? %></td>
|
||||
<td class="action unwrappable"><%= action_links(m) %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% else %>
|
||||
<p class="nodata"><%= l(:label_no_data) %></p>
|
||||
<% end %>
|
11
app/views/targets/index.html.erb
Normal file
11
app/views/targets/index.html.erb
Normal file
@ -0,0 +1,11 @@
|
||||
<div class="contextual">
|
||||
<%= render partial: 'targets/contextual' %>
|
||||
</div>
|
||||
|
||||
<div id="new-target">
|
||||
</div>
|
||||
|
||||
<h2><%= t ".heading" %></h2>
|
||||
<div id='targets'>
|
||||
<%= render partial: 'targets/index' %>
|
||||
</div>
|
@ -70,6 +70,8 @@ en:
|
||||
heading_diet: 'Diet'
|
||||
heading_common: 'Common'
|
||||
link_summary: 'Summary'
|
||||
link_targets: 'Targets'
|
||||
link_goals: 'Goals'
|
||||
link_measurements: 'Measurements'
|
||||
link_meals: 'Meals'
|
||||
link_foods: 'Foods'
|
||||
@ -79,6 +81,18 @@ en:
|
||||
link_units: 'Units'
|
||||
link_defaults: 'Load defaults'
|
||||
confirm_defaults: 'This will load default data sources, quantities and units. Continue?'
|
||||
targets:
|
||||
contextual:
|
||||
link_new_target: 'New target'
|
||||
form:
|
||||
button_new_target: 'Add target'
|
||||
button_delete_target: 'Delete'
|
||||
new_form:
|
||||
heading_new_target: 'New target'
|
||||
index:
|
||||
heading: 'Targets'
|
||||
show:
|
||||
label_target: 'Target'
|
||||
meals:
|
||||
contextual:
|
||||
link_new_meal: 'New meal'
|
||||
|
@ -7,10 +7,11 @@ resources :projects, shallow: true do
|
||||
post 'defaults'
|
||||
end
|
||||
end
|
||||
resources :targets, except: [:show]
|
||||
resources :ingredients, only: [] do
|
||||
post 'adjust/:adjustment', to: 'meals#adjust', as: :adjust, on: :member
|
||||
end
|
||||
resources :meals, only: [:index, :new, :create, :edit, :update, :destroy] do
|
||||
resources :meals, except: [:show] do
|
||||
member do
|
||||
get 'edit_notes'
|
||||
patch 'update_notes'
|
||||
@ -26,7 +27,7 @@ resources :projects, shallow: true do
|
||||
post 'toggle_exposure', to: 'measurements#toggle_exposure'
|
||||
end
|
||||
end
|
||||
resources :measurements, only: [:index, :new, :create, :edit, :update, :destroy] do
|
||||
resources :measurements, except: [:show] do
|
||||
member do
|
||||
get 'retake'
|
||||
end
|
||||
@ -34,7 +35,7 @@ resources :projects, shallow: true do
|
||||
get 'filter'
|
||||
end
|
||||
end
|
||||
resources :foods, only: [:index, :new, :create, :edit, :update, :destroy] do
|
||||
resources :foods, except: [:show] do
|
||||
post 'toggle', on: :member
|
||||
collection do
|
||||
get 'nutrients'
|
||||
@ -45,7 +46,7 @@ resources :projects, shallow: true do
|
||||
end
|
||||
end
|
||||
resources :sources, only: [:index, :create, :destroy]
|
||||
resources :quantities, only: [:index, :new, :create, :edit, :update, :destroy] do
|
||||
resources :quantities, except: [:show] do
|
||||
member do
|
||||
get 'new_child'
|
||||
post 'create_child'
|
||||
|
@ -93,6 +93,13 @@ class CreateSchema < ActiveRecord::Migration
|
||||
t.timestamps null: false
|
||||
end
|
||||
|
||||
create_table :goals do |t|
|
||||
t.references :project
|
||||
t.string :name
|
||||
t.text :description
|
||||
t.timestamps null: false
|
||||
end
|
||||
|
||||
create_table :targets do |t|
|
||||
t.references :goal
|
||||
t.references :item, polymorphic: true
|
||||
|
2
init.rb
2
init.rb
@ -14,6 +14,7 @@ Redmine::Plugin.register :body_tracking do
|
||||
project_module :body_tracking do
|
||||
permission :view_body_trackers, {
|
||||
body_trackers: [:index],
|
||||
targets: [:index],
|
||||
meals: [:index],
|
||||
measurement_routines: [:show],
|
||||
measurements: [:index, :readouts, :filter],
|
||||
@ -24,6 +25,7 @@ Redmine::Plugin.register :body_tracking do
|
||||
}, read: true
|
||||
permission :manage_common, {
|
||||
body_trackers: [:defaults],
|
||||
targets: [:new, :create, :edit, :update, :destroy],
|
||||
meals: [:new, :create, :edit, :update, :destroy, :edit_notes, :update_notes,
|
||||
:toggle_eaten, :toggle_exposure, :adjust],
|
||||
measurement_routines: [:edit],
|
||||
|
@ -15,8 +15,8 @@ module BodyTracking::ProjectPatch
|
||||
source: 'quantity'
|
||||
|
||||
has_many :measurement_routines, dependent: :destroy
|
||||
has_many :measurements, -> { order "taken_at DESC" }, dependent: :destroy,
|
||||
extend: BodyTracking::ItemsWithQuantities, through: :measurement_routines
|
||||
has_many :measurements, -> { order "taken_at DESC" }, through: :measurement_routines,
|
||||
extend: BodyTracking::ItemsWithQuantities
|
||||
|
||||
has_many :meals, -> { order "eaten_at DESC" }, dependent: :destroy
|
||||
has_many :meal_ingredients, through: :meals, source: 'ingredients',
|
||||
@ -28,5 +28,8 @@ module BodyTracking::ProjectPatch
|
||||
class_name: 'Exposure', extend: BodyTracking::TogglableExposures
|
||||
has_many :meal_quantities, -> { order "lft" }, through: :meal_exposures,
|
||||
source: 'quantity'
|
||||
|
||||
has_many :thresholds, through: :quantities
|
||||
has_many :targets, through: :thresholds, source_type: 'Target'
|
||||
end
|
||||
end
|
||||
|
Reference in New Issue
Block a user