Added Target create action
This commit is contained in:
parent
5b83860ed7
commit
ffcc9553d5
@ -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
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -1,6 +1,6 @@
|
||||
module TargetsHelper
|
||||
def condition_options
|
||||
Target::CONDITIONS.each_with_index.to_a
|
||||
Target::CONDITIONS
|
||||
end
|
||||
|
||||
def action_links(m)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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),
|
||||
|
2
app/views/goals/show.js.erb
Normal file
2
app/views/goals/show.js.erb
Normal 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? %>)
|
@ -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 %>
|
||||
|
@ -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| %>
|
||||
|
7
app/views/targets/create.js.erb
Normal file
7
app/views/targets/create.js.erb
Normal 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 %>
|
@ -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:'
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user