From 18419f1aeb8a582bd2f7e0df051b343b6a65cb9e Mon Sep 17 00:00:00 2001 From: cryptogopher Date: Sun, 29 Mar 2020 00:56:37 +0100 Subject: [PATCH] Added MeasurementRoutine as a nested Measurement model Updated ItemsWithQuantities to work with MeasurementRoutine Replaced ColumnViews HABTM with polymorphic HMT Added Measurement notes Added destroy restrictions on Quantity Replaced BodyTrackingPluginController with Finders concern Removed 'body_trackers' prefix from paths Unified styling for textarea --- app/controllers/body_trackers_controller.rb | 11 +-- .../body_tracking_plugin_controller.rb | 17 ----- app/controllers/concerns/finders.rb | 42 ++++++++++++ app/controllers/ingredients_controller.rb | 19 ++---- app/controllers/measurements_controller.rb | 48 ++++++------- app/controllers/quantities_controller.rb | 8 ++- app/controllers/sources_controller.rb | 5 +- app/controllers/units_controller.rb | 16 ++--- app/helpers/ingredients_helper.rb | 4 +- app/helpers/measurements_helper.rb | 4 +- app/models/column.rb | 4 ++ app/models/column_view.rb | 19 ------ app/models/measurement.rb | 47 ++----------- app/models/measurement_routine.rb | 11 +++ app/models/quantity.rb | 11 +-- app/views/measurements/_form.html.erb | 41 +++++++---- app/views/measurements/_index.html.erb | 2 +- app/views/measurements/_options.html.erb | 2 +- app/views/measurements/_readouts.html.erb | 2 +- app/views/measurements/readouts.html.erb | 2 +- app/views/quantities/_index.html.erb | 4 +- assets/stylesheets/body_tracking.css | 4 +- config/locales/en.yml | 2 + config/routes.rb | 68 ++++++++++--------- db/migrate/001_create_schema.rb | 19 +++--- lib/body_tracking/items_with_quantities.rb | 53 +++++++++------ lib/body_tracking/project_patch.rb | 31 ++++----- lib/body_tracking/togglable_columns.rb | 7 ++ 28 files changed, 260 insertions(+), 243 deletions(-) delete mode 100644 app/controllers/body_tracking_plugin_controller.rb create mode 100644 app/controllers/concerns/finders.rb create mode 100644 app/models/column.rb delete mode 100644 app/models/column_view.rb create mode 100644 app/models/measurement_routine.rb create mode 100644 lib/body_tracking/togglable_columns.rb diff --git a/app/controllers/body_trackers_controller.rb b/app/controllers/body_trackers_controller.rb index 61e9ff6..6cd622b 100644 --- a/app/controllers/body_trackers_controller.rb +++ b/app/controllers/body_trackers_controller.rb @@ -1,4 +1,7 @@ -class BodyTrackersController < BodyTrackingPluginController +class BodyTrackersController < ApplicationController + layout 'body_tracking' + menu_item :body_trackers + before_action :find_project_by_project_id, only: [:index, :defaults] before_action :authorize @@ -43,10 +46,8 @@ class BodyTrackersController < BodyTrackingPluginController flash[:notice] += ", #{new_quantities_count > 0 ? new_quantities_count : "no" } new" \ " #{'quantity'.pluralize(new_quantities_count)}" - ncv = @project.nutrients_column_view - if ncv.quantities.count == 0 - ncv.quantities.append(@project.quantities.roots.first(6)) - ncv.save! + if @project.nutrient_quantities.empty? + @project.nutrient_quantities << @project.quantities.diet.roots.first(6) end # Sources diff --git a/app/controllers/body_tracking_plugin_controller.rb b/app/controllers/body_tracking_plugin_controller.rb deleted file mode 100644 index bb9732c..0000000 --- a/app/controllers/body_tracking_plugin_controller.rb +++ /dev/null @@ -1,17 +0,0 @@ -class BodyTrackingPluginController < ApplicationController - menu_item :body_trackers - layout 'body_tracking' - - private - - def find_quantity(id = :id) - @quantity = Quantity.find(params[id]) - @project = @quantity.project - rescue ActiveRecord::RecordNotFound - render_404 - end - - def find_quantity_by_quantity_id - find_quantity(:quantity_id) - end -end diff --git a/app/controllers/concerns/finders.rb b/app/controllers/concerns/finders.rb new file mode 100644 index 0000000..2937225 --- /dev/null +++ b/app/controllers/concerns/finders.rb @@ -0,0 +1,42 @@ +module Concerns::Finders + private + + def find_ingredient + @ingredient = Ingredient.find(params[:id]) + @project = @ingredient.project + rescue ActiveRecord::RecordNotFound + render_404 + end + + def find_measurement + @measurement = Measurement.find(params[:id]) + @project = @measurement.routine.project + rescue ActiveRecord::RecordNotFound + render_404 + end + + def find_measurement_routine + @routine = MeasurementRoutine.find(params[:id]) + @project = @routine.project + rescue ActiveRecord::RecordNotFound + render_404 + end + + def find_quantity(id = :id) + @quantity = Quantity.find(params[id]) + @project = @quantity.project + rescue ActiveRecord::RecordNotFound + render_404 + end + + def find_quantity_by_quantity_id + find_quantity(:quantity_id) + end + + def find_unit + @unit = Unit.find(params[:id]) + @project = @unit.project + rescue ActiveRecord::RecordNotFound + render_404 + end +end diff --git a/app/controllers/ingredients_controller.rb b/app/controllers/ingredients_controller.rb index 3ec895d..b62fdf5 100644 --- a/app/controllers/ingredients_controller.rb +++ b/app/controllers/ingredients_controller.rb @@ -1,9 +1,13 @@ -class IngredientsController < BodyTrackingPluginController +class IngredientsController < ApplicationController require 'csv' + layout 'body_tracking' + menu_item :body_trackers helper :body_trackers helper_method :current_view + include Concerns::Finders + before_action :init_session_filters before_action :find_project_by_project_id, only: [:index, :new, :create, :nutrients, :filter, :import] @@ -63,7 +67,7 @@ class IngredientsController < BodyTrackingPluginController end def toggle_column - @project.nutrients_column_view.toggle_column!(@quantity) + @project.nutrient_columns.toggle!(@quantity) prepare_items render :index end @@ -201,15 +205,6 @@ class IngredientsController < BodyTrackingPluginController ) end - # :find_* methods are called before :authorize, - # @project is required for :authorize to succeed - def find_ingredient - @ingredient = Ingredient.find(params[:id]) - @project = @ingredient.project - rescue ActiveRecord::RecordNotFound - render_404 - end - def prepare_ingredients @ingredients, @formula_q = @project.ingredients .includes(:ref_unit, :source) @@ -217,7 +212,7 @@ class IngredientsController < BodyTrackingPluginController end def prepare_nutrients - @quantities = @project.nutrients_column_view.quantities.includes(:formula) + @quantities = @project.nutrient_quantities.includes(:formula) @ingredients, @requested_n, @extra_n, @formula_q = @project.ingredients .filter(session[:i_filters], @quantities) end diff --git a/app/controllers/measurements_controller.rb b/app/controllers/measurements_controller.rb index dfbfb3c..65ef520 100644 --- a/app/controllers/measurements_controller.rb +++ b/app/controllers/measurements_controller.rb @@ -1,11 +1,15 @@ -class MeasurementsController < BodyTrackingPluginController +class MeasurementsController < ApplicationController + layout 'body_tracking' + menu_item :body_trackers helper :body_trackers + include Concerns::Finders + 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] - before_action :find_measurement, - only: [:edit, :update, :destroy, :retake, :readouts, :toggle_column] + before_action :find_measurement, only: [:edit, :update, :destroy, :retake] + before_action :find_measurement_routine, only: [:readouts, :toggle_column] before_action :authorize def index @@ -15,12 +19,18 @@ class MeasurementsController < BodyTrackingPluginController def new @measurement = @project.measurements.new + @measurement.build_routine @measurement.readouts.new end def create @measurement = @project.measurements.new(measurement_params) + @measurement.routine.project = @project if @measurement.save + if @measurement.routine.columns.empty? + @measurement.routine.quantities << @measurement.readouts.map(&:quantity).first(6) + end + flash[:notice] = 'Created new measurement' readouts_view? ? prepare_readouts : prepare_measurements else @@ -58,12 +68,12 @@ class MeasurementsController < BodyTrackingPluginController end def readouts - session[:m_filters][:scope] = {name: @measurement.name} + #session[:m_filters][:scope] = {routine: @routine} prepare_readouts end def toggle_column - @measurement.column_view.toggle_column!(@quantity) + @routine.columns.toggle!(@quantity) prepare_readouts render :index end @@ -82,8 +92,13 @@ class MeasurementsController < BodyTrackingPluginController def measurement_params params.require(:measurement).permit( - :name, + :notes, :source_id, + routine_attributes: + [ + :name, + :description + ], readouts_attributes: [ :id, @@ -95,30 +110,17 @@ class MeasurementsController < BodyTrackingPluginController ) end - # :find_* methods are called before :authorize, - # @project is required for :authorize to succeed - def find_measurement - @measurement = Measurement.find(params[:id]) - @project = @measurement.project - rescue ActiveRecord::RecordNotFound - render_404 - end - def prepare_measurements @measurements, @formula_q = @project.measurements - .includes(:source, :readouts) + .includes(:routine, :source, :readouts) .filter(session[:m_filters]) end def prepare_readouts - @scoping_measurement = @project.measurements.where(session[:m_filters][:scope]).first! - @quantities = @scoping_measurement.column_view.quantities.includes(:formula) - @measurements, @requested_r, @extra_r, @formula_q = @project.measurements - .includes(:source) + @quantities = @routine.quantities.includes(:formula) + @measurements, @requested_r, @extra_r, @formula_q = @routine.measurements + .includes(:routine, :source) .filter(session[:m_filters], @quantities) - rescue ActiveRecord::RecordNotFound - session[:m_filters][:scope] = {} - render_404 end def readouts_view? diff --git a/app/controllers/quantities_controller.rb b/app/controllers/quantities_controller.rb index 491fe53..85ca764 100644 --- a/app/controllers/quantities_controller.rb +++ b/app/controllers/quantities_controller.rb @@ -1,6 +1,10 @@ -class QuantitiesController < BodyTrackingPluginController +class QuantitiesController < ApplicationController + layout 'body_tracking' + menu_item :body_trackers helper :body_trackers + include Concerns::Finders + before_action :init_session_filters before_action :find_project_by_project_id, only: [:index, :new, :create, :filter, :parents] before_action :find_quantity, only: [:edit, :update, :destroy, :move, @@ -118,6 +122,6 @@ class QuantitiesController < BodyTrackingPluginController def prepare_quantities @quantities = @project.quantities.filter(@project, session[:q_filters]) - .includes(:column_views, :formula, :parent) + .includes(:columns, :formula, :parent) end end diff --git a/app/controllers/sources_controller.rb b/app/controllers/sources_controller.rb index 1a5195f..a96ca32 100644 --- a/app/controllers/sources_controller.rb +++ b/app/controllers/sources_controller.rb @@ -1,4 +1,7 @@ -class SourcesController < BodyTrackingPluginController +class SourcesController < ApplicationController + layout 'body_tracking' + menu_item :body_trackers + before_action :find_project_by_project_id, only: [:index, :create] before_action :find_source, only: [:destroy] before_action :authorize diff --git a/app/controllers/units_controller.rb b/app/controllers/units_controller.rb index 65509cf..4affd4c 100644 --- a/app/controllers/units_controller.rb +++ b/app/controllers/units_controller.rb @@ -1,4 +1,9 @@ -class UnitsController < BodyTrackingPluginController +class UnitsController < ApplicationController + layout 'body_tracking' + menu_item :body_trackers + + include Concerns::Finders + before_action :find_project_by_project_id, only: [:index, :create] before_action :find_unit, only: [:destroy] before_action :authorize @@ -34,13 +39,4 @@ class UnitsController < BodyTrackingPluginController :shortname ) end - - # :find_* methods are called before :authorize, - # @project is required for :authorize to succeed - def find_unit - @unit = Unit.find(params[:id]) - @project = @unit.project - rescue ActiveRecord::RecordNotFound - render_404 - end end diff --git a/app/helpers/ingredients_helper.rb b/app/helpers/ingredients_helper.rb index 8107d62..24ad9ef 100644 --- a/app/helpers/ingredients_helper.rb +++ b/app/helpers/ingredients_helper.rb @@ -7,9 +7,9 @@ module IngredientsHelper def toggle_column_options disabled = [] - enabled_columns = @project.nutrients_column_view.quantities.to_a + enabled_quantities = @project.nutrient_quantities.to_a options = nested_set_options(@project.quantities.diet) do |q| - disabled << q.id if enabled_columns.include?(q) + disabled << q.id if enabled_quantities.include?(q) raw("#{' ' * q.level}#{q.name}") end options_for_select(options, disabled: disabled) diff --git a/app/helpers/measurements_helper.rb b/app/helpers/measurements_helper.rb index bbe6530..2be63b2 100644 --- a/app/helpers/measurements_helper.rb +++ b/app/helpers/measurements_helper.rb @@ -11,9 +11,9 @@ module MeasurementsHelper def toggle_column_options disabled = [] - enabled_columns = @scoping_measurement.column_view.quantities + enabled_quantities = @routine.quantities.to_a options = nested_set_options(@project.quantities.measurement) do |q| - disabled << q.id if enabled_columns.include?(q) + disabled << q.id if enabled_quantities.include?(q) raw("#{' ' * q.level}#{q.name}") end options_for_select(options, disabled: disabled) diff --git a/app/models/column.rb b/app/models/column.rb new file mode 100644 index 0000000..9b2736c --- /dev/null +++ b/app/models/column.rb @@ -0,0 +1,4 @@ +class Column < ActiveRecord::Base + belongs_to :column_view, polymorphic: true + belongs_to :quantity +end diff --git a/app/models/column_view.rb b/app/models/column_view.rb deleted file mode 100644 index 7a1f175..0000000 --- a/app/models/column_view.rb +++ /dev/null @@ -1,19 +0,0 @@ -class ColumnView < ActiveRecord::Base - enum domain: Quantity.domains - - belongs_to :project, required: true - has_and_belongs_to_many :quantities, -> { order "lft" } - - validates :name, presence: true, uniqueness: {scope: :domain} - validates :domain, inclusion: {in: domains.keys} - - # TODO: enforce column_view - quantity 'domain' identity - def toggle_column!(q) - column = self.quantities.find(q.id) - self.quantities.destroy(column) - rescue ActiveRecord::RecordNotFound - # Cannot 'create' association, as ColumnView (parent) may not be saved yet - self.quantities.append(q) - self.save! - end -end diff --git a/app/models/measurement.rb b/app/models/measurement.rb index 800b14b..7ea473e 100644 --- a/app/models/measurement.rb +++ b/app/models/measurement.rb @@ -1,5 +1,11 @@ class Measurement < ActiveRecord::Base - belongs_to :project, required: true + belongs_to :routine, required: true, inverse_of: :measurements, + class_name: 'MeasurementRoutine' + accepts_nested_attributes_for :routine, allow_destroy: true, reject_if: proc { |attrs| + attrs['name'].blank? + } + after_destroy { self.routine.destroy if self.routine.measurements.empty? } + belongs_to :source, required: false has_many :readouts, inverse_of: :measurement, dependent: :destroy, validate: true @@ -17,7 +23,6 @@ class Measurement < ActiveRecord::Base end end - validates :name, presence: true validates :taken_at, presence: true after_initialize do @@ -26,21 +31,6 @@ class Measurement < ActiveRecord::Base end end - after_create :seed_column_view, if: -> {self.column_view.quantities.count == 0} - after_save :cleanup_column_view, if: :name_changed? - - # Destroy ColumnView after last Measurement destruction - after_destroy do - unless self.project.measurements.exists?(name: self.name) - self.column_view.destroy! - end - end - - def column_view - self.project.column_views - .find_or_create_by(name: self.name, domain: ColumnView.domains[:measurement]) - end - def taken_at_date self.taken_at.getlocal end @@ -54,27 +44,4 @@ class Measurement < ActiveRecord::Base def taken_at_time=(value) self.taken_at = Time.parse(value, self.taken_at) end - - private - - def seed_column_view - quantities = self.project.quantities.joins(:readouts).includes(readouts: [:measurement]) - .where(measurements: {name: self.name}).first(6) - self.column_view.quantities.append(quantities) - self.column_view.save! - end - - # Copy/rename ColumnView on Measurement rename - def cleanup_column_view - old_column_view = self.project.column_views - .find_by(name: self.name_was, domain: ColumnView.domains[:measurement]) - return unless old_column_view - - if self.project.measurements.exists?(name: self.name_was) - self.column_view.quantities.append(old_column_view.quantities) - self.column_view.save! - else - old_column_view.update!(name: self.name) - end - end end diff --git a/app/models/measurement_routine.rb b/app/models/measurement_routine.rb new file mode 100644 index 0000000..85cf4c2 --- /dev/null +++ b/app/models/measurement_routine.rb @@ -0,0 +1,11 @@ +class MeasurementRoutine < ActiveRecord::Base + belongs_to :project, required: true + has_many :measurements, -> { order "taken_at DESC" }, inverse_of: :routine, + foreign_key: 'routine_id', dependent: :restrict_with_error, + extend: BodyTracking::ItemsWithQuantities + has_many :columns, as: :column_view, dependent: :destroy, + extend: BodyTracking::TogglableColumns + has_many :quantities, -> { order "lft" }, through: :columns + + validates :name, presence: true, uniqueness: {scope: :project_id} +end diff --git a/app/models/quantity.rb b/app/models/quantity.rb index e01556a..83d9041 100644 --- a/app/models/quantity.rb +++ b/app/models/quantity.rb @@ -5,16 +5,11 @@ class Quantity < ActiveRecord::Base exercise: 2 } - # Has to go before any 'dependent:' association - before_destroy do - # FIXME: disallow destruction if any object depends on this quantity - nil - end - acts_as_nested_set dependent: :destroy, scope: :project belongs_to :project, required: false - has_and_belongs_to_many :column_views - has_many :readouts + has_many :nutrients, dependent: :restrict_with_error + has_many :readouts, dependent: :restrict_with_error + has_many :columns, dependent: :destroy has_one :formula, inverse_of: :quantity, dependent: :destroy, validate: true accepts_nested_attributes_for :formula, allow_destroy: true, reject_if: proc { |attrs| diff --git a/app/views/measurements/_form.html.erb b/app/views/measurements/_form.html.erb index 6d3a3ad..7f8b8aa 100644 --- a/app/views/measurements/_form.html.erb +++ b/app/views/measurements/_form.html.erb @@ -1,17 +1,29 @@ <%= error_messages_for @measurement %> -
-

<%= f.select :source_id, source_options, - {required: false, include_blank: t('.null_source')} %>

-

<%= f.text_field :name, size: 40, required: true %>

-

+

+ <%= t('.label_routine') %> +
+ <%= f.fields_for :routine do |ff| %> +

<%= ff.text_field :name, required: true, style: "width: 95%;" %>

+

<%= ff.text_area :description, cols: 40, rows: 3, style: "width: 95%;" %>

+ <% end %> +
+
+ +
+ <%= t('.label_measurement') %> +
+

<%= f.select :source_id, source_options, + {required: false, include_blank: t('.null_source')} %>

+

<%= f.text_area :notes, cols: 40, rows: 1, style: "width: 95%;" %>

+

<%= f.date_field :taken_at_date, required: true %> <%= f.time_field :taken_at_time, value: format_time(@measurement), required: true, label: '' %> -

- <% @measurement.readouts.each_with_index do |r, index| %> - <%= f.fields_for 'readouts_attributes', r, index: '' do |ff| %> -

+

+ <% @measurement.readouts.each_with_index do |r, index| %> + <%= f.fields_for 'readouts_attributes', r, index: '' do |ff| %> +

<%= ff.hidden_field :id %> <%= ff.select :quantity_id, quantity_options, {include_blank: true, required: true, label: (index > 0 ? '' : :field_readouts)} %> @@ -22,14 +34,15 @@ class: 'icon icon-del', style: (@measurement.readouts.length > 1 ? "" : "display:none"), onclick: "deleteReadout(); return false;" %> -

+

+ <% end %> <% end %> - <% end %> -

+

<%= link_to t(".button_new_readout"), '#', class: 'icon icon-add', onclick: 'newReadout(); return false;' %> -

-
+

+
+ <%= javascript_tag do %> function newReadout() { diff --git a/app/views/measurements/_index.html.erb b/app/views/measurements/_index.html.erb index a59cdd3..b87c246 100644 --- a/app/views/measurements/_index.html.erb +++ b/app/views/measurements/_index.html.erb @@ -20,7 +20,7 @@ <%= format_datetime(m) %>
- <%= link_to m.name, readouts_measurement_path(m) %> + <%= link_to m.routine.name, readouts_measurement_routine_path(m.routine) %>
<%= " (#{pluralize(m.readouts.size, 'readout')})" %> diff --git a/app/views/measurements/_options.html.erb b/app/views/measurements/_options.html.erb index 723656a..96e915f 100644 --- a/app/views/measurements/_options.html.erb +++ b/app/views/measurements/_options.html.erb @@ -1,7 +1,7 @@
<%= l(:label_options) %>
- <%= form_tag toggle_column_measurement_path(@scoping_measurement), + <%= form_tag toggle_column_measurement_routine_path(@routine), id: 'toggle-column-form', name: 'toggle-column-form', method: :post, remote: true do %> diff --git a/app/views/measurements/_readouts.html.erb b/app/views/measurements/_readouts.html.erb index c9be482..32a8746 100644 --- a/app/views/measurements/_readouts.html.erb +++ b/app/views/measurements/_readouts.html.erb @@ -17,7 +17,7 @@
<%= link_to '', - toggle_column_measurement_path(@scoping_measurement, quantity_id: q.id), + toggle_column_measurement_routine_path(@routine, quantity_id: q.id), {class: "icon icon-close", method: :post, remote: true} %>
<%= q.name %> diff --git a/app/views/measurements/readouts.html.erb b/app/views/measurements/readouts.html.erb index 1ad7fab..4660bee 100644 --- a/app/views/measurements/readouts.html.erb +++ b/app/views/measurements/readouts.html.erb @@ -8,7 +8,7 @@

<%= link_to t("measurements.index.heading"), project_measurements_path(@project) %> > - <%= @measurements.first.name %> + <%= @routine.name %>

<%= render partial: 'measurements/readouts' %> diff --git a/app/views/quantities/_index.html.erb b/app/views/quantities/_index.html.erb index b63cfd6..280d64f 100644 --- a/app/views/quantities/_index.html.erb +++ b/app/views/quantities/_index.html.erb @@ -18,12 +18,12 @@ <% next if q.new_record? quantity_class = "quantity" - quantity_class += " primary" unless q.column_views.empty? + quantity_class += " primary" unless q.columns.empty? quantity_class += " project idnt idnt-#{level+1}" %> -
+
<%= q.name %>
diff --git a/assets/stylesheets/body_tracking.css b/assets/stylesheets/body_tracking.css index e357ca2..f7d4a50 100644 --- a/assets/stylesheets/body_tracking.css +++ b/assets/stylesheets/body_tracking.css @@ -37,7 +37,7 @@ input[type=number] { -moz-appearance:textfield; text-align: right; } -input[type=date], input[type=time], input[type=number] { - border:1px solid #9EB1C2; +input[type=date], input[type=time], input[type=number], textarea { + border:1px solid #d7d7d7; padding: 3px; } diff --git a/config/locales/en.yml b/config/locales/en.yml index 2f0bb66..067c0b6 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -68,6 +68,8 @@ en: filters: zero_nil: 'missing -> 0?' form: + label_routine: 'Measurement routine (shared among all measurements of this kind)' + label_measurement: 'Measurement' button_new_readout: 'Add readout' button_delete_readout: 'Delete' null_source: '- unspecified -' diff --git a/config/routes.rb b/config/routes.rb index 22ca0f1..39d5f23 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -5,38 +5,42 @@ resources :projects, shallow: true do resources :body_trackers, only: [:index] do collection do post 'defaults' - resources :measurements, only: [:index, :new, :create, :edit, :update, :destroy] do - member do - get 'retake' - get 'readouts' - post 'toggle_column' - end - collection do - get 'filter' - end - end - resources :ingredients, only: [:index, :new, :create, :edit, :update, :destroy] do - post 'toggle', on: :member - collection do - get 'nutrients' - post 'toggle_column' - get 'filter' - post 'import' - end - end - resources :sources, only: [:index, :create, :destroy] - resources :quantities, only: [:index, :new, :create, :edit, :update, :destroy] do - member do - get 'new_child' - post 'create_child' - post 'move/:direction', to: 'quantities#move', as: :move - end - collection do - get 'parents' - get 'filter' - end - end - resources :units, only: [:index, :create, :destroy] end end + resources :measurement_routines, only: [] do + member do + get 'readouts', to: 'measurements#readouts' + post 'toggle_column', to: 'measurements#toggle_column' + end + end + resources :measurements, only: [:index, :new, :create, :edit, :update, :destroy] do + member do + get 'retake' + end + collection do + get 'filter' + end + end + resources :ingredients, only: [:index, :new, :create, :edit, :update, :destroy] do + post 'toggle', on: :member + collection do + get 'nutrients' + post 'toggle_column' + get 'filter' + post 'import' + end + end + resources :sources, only: [:index, :create, :destroy] + resources :quantities, only: [:index, :new, :create, :edit, :update, :destroy] do + member do + get 'new_child' + post 'create_child' + post 'move/:direction', to: 'quantities#move', as: :move + end + collection do + get 'parents' + get 'filter' + end + end + resources :units, only: [:index, :create, :destroy] end diff --git a/db/migrate/001_create_schema.rb b/db/migrate/001_create_schema.rb index 69f1e6f..40175d1 100644 --- a/db/migrate/001_create_schema.rb +++ b/db/migrate/001_create_schema.rb @@ -27,14 +27,8 @@ class CreateSchema < ActiveRecord::Migration t.timestamps null: false end - create_table :column_views do |t| - t.references :project - t.string :name - t.integer :domain - end - - create_table :column_views_quantities do |t| - t.references :column_view + create_table :columns do |t| + t.references :column_view, polymorphic: true t.references :quantity end @@ -66,10 +60,17 @@ class CreateSchema < ActiveRecord::Migration t.timestamps null: false end - create_table :measurements do |t| + create_table :measurement_routines do |t| t.references :project t.string :name + t.text :description + t.timestamps null: false + end + + create_table :measurements do |t| + t.references :routine t.references :source + t.text :notes t.timestamp :taken_at t.timestamps null: false end diff --git a/lib/body_tracking/items_with_quantities.rb b/lib/body_tracking/items_with_quantities.rb index 17c5a9e..d29eade 100644 --- a/lib/body_tracking/items_with_quantities.rb +++ b/lib/body_tracking/items_with_quantities.rb @@ -1,12 +1,18 @@ module BodyTracking module ItemsWithQuantities - QUANTITY_DOMAINS = { - Measurement => :measurement, - Ingredient => :diet - } - VALUE_COLUMNS = { - Measurement => :value, - Ingredient => :amount + RELATIONS = { + 'Ingredient' => { + domain: :diet, + foreign_key: :ingredient_id, + subitem_class: Nutrient, + value_field: :amount + }, + 'Measurement' => { + domain: :measurement, + foreign_key: :measurement_id, + subitem_class: Readout, + value_field: :value + } } def filter(filters, requested_q = nil) @@ -20,13 +26,18 @@ module BodyTracking items = items.where(hidden: filters[:visibility] == "1" ? false : true) end - project = proxy_association.owner - domain = QUANTITY_DOMAINS[proxy_association.klass] - formula_q = if filters[:formula].present? - project.quantities.new(name: 'Filter formula', - formula_attributes: filters[:formula], - domain: domain) - end + formula_q = + if filters[:formula].present? + owner = proxy_association.owner + project = owner.is_a?(Project) ? owner : owner.project + domain = RELATIONS[proxy_association.klass.name][:domain] + formula_q_attrs = { + name: 'Filter formula', + formula_attributes: filters[:formula], + domain: domain + } + project.quantities.new(formula_q_attrs) + end apply_formula = formula_q.present? && formula_q.valid? result = @@ -45,15 +56,13 @@ module BodyTracking unchecked_q = requested_q.map { |q| [q, nil] } unchecked_q << [filter_q, nil] if filter_q - item_class = proxy_association.klass - subitem_type = item_class.nested_attributes_options.keys.first.to_s - subitem_reflection = item_class.reflections[subitem_type] - subitem_class = subitem_reflection.klass - subitems_scope = subitem_class.where(subitem_reflection.options[:inverse_of] => items) + relations = RELATIONS[proxy_association.klass.name] subitems = Hash.new { |h,k| h[k] = {} } - subitems_scope.includes(:quantity, :unit).order('quantities.lft').each do |s| - item_id = s.send(subitem_reflection.foreign_key) - subitem_value = s.send(VALUE_COLUMNS[item_class]) + relations[:subitem_class].where(relations[:foreign_key] => items) + .includes(:quantity, :unit).order('quantities.lft').each do |s| + + item_id = s.send(relations[:foreign_key]) + subitem_value = s.send(relations[:value_field]) subitems[s.quantity][item_id] = [subitem_value, s.unit] end diff --git a/lib/body_tracking/project_patch.rb b/lib/body_tracking/project_patch.rb index 994ee52..1e52afe 100644 --- a/lib/body_tracking/project_patch.rb +++ b/lib/body_tracking/project_patch.rb @@ -1,21 +1,18 @@ -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, - extend: ItemsWithQuantities +module BodyTracking::ProjectPatch + Project.class_eval do + has_many :measurement_routines, dependent: :destroy + has_many :measurements, -> { order "taken_at DESC" }, dependent: :destroy, + extend: BodyTracking::ItemsWithQuantities, through: :measurement_routines + has_many :ingredients, -> { order "name" }, dependent: :destroy, + extend: BodyTracking::ItemsWithQuantities - has_many :sources, dependent: :destroy - has_many :column_views, dependent: :destroy - has_many :quantities, -> { order "lft" }, dependent: :destroy - has_many :units, dependent: :destroy + has_many :sources, dependent: :destroy + has_many :quantities, -> { order "lft" }, dependent: :destroy + has_many :units, dependent: :destroy - def nutrients_column_view - self.column_views - .find_or_create_by(name: 'Nutrients', domain: ColumnView.domains[:diet]) - end - end + has_many :nutrient_columns, as: :column_view, dependent: :destroy, class_name: 'Column', + extend: BodyTracking::TogglableColumns + has_many :nutrient_quantities, -> { order "lft" }, through: :nutrient_columns, + source: 'quantity' end end - diff --git a/lib/body_tracking/togglable_columns.rb b/lib/body_tracking/togglable_columns.rb new file mode 100644 index 0000000..5895755 --- /dev/null +++ b/lib/body_tracking/togglable_columns.rb @@ -0,0 +1,7 @@ +module BodyTracking::TogglableColumns + # TODO: enforce 'domain' identity between quantites and receiving collection? + def toggle!(q) + column = find_by(quantity: q) + column ? destroy(column) : create(quantity: q) + end +end