Add GoalController: #index, #new, #create
This commit is contained in:
parent
ea308a1e4a
commit
7f87b3bc84
@ -1,12 +1,60 @@
|
||||
class GoalsController < ApplicationController
|
||||
layout 'body_tracking', except: :subthresholds
|
||||
menu_item :body_trackers
|
||||
helper :body_trackers
|
||||
|
||||
include Concerns::Finders
|
||||
|
||||
before_action :find_project_by_project_id, only: [:index, :new, :create]
|
||||
before_action :find_goal, only: [:show, :edit]
|
||||
before_action :authorize
|
||||
|
||||
def index
|
||||
@goals = @project.goals
|
||||
end
|
||||
|
||||
def new
|
||||
@goal = @project.goals.new
|
||||
@targets = @goal.targets
|
||||
end
|
||||
|
||||
def create
|
||||
@goal = @project.goals.new(goal_params)
|
||||
if @goal.save
|
||||
flash.now[:notice] = 'Created new goal'
|
||||
@goals = @project.goals
|
||||
else
|
||||
@targets = @goal.targets
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
end
|
||||
|
||||
def edit
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def goal_params
|
||||
params.require(:goal).permit(
|
||||
:name,
|
||||
:description,
|
||||
targets_attributes:
|
||||
[
|
||||
:id,
|
||||
:quantity_id,
|
||||
:scope,
|
||||
:destroy,
|
||||
thresholds_attributes: [
|
||||
:id,
|
||||
:quantity_id,
|
||||
:value,
|
||||
:unit_id,
|
||||
:_destroy
|
||||
]
|
||||
]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
@ -33,10 +33,6 @@ class TargetsController < ApplicationController
|
||||
target.effective_from = params[:target][:effective_from]
|
||||
end
|
||||
|
||||
if @goal.target_exposures.empty?
|
||||
@goal.quantities << @targets.map(&:quantity)[0..5]
|
||||
end
|
||||
|
||||
# :save only after build, to re-display values in case records are invalid
|
||||
if @goal.save
|
||||
flash.now[:notice] = 'Created new target(s)'
|
||||
@ -85,13 +81,6 @@ class TargetsController < ApplicationController
|
||||
|
||||
private
|
||||
|
||||
def goal_params
|
||||
params.require(:goal).permit(
|
||||
:name,
|
||||
:description
|
||||
)
|
||||
end
|
||||
|
||||
def targets_params
|
||||
params.require(:goal).permit(
|
||||
targets_attributes:
|
||||
|
@ -6,18 +6,22 @@ class Goal < ActiveRecord::Base
|
||||
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?
|
||||
accepts_nested_attributes_for :targets, allow_destroy: true
|
||||
validates :is_binding, uniqueness: {scope: :project_id}, if: :is_binding?
|
||||
validates :name, presence: true, uniqueness: {scope: :project_id},
|
||||
exclusion: {in: [I18n.t('targets.form.binding_goal')], unless: :is_binding?}
|
||||
exclusion: {in: [I18n.t('goals.binding.name')], unless: :is_binding?}
|
||||
|
||||
after_initialize do
|
||||
if new_record?
|
||||
self.is_binding = false if self.is_binding.nil?
|
||||
self.targets.new if self.targets.empty?
|
||||
end
|
||||
end
|
||||
|
||||
before_save do
|
||||
quantities << targets.map(&:quantity)[0..5] if target_exposures.empty?
|
||||
end
|
||||
|
||||
before_destroy prepend: true do
|
||||
!is_binding?
|
||||
end
|
||||
|
@ -5,7 +5,7 @@ class QuantityValue < ActiveRecord::Base
|
||||
# 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, -> { where(domain: DOMAIN) }, required: true
|
||||
belongs_to :quantity, ->(qv) { where(domain: qv.class::DOMAIN) }, required: true
|
||||
belongs_to :unit, required: true
|
||||
|
||||
# Uniqueness is checked exclusively on the other end of association level.
|
||||
|
@ -11,17 +11,20 @@ class Target < ActiveRecord::Base
|
||||
reject_if: proc { |attrs| attrs['quantity_id'].blank? && attrs['value'].blank? }
|
||||
validate do
|
||||
quantities = thresholds.map(&:quantity)
|
||||
ancestors = quantities.max_by(:lft).self_and_ancestors
|
||||
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 :scope, inclusion: {in: [:ingredient, :meal, :day], if: -> { quantity.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?
|
||||
# Target should be only instantiated through Goal, so :is_binding? will be available
|
||||
self.effective_from ||= Date.current if is_binding?
|
||||
if self.thresholds.empty?
|
||||
self.thresholds.new(quantity: self.goal.project.quantities.target.first)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1,13 +1,10 @@
|
||||
<div class="tabular">
|
||||
<%= fields_for 'measurement[routine_attributes]', routine do |ff| %>
|
||||
<%= ff.hidden_field :id %>
|
||||
<p>
|
||||
<label><%= l(:field_name) %><span class="required"> *</span></label>
|
||||
<%= ff.text_field :name, required: true, style: "width: 95%;" %>
|
||||
</p>
|
||||
<p>
|
||||
<label><%= l(:field_description) %></label>
|
||||
<%= ff.text_area :description, cols: 40, rows: 3, style: "width: 95%;" %>
|
||||
</p>
|
||||
<% end %>
|
||||
<%= error_messages_for @goal %>
|
||||
|
||||
<div class="box tabular">
|
||||
<p><%= goal_f.text_field :name, required: true, style: "width: 95%;" %></p>
|
||||
<p><%= goal_f.text_area :description, rows: 3, style: "width: 95%;" %></p>
|
||||
|
||||
<hr style="width: 95%;">
|
||||
|
||||
<%= render partial: 'targets/form', locals: {goal_f: goal_f} %>
|
||||
</div>
|
||||
|
32
app/views/goals/_index.html.erb
Normal file
32
app/views/goals/_index.html.erb
Normal file
@ -0,0 +1,32 @@
|
||||
<% if @goals.any? %>
|
||||
<table id="goals" class="list odd-even">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><%= l(:field_name) %></th>
|
||||
<th><%= l(:field_description) %></th>
|
||||
<th style="width:5%"><%= l(:field_action) %></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @goals.each do |g| %>
|
||||
<tr id="goal-<%= g.id %>" class="primary goal">
|
||||
<td class="name unwrappable">
|
||||
<div style="float:left;">
|
||||
<%= checked_image g.is_binding %><%= link_to g.name, g %>
|
||||
</div>
|
||||
<div style="float:right;">
|
||||
<%# TODO: display # of active targets/targeted quantities %>
|
||||
<small><%#= " (#{pluralize(m.readouts.size, 'readout')})" %></small>
|
||||
</div>
|
||||
</td>
|
||||
<td class="description ellipsible"><%= g.description %></td>
|
||||
<td class="action unwrappable">
|
||||
<%= delete_link(g, {remote: true, data: {}}) unless g.is_binding %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% else %>
|
||||
<p class="nodata"><%= l(:label_no_data) %></p>
|
||||
<% end %>
|
16
app/views/goals/_new_form.html.erb
Normal file
16
app/views/goals/_new_form.html.erb
Normal file
@ -0,0 +1,16 @@
|
||||
<h2><%= t ".heading_new_goal" %></h2>
|
||||
|
||||
<%= labelled_form_for [@project, @goal], remote: true,
|
||||
html: {id: 'new-goal-form', name: 'new-goal-form'} do |goal_f| %>
|
||||
|
||||
<%= render partial: 'goals/form', locals: {goal_f: goal_f} %>
|
||||
|
||||
<div class="tabular">
|
||||
<p>
|
||||
<%= submit_tag l(:button_create) %>
|
||||
<%= link_to l(:button_cancel), "#",
|
||||
onclick: "$('#new-goal').empty(); return false;" %>
|
||||
</p>
|
||||
</div>
|
||||
<% end %>
|
||||
<hr>
|
2
app/views/goals/create.js.erb
Normal file
2
app/views/goals/create.js.erb
Normal file
@ -0,0 +1,2 @@
|
||||
$('#new-goal').empty();
|
||||
$('#goals').html('<%= j render partial: 'goals/index' %>');
|
13
app/views/goals/index.html.erb
Normal file
13
app/views/goals/index.html.erb
Normal file
@ -0,0 +1,13 @@
|
||||
<div class="contextual">
|
||||
<%= link_to_if User.current.allowed_to?(:manage_body_trackers, @project),
|
||||
t(".link_new_goal"), new_project_goal_path(@project),
|
||||
{remote: true, class: 'icon icon-add'} %>
|
||||
</div>
|
||||
|
||||
<div id="new-goal">
|
||||
</div>
|
||||
|
||||
<%= title t(:body_trackers_title), t(".heading") %>
|
||||
<div id='goals'>
|
||||
<%= render partial: 'goals/index' %>
|
||||
</div>
|
1
app/views/goals/new.js.erb
Normal file
1
app/views/goals/new.js.erb
Normal file
@ -0,0 +1 @@
|
||||
$('#new-goal').html('<%= j render partial: 'goals/new_form' %>');
|
@ -17,8 +17,7 @@
|
||||
<li>
|
||||
<%= link_to t(".link_targets"), project_targets_path(@project) %>
|
||||
/
|
||||
<%#= link_to t(".link_goals"), nutrients_project_foods_path(@project) %>
|
||||
Goals
|
||||
<%= link_to t(".link_goals"), project_goals_path(@project) %>
|
||||
</li>
|
||||
<li><%= link_to t(".link_quantities"), project_quantities_path(@project) %></li>
|
||||
<li><%= link_to t(".link_units"), project_units_path(@project) %></li>
|
||||
|
@ -1,42 +1,30 @@
|
||||
<%= error_messages_for *@targets %>
|
||||
<% if goal_f.object.is_binding? %>
|
||||
<p>
|
||||
<%= date_field :effective_from, value: @effective_from %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<div class="box">
|
||||
<div id='goal-form' class="tabular">
|
||||
<% if goal_f.object.persisted? %>
|
||||
<p><%= render partial: 'goals/show_form', locals: {goal_f: goal_f} %></p>
|
||||
<p><%= goal_f.date_field :effective_from, value: @effective_from,
|
||||
disabled: !goal_f.object.is_binding? %></p>
|
||||
<% else %>
|
||||
<%= render partial: 'goals/form' %>
|
||||
<% end %>
|
||||
</div>
|
||||
<%= goal_f.fields_for :targets, @targets, child_index: '' do |target_f| %>
|
||||
<p class="target">
|
||||
<%= target_f.hidden_field :_destroy %>
|
||||
<em class="info"><%= t ".choose_quantity" %></em>
|
||||
<%= target_f.select :quantity_id, quantity_options,
|
||||
{include_blank: true, required: true, label: :field_target},
|
||||
onchange: "showQuantityPath(event);" %>
|
||||
|
||||
<hr style="width: 95%;">
|
||||
<%= render partial: 'targets/thresholds_form',
|
||||
locals: {target_f: target_f,
|
||||
last_quantity: target_f.object.thresholds.last.quantity} %>
|
||||
|
||||
<div class="tabular">
|
||||
<p class="target">
|
||||
<%= goal_f.fields_for :targets, @targets, child_index: '' do |target_f| %>
|
||||
<%= target_f.hidden_field :_destroy %>
|
||||
<em class="info"><%= t ".choose_quantity" %></em>
|
||||
<%= target_f.select :quantity_id, quantity_options,
|
||||
{include_blank: true, required: true, label: :field_target},
|
||||
onchange: "showQuantityPath(event);" %>
|
||||
|
||||
<%= render partial: 'targets/thresholds_form',
|
||||
locals: {target_f: target_f,
|
||||
last_quantity: target_f.object.thresholds.last.quantity} %>
|
||||
|
||||
<%= link_to t(".button_delete_target"), '#', class: 'icon icon-del',
|
||||
style: (@targets.many? ? "" : "display:none"),
|
||||
onclick: "deleteTarget(); return false;" %>
|
||||
<% end %>
|
||||
</p>
|
||||
<p>
|
||||
<%= link_to t(".button_new_target"), '#', class: 'icon icon-add',
|
||||
onclick: 'newTarget(); return false;' %>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<%= link_to t(".button_delete_target"), '#', class: 'icon icon-del',
|
||||
style: (@targets.many? ? "" : "display:none"),
|
||||
onclick: "deleteTarget(); return false;" %>
|
||||
</p>
|
||||
<% end %>
|
||||
<p>
|
||||
<%= link_to t(".button_new_target"), '#', class: 'icon icon-add',
|
||||
onclick: 'newTarget(); return false;' %>
|
||||
</p>
|
||||
|
||||
<%= javascript_tag do %>
|
||||
function showQuantityPath(event) {
|
||||
|
@ -4,7 +4,10 @@
|
||||
<%= labelled_form_for [@project, @goal], remote: true,
|
||||
html: {id: 'new-target-form', name: 'new-target-form'} do |goal_f| %>
|
||||
|
||||
<%= render partial: 'targets/form', locals: {goal_f: goal_f} %>
|
||||
<%= error_messages_for *@targets %>
|
||||
<div class="box tabular">
|
||||
<%= render partial: 'targets/form', locals: {goal_f: goal_f} %>
|
||||
</div>
|
||||
|
||||
<div class="tabular">
|
||||
<p>
|
||||
|
@ -89,11 +89,20 @@ en:
|
||||
link_units: 'Units'
|
||||
link_defaults: 'Load defaults'
|
||||
confirm_defaults: 'This will load default data sources, quantities and units. Continue?'
|
||||
goals:
|
||||
binding:
|
||||
name: 'Binding goal'
|
||||
description: 'Targets from this goal are applied throughout application'
|
||||
form:
|
||||
new_form:
|
||||
heading_new_goal: 'New goal'
|
||||
index:
|
||||
heading: 'Goals'
|
||||
link_new_goal: 'New goal'
|
||||
targets:
|
||||
contextual:
|
||||
link_new_target: 'New target'
|
||||
form:
|
||||
binding_goal: '- binding -'
|
||||
choose_quantity: 'Choose quantity'
|
||||
button_new_target: 'Add target'
|
||||
button_delete_target: 'Delete'
|
||||
|
@ -7,7 +7,7 @@ resources :projects, shallow: true do
|
||||
post 'defaults'
|
||||
end
|
||||
end
|
||||
resources :goals, only: [:show, :edit] do
|
||||
resources :goals do
|
||||
member do
|
||||
post 'toggle_exposure', controller: :targets
|
||||
end
|
||||
|
4
init.rb
4
init.rb
@ -15,7 +15,7 @@ Redmine::Plugin.register :body_tracking do
|
||||
project_module :body_tracking do
|
||||
permission :view_body_trackers, {
|
||||
body_trackers: [:index],
|
||||
goals: [:show],
|
||||
goals: [:index, :show],
|
||||
targets: [:index],
|
||||
meals: [:index],
|
||||
measurement_routines: [:show],
|
||||
@ -27,7 +27,7 @@ Redmine::Plugin.register :body_tracking do
|
||||
}, read: true
|
||||
permission :manage_body_trackers, {
|
||||
body_trackers: [:defaults],
|
||||
goals: [:edit],
|
||||
goals: [:new, :create, :edit],
|
||||
targets: [:new, :create, :edit, :update, :destroy, :reapply, :toggle_exposure,
|
||||
:subthresholds],
|
||||
meals: [:new, :create, :edit, :update, :destroy, :edit_notes, :update_notes,
|
||||
|
@ -32,7 +32,8 @@ module BodyTracking::ProjectPatch
|
||||
has_many :goals, dependent: :destroy do
|
||||
def binding
|
||||
find_or_create_by(is_binding: true) do |goal|
|
||||
goal.name = I18n.t('targets.form.binding_goal')
|
||||
goal.name = I18n.t('goals.binding.name')
|
||||
goal.description = I18n.t('goals.binding.description')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
3
test/fixtures/goals.yml
vendored
3
test/fixtures/goals.yml
vendored
@ -1,7 +1,8 @@
|
||||
goals_binding:
|
||||
project_id: 1
|
||||
is_binding: true
|
||||
name: "<%= I18n.t 'targets.form.binding_goal' %>"
|
||||
name: "<%= I18n.t 'goals.binding.name' %>"
|
||||
description: "<%= I18n.t 'goals.binding.description' %>"
|
||||
|
||||
goals_non_binding:
|
||||
project_id: 1
|
||||
|
@ -46,7 +46,7 @@ class TargetsTest < BodyTrackingSystemTestCase
|
||||
click_link t('targets.contextual.link_new_target')
|
||||
assert_selector 'form#new-target-form', count: 1
|
||||
within 'form#new-target-form' do
|
||||
assert has_select?(t(:field_goal), selected: t('targets.form.binding_goal'))
|
||||
assert has_select?(t(:field_goal), selected: t('goals.binding.name'))
|
||||
assert has_field?(t(:field_effective_from), with: Date.current)
|
||||
assert has_no_link?(t('targets.form.button_delete_target'))
|
||||
end
|
||||
|
Reference in New Issue
Block a user