module Jobshop class Inspection::DeviationCriterion < ApplicationRecord self.primary_keys = [ :organization_id, :report_id, :criterion_id, :criterion_type ] after_initialize do self.criterion_id ||= SecureRandom.uuid if new_record? end belongs_to :organization belongs_to :report, class_name: "Jobshop::Inspection::Report", foreign_key: [ :organization_id, :report_id ], inverse_of: :criteria has_one :criterion, as: :criterion, autosave: true, dependent: :destroy, foreign_key: [ :organization_id, :report_id, :criterion_id, :criterion_type ] alias_method :criterion_without_build, :criterion def criterion_with_build criterion_without_build || build_criterion end; alias_method :criterion, :criterion_with_build delegate :name, :name=, :position, :position=, to: :criterion validates :nominal, presence: true validates :lower, presence: true validates :upper, presence: true validate :lower_less_than_upper def mean ((nominal + lower) + (nominal + upper)) / 2 end def nominal self[:nominal] && Unitwise(self[:nominal].truncate(4), self[:unit]) end def lower self[:lower] && Unitwise(self[:lower].truncate(4), self[:unit]) end def lower=(value) self[:lower] = if value.respond_to?(:unit) value.convert_to(self[:unit]).value else value end end def upper self[:upper] && Unitwise(self[:upper].truncate(4), self[:unit]) end def upper=(value) self[:upper] = if value.respond_to?(:unit) value.convert_to(self[:unit]).value else value end end def lower_less_than_upper return unless lower && upper if lower > upper errors[:base] << "minimum must be less than maximum" end end def specification @specfication ||= if symmetric? "#{nominal}±#{abs(upper)}" else "#{nominal}\n#{lower}/#{upper}" end end def symmetric? @symmetrical ||= lower == upper * -1 end def pass?(value) !undersize?(value) && !oversize?(value) end # Generate a random value that has a 90% chance of being in spec. # TODO: This needs to go in the tests somewhere, not really in the model. def random rand_lower = nominal.to_f + lower.to_f * 1.1 rand_upper = nominal.to_f + upper.to_f * 1.1 value = rand(rand_lower..rand_upper) Unitwise(value.truncate(4), self[:unit]) end private def oversize?(value) value = if value.respond_to?(:unit) value.convert_to(self[:unit]) else Unitwise(value, self[:unit]) end value > upper || false end private def undersize?(value) value = if value.respond_to?(:unit) value.convert_to(self[:unit]) else Unitwise(value, self[:unit]) end value < nominal - lower || false end end end