1
0

Added Target create action

This commit is contained in:
cryptogopher 2020-07-25 16:28:55 +02:00
parent 5b83860ed7
commit ffcc9553d5
12 changed files with 154 additions and 66 deletions

View File

@ -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

View File

@ -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

View File

@ -33,8 +33,13 @@ module BodyTrackersHelper
end
def quantity_options(domain = :all)
nested_set_options(@project.quantities.send(domain)) do |q|
raw("#{'&ensp;' * q.level}#{q.name}")
Quantity.each_with_ancestors(@project.quantities.send(domain)).map do |ancestors|
quantity = ancestors.last
[
raw("#{'&ensp;' * (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:

View File

@ -1,6 +1,6 @@
module TargetsHelper
def condition_options
Target::CONDITIONS.each_with_index.to_a
Target::CONDITIONS
end
def action_links(m)

View File

@ -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

View File

@ -1,4 +1,4 @@
<%= fields_for 'target[goal_attributes]', goal do |ff| %>
<%= fields_for 'goal', goal do |ff| %>
<label><%= l(:field_goal) %><span class="required"> *</span></label>
<%= ff.select :id,
options_from_collection_for_select(@project.goals, :id, :name, goal.id),

View File

@ -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? %>)

View File

@ -1,66 +1,86 @@
<%= error_messages_for @target %>
<%= error_messages_for *@targets %>
<div class="box">
<div id='goal-form' class="tabular">
<% if @target.goal.persisted? %>
<p><%= render partial: 'goals/show_form', locals: {goal: @target.goal} %></p>
<%#= t '.effective_from' %>
<p><%= f.date_field :effective_from, disabled: !@target.is_binding? %></p>
<% else %>
<%= render partial: 'goals/form', locals: {goal: @target.goal} %>
<% end %>
</div>
<hr style="width: 95%;">
<div class="tabular">
<p class="target">
<% @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| %>
<div class="box">
<div id='goal-form' class="tabular">
<% if goal.persisted? %>
<p><%= render partial: 'goals/show_form', locals: {goal: goal} %></p>
<%#= t '.effective_from' %>
<p><%= f.date_field :effective_from, disabled: goal.is_binding? %></p>
<% else %>
<%= render partial: 'goals/form', locals: {goal: goal} %>
<% end %>
<% end %>
<%= link_to t(".button_delete_target"), '#', class: 'icon icon-del',
onclick: "deleteTarget(); return false;" %>
</p>
<p>
<%= link_to t(".button_new_target"), '#', class: 'icon icon-add',
onclick: 'newTarget(); return false;' %>
</p>
</div>
<hr style="width: 95%;">
<div class="tabular">
<% targets.each do |target| %>
<p class="target">
<%= f.fields_for 'targets', target, index: '' do |target_f| %>
<%= target_f.hidden_field :id %>
<%= target_f.hidden_field :_destroy %>
<em class="info"><%= t ".choose_quantity" %></em>
<% 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 %>
</p>
<% end %>
<p>
<%= link_to t(".button_new_target"), '#', class: 'icon icon-add',
onclick: 'newTarget(); return false;' %>
</p>
</div>
</div>
</div>
<% end %>
<%= javascript_tag do %>
function newReadout() {
function showQuantityPath(event) {
$(event.target).prevAll('em').text($('option:selected', event.target).attr('data-path'));
}
$(document).ajaxComplete(function() {
$('select[id$=__quantity_id]').trigger(jQuery.Event('change'));
})
function newTarget() {
var form = $(event.target).closest('form');
var row = form.find('p.readout:visible:last');
var row = form.find('p.target:visible:last');
var new_row = row.clone().insertAfter(row);
new_row.find('input[id$=__id], input[id$=__value], select[id$=_quantity__id]').val('');
new_row.find('em').text('<%= t ".choose_quantity" %>');
new_row.find('input, select').val('');
new_row.find('select[id$=__unit_id]').val(row.find('select[id$=__unit_id]').val());
new_row.find('input[id$=__destroy]').val('');
new_row.find('label:first').hide();
form.find('p.readout:visible a.icon-del').show();
form.find('p.target:visible a.icon-del').show();
}
function deleteReadout() {
function deleteTarget() {
var form = $(event.target).closest('form');
var row = $(event.target).closest('p.readout');
var row = $(event.target).closest('p.target');
if (row.find('input[id$=__id]').val()) {
row.hide();
row.find('input[id$=__destroy]').val('1');
} else {
row.remove();
}
form.find('p.readout:visible:first label:first').show();
if (form.find('p.readout:visible').length <= 1) {
form.find('p.readout:visible a.icon-del').hide();
form.find('p.target:visible:first label:first').show();
if (form.find('p.target:visible').length <= 1) {
form.find('p.target:visible a.icon-del').hide();
}
}
<% end %>

View File

@ -1,6 +1,6 @@
<h2><%= t ".heading_new_target" %></h2>
<%= labelled_form_for @target,
<%= labelled_form_for @targets,
url: project_targets_path(@project, @view_params),
remote: true,
html: {id: 'new-target-form', name: 'new-target-form'} do |f| %>

View File

@ -0,0 +1,7 @@
$('#new-targets').empty();
<% case @view_params[:view] %>
<% when :by_date %>
$('#targets').html('<%= j render partial: 'targets/by_date' %>');
<% else %>
$('#targets').html('<%= j render partial: 'targets/index' %>');
<% end %>

View File

@ -93,6 +93,7 @@ en:
contextual:
link_new_target: 'New target'
form:
choose_quantity: "Choose quantity"
button_new_target: 'Add target'
button_delete_target: 'Delete'
effective_from: ', effective from:'

View File

@ -2,21 +2,31 @@ module BodyTracking::AwesomeNestedSetPatch
CollectiveIdea::Acts::NestedSet.class_eval do
module CollectiveIdea::Acts::NestedSet
class Iterator
def each_with_path
def each_with_ancestors
return to_enum(__method__) { objects.length } unless block_given?
path = [nil]
ancestors = [nil]
objects.each do |o|
path[path.rindex(o.parent)+1..-1] = o
yield [o, path.map { |q| q.try(:name) }.join('::')]
ancestors[ancestors.rindex(o.parent)+1..-1] = o
yield ancestors
end
end
end
module Model
module ClassMethods
def each_with_path(objects, &block)
Iterator.new(objects).each_with_path(&block)
def each_with_path(objects)
return to_enum(__method__, objects) { objects.length } unless block_given?
Iterator.new(objects).each_with_ancestors do |ancestors|
yield [ancestors.last, ancestors.map { |q| q.try(:name) }.join('::')]
end
end
def each_with_ancestors(objects)
return to_enum(__method__, objects) { objects.length } unless block_given?
Iterator.new(objects).each_with_ancestors { |ancestors| yield ancestors.dup }
end
end
end