Formula reports ambiguous quantity identifiers
Formula returns error.details for Rails 4 Formula distinguishes root path (::Identifier) Quantity validates name changes against introducing formulas' ambiguities
This commit is contained in:
parent
74c863c73a
commit
6b3714817c
@ -8,7 +8,16 @@ class Formula < ActiveRecord::Base
|
|||||||
|
|
||||||
validates :code, presence: true
|
validates :code, presence: true
|
||||||
validate do
|
validate do
|
||||||
parse.each { |message, params| errors.add(:code, message, params) }
|
messages = parse.each { |message, params| errors.add(:code, message, params) }
|
||||||
|
# NOTE: mimics errors.details available in Rails 5.2; remove once not needed;
|
||||||
|
# used only in Quantity :name change validation
|
||||||
|
unless errors.instance_variable_defined?(:@details)
|
||||||
|
class << errors
|
||||||
|
attr_accessor :details
|
||||||
|
end
|
||||||
|
errors.details = Hash.new { |h,k| h[k] = []}
|
||||||
|
messages.each { |message, *| errors.details[:code] << {error: message} }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
after_initialize do
|
after_initialize do
|
||||||
@ -86,15 +95,23 @@ class Formula < ActiveRecord::Base
|
|||||||
(quantity.project.try(&:quantities) || Quantity.where(project: nil))
|
(quantity.project.try(&:quantities) || Quantity.where(project: nil))
|
||||||
.select { |q| q_names.include?(q.name) }.each do |q|
|
.select { |q| q_names.include?(q.name) }.each do |q|
|
||||||
|
|
||||||
# NOTE: after upgrade do Ruby 2.7 replace with Enumerator#produce
|
# NOTE: after upgrade to Ruby 2.7 replace with Enumerator#produce
|
||||||
current, path = q, q.name
|
current, path = q, q.name
|
||||||
while current
|
loop do
|
||||||
q_paths[path] = q_paths.has_key?(path) ? nil : q
|
q_paths[path] = q_paths.has_key?(path) ? nil : q
|
||||||
|
break unless current
|
||||||
current, path = current.parent, "#{current.parent.try(:name)}::#{path}"
|
current, path = current.parent, "#{current.parent.try(:name)}::#{path}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
quantities = []
|
quantities = []
|
||||||
identifiers.reject! { |i| q_paths[i] && quantities << q_paths[i] }
|
identifiers.reject! do |i|
|
||||||
|
if q_paths.has_key?(i)
|
||||||
|
q = q_paths[i]
|
||||||
|
q.nil? ? errors << [:ambiguous_dependency, {identifier: i}] : quantities << q
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
models = identifiers.map(&:safe_constantize).compact || []
|
models = identifiers.map(&:safe_constantize).compact || []
|
||||||
(identifiers - models.map(&:class_name)).each do |i|
|
(identifiers - models.map(&:class_name)).each do |i|
|
||||||
errors << [:unknown_dependency, {identifier: i}]
|
errors << [:unknown_dependency, {identifier: i}]
|
||||||
|
@ -6,7 +6,7 @@ class Quantity < ActiveRecord::Base
|
|||||||
}
|
}
|
||||||
|
|
||||||
acts_as_nested_set dependent: :destroy, scope: :project
|
acts_as_nested_set dependent: :destroy, scope: :project
|
||||||
belongs_to :project, required: false
|
belongs_to :project, inverse_of: :quantities, required: false
|
||||||
has_many :nutrients, dependent: :restrict_with_error
|
has_many :nutrients, dependent: :restrict_with_error
|
||||||
has_many :readouts, dependent: :restrict_with_error
|
has_many :readouts, dependent: :restrict_with_error
|
||||||
has_many :values, class_name: 'QuantityValue', dependent: :restrict_with_error
|
has_many :values, class_name: 'QuantityValue', dependent: :restrict_with_error
|
||||||
@ -19,17 +19,26 @@ class Quantity < ActiveRecord::Base
|
|||||||
formula.mark_for_destruction if formula.present? && formula.code.blank?
|
formula.mark_for_destruction if formula.present? && formula.code.blank?
|
||||||
end
|
end
|
||||||
|
|
||||||
validates :name, presence: true
|
# TODO: :name should be validated against model names (Meal, Ingredient etc.)
|
||||||
# Quantity :name uniqueness relaxed to formulas unambiguity
|
# Quantity :name uniqueness relaxed to formulas unambiguity
|
||||||
validate if: -> { name_changed? } do
|
validates :name, presence: true, uniqueness: {scope: [:project_id, :parent_id]}
|
||||||
|
# Formula ambiguity vlidation delayed after save, as otherwise there seems to
|
||||||
|
# be no other way to validate against newly changed :name
|
||||||
|
after_save do
|
||||||
|
next unless name_changed?
|
||||||
formulas = project.formulas.where('formulas.code LIKE ?', "%#{name}%").includes(:quantity)
|
formulas = project.formulas.where('formulas.code LIKE ?', "%#{name}%").includes(:quantity)
|
||||||
# FIXME: should actually parse formulas in formulas and check for exact name match;
|
next unless formulas.exists?
|
||||||
# current code is just quick'n'dirty partial solution
|
|
||||||
if formulas.exists?
|
quantity_names = formulas.reject(&:valid?)
|
||||||
quantity_names = formulas.map { |f| "'#{f.quantity.name}'" }.join(',')
|
.select { |f| f.errors.details[:code].any? { |e| e[:error] == :ambiguous_dependency } }
|
||||||
|
.map { |f| "'#{f.quantity.name}'" }.join(', ')
|
||||||
|
|
||||||
|
unless quantity_names.blank?
|
||||||
errors.add(:name, :name_ambiguous, names: quantity_names)
|
errors.add(:name, :name_ambiguous, names: quantity_names)
|
||||||
|
raise ActiveRecord::RecordInvalid.new(self)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
validates :domain, inclusion: {in: domains.keys}
|
validates :domain, inclusion: {in: domains.keys}
|
||||||
validate if: -> { parent.present? } do
|
validate if: -> { parent.present? } do
|
||||||
errors.add(:parent, :parent_domain_mismatch) unless domain == parent.domain
|
errors.add(:parent, :parent_domain_mismatch) unless domain == parent.domain
|
||||||
|
@ -54,6 +54,7 @@ en:
|
|||||||
disallowed_keyword: 'includes disallowed keyword "%{keyword}"'
|
disallowed_keyword: 'includes disallowed keyword "%{keyword}"'
|
||||||
disallowed_method: 'includes disallowed method call "%{method}"'
|
disallowed_method: 'includes disallowed method call "%{method}"'
|
||||||
unknown_dependency: 'contains undefined dependency "%{identifier}"'
|
unknown_dependency: 'contains undefined dependency "%{identifier}"'
|
||||||
|
ambiguous_dependency: 'contains ambiguous reference to "%{identifier}"'
|
||||||
computation_failed: 'computation failed for "%{quantity}": %{description}
|
computation_failed: 'computation failed for "%{quantity}": %{description}
|
||||||
(%{count} values missing)'
|
(%{count} values missing)'
|
||||||
body_trackers:
|
body_trackers:
|
||||||
|
Reference in New Issue
Block a user