diff --git a/app/controllers/goals_controller.rb b/app/controllers/goals_controller.rb index 534668f..b6fd049 100644 --- a/app/controllers/goals_controller.rb +++ b/app/controllers/goals_controller.rb @@ -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 diff --git a/app/controllers/targets_controller.rb b/app/controllers/targets_controller.rb index 261fea9..b6155e6 100644 --- a/app/controllers/targets_controller.rb +++ b/app/controllers/targets_controller.rb @@ -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: diff --git a/app/models/goal.rb b/app/models/goal.rb index 5c27563..2dfc7d0 100644 --- a/app/models/goal.rb +++ b/app/models/goal.rb @@ -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 diff --git a/app/models/quantity_value.rb b/app/models/quantity_value.rb index 74bdb14..8925f2b 100644 --- a/app/models/quantity_value.rb +++ b/app/models/quantity_value.rb @@ -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. diff --git a/app/models/target.rb b/app/models/target.rb index 11fbdb7..2f3c206 100644 --- a/app/models/target.rb +++ b/app/models/target.rb @@ -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 diff --git a/app/views/goals/_form.html.erb b/app/views/goals/_form.html.erb index ce32e44..e7234c7 100644 --- a/app/views/goals/_form.html.erb +++ b/app/views/goals/_form.html.erb @@ -1,13 +1,10 @@ -
- - <%= ff.text_field :name, required: true, style: "width: 95%;" %> -
-- - <%= ff.text_area :description, cols: 40, rows: 3, style: "width: 95%;" %> -
- <% end %> +<%= error_messages_for @goal %> + +<%= goal_f.text_field :name, required: true, style: "width: 95%;" %>
+<%= goal_f.text_area :description, rows: 3, style: "width: 95%;" %>
+ +<%= l(:field_name) %> | +<%= l(:field_description) %> | +<%= l(:field_action) %> | +
---|---|---|
+
+ <%= checked_image g.is_binding %><%= link_to g.name, g %>
+
+
+ <%# TODO: display # of active targets/targeted quantities %>
+ <%#= " (#{pluralize(m.readouts.size, 'readout')})" %>
+
+ |
+ <%= g.description %> | ++ <%= delete_link(g, {remote: true, data: {}}) unless g.is_binding %> + | +
<%= l(:label_no_data) %>
+<% end %> diff --git a/app/views/goals/_new_form.html.erb b/app/views/goals/_new_form.html.erb new file mode 100644 index 0000000..3ff7388 --- /dev/null +++ b/app/views/goals/_new_form.html.erb @@ -0,0 +1,16 @@ ++ <%= submit_tag l(:button_create) %> + <%= link_to l(:button_cancel), "#", + onclick: "$('#new-goal').empty(); return false;" %> +
++ <%= date_field :effective_from, value: @effective_from %> +
+<% end %> -<%= 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 %> -+ <%= 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);" %> -
- <%= 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);" %> - - <%= 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 %> -
-- <%= link_to t(".button_new_target"), '#', class: 'icon icon-add', - onclick: 'newTarget(); return false;' %> -
-+<%= link_to t(".button_new_target"), '#', class: 'icon icon-add', + onclick: 'newTarget(); return false;' %> +
<%= javascript_tag do %> function showQuantityPath(event) { diff --git a/app/views/targets/_new_form.html.erb b/app/views/targets/_new_form.html.erb index 932c4a0..b42a525 100644 --- a/app/views/targets/_new_form.html.erb +++ b/app/views/targets/_new_form.html.erb @@ -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 %> +diff --git a/config/locales/en.yml b/config/locales/en.yml index 0560155..b336c54 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -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' diff --git a/config/routes.rb b/config/routes.rb index 5e12b07..41b6cc4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -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 diff --git a/init.rb b/init.rb index 3cb4210..5f531d2 100644 --- a/init.rb +++ b/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, diff --git a/lib/body_tracking/project_patch.rb b/lib/body_tracking/project_patch.rb index c7ddd72..cf1989a 100644 --- a/lib/body_tracking/project_patch.rb +++ b/lib/body_tracking/project_patch.rb @@ -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 diff --git a/test/fixtures/goals.yml b/test/fixtures/goals.yml index 3aeece0..81fdf34 100644 --- a/test/fixtures/goals.yml +++ b/test/fixtures/goals.yml @@ -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 diff --git a/test/system/targets_test.rb b/test/system/targets_test.rb index 8023442..489a8f6 100644 --- a/test/system/targets_test.rb +++ b/test/system/targets_test.rb @@ -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