From 7f87b3bc845c6fb62114e536daea283d7e1f06b7 Mon Sep 17 00:00:00 2001 From: cryptogopher Date: Sun, 21 Feb 2021 18:10:15 +0100 Subject: [PATCH] Add GoalController: #index, #new, #create --- app/controllers/goals_controller.rb | 48 +++++++++++++++++++++ app/controllers/targets_controller.rb | 11 ----- app/models/goal.rb | 12 ++++-- app/models/quantity_value.rb | 2 +- app/models/target.rb | 7 +++- app/views/goals/_form.html.erb | 21 ++++------ app/views/goals/_index.html.erb | 32 ++++++++++++++ app/views/goals/_new_form.html.erb | 16 +++++++ app/views/goals/create.js.erb | 2 + app/views/goals/index.html.erb | 13 ++++++ app/views/goals/new.js.erb | 1 + app/views/layouts/_sidebar.html.erb | 3 +- app/views/targets/_form.html.erb | 60 +++++++++++---------------- app/views/targets/_new_form.html.erb | 5 ++- config/locales/en.yml | 11 ++++- config/routes.rb | 2 +- init.rb | 4 +- lib/body_tracking/project_patch.rb | 3 +- test/fixtures/goals.yml | 3 +- test/system/targets_test.rb | 2 +- 20 files changed, 182 insertions(+), 76 deletions(-) create mode 100644 app/views/goals/_index.html.erb create mode 100644 app/views/goals/_new_form.html.erb create mode 100644 app/views/goals/create.js.erb create mode 100644 app/views/goals/index.html.erb create mode 100644 app/views/goals/new.js.erb 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 @@ -
- <%= fields_for 'measurement[routine_attributes]', routine do |ff| %> - <%= ff.hidden_field :id %> -

- - <%= 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%;" %>

+ +
+ + <%= render partial: 'targets/form', locals: {goal_f: goal_f} %>
diff --git a/app/views/goals/_index.html.erb b/app/views/goals/_index.html.erb new file mode 100644 index 0000000..b090919 --- /dev/null +++ b/app/views/goals/_index.html.erb @@ -0,0 +1,32 @@ +<% if @goals.any? %> + + + + + + + + + + <% @goals.each do |g| %> + + + + + + <% end %> + +
<%= 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 %> +
+<% else %> +

<%= 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 @@ +

<%= t ".heading_new_goal" %>

+ +<%= 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} %> + +
+

+ <%= submit_tag l(:button_create) %> + <%= link_to l(:button_cancel), "#", + onclick: "$('#new-goal').empty(); return false;" %> +

+
+<% end %> +
diff --git a/app/views/goals/create.js.erb b/app/views/goals/create.js.erb new file mode 100644 index 0000000..7019a58 --- /dev/null +++ b/app/views/goals/create.js.erb @@ -0,0 +1,2 @@ +$('#new-goal').empty(); +$('#goals').html('<%= j render partial: 'goals/index' %>'); diff --git a/app/views/goals/index.html.erb b/app/views/goals/index.html.erb new file mode 100644 index 0000000..f0dc038 --- /dev/null +++ b/app/views/goals/index.html.erb @@ -0,0 +1,13 @@ +
+ <%= 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'} %> +
+ +
+
+ +<%= title t(:body_trackers_title), t(".heading") %> +
+ <%= render partial: 'goals/index' %> +
diff --git a/app/views/goals/new.js.erb b/app/views/goals/new.js.erb new file mode 100644 index 0000000..6df8537 --- /dev/null +++ b/app/views/goals/new.js.erb @@ -0,0 +1 @@ +$('#new-goal').html('<%= j render partial: 'goals/new_form' %>'); diff --git a/app/views/layouts/_sidebar.html.erb b/app/views/layouts/_sidebar.html.erb index ad13a09..8b61c5c 100644 --- a/app/views/layouts/_sidebar.html.erb +++ b/app/views/layouts/_sidebar.html.erb @@ -17,8 +17,7 @@
  • <%= 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) %>
  • <%= link_to t(".link_quantities"), project_quantities_path(@project) %>
  • <%= link_to t(".link_units"), project_units_path(@project) %>
  • diff --git a/app/views/targets/_form.html.erb b/app/views/targets/_form.html.erb index 81cf015..8983381 100644 --- a/app/views/targets/_form.html.erb +++ b/app/views/targets/_form.html.erb @@ -1,42 +1,30 @@ -<%= error_messages_for *@targets %> +<% if goal_f.object.is_binding? %> +

    + <%= date_field :effective_from, value: @effective_from %> +

    +<% end %> -
    -
    - <% 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 %> -
    +<%= 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} %> -
    -

    - <%= 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_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;' %> +

    <%= 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 %> +
    + <%= render partial: 'targets/form', locals: {goal_f: goal_f} %> +

    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