From 20deb944c33915fb0e299a9eb80119125b6303d8 Mon Sep 17 00:00:00 2001 From: cryptogopher Date: Sun, 29 Dec 2019 15:12:05 +0100 Subject: [PATCH] Updated Ingredients to use ItemsWithQuantities --- app/controllers/ingredients_controller.rb | 27 ++---- app/controllers/measurements_controller.rb | 2 + app/helpers/body_trackers_helper.rb | 4 + app/helpers/ingredients_helper.rb | 4 + app/helpers/measurements_helper.rb | 5 - app/models/ingredient.rb | 96 +------------------ app/models/measurement.rb | 5 +- app/views/ingredients/_filters.html.erb | 2 +- .../{_list.html.erb => _index.html.erb} | 4 +- .../ingredients/_list_nutrients.html.erb | 72 -------------- app/views/ingredients/_nutrients.html.erb | 79 +++++++++++++++ app/views/ingredients/_options.html.erb | 22 +---- app/views/ingredients/index.html.erb | 2 +- app/views/ingredients/nutrients.html.erb | 2 +- app/views/ingredients/toggle.js.erb | 2 +- .../ingredients/toggle_nutrient_column.js.erb | 2 +- app/views/measurements/_readouts.html.erb | 1 + lib/body_tracking/items_with_quantities.rb | 14 ++- lib/body_tracking/project_patch.rb | 6 +- 19 files changed, 125 insertions(+), 226 deletions(-) rename app/views/ingredients/{_list.html.erb => _index.html.erb} (91%) delete mode 100644 app/views/ingredients/_list_nutrients.html.erb create mode 100644 app/views/ingredients/_nutrients.html.erb diff --git a/app/controllers/ingredients_controller.rb b/app/controllers/ingredients_controller.rb index 642b39b..6cdd56d 100644 --- a/app/controllers/ingredients_controller.rb +++ b/app/controllers/ingredients_controller.rb @@ -3,6 +3,8 @@ class IngredientsController < ApplicationController menu_item :body_trackers + helper :body_trackers + before_action :init_session_filters before_action :find_project_by_project_id, only: [:index, :nutrients, :create, :import, :filter, :filter_nutrients] @@ -204,29 +206,14 @@ class IngredientsController < ApplicationController end def prepare_ingredients - @ingredients, @formula_q = @project.ingredients.includes(:ref_unit, :source) - .filter(@project, session[:i_filters]) + @ingredients, @formula_q = @project.ingredients + .includes(:ref_unit, :source) + .filter(session[:i_filters]) end def prepare_nutrients @quantities = @project.nutrients_column_view.quantities - ingredients, requested_n, extra_n, @formula_q = @project.ingredients - .filter(@project, session[:i_filters], @quantities) - - @nutrients = {} - @extra_nutrients = {} - ingredients.each_with_index do |i, index| - @nutrients[i] = [] - requested_n[index].each do |q_name, value| - amount, unitname = value - @nutrients[i] << [q_name, amount.nil? ? '-' : "#{amount} [#{unitname || '-'}]"] - end - - @extra_nutrients[i] = [] - extra_n[index].each do |q_name, value| - amount, unitname = value - @extra_nutrients[i] << [q_name, amount.nil? ? '-' : "#{amount} [#{unitname || '-'}]"] - end - end + @ingredients, @requested_n, @extra_n, @formula_q = @project.ingredients + .filter(session[:i_filters], @quantities) end end diff --git a/app/controllers/measurements_controller.rb b/app/controllers/measurements_controller.rb index eb00545..9de8107 100644 --- a/app/controllers/measurements_controller.rb +++ b/app/controllers/measurements_controller.rb @@ -1,6 +1,8 @@ class MeasurementsController < ApplicationController menu_item :body_trackers + helper :body_trackers + before_action :init_session_filters before_action :find_project_by_project_id, only: [:index, :new, :create, :filter] before_action :find_quantity_by_quantity_id, only: [:toggle_column] diff --git a/app/helpers/body_trackers_helper.rb b/app/helpers/body_trackers_helper.rb index 39a5f1c..ef34b8a 100644 --- a/app/helpers/body_trackers_helper.rb +++ b/app/helpers/body_trackers_helper.rb @@ -1,2 +1,6 @@ module BodyTrackersHelper + def format_value(value) + amount, unitname = value + amount.nil? ? '-' : "#{amount} [#{unitname || '-'}]" + end end diff --git a/app/helpers/ingredients_helper.rb b/app/helpers/ingredients_helper.rb index 53eaf9b..3c9a476 100644 --- a/app/helpers/ingredients_helper.rb +++ b/app/helpers/ingredients_helper.rb @@ -38,4 +38,8 @@ module IngredientsHelper [translations[k.to_sym], k] end end + + def action_links(i) + delete_link(ingredient_path(i), {remote: true, data: {}}) + end end diff --git a/app/helpers/measurements_helper.rb b/app/helpers/measurements_helper.rb index 484b873..5dc740d 100644 --- a/app/helpers/measurements_helper.rb +++ b/app/helpers/measurements_helper.rb @@ -9,11 +9,6 @@ module MeasurementsHelper m.taken_at.getlocal.strftime("%R") end - def format_value(value) - amount, unitname = value - amount.nil? ? '-' : "#{amount} [#{unitname || '-'}]" - end - def toggle_column_options disabled = [] enabled_columns = @scoping_measurement.column_view.quantities diff --git a/app/models/ingredient.rb b/app/models/ingredient.rb index 0e90b92..88749ec 100644 --- a/app/models/ingredient.rb +++ b/app/models/ingredient.rb @@ -1,6 +1,4 @@ class Ingredient < ActiveRecord::Base - include BodyTracking::Formula - enum group: { other: 0, meat: 1 @@ -23,7 +21,8 @@ class Ingredient < ActiveRecord::Base } # Nutrient quantity_id uniqueness check for nested attributes validate do - quantities = self.nutrients.map { |n| n.quantity_id } + quantities = self.nutrients.reject { |n| n.marked_for_destruction? } + .map { |n| n.quantity_id } if quantities.length != quantities.uniq.length errors.add(:nutrients, :duplicated_quantity) end @@ -46,95 +45,4 @@ class Ingredient < ActiveRecord::Base def toggle_hidden! self.toggle!(:hidden) end - - def self.filter(project, filters, requested_q = Quantity.none) - ingredients = all - - if filters[:name].present? - ingredients = ingredients.where("name LIKE ?", "%#{filters[:name]}%") - end - - if filters[:visibility].present? - ingredients = ingredients.where(hidden: filters[:visibility] == "1" ? false : true) - end - - formula_q = if filters[:nutrients].present? - project.quantities.new(name: '__internal_q', - formula: filters[:nutrients], - domain: :diet) - end - apply_formula = formula_q.present? && formula_q.valid? - - result = - if !requested_q.empty? || apply_formula - computed = ingredients.compute_nutrients(requested_q, apply_formula && formula_q) - requested_q.present? ? computed : [computed[0]] - else - [ingredients] - end - result.push(formula_q) - end - - def self.compute_nutrients(requested_q, filter_q = nil) - ingredients = all - unchecked_q = requested_q.map { |q| [q, nil] } - unchecked_q << [filter_q, nil] if filter_q - - nutrients = Hash.new { |h,k| h[k] = {} } - Nutrient.where(ingredient: ingredients).includes(:quantity, :unit) - .order('quantities.lft') - .pluck('quantities.name', :ingredient_id, :amount, 'units.shortname') - .each { |q_name, i_id, a, u_id| nutrients[q_name][i_id] = [a, u_id] } - - extra_q = nutrients.keys - requested_q.pluck(:name) - - completed_q = {} - # FIXME: loop should finish unless there is circular dependency in formulas - # for now we don't guard against that - while !unchecked_q.empty? - q, deps = unchecked_q.shift - - # quantity not computable (no formula) or not requiring calculation/computed - if q.formula.blank? || (nutrients[q.name].length == ingredients.count) - completed_q[q.name] = nutrients.delete(q.name) { {} } - next - end - - # quantity with formula requires refresh of dependencies availability - if deps.nil? || !deps.empty? - deps ||= q.formula_quantities - deps.reject! { |q| completed_q.has_key?(q.name) } - deps.each { |q| unchecked_q << [q, nil] unless unchecked_q.index { |u| u[0] == q } } - end - - # quantity with formula has all dependencies satisfied, requires calculation - if deps.empty? - input_q = q.formula_quantities - inputs = ingredients.select { |i| nutrients[q.name][i.id].nil? }.map do |i| - [ - i, - input_q.map do |i_q| - nutrient_data = completed_q[i_q.name][i.id] || [nil, nil] - [i_q.name, nutrient_data[0]] - end.to_h - ] - end - q.calculate(inputs).each { |i, result| nutrients[q.name][i.id] = result } - unchecked_q.unshift([q, deps]) - next - end - - # quantity still has unsatisfied dependencies, move to the end of queue - unchecked_q << [q, deps] - end - - all_q = nutrients.merge(completed_q) - [ - filter_q ? ingredients.to_a.keep_if { |i| all_q[filter_q.name][i.id][0] } : ingredients, - ingredients.map { |i| requested_q.map { |q| [q.name, all_q[q.name][i.id]] } }, - ingredients.map do |i| - extra_q.map { |q_name| [q_name, all_q[q_name][i.id]] if all_q[q_name][i.id] } - end - ] - end end diff --git a/app/models/measurement.rb b/app/models/measurement.rb index ff92746..800b14b 100644 --- a/app/models/measurement.rb +++ b/app/models/measurement.rb @@ -10,9 +10,8 @@ class Measurement < ActiveRecord::Base # Readout quantity_id + unit_id uniqueness validation. Cannot be effectively # checked on Readout model level. validate do - quantities = self.readouts.map do |r| - [r.quantity_id, r.unit_id] unless r.marked_for_destruction? - end + quantities = self.readouts.reject { |r| r.marked_for_destruction? } + .map { |r| [r.quantity_id, r.unit_id] } if quantities.length != quantities.uniq.length errors.add(:readouts, :duplicated_quantity_unit_pair) end diff --git a/app/views/ingredients/_filters.html.erb b/app/views/ingredients/_filters.html.erb index bcc4d21..7f7be6a 100644 --- a/app/views/ingredients/_filters.html.erb +++ b/app/views/ingredients/_filters.html.erb @@ -19,7 +19,7 @@ onchange: '$("#filters-form").submit();' %> - <%= text_field_tag 'filters[nutrients]', session[:i_filters][:nutrients], + <%= text_field_tag 'filters[formula]', session[:i_filters][:formula], placeholder: 'conditional expression including nutrients', size: 40, style: 'box-sizing:border-box; width:100%;', onblur: '$("#filters-form").submit(); return false;' %> diff --git a/app/views/ingredients/_list.html.erb b/app/views/ingredients/_index.html.erb similarity index 91% rename from app/views/ingredients/_list.html.erb rename to app/views/ingredients/_index.html.erb index 8568a62..a3c0bd3 100644 --- a/app/views/ingredients/_list.html.erb +++ b/app/views/ingredients/_index.html.erb @@ -32,9 +32,7 @@ <%= i.source.name if i.source.present? %> <%= ", #{i.source_ident}" if i.source_ident.present? %> - - <%= delete_link ingredient_path(i), {remote: true, data: {}} %> - + <%= action_links(i) %> <% end %> diff --git a/app/views/ingredients/_list_nutrients.html.erb b/app/views/ingredients/_list_nutrients.html.erb deleted file mode 100644 index d6e6ca2..0000000 --- a/app/views/ingredients/_list_nutrients.html.erb +++ /dev/null @@ -1,72 +0,0 @@ -<%= render partial: 'ingredients/filters', - locals: {url: filter_nutrients_project_ingredients_path(@project)} %> - -<% if @nutrients.any? %> - <%= render partial: 'ingredients/options' %> - - - - - <% total_width = 3 + @quantities.length %> - - <% @quantities.each do |q| %> - - <% end %> - - - - <% @nutrients.each do |i, values| %> - <% row_class = "ingredient#{' hidden' if i.hidden} #{cycle('odd', 'even')}" %> - - - <% values.each do |*, value| %> - - <% end %> - - - - - <% values.each do |q_name, *| %> - - <% end %> - - - - <% values.each do |*, value| %> - - <% end %> - - - <% extras = @extra_nutrients[i] %> - <% extras.each_slice(@quantities.length).with_index do |values, index| %> - - - <% values.each do |q_name, *| %> - - <% end %> - <% if @quantities.length > values.length %> - - <% end %> - - - - <% values.each do |*, value| %> - - <% end %> - <% if @quantities.length > values.length %> - - <% end %> - - <% end %> - - <% end %> - -
<%= l(:field_name) %><%= q.name %>
- <%= i.name %> - <%= value %>
-<% else %> -

<%= l(:label_no_data) %>

-<% end %> diff --git a/app/views/ingredients/_nutrients.html.erb b/app/views/ingredients/_nutrients.html.erb new file mode 100644 index 0000000..3c801d5 --- /dev/null +++ b/app/views/ingredients/_nutrients.html.erb @@ -0,0 +1,79 @@ +<%= render partial: 'ingredients/filters', + locals: {url: filter_nutrients_project_ingredients_path(@project)} %> + +<% if @ingredients.any? %> + <%= render partial: 'ingredients/options' %> + + + + + <% total_width = 4 + @quantities.length %> + + <% @quantities.each do |q| %> + + <% end %> + + + + + + <% @ingredients.each_with_index do |i, index| %> + <% row_class = "ingredient#{' hidden' if i.hidden} #{cycle('odd', 'even')}" %> + + + <% @requested_n[index].each do |*, value| %> + + <% end %> + + + + + <% if @quantities.length > 0 + rows = (@extra_n[index].length - 1) / @quantities.length + 2 + else + rows = 1 + end %> + + <% @requested_n[index].each do |q_name, value| %> + + <% end %> + + + + <% next unless @quantities.length > 0 %> + <% @extra_n[index].each_slice(@quantities.length) do |values| %> + + <% values.each do |q_name, value| %> + + <% end %> + <% if @quantities.length > values.length %> + + <% end %> + + <% end %> + + <% end %> + +
<%= l(:field_name) %> +
+ <%= link_to '', + toggle_column_project_ingredients_path(@project, quantity_id: q.id), + {class: "icon icon-close", method: :post, remote: true} %> +
+ <%= q.name %> +
<%= l(:field_action) %>
+ <%= i.name %> + <%= format_value(value) %><%= action_links(i) %>
+<% else %> +

<%= l(:label_no_data) %>

+<% end %> diff --git a/app/views/ingredients/_options.html.erb b/app/views/ingredients/_options.html.erb index 5c1475f..9b9708e 100644 --- a/app/views/ingredients/_options.html.erb +++ b/app/views/ingredients/_options.html.erb @@ -8,28 +8,10 @@ - - + +
- <%= select_tag 'quantity_id', toggle_column_options %> - - <%= submit_tag l(:button_add) %> - <%= select_tag 'quantity_id', toggle_column_options %><%= submit_tag l(:button_add) %>
<% end %> - - - - <% total_width = 3 + @quantities.length %> - - <% @quantities.each do |q| %> - - <% end %> - -
- <%= link_to l(:button_hide), - toggle_column_project_ingredients_path(@project, quantity_id: q.id), - {class: "icon icon-close", method: :post, remote: true} %> -
diff --git a/app/views/ingredients/index.html.erb b/app/views/ingredients/index.html.erb index d932a9d..e56fa40 100644 --- a/app/views/ingredients/index.html.erb +++ b/app/views/ingredients/index.html.erb @@ -10,5 +10,5 @@

<%= t ".heading" %>

- <%= render partial: 'ingredients/list' %> + <%= render partial: 'ingredients/index' %>
diff --git a/app/views/ingredients/nutrients.html.erb b/app/views/ingredients/nutrients.html.erb index e109571..6218582 100644 --- a/app/views/ingredients/nutrients.html.erb +++ b/app/views/ingredients/nutrients.html.erb @@ -10,5 +10,5 @@

<%= t ".heading" %>

- <%= render partial: 'ingredients/list_nutrients' %> + <%= render partial: 'ingredients/nutrients' %>
diff --git a/app/views/ingredients/toggle.js.erb b/app/views/ingredients/toggle.js.erb index c6f9efc..b78ba15 100644 --- a/app/views/ingredients/toggle.js.erb +++ b/app/views/ingredients/toggle.js.erb @@ -1,3 +1,3 @@ $('div[id^=flash_]').remove(); $('#content').prepend('<%= escape_javascript(render_flash_messages) %>'); -$('#ingredients').html('<%= escape_javascript(render partial: 'ingredients/list') %>'); +$('#ingredients').html('<%= escape_javascript(render partial: 'ingredients/index') %>'); diff --git a/app/views/ingredients/toggle_nutrient_column.js.erb b/app/views/ingredients/toggle_nutrient_column.js.erb index 8db6246..06a87bd 100644 --- a/app/views/ingredients/toggle_nutrient_column.js.erb +++ b/app/views/ingredients/toggle_nutrient_column.js.erb @@ -1,3 +1,3 @@ $('div[id^=flash_]').remove(); $('#content').prepend('<%= escape_javascript(render_flash_messages) %>'); -$('#nutrients').html('<%= escape_javascript(render partial: 'ingredients/list_nutrients') %>'); +$('#nutrients').html('<%= escape_javascript(render partial: 'ingredients/nutrients') %>'); diff --git a/app/views/measurements/_readouts.html.erb b/app/views/measurements/_readouts.html.erb index 18de1fd..2e7506a 100644 --- a/app/views/measurements/_readouts.html.erb +++ b/app/views/measurements/_readouts.html.erb @@ -22,6 +22,7 @@ <%= l(:field_action) %> + <% @measurements.each_with_index do |m, index| %> <% row_class = "measurement #{cycle('odd', 'even')}" %> diff --git a/lib/body_tracking/items_with_quantities.rb b/lib/body_tracking/items_with_quantities.rb index 51890af..b930133 100644 --- a/lib/body_tracking/items_with_quantities.rb +++ b/lib/body_tracking/items_with_quantities.rb @@ -1,5 +1,14 @@ module BodyTracking module ItemsWithQuantities + QUANTITY_DOMAINS = { + Measurement => :measurement, + Ingredient => :diet + } + VALUE_COLUMNS = { + Measurement => :value, + Ingredient => :amount + } + def filter(filters, requested_q = nil) items = all.where(filters[:scope]) @@ -12,7 +21,7 @@ module BodyTracking end project = proxy_association.owner - domain = {Measurement => :measurement, Ingredient => :diet}[proxy_association.klass] + domain = QUANTITY_DOMAINS[proxy_association.klass] formula_q = if filters[:formula].present? project.quantities.new(name: '__internal_q', formula: filters[:formula], @@ -45,7 +54,8 @@ module BodyTracking item_foreign_key = subitem_reflection.foreign_key subitems_scope.includes(:quantity, :unit) .order('quantities.lft') - .pluck(item_foreign_key, 'quantities.name', :value, 'units.shortname') + .pluck(item_foreign_key, 'quantities.name', VALUE_COLUMNS[item_class], + 'units.shortname') .each { |item_id, q_name, a, u_id| subitems[q_name][item_id] = [a, u_id] } extra_q = subitems.keys - requested_q.pluck(:name) diff --git a/lib/body_tracking/project_patch.rb b/lib/body_tracking/project_patch.rb index 4d39353..994ee52 100644 --- a/lib/body_tracking/project_patch.rb +++ b/lib/body_tracking/project_patch.rb @@ -1,8 +1,10 @@ module BodyTracking module ProjectPatch Project.class_eval do - has_many :measurements, -> { order "taken_at DESC" }, dependent: :destroy, extend: ItemsWithQuantities - has_many :ingredients, -> { order "name" }, dependent: :destroy + has_many :measurements, -> { order "taken_at DESC" }, dependent: :destroy, + extend: ItemsWithQuantities + has_many :ingredients, -> { order "name" }, dependent: :destroy, + extend: ItemsWithQuantities has_many :sources, dependent: :destroy has_many :column_views, dependent: :destroy