1
0

Added MealsController#new and form autocomplete for Food

Renamed QuantityColumn -> Exposure
This commit is contained in:
cryptogopher
2020-04-15 23:42:58 +02:00
parent 8e8160c41a
commit e78803e474
27 changed files with 227 additions and 26 deletions

View File

@@ -9,7 +9,7 @@ class FoodsController < ApplicationController
before_action :init_session_filters
before_action :find_project_by_project_id,
only: [:index, :new, :create, :nutrients, :filter, :import]
only: [:index, :new, :create, :nutrients, :filter, :autocomplete, :import]
before_action :find_quantity_by_quantity_id, only: [:toggle_column]
before_action :find_food, only: [:edit, :update, :destroy, :toggle]
before_action :authorize
@@ -63,7 +63,7 @@ class FoodsController < ApplicationController
end
def toggle_column
@project.nutrient_columns.toggle!(@quantity)
@project.nutrient_exposures.toggle!(@quantity)
prepare_nutrients
end
@@ -73,6 +73,10 @@ class FoodsController < ApplicationController
render :index
end
def autocomplete
@foods = @project.foods.where("name LIKE ?", "%#{params[:term]}%")
end
def import
warnings = []

View File

@@ -13,6 +13,17 @@ class MealsController < ApplicationController
prepare_meals
end
def new
@meal = @project.meals.new
@meal.ingredients.new
end
def create
end
def destroy
end
private
def prepare_meals

View File

@@ -33,7 +33,7 @@ class MeasurementsController < ApplicationController
@measurement.routine.project = @project
@routine = @measurement.routine
if @measurement.save
if @routine.columns.empty?
if @routine.exposures.empty?
@routine.quantities << @measurement.readouts.map(&:quantity).first(6)
end
@@ -83,7 +83,7 @@ class MeasurementsController < ApplicationController
end
def toggle_column
@routine.columns.toggle!(@quantity)
@routine.exposures.toggle!(@quantity)
prepare_readouts
end

View File

@@ -122,6 +122,6 @@ class QuantitiesController < ApplicationController
def prepare_quantities
@quantities = @project.quantities.filter(@project, session[:q_filters])
.includes(:columns, :formula, :parent)
.includes(:exposures, :formula, :parent)
end
end

View File

@@ -0,0 +1,5 @@
module MealsHelper
def action_links(m)
delete_link(meal_path(m), {remote: true, data: {}}) if m.persisted?
end
end

4
app/models/exposure.rb Normal file
View File

@@ -0,0 +1,4 @@
class Exposure < ActiveRecord::Base
belongs_to :view, polymorphic: true
belongs_to :quantity
end

View File

@@ -34,6 +34,9 @@ class Food < ActiveRecord::Base
validates :ref_amount, numericality: {greater_than: 0}
validates :group, inclusion: {in: groups.keys}
scope :visible, -> { where(hidden: false) }
scope :hidden, -> { where(hidden: true) }
after_initialize do
if new_record?
self.ref_amount ||= 100

8
app/models/ingredient.rb Normal file
View File

@@ -0,0 +1,8 @@
class Ingredient < ActiveRecord::Base
belongs_to :composition, inverse_of: :ingredients, required: true
belongs_to :food, required: true
belongs_to :part_of, required: false
validates :ready_ratio, numericality: {greater_than_or_equal_to: 0.0}
validates :amount, numericality: {greater_than_or_equal_to: 0.0}
end

View File

@@ -1,6 +1,19 @@
class Meal < ActiveRecord::Base
belongs_to :project, required: true
has_many :ingredients, as: :composition, dependent: :destroy
has_many :ingredients, as: :composition, dependent: :destroy, validate: true
has_many :foods, through: :ingredients
validates :ingredients, presence: true
accepts_nested_attributes_for :ingredients, allow_destroy: true, reject_if: proc { |attrs|
attrs['food_id'].blank? && attrs['amount'].blank?
}
# Ingredient food_id + part_of_id uniqueness validation. Cannot be effectively
# checked on Ingredient model level.
validate do
ingredients = self.ingredients.reject { |i| i.marked_for_destruction? }
.map { |i| [i.food_id, i.part_of_id] }
if ingredients.length != ingredients.uniq.length
errors.add(:ingredients, :duplicated_ingredient)
end
end
end

View File

@@ -3,9 +3,9 @@ class MeasurementRoutine < ActiveRecord::Base
has_many :measurements, -> { order "taken_at DESC" }, inverse_of: :routine,
foreign_key: 'routine_id', dependent: :restrict_with_error,
extend: BodyTracking::ItemsWithQuantities
has_many :readout_columns, as: :column_view, dependent: :destroy,
class_name: 'QuantityColumn', extend: BodyTracking::TogglableColumns
has_many :quantities, -> { order "lft" }, through: :readout_columns
has_many :readout_exposures, as: :view, dependent: :destroy,
class_name: 'Exposure', extend: BodyTracking::TogglableColumns
has_many :quantities, -> { order "lft" }, through: :readout_exposures
validates :name, presence: true, uniqueness: {scope: :project_id}
end

View File

@@ -9,7 +9,7 @@ class Quantity < ActiveRecord::Base
belongs_to :project, required: false
has_many :nutrients, dependent: :restrict_with_error
has_many :readouts, dependent: :restrict_with_error
has_many :columns, dependent: :destroy
has_many :exposures, dependent: :destroy
has_one :formula, inverse_of: :quantity, dependent: :destroy, validate: true
accepts_nested_attributes_for :formula, allow_destroy: true, reject_if: proc { |attrs|

View File

@@ -1,4 +0,0 @@
class QuantityColumn < ActiveRecord::Base
belongs_to :column_view, polymorphic: true
belongs_to :quantity
end

View File

@@ -0,0 +1 @@
<%= raw @foods.map { |f| {id: f.id, label: f.name, value: f.name} }.to_json %>

View File

@@ -0,0 +1,4 @@
<% if User.current.allowed_to?(:manage_common, @project) %>
<%= link_to t(".link_new_meal"), new_project_meal_path(@project),
{remote: true, class: 'icon icon-add'} %>
<% end %>

View File

@@ -0,0 +1,79 @@
<%= error_messages_for @meal %>
<div class="box">
<div class="tabular">
<% @meal.ingredients.each_with_index do |i, index| %>
<table style="width:95%;">
<%= f.fields_for 'ingredients_attributes', i, index: '' do |ff| %>
<tr class="ingredient">
<td style="width:90%;">
<p>
<%= ff.hidden_field :id %>
<%= ff.text_field :food_id, {class: "autocomplete food-autocomplete",
style: "width: 80%;",
required: true,
label: (index > 0 ? '' : :field_ingredients)} %>
<%= ff.number_field :amount, {style: "width: 8%", step: :any, label: ''} %>
<%= i.food.ref_unit.shortname if i.food %>
<%= ff.hidden_field :_destroy %>
</p>
</td>
<td style="width:10%;">
<%= link_to t(".button_delete_ingredient"), '#',
class: 'icon icon-del',
style: (@meal.ingredients.length > 1 ? "" : "display:none"),
onclick: "deleteIngredient(); return false;" %>
</td>
</tr>
<% end %>
</table>
<% end %>
<p>
<%= link_to t(".button_new_ingredient"), '#', class: 'icon icon-add',
onclick: 'newIngredient(); return false;' %>
</p>
</div>
</div>
<%= javascript_tag do %>
function autocompleteFood($row) {
$row.find('.food-autocomplete').autocomplete({
source: '<%= j autocomplete_project_foods_path(@project) %>',
minLength: 2,
position: {collision: 'flipfit'},
search: function(event){
$(event.target).closest('.food-autocomplete').addClass('ajax-loading');
},
response: function(event){
$(event.target).closest('.food-autocomplete').removeClass('ajax-loading');
}
});
}
autocompleteFood($('tr.ingredient:visible'));
function newIngredient() {
var form = $(event.target).closest('form');
var row = form.find('tr.ingredient:visible:last');
var new_row = row.clone().insertAfter(row);
new_row.find('input[id$=__id], input[id$=__amount], input[id$=__food_id]').val('');
new_row.find('input[id$=__destroy]').val('');
new_row.find('label:first').hide();
form.find('tr.ingredient:visible a.icon-del').show();
autocompleteFood(new_row);
}
function deleteIngredient() {
var form = $(event.target).closest('form');
var row = $(event.target).closest('tr.ingredient');
if (row.find('input[id$=__id]').val()) {
row.hide();
row.find('input[id$=__destroy]').val('1');
} else {
row.remove();
}
form.find('tr.ingredient:visible:first label:first').show();
if (form.find('tr.ingredient:visible').length <= 1) {
form.find('tr.ingredient:visible a.icon-del').hide();
}
}
<% end %>

View File

@@ -0,0 +1,21 @@
<% if @meals.any? { |m| m.persisted? } %>
<table id="meals" class="odd-even">
<tbody>
<% @meals.group_by { |m| m.eaten_at.date if m.eaten_at }.each do |d, meals| %>
<% meals.each_with_index do |m, index| %>
<tr id="meal-<%= m.new_record? ? 'new' : m.id %>" class="primary meal">
<td class="name">
<h4>
<%= "#{t '.label_meal'}" %><%= index ? " ##{index+1}" : " (new)" %>
<%= ", #{m.eaten_at.time}" if m.eaten_at %>
</h4>
</td>
<td class="action unwrappable" style="width:5%"><%= action_links(m) %></td>
</tr>
<% end %>
<% end %>
</tbody>
</table>
<% else %>
<p class="nodata"><%= l(:label_no_data) %></p>
<% end %>

View File

@@ -0,0 +1,18 @@
<h2><%= t ".heading_new_meal" %></h2>
<%= labelled_form_for @meal,
url: project_meals_path(@project),
remote: true,
html: {id: 'new-meal-form', name: 'new-meal-form'} do |f| %>
<%= render partial: 'meals/form', locals: {f: f} %>
<div class="tabular">
<p>
<%= submit_tag l(:button_create) %>
<%= link_to l(:button_cancel), "#",
onclick: '$("#new-meal").empty(); return false;' %>
</p>
</div>
<% end %>
<hr>

View File

View File

@@ -0,0 +1,11 @@
<div class="contextual">
<%= render partial: 'meals/contextual' %>
</div>
<div id="new-meal">
</div>
<h2><%= t ".heading" %></h2>
<div id='meals'>
<%= render partial: 'meals/index' %>
</div>

View File

@@ -0,0 +1,2 @@
$('#new-meal')
.html('<%= j render partial: 'meals/new_form' %>');

View File

@@ -18,12 +18,12 @@
<%
next if q.new_record?
quantity_class = "quantity"
quantity_class += " primary" unless q.columns.empty?
quantity_class += " primary" unless q.exposures.empty?
quantity_class += " project idnt idnt-#{level+1}"
%>
<tr id="quantity-<%= q.id %>" class="<%= quantity_class %>">
<td class="name unwrappable">
<div class="icon <%= q.columns.empty? ? 'icon-fav-off' : 'icon-fav' %>">
<div class="icon <%= q.exposures.empty? ? 'icon-fav-off' : 'icon-fav' %>">
<%= q.name %>
</div>
</td>