1
0

Added test_defaults_seed_and_load_into_empty_project

Added :defaults scopes to models
Added :sources and :formulas fixtures
Loading defaults from seeds.rb using rake task instead of migration
This commit is contained in:
cryptogopher 2020-08-29 01:26:36 +02:00
parent 8f2a455561
commit 1b7f2f0abd
11 changed files with 245 additions and 10 deletions

View File

@ -13,7 +13,7 @@ class BodyTrackersController < ApplicationController
# Units # Units
available_units = @project.units.pluck(:shortname, :id).to_h available_units = @project.units.pluck(:shortname, :id).to_h
defaults = Unit.where(project: nil).map do |u| defaults = Unit.defaults.map do |u|
u.attributes.except('id', 'project_id', 'created_at', 'updated_at') u.attributes.except('id', 'project_id', 'created_at', 'updated_at')
end end
defaults.delete_if { |u| available_units.has_key?(u['shortname']) } defaults.delete_if { |u| available_units.has_key?(u['shortname']) }
@ -26,7 +26,7 @@ class BodyTrackersController < ApplicationController
# Quantities # Quantities
available_quantities = Quantity.each_with_path(@project.quantities).map(&:rotate).to_h available_quantities = Quantity.each_with_path(@project.quantities).map(&:rotate).to_h
quantities_count = available_quantities.length quantities_count = available_quantities.length
defaults = Quantity.where(project: nil) defaults = Quantity.defaults
Quantity.each_with_path(defaults) do |q, path| Quantity.each_with_path(defaults) do |q, path|
unless available_quantities.has_key?(path) unless available_quantities.has_key?(path)
attrs = q.attributes.except('id', 'project_id', 'parent_id', 'lft', 'rgt', attrs = q.attributes.except('id', 'project_id', 'parent_id', 'lft', 'rgt',
@ -56,7 +56,7 @@ class BodyTrackersController < ApplicationController
# Sources # Sources
available_sources = @project.sources.pluck(:name, :id).to_h available_sources = @project.sources.pluck(:name, :id).to_h
defaults = Source.where(project: nil).map do |s| defaults = Source.defaults.map do |s|
s.attributes.except('id', 'project_id', 'created_at', 'updated_at') s.attributes.except('id', 'project_id', 'created_at', 'updated_at')
end end
defaults.delete_if { |s| available_sources.has_key?(s['name']) } defaults.delete_if { |s| available_sources.has_key?(s['name']) }

View File

@ -6,6 +6,8 @@ class Formula < ActiveRecord::Base
belongs_to :quantity, inverse_of: :formula, required: true belongs_to :quantity, inverse_of: :formula, required: true
belongs_to :unit belongs_to :unit
scope :defaults, -> { includes(:quantity).where(quantities: {project: nil}) }
validates :code, presence: true validates :code, presence: true
validate do validate do
messages = parse.each { |message, params| errors.add(:code, message, params) } messages = parse.each { |message, params| errors.add(:code, message, params) }
@ -92,7 +94,7 @@ class Formula < ActiveRecord::Base
# e.g. during import of defaults (so impossible to use recursive sql instead) # e.g. during import of defaults (so impossible to use recursive sql instead)
q_names = identifiers.map { |i| i.split('::').last } q_names = identifiers.map { |i| i.split('::').last }
q_paths = {} q_paths = {}
(quantity.project.try(&:quantities) || Quantity.where(project: nil)) (quantity.project.try(&:quantities) || Quantity.defaults)
.select { |q| q_names.include?(q.name) }.each do |q| .select { |q| q_names.include?(q.name) }.each do |q|
# NOTE: after upgrade to Ruby 2.7 replace with Enumerator#produce # NOTE: after upgrade to Ruby 2.7 replace with Enumerator#produce

View File

@ -13,6 +13,8 @@ class Quantity < ActiveRecord::Base
has_many :values, class_name: 'QuantityValue', dependent: :restrict_with_error has_many :values, class_name: 'QuantityValue', dependent: :restrict_with_error
has_many :exposures, dependent: :destroy has_many :exposures, dependent: :destroy
scope :defaults, -> { where(project: nil) }
has_one :formula, inverse_of: :quantity, dependent: :destroy, validate: true has_one :formula, inverse_of: :quantity, dependent: :destroy, validate: true
accepts_nested_attributes_for :formula, allow_destroy: true, accepts_nested_attributes_for :formula, allow_destroy: true,
reject_if: proc { |attrs| attrs['id'].blank? && attrs['code'].blank? } reject_if: proc { |attrs| attrs['id'].blank? && attrs['code'].blank? }

View File

@ -1,6 +1,8 @@
class Source < ActiveRecord::Base class Source < ActiveRecord::Base
belongs_to :project, required: false belongs_to :project, required: false
scope :defaults, -> { where(project: nil) }
validates :name, presence: true, uniqueness: {scope: :project_id} validates :name, presence: true, uniqueness: {scope: :project_id}
# Has to go before any 'dependent:' association # Has to go before any 'dependent:' association

View File

@ -1,6 +1,8 @@
class Unit < ActiveRecord::Base class Unit < ActiveRecord::Base
belongs_to :project, required: false belongs_to :project, required: false
scope :defaults, -> { where(project: nil) }
validates :shortname, presence: true, uniqueness: {scope: :project_id} validates :shortname, presence: true, uniqueness: {scope: :project_id}
# Has to go before any 'dependent:' association # Has to go before any 'dependent:' association

194
db/seeds.rb Normal file
View File

@ -0,0 +1,194 @@
# Formulas will be deleted as dependent on Quantities
[Source, Quantity, Unit].each { |model| model.defaults.delete_all }
# Units
u_a = Unit.create shortname: "g", name: "gram"
u_aa = Unit.create shortname: "ug", name: "microgram"
u_ab = Unit.create shortname: "mg", name: "milligram"
u_ac = Unit.create shortname: "kg", name: "kilogram"
u_b = Unit.create shortname: "kcal", name: "kilocalorie"
u_c = Unit.create shortname: "%", name: "percent"
# Quantities
# https://www.fsai.ie/uploadedFiles/Consol_Reg1169_2011.pdf
# https://www.fsai.ie/legislation/food_legislation/food_information_fic/nutrition_labelling.html
# -> Energy
e_a = Quantity.create name: "Energy", domain: :diet, parent: nil,
description: "Total energy"
e_aa = Quantity.create name: "calculated", domain: :diet, parent: e_a,
description: "Total energy calculated from macronutrients"
e_ab = Quantity.create name: "as %RM", domain: :diet, parent: e_a,
description: "Total energy percent value relative to current" \
" resting metabolism"
e_ac = Quantity.create name: "proteins", domain: :diet, parent: e_a,
description: "Calculated proteins energy"
e_aca = Quantity.create name: "as %RM", domain: :diet, parent: e_ac,
description: ""
e_ad = Quantity.create name: "fats", domain: :diet, parent: e_a,
description: "Calculated fats energy"
e_ada = Quantity.create name: "as %RM", domain: :diet, parent: e_ad,
description: ""
e_ae = Quantity.create name: "carbs", domain: :diet, parent: e_a,
description: "Calculated carbs energy"
e_aea = Quantity.create name: "as %RM", domain: :diet, parent: e_ae,
description: ""
# -> Proteins
p_a = Quantity.create name: "Proteins", domain: :diet, parent: nil,
description: "Total amount of proteins"
# -> Fats
f_a = Quantity.create name: "Fats", domain: :diet, parent: nil,
description: "Total lipids, including phospholipids"
f_aa = Quantity.create name: "Fatty acids", domain: :diet, parent: f_a,
description: ""
f_aaa = Quantity.create name: "Saturated", domain: :diet, parent: f_aa,
description: "Fatty acids without double bond"
f_aab = Quantity.create name: "Unsaturated", domain: :diet, parent: f_aa,
description: ""
f_aaba = Quantity.create name: "Monounsaturated", domain: :diet, parent: f_aab,
description: "Fatty acids with one cis double bond"
f_aabb = Quantity.create name: "Polyunsaturated", domain: :diet, parent: f_aab,
description: "Fatty acids with two or more cis, cis-methylene" \
" interrupted double bonds; PUFA"
f_aabba = Quantity.create name: "Omega-3 (n-3)", domain: :diet, parent: f_aabb,
description: ""
f_aabbaa = Quantity.create name: "ALA 18:3(n-3)", domain: :diet, parent: f_aabba,
description: "alpha-Linolenic acid"
f_aabbab = Quantity.create name: "EPA 20:5(n-3)", domain: :diet, parent: f_aabba,
description: "Eicosapentaenoic acid; also icosapentaenoic acid"
f_aabbac = Quantity.create name: "DHA 22:6(n-3)", domain: :diet, parent: f_aabba,
description: "Docosahexaenoic acid"
f_aabbb = Quantity.create name: "Omega-6 (n-6)", domain: :diet, parent: f_aabb,
description: ""
f_aabc = Quantity.create name: "Trans", domain: :diet, parent: f_aab,
description: "Fatty acids with at least one non-conjugated C-C" \
" double bond in the trans configuration"
# -> Carbs
c_a = Quantity.create name: "Carbs", domain: :diet, parent: nil,
description: "Total amount of carbohydrates"
c_aa = Quantity.create name: "Digestible", domain: :diet, parent: c_a,
description: ""
c_aaa = Quantity.create name: "Sugars", domain: :diet, parent: c_aa,
description: "Monosaccharides and disaccharides, excluding" \
" polyols"
c_aaaa = Quantity.create name: "Monosaccharides", domain: :diet, parent: c_aaa,
description: ""
c_aaaaa = Quantity.create name: "Glucose", domain: :diet, parent: c_aaaa,
description: ""
c_aaaab = Quantity.create name: "Fructose", domain: :diet, parent: c_aaaa,
description: ""
c_aaab = Quantity.create name: "Disaccharides", domain: :diet, parent: c_aaa,
description: ""
c_aaaba = Quantity.create name: "Sucrose", domain: :diet, parent: c_aaab,
description: ""
c_aaabb = Quantity.create name: "Lactose", domain: :diet, parent: c_aaab,
description: ""
c_aab = Quantity.create name: "Polyols", domain: :diet, parent: c_aa,
description: "Alcohols containing more than 2 hydroxyl groups"
c_aac = Quantity.create name: "Polysaccharides", domain: :diet, parent: c_aa,
description: ""
c_aaca = Quantity.create name: "Starch", domain: :diet, parent: c_aac,
description: ""
c_ab = Quantity.create name: "Indigestible", domain: :diet, parent: c_a,
description: ""
c_aba = Quantity.create name: "Fibre", domain: :diet, parent: c_ab,
description: "Carbohydrate polymers with 3 or more monomeric" \
" units, which are neither digested nor absorbed" \
" in the human small intestine"
# -> Minerals
m_a = Quantity.create name: "Minerals", domain: :diet, parent: nil,
description: ""
m_aa = Quantity.create name: "Salt", domain: :diet, parent: m_a,
description: "Sodium chloride"
# -> Vitamins
v_a = Quantity.create name: "Vitamins", domain: :diet, parent: nil,
description: ""
v_aa = Quantity.create name: "Vitamin A", domain: :diet, parent: v_a,
description: ""
v_aaa = Quantity.create name: "Retinol (A1)", domain: :diet, parent: v_aa,
description: ""
v_ab = Quantity.create name: "Provitamin A", domain: :diet, parent: v_a,
description: ""
v_aba = Quantity.create name: "beta-Carotene", domain: :diet, parent: v_ab,
description: ""
v_ac = Quantity.create name: "Vitamin B", domain: :diet, parent: v_a,
description: ""
v_aca = Quantity.create name: "Thiamine (B1)", domain: :diet, parent: v_ac,
description: ""
v_acb = Quantity.create name: "Riboflavin (B2)", domain: :diet, parent: v_ac,
description: "Vitamin G"
v_acc = Quantity.create name: "Vitamin B3", domain: :diet, parent: v_ac,
description: "Vitamin PP"
v_acca = Quantity.create name: "Niacin", domain: :diet, parent: v_acc,
description: "Nicotinic acid"
v_acd = Quantity.create name: "Vitamin B5", domain: :diet, parent: v_ac,
description: "Pantothenic acid"
v_ace = Quantity.create name: "Vitamin B6", domain: :diet, parent: v_ac,
description: ""
v_acf = Quantity.create name: "Biotin (B7)", domain: :diet, parent: v_ac,
description: "Vitamin H, also coenzyme R"
v_acg = Quantity.create name: "Folate", domain: :diet, parent: v_ac,
description: "Includes: folic acid, folacin and vitamin B9"
v_acga = Quantity.create name: "Vitamin B9", domain: :diet, parent: v_acg,
description: ""
v_ach = Quantity.create name: "Cobalamin (B12)", domain: :diet, parent: v_ac,
description: ""
v_ad = Quantity.create name: "Vitamin C", domain: :diet, parent: v_a,
description: ""
v_ae = Quantity.create name: "Vitamin D", domain: :diet, parent: v_a,
description: "Calciferol"
v_aea = Quantity.create name: "Cholecalciferol (D3)", domain: :diet, parent: v_ae,
description: ""
v_af = Quantity.create name: "Vitamin E", domain: :diet, parent: v_a,
description: ""
v_ag = Quantity.create name: "Vitamin K", domain: :diet, parent: v_a,
description: ""
# -> Body composition
b_a = Quantity.create name: "Body composition", domain: :measurement, parent: nil,
description: ""
b_aa = Quantity.create name: "Weight", domain: :measurement, parent: b_a,
description: "Total weight"
b_aaa = Quantity.create name: "Fat", domain: :measurement, parent: b_aa,
description: "Fat weight"
b_aab = Quantity.create name: "Muscle", domain: :measurement, parent: b_aa,
description: "Muscle weight"
b_ab = Quantity.create name: "Composition", domain: :measurement, parent: b_a,
description: ""
b_aba = Quantity.create name: "% fat", domain: :measurement, parent: b_ab,
description: "Fat as a % of total body weight"
b_abb = Quantity.create name: "% muscle", domain: :measurement, parent: b_ab,
description: "Muscle as a % of total body weight"
b_ac = Quantity.create name: "RM", domain: :measurement, parent: b_a,
description: "Resting metabolism"
b_ad = Quantity.create name: "VF", domain: :measurement, parent: b_a,
description: "Visceral fat"
# Formulas go at the and to make sure dependencies exist
e_aa.create_formula zero_nil: true, unit: u_b,
code: "4*Proteins + 9*Fats + 4*Carbs + 2*Fibre"
e_ab.create_formula zero_nil: true, unit: u_c,
code: "100*Energy/RM.lastBefore(Meal.eaten_at||Meal.created_at)"
e_ac.create_formula zero_nil: true, unit: u_b,
code: "4*Proteins"
e_aca.create_formula zero_nil: true, unit: u_c,
code: "100*proteins/RM.lastBefore(Meal.eaten_at||Meal.created_at)"
e_ad.create_formula zero_nil: true, unit: u_b,
code: "4*Fats"
e_ada.create_formula zero_nil: true, unit: u_c,
code: "100*fats/RM.lastBefore(Meal.eaten_at||Meal.created_at)"
e_ae.create_formula zero_nil: true, unit: u_b,
code: "4*Carbs"
e_aea.create_formula zero_nil: true, unit: u_c,
code: "100*carbs/RM.lastBefore(Meal.eaten_at||Meal.created_at)"
b_aaa.create_formula zero_nil: true, unit: u_ac,
code: "'% fat' * Weight"
# Sources
s_a = Source.create name: "nutrition label",
description: "nutrition facts taken from package nutrition label"

View File

@ -0,0 +1,20 @@
require Rails.root.join('config', 'environment')
namespace :redmine do
namespace :body_tracking do
desc "Loads body_tracking plugin seed data from db/seeds.rb. Requires pending" \
" migrations to be applied before running. Purges and reloads all seed data."
task seed: [:environment, 'db:abort_if_pending_migrations'] do
seed_fn = Rails.root.join('plugins', 'body_tracking', 'db', 'seeds.rb')
if seed_fn.exist?
print "Loading seed data from #{seed_fn}..."
load(seed_fn)
puts "done"
else
puts "Seed data file #{seed_fn} is missing :/"
end
end
end
end

View File

@ -25,7 +25,7 @@ class BodyTrackingSystemTestCase < ApplicationSystemTestCase
# Redmine fixtures use explicit IDs, so it's impossible to access them by name. # Redmine fixtures use explicit IDs, so it's impossible to access them by name.
# Use: 'project_id: 1' and NOT 'project: projects_001' # Use: 'project_id: 1' and NOT 'project: projects_001'
plugin_fixtures :enabled_modules, :roles, :member_roles, plugin_fixtures :enabled_modules, :roles, :member_roles,
:quantities, :units, :goals, :exposures, :targets, :quantity_values :sources, :quantities, :units, :formulas, :goals, :exposures, :targets, :quantity_values
include AbstractController::Translation include AbstractController::Translation

0
test/fixtures/formulas.yml vendored Normal file
View File

0
test/fixtures/sources.yml vendored Normal file
View File

View File

@ -1,4 +1,5 @@
require File.expand_path('../../application_system_test_case', __FILE__) require File.expand_path('../../application_system_test_case', __FILE__)
require 'rake'
class BodyTrackersTest < BodyTrackingSystemTestCase class BodyTrackersTest < BodyTrackingSystemTestCase
def setup def setup
@ -7,12 +8,24 @@ class BodyTrackersTest < BodyTrackingSystemTestCase
log_user 'jsmith', 'jsmith' log_user 'jsmith', 'jsmith'
end end
def test_defaults_load def test_defaults_seed_and_load_into_empty_project
Rails.application.load_tasks
Rake::Task['redmine:body_tracking:seed'].invoke
counts = [Source, Quantity, Formula, Unit].map do |model|
assoc = model.to_s.downcase.pluralize
@project1.send(assoc).delete_all unless assoc == 'formulas'
["@project1.#{assoc}.reload.count", model.defaults.count]
end.to_h
visit project_body_trackers_path(@project1) visit project_body_trackers_path(@project1)
assert_difference counts do
accept_alert t('layouts.sidebar.confirm_defaults') do accept_alert t('layouts.sidebar.confirm_defaults') do
click_link t('layouts.sidebar.link_defaults') click_link t('layouts.sidebar.link_defaults')
end end
# click_link is asynchronuous, need to wait for page reload before
# checking differences
assert_selector 'div#flash_notice' assert_selector 'div#flash_notice'
assert_no_selector 'div#flash_error' assert_no_selector 'div#flash_error'
end end
end end
end