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
|
# 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
|
# existing nested object (MeasurementRoutine) if it's not associated with outer object
|
||||||
# https://stackoverflow.com/questions/6346134/
|
# 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
|
@measurement = @project.measurements.new
|
||||||
update_routine_from_params
|
update_routine_from_params
|
||||||
@measurement.attributes = measurement_params
|
@measurement.attributes = measurement_params
|
||||||
@ -127,7 +127,8 @@ class MeasurementsController < ApplicationController
|
|||||||
|
|
||||||
def update_routine_from_params
|
def update_routine_from_params
|
||||||
routine_id = params[:measurement][:routine_attributes][:id]
|
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
|
end
|
||||||
|
|
||||||
def prepare_items
|
def prepare_items
|
||||||
|
@ -5,19 +5,59 @@ class TargetsController < ApplicationController
|
|||||||
|
|
||||||
include Concerns::Finders
|
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
|
def index
|
||||||
prepare_targets
|
prepare_targets
|
||||||
end
|
end
|
||||||
|
|
||||||
def new
|
def new
|
||||||
@target = (@goal || @project.goals.binding).targets.new
|
target = (@goal || @project.goals.binding).targets.new
|
||||||
@target.arity.times { @target.thresholds.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
|
end
|
||||||
|
|
||||||
private
|
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
|
def prepare_targets
|
||||||
@targets = @project.targets.includes(:item, :thresholds)
|
@targets = @project.targets.includes(:item, :thresholds)
|
||||||
end
|
end
|
||||||
|
@ -33,8 +33,13 @@ module BodyTrackersHelper
|
|||||||
end
|
end
|
||||||
|
|
||||||
def quantity_options(domain = :all)
|
def quantity_options(domain = :all)
|
||||||
nested_set_options(@project.quantities.send(domain)) do |q|
|
Quantity.each_with_ancestors(@project.quantities.send(domain)).map do |ancestors|
|
||||||
raw("#{' ' * q.level}#{q.name}")
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -44,6 +49,7 @@ module BodyTrackersHelper
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# TODO: rename to quantities_table_header
|
||||||
def table_header_spec(quantities)
|
def table_header_spec(quantities)
|
||||||
# spec: table of rows (tr), where each row is a hash of cells (td) (hash keeps items
|
# 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:
|
# ordered the way they were added). Hash values determine cell property:
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
module TargetsHelper
|
module TargetsHelper
|
||||||
def condition_options
|
def condition_options
|
||||||
Target::CONDITIONS.each_with_index.to_a
|
Target::CONDITIONS
|
||||||
end
|
end
|
||||||
|
|
||||||
def action_links(m)
|
def action_links(m)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
class Target < ActiveRecord::Base
|
class Target < ActiveRecord::Base
|
||||||
CONDITIONS = [:<, :<=, :>, :>=, :==]
|
CONDITIONS = ['<', '<=', '>', '>=', '==']
|
||||||
|
|
||||||
belongs_to :goal, inverse_of: :targets, required: true
|
belongs_to :goal, inverse_of: :targets, required: true
|
||||||
belongs_to :item, polymorphic: true, inverse_of: :targets
|
belongs_to :item, polymorphic: true, inverse_of: :targets
|
||||||
@ -10,8 +10,8 @@ class Target < ActiveRecord::Base
|
|||||||
accepts_nested_attributes_for :thresholds, allow_destroy: true,
|
accepts_nested_attributes_for :thresholds, allow_destroy: true,
|
||||||
reject_if: proc { |attrs| attrs['quantity_id'].blank? && attrs['value'].blank? }
|
reject_if: proc { |attrs| attrs['quantity_id'].blank? && attrs['value'].blank? }
|
||||||
validate do
|
validate do
|
||||||
errors.add(:thresholds, :count_mismatch) unless thresholds.count == arity
|
errors.add(:thresholds, :count_mismatch) unless thresholds.length == arity
|
||||||
errors.add(:thresholds, :quantity_mismatch) if thresholds.to_a.uniq(&:quantity) != 1
|
errors.add(:thresholds, :quantity_mismatch) if thresholds.to_a.uniq(&:quantity).length != 1
|
||||||
end
|
end
|
||||||
validates :condition, inclusion: {in: CONDITIONS}
|
validates :condition, inclusion: {in: CONDITIONS}
|
||||||
validates :scope, inclusion: {in: [:ingredient, :meal, :day],
|
validates :scope, inclusion: {in: [:ingredient, :meal, :day],
|
||||||
@ -20,8 +20,9 @@ class Target < ActiveRecord::Base
|
|||||||
|
|
||||||
after_initialize do
|
after_initialize do
|
||||||
if new_record?
|
if new_record?
|
||||||
self.condition = CONDITIONS.first
|
self.condition ||= CONDITIONS.first
|
||||||
self.effective_from = Date.current if is_binding?
|
# Target should be only instantiated through Goal, so :is_binding? will be available
|
||||||
|
self.effective_from ||= Date.current if is_binding?
|
||||||
end
|
end
|
||||||
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>
|
<label><%= l(:field_goal) %><span class="required"> *</span></label>
|
||||||
<%= ff.select :id,
|
<%= ff.select :id,
|
||||||
options_from_collection_for_select(@project.goals, :id, :name, goal.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">
|
<% @targets.group_by(&:goal).each do |goal, targets| %>
|
||||||
|
<div class="box">
|
||||||
<div id='goal-form' class="tabular">
|
<div id='goal-form' class="tabular">
|
||||||
<% if @target.goal.persisted? %>
|
<% if goal.persisted? %>
|
||||||
<p><%= render partial: 'goals/show_form', locals: {goal: @target.goal} %></p>
|
<p><%= render partial: 'goals/show_form', locals: {goal: goal} %></p>
|
||||||
<%#= t '.effective_from' %>
|
<%#= t '.effective_from' %>
|
||||||
<p><%= f.date_field :effective_from, disabled: !@target.is_binding? %></p>
|
<p><%= f.date_field :effective_from, disabled: goal.is_binding? %></p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= render partial: 'goals/form', locals: {goal: @target.goal} %>
|
<%= render partial: 'goals/form', locals: {goal: goal} %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr style="width: 95%;">
|
<hr style="width: 95%;">
|
||||||
|
|
||||||
<div class="tabular">
|
<div class="tabular">
|
||||||
|
<% targets.each do |target| %>
|
||||||
<p class="target">
|
<p class="target">
|
||||||
<% @target.thresholds.each_with_index do |t, index| %>
|
<%= f.fields_for 'targets', target, index: '' do |target_f| %>
|
||||||
<%= f.fields_for 'thresholds_attributes', t, index: '' do |ff| %>
|
<%= 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 %>
|
<% if index == 0 %>
|
||||||
<%= ff.select :quantity_id, quantity_options,
|
<%= threshold_f.select :quantity_id, quantity_options,
|
||||||
{include_blank: true, required: true, label: :field_target} %>
|
{include_blank: true, required: true, label: :field_target},
|
||||||
<%= f.select :condition, condition_options, required: true, label: '' %>
|
onchange: "showQuantityPath(event);" %>
|
||||||
|
<%= target_f.select :condition, condition_options, required: true,
|
||||||
|
label: '' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= ff.hidden_field :id %>
|
<%= threshold_f.number_field :value, {size: 8, step: :any, label: ''} %>
|
||||||
<%= ff.number_field :value, {size: 8, step: :any, label: ''} %>
|
|
||||||
<% if index == 0 %>
|
<% if index == 0 %>
|
||||||
<%= ff.select :unit_id, unit_options, {label: ''} %>
|
<%= threshold_f.select :unit_id, unit_options, {label: ''} %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= link_to t(".button_delete_target"), '#', class: 'icon icon-del',
|
<%= link_to t(".button_delete_target"), '#', class: 'icon icon-del',
|
||||||
onclick: "deleteTarget(); return false;" %>
|
onclick: "deleteTarget(); return false;" %>
|
||||||
|
<% end %>
|
||||||
</p>
|
</p>
|
||||||
|
<% end %>
|
||||||
<p>
|
<p>
|
||||||
<%= link_to t(".button_new_target"), '#', class: 'icon icon-add',
|
<%= link_to t(".button_new_target"), '#', class: 'icon icon-add',
|
||||||
onclick: 'newTarget(); return false;' %>
|
onclick: 'newTarget(); return false;' %>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<%= javascript_tag do %>
|
<%= 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 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);
|
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('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();
|
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 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()) {
|
if (row.find('input[id$=__id]').val()) {
|
||||||
row.hide();
|
row.hide();
|
||||||
row.find('input[id$=__destroy]').val('1');
|
row.find('input[id$=__destroy]').val('1');
|
||||||
} else {
|
} else {
|
||||||
row.remove();
|
row.remove();
|
||||||
}
|
}
|
||||||
form.find('p.readout:visible:first label:first').show();
|
form.find('p.target:visible:first label:first').show();
|
||||||
if (form.find('p.readout:visible').length <= 1) {
|
if (form.find('p.target:visible').length <= 1) {
|
||||||
form.find('p.readout:visible a.icon-del').hide();
|
form.find('p.target:visible a.icon-del').hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
<% end %>
|
<% end %>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<h2><%= t ".heading_new_target" %></h2>
|
<h2><%= t ".heading_new_target" %></h2>
|
||||||
|
|
||||||
<%= labelled_form_for @target,
|
<%= labelled_form_for @targets,
|
||||||
url: project_targets_path(@project, @view_params),
|
url: project_targets_path(@project, @view_params),
|
||||||
remote: true,
|
remote: true,
|
||||||
html: {id: 'new-target-form', name: 'new-target-form'} do |f| %>
|
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:
|
contextual:
|
||||||
link_new_target: 'New target'
|
link_new_target: 'New target'
|
||||||
form:
|
form:
|
||||||
|
choose_quantity: "Choose quantity"
|
||||||
button_new_target: 'Add target'
|
button_new_target: 'Add target'
|
||||||
button_delete_target: 'Delete'
|
button_delete_target: 'Delete'
|
||||||
effective_from: ', effective from:'
|
effective_from: ', effective from:'
|
||||||
|
@ -2,21 +2,31 @@ module BodyTracking::AwesomeNestedSetPatch
|
|||||||
CollectiveIdea::Acts::NestedSet.class_eval do
|
CollectiveIdea::Acts::NestedSet.class_eval do
|
||||||
module CollectiveIdea::Acts::NestedSet
|
module CollectiveIdea::Acts::NestedSet
|
||||||
class Iterator
|
class Iterator
|
||||||
def each_with_path
|
def each_with_ancestors
|
||||||
return to_enum(__method__) { objects.length } unless block_given?
|
return to_enum(__method__) { objects.length } unless block_given?
|
||||||
|
|
||||||
path = [nil]
|
ancestors = [nil]
|
||||||
objects.each do |o|
|
objects.each do |o|
|
||||||
path[path.rindex(o.parent)+1..-1] = o
|
ancestors[ancestors.rindex(o.parent)+1..-1] = o
|
||||||
yield [o, path.map { |q| q.try(:name) }.join('::')]
|
yield ancestors
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module Model
|
module Model
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
def each_with_path(objects, &block)
|
def each_with_path(objects)
|
||||||
Iterator.new(objects).each_with_path(&block)
|
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
|
end
|
||||||
end
|
end
|
||||||
|
Reference in New Issue
Block a user