diff --git a/app/controllers/measurements_controller.rb b/app/controllers/measurements_controller.rb index 3b40b71..8fdb316 100644 --- a/app/controllers/measurements_controller.rb +++ b/app/controllers/measurements_controller.rb @@ -31,7 +31,7 @@ class MeasurementsController < ApplicationController # Nested attributes cannot create outer object (Measurement) and at the same time edit # existing nested object (MeasurementRoutine) if it's not associated with outer object # https://stackoverflow.com/questions/6346134/ - # That's why routine needs to be found and associated before measurement initialization + # That's why Routine needs to be found and associated before Measurement initialization @measurement = @project.measurements.new update_routine_from_params @measurement.attributes = measurement_params @@ -127,7 +127,8 @@ class MeasurementsController < ApplicationController def update_routine_from_params routine_id = params[:measurement][:routine_attributes][:id] - @measurement.routine = @project.measurement_routines.find_by(id: routine_id) if routine_id + return unless routine_id + @measurement.routine = @project.measurement_routines.find_by(id: routine_id) end def prepare_items diff --git a/app/controllers/targets_controller.rb b/app/controllers/targets_controller.rb index d5804f4..154ed8a 100644 --- a/app/controllers/targets_controller.rb +++ b/app/controllers/targets_controller.rb @@ -5,19 +5,59 @@ class TargetsController < ApplicationController include Concerns::Finders - before_action :find_project_by_project_id, only: [:index, :new] + before_action :find_project_by_project_id, only: [:index, :new, :create] def index prepare_targets end def new - @target = (@goal || @project.goals.binding).targets.new - @target.arity.times { @target.thresholds.new } + target = (@goal || @project.goals.binding).targets.new + target.arity.times { target.thresholds.new } + @targets = [target] + end + + def create + goal = @project.goals.find_by(id: params[:goal][:id]) || @project.goals.build(goal_params) + @targets = goal.targets.build(targets_params[:targets]) do |target| + target.effective_from = params[:target][:effective_from] + end + + # :save only after build, to re-display values in case records are invalid + if goal.save && Target.transaction { @targets.all?(&:save) } + flash[:notice] = 'Created new target(s)' + prepare_targets + else + @targets.each do |target| + (target.thresholds.length...target.arity).each { target.thresholds.new } + target.thresholds[target.arity..-1].map(&:destroy) + end + render :new + end end private + def goal_params + params.require(:goal).permit(:id, :name, :description) + end + + def targets_params + params.require(:target).permit( + targets: [ + :id, + :condition, + :scope, + thresholds_attributes: [ + :id, + :quantity_id, + :value, + :unit_id + ] + ] + ) + end + def prepare_targets @targets = @project.targets.includes(:item, :thresholds) end diff --git a/app/helpers/body_trackers_helper.rb b/app/helpers/body_trackers_helper.rb index ef383ab..a9aa842 100644 --- a/app/helpers/body_trackers_helper.rb +++ b/app/helpers/body_trackers_helper.rb @@ -33,8 +33,13 @@ module BodyTrackersHelper end def quantity_options(domain = :all) - nested_set_options(@project.quantities.send(domain)) do |q| - raw("#{' ' * q.level}#{q.name}") + Quantity.each_with_ancestors(@project.quantities.send(domain)).map do |ancestors| + quantity = ancestors.last + [ + raw("#{' ' * (ancestors.length-2)}#{quantity.name}"), + quantity.id, + {'data-path' => ancestors[1..-2].reduce('::') { |m, q| "#{m}#{q.try(:name)}::" }} + ] end end @@ -44,6 +49,7 @@ module BodyTrackersHelper end end + # TODO: rename to quantities_table_header def table_header_spec(quantities) # spec: table of rows (tr), where each row is a hash of cells (td) (hash keeps items # ordered the way they were added). Hash values determine cell property: diff --git a/app/helpers/targets_helper.rb b/app/helpers/targets_helper.rb index e695f23..9365156 100644 --- a/app/helpers/targets_helper.rb +++ b/app/helpers/targets_helper.rb @@ -1,6 +1,6 @@ module TargetsHelper def condition_options - Target::CONDITIONS.each_with_index.to_a + Target::CONDITIONS end def action_links(m) diff --git a/app/models/target.rb b/app/models/target.rb index 7569e1a..8a639ca 100644 --- a/app/models/target.rb +++ b/app/models/target.rb @@ -1,5 +1,5 @@ class Target < ActiveRecord::Base - CONDITIONS = [:<, :<=, :>, :>=, :==] + CONDITIONS = ['<', '<=', '>', '>=', '=='] belongs_to :goal, inverse_of: :targets, required: true belongs_to :item, polymorphic: true, inverse_of: :targets @@ -10,8 +10,8 @@ class Target < ActiveRecord::Base accepts_nested_attributes_for :thresholds, allow_destroy: true, reject_if: proc { |attrs| attrs['quantity_id'].blank? && attrs['value'].blank? } validate do - errors.add(:thresholds, :count_mismatch) unless thresholds.count == arity - errors.add(:thresholds, :quantity_mismatch) if thresholds.to_a.uniq(&:quantity) != 1 + errors.add(:thresholds, :count_mismatch) unless thresholds.length == arity + errors.add(:thresholds, :quantity_mismatch) if thresholds.to_a.uniq(&:quantity).length != 1 end validates :condition, inclusion: {in: CONDITIONS} validates :scope, inclusion: {in: [:ingredient, :meal, :day], @@ -20,8 +20,9 @@ class Target < ActiveRecord::Base after_initialize do if new_record? - self.condition = CONDITIONS.first - self.effective_from = Date.current if is_binding? + self.condition ||= CONDITIONS.first + # Target should be only instantiated through Goal, so :is_binding? will be available + self.effective_from ||= Date.current if is_binding? end end diff --git a/app/views/goals/_show_form.html.erb b/app/views/goals/_show_form.html.erb index 121eb9d..4ade2c8 100644 --- a/app/views/goals/_show_form.html.erb +++ b/app/views/goals/_show_form.html.erb @@ -1,4 +1,4 @@ -<%= fields_for 'target[goal_attributes]', goal do |ff| %> +<%= fields_for 'goal', goal do |ff| %> <%= ff.select :id, options_from_collection_for_select(@project.goals, :id, :name, goal.id), diff --git a/app/views/goals/show.js.erb b/app/views/goals/show.js.erb new file mode 100644 index 0000000..fef1988 --- /dev/null +++ b/app/views/goals/show.js.erb @@ -0,0 +1,2 @@ +$('#goal-form').html('<%= j render partial: 'goals/show_form', locals: {goal: @goal} %>'); +$('input#target_effective_from').prop('disabled', <%= @goal.is_binding? %>) diff --git a/app/views/targets/_form.html.erb b/app/views/targets/_form.html.erb index 2fe4acd..23d9e9d 100644 --- a/app/views/targets/_form.html.erb +++ b/app/views/targets/_form.html.erb @@ -1,66 +1,86 @@ -<%= error_messages_for @target %> +<%= error_messages_for *@targets %> -
<%= render partial: 'goals/show_form', locals: {goal: @target.goal} %>
- <%#= t '.effective_from' %> -<%= f.date_field :effective_from, disabled: !@target.is_binding? %>
- <% else %> - <%= render partial: 'goals/form', locals: {goal: @target.goal} %> - <% end %> -- <% @target.thresholds.each_with_index do |t, index| %> - <%= f.fields_for 'thresholds_attributes', t, index: '' do |ff| %> - <% if index == 0 %> - <%= ff.select :quantity_id, quantity_options, - {include_blank: true, required: true, label: :field_target} %> - <%= f.select :condition, condition_options, required: true, label: '' %> - <% end %> - <%= ff.hidden_field :id %> - <%= ff.number_field :value, {size: 8, step: :any, label: ''} %> - <% if index == 0 %> - <%= ff.select :unit_id, unit_options, {label: ''} %> - <% end %> +<% @targets.group_by(&:goal).each do |goal, targets| %> +
<%= render partial: 'goals/show_form', locals: {goal: goal} %>
+ <%#= t '.effective_from' %> +<%= f.date_field :effective_from, disabled: goal.is_binding? %>
+ <% else %> + <%= render partial: 'goals/form', locals: {goal: goal} %> <% end %> - <% end %> - <%= link_to t(".button_delete_target"), '#', class: 'icon icon-del', - onclick: "deleteTarget(); return false;" %> - -- <%= link_to t(".button_new_target"), '#', class: 'icon icon-add', - onclick: 'newTarget(); return false;' %> -
++ <%= f.fields_for 'targets', target, index: '' do |target_f| %> + <%= target_f.hidden_field :id %> + <%= target_f.hidden_field :_destroy %> + <%= t ".choose_quantity" %> + <% target.thresholds.each_with_index do |thr, index| %> + <%= 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}, + onchange: "showQuantityPath(event);" %> + <%= target_f.select :condition, condition_options, required: true, + label: '' %> + <% end %> + <%= threshold_f.number_field :value, {size: 8, step: :any, label: ''} %> + <% if index == 0 %> + <%= threshold_f.select :unit_id, unit_options, {label: ''} %> + <% end %> + <% end %> + <% end %> + <%= link_to t(".button_delete_target"), '#', class: 'icon icon-del', + onclick: "deleteTarget(); return false;" %> + <% end %> +
+ <% end %> ++ <%= link_to t(".button_new_target"), '#', class: 'icon icon-add', + onclick: 'newTarget(); return false;' %> +
+