diff --git a/app/controllers/quantities_controller.rb b/app/controllers/quantities_controller.rb index 73af8be..e9c98b1 100644 --- a/app/controllers/quantities_controller.rb +++ b/app/controllers/quantities_controller.rb @@ -91,12 +91,12 @@ class QuantitiesController < ApplicationController def create_child @quantity = @project.quantities.new(quantity_params) - if @quantity.save - flash[:notice] = 'Created new quantity' - prepare_quantities - else + unless @quantity.save + @parent_quantity = @quantity.parent render :new_child end + flash[:notice] = 'Created new quantity' + prepare_quantities end private diff --git a/app/models/formula.rb b/app/models/formula.rb index a2b8d03..3f4c139 100644 --- a/app/models/formula.rb +++ b/app/models/formula.rb @@ -39,12 +39,16 @@ class Formula < ActiveRecord::Base private def parse - parser = FormulaBuilder.new(self.code) + d_methods = ['abs', 'nil?'] + q_methods = Hash.new(['all', 'lastBefore']) + q_methods['Meal'] = Meal.attribute_names + + parser = FormulaBuilder.new(self.code, d_methods: d_methods, q_methods: q_methods) identifiers, parts = parser.parse errors = parser.errors quantities = Quantity.where(project: self.quantity.project, name: identifiers) - (identifiers - quantities.map(&:name)).each do |q| + (identifiers - quantities.map(&:name) - quantity_m.keys).each do |q| errors << [:unknown_quantity, {quantity: q}] end diff --git a/lib/body_tracking/formula_builder.rb b/lib/body_tracking/formula_builder.rb index 8ff3832..fab0daa 100644 --- a/lib/body_tracking/formula_builder.rb +++ b/lib/body_tracking/formula_builder.rb @@ -9,25 +9,25 @@ module BodyTracking # List of events with parameter count: # https://github.com/racker/ruby-1.9.3-lucid/blob/master/ext/ripper/eventids1.c class FormulaBuilder < Ripper::SexpBuilder - def initialize(*args) + def initialize(*args, d_methods: [], q_methods: Hash.new([])) super(*args) @disallowed = Hash.new { |h,k| h[k] = Set.new } @identifiers = Set.new @parts = [] + @decimal_methods = d_methods + @quantity_methods = q_methods end def errors @errors = [] - @disallowed.each { |k, v| v.each { |e| @errors << ["disallowed_#{k}", {k => e} ] } } + @disallowed.each do |k, v| + v.each { |e| @errors << ["disallowed_#{k}".to_sym, {k => e} ] } + end @errors end private - DECIMAL_METHODS = ['abs', 'nil?'] - QUANTITY_METHODS = ['all', 'lastBefore'] - METHODS = DECIMAL_METHODS + QUANTITY_METHODS - events = private_instance_methods(false).grep(/\Aon_/) {$'.to_sym} (PARSER_EVENTS - events).each do |event| module_eval(<<-End, __FILE__, __LINE__ + 1) @@ -44,7 +44,7 @@ module BodyTracking def on_program(stmts) @parts << {type: :indexed, content: join_stmts(stmts)} - [@identifiers.to_a, @parts] + [@identifiers, @parts] end def on_string_content @@ -106,45 +106,35 @@ module BodyTracking end def on_call(left, dot, right) - method, mtype = - case right[0] - when :bt_ident - case - when DECIMAL_METHODS.include?(right[1]) - [right[1], :numeric_method] - when QUANTITY_METHODS.include?(right[1]) - [right[1], :quantity_method] - else - @disallowed[:method] << right[1] - [right[1], :unknown_method] - end - else - raise NotImplementedError, right.inspect - end + raise(NotImplementedError, right.inspect) unless right[0] == :bt_ident case left[0] when :bt_quantity - if mtype == :quantity_method + if @quantity_methods[left[1]].include?(right[1]) part_index = @parts.length @parts << {type: :unindexed, - content: "quantities['#{left[1]}']#{dot.to_s}#{method}"} + content: "quantities['#{left[1]}']#{dot.to_s}#{right[1]}"} [:bt_quantity_method_call, "parts[#{part_index}]", part_index] else - [:bt_numeric_method_call, "quantities['#{left[1]}'][index]#{dot.to_s}#{method}"] + @disallowed[:method] << right[1] unless @decimal_methods.include?(right[1]) + [:bt_numeric_method_call, + "quantities['#{left[1]}'][_index]#{dot.to_s}#{right[1]}"] end when :bt_quantity_method_call - if mtype == :quantity_method - @parts[left[2]][:content] << "#{dot.to_s}#{method}" + if @quantity_methods.default.include?(right[1]) + @parts[left[2]][:content] << "#{dot.to_s}#{right[1]}" left else - [:bt_numeric_method_call, "#{left[1]}#{dot.to_s}#{method}"] + @disallowed[:method] << right[1] unless @decimal_methods.include?(right[1]) + [:bt_numeric_method_call, "#{left[1]}#{dot.to_s}#{right[1]}"] end when :bt_numeric_method_call, :bt_expression - if mtype == :quantity_method + if @quantity_methods.default.include?(right[1]) # TODO: add error reporting raise NotImplementedError else - [:bt_numeric_method_call, "#{left[1]}#{dot.to_s}#{method}"] + @disallowed[:method] << right[1] unless @decimal_methods.include?(right[1]) + [:bt_numeric_method_call, "#{left[1]}#{dot.to_s}#{right[1]}"] end else raise NotImplementedError, left.inspect @@ -182,7 +172,7 @@ module BodyTracking [ :bt_expression, [left, right].map do |side| - side[0] == :bt_quantity ? "quantities['#{side[1]}'][index]" : "#{side[1]}" + side[0] == :bt_quantity ? "quantities['#{side[1]}'][_index]" : "#{side[1]}" end.join(op.to_s) ] end @@ -234,7 +224,7 @@ module BodyTracking when :bt_expression, :bt_numeric_method_call token when :bt_quantity - "quantities['#{token}'][index]" + "quantities['#{token}'][_index]" else raise NotImplementedError, stmt.inspect end diff --git a/test/unit/formula_test.rb b/test/unit/formula_test.rb index 570430c..2b588f0 100644 --- a/test/unit/formula_test.rb +++ b/test/unit/formula_test.rb @@ -62,10 +62,16 @@ class FormulaTest < ActiveSupport::TestCase {type: :indexed, content: "quantities['Fats'][_index].nil?||" \ "quantities['Fats'][_index]/quantities['Proteins'][_index]>2"} ], + + # Model method calls + '100*Energy/RM.lastBefore(Meal.eaten_at||Meal.created_at)', Set['Energy', 'RM', 'Meal'], + # TODO: fill parts + [] ] + d_methods = ['nil?', 'abs'] vector.each_slice(3) do |formula, identifiers, parts| - parser = FormulaBuilder.new(formula) + parser = FormulaBuilder.new(formula, d_methods: d_methods) i, p = parser.parse assert_empty parser.errors assert_equal identifiers, i