# Copyright (C) 2011 AMEE UK Ltd. - http://www.amee.com
# Released as Open Source Software under the BSD 3-Clause license. See LICENSE.txt for details.
# :title: Class: AMEE::Db::Calculation
module AMEE
module Db
# This class represents a database record for a calculation performed using
# the AMEE:DataAbstraction::OngoingCalculation class. This class stores
# the primary calculation level attributes such as the calculation
# calculation_type, profile_uid and profile_item_uid.
#
# The values and attributes of specific calculation terms are stored via the
# related class AMEE::Db::Term.
#
# This class is typically used by proxy, via the find,
# find_by_type, #save, #delete, and
# #get_db_calculation methods associated with the
# AMEE:DataAbstraction::OngoingCalculation class.
#
class Calculation < ActiveRecord::Base
has_many :terms, :class_name => "AMEE::Db::Term", :dependent => :destroy
validates_presence_of :calculation_type
validates_format_of :profile_item_uid, :with => /\A([A-Z0-9]{12})\z/, :allow_nil => true, :allow_blank => true
validates_format_of :profile_uid, :with => /\A([A-Z0-9]{12})\z/, :allow_nil => true, :allow_blank => true
before_save :validate_calculation_type
# Standardize the calculation_type attribute to String
# format. Called using before filter prior to record saving to ensure
# string serialization.
#
def validate_calculation_type
self.calculation_type = calculation_type.to_s
end
# Convenience method for returning the calculation_type attribute
# of self in canonical symbol form.
#
def type
calculation_type.to_sym
end
# Returns the subset of all instance attributes which should be editable via
# mass update methods and which should be included in hash representations of
# self, i.e. those passed in explcitly as data rather than added by
# ActiveRecord (e.g. id, created_at, etc.).
#
def primary_calculation_attributes
attributes.keys.reject {|attr| ['id','created_at','updated_at'].include? attr }
end
# Update the attributes of self and those of any related terms,
# according to the passed options hash. Any associated terms which
# are not represented in options are deleted.
#
# Term attributes provided in options should be keyed with the
# term label and include a sub-hash with keys represent one or more of
# :value, :unit and :per_unit. E.g.,
#
# options = { :profile_item_uid => "W93UEY573U4E8",
# :mass => { :value => 23 },
# :distance => { :value => 1400,
# :unit => }}
#
# my_calculation.update_calculation!(options)
#
def update_calculation!(options)
primary_calculation_attributes.each do |attr|
if options.keys.include? attr.to_sym
update_calculation_attribute!(attr,options.delete(attr.to_sym),false)
end
end
save!
options.each_pair do |attribute,value|
add_or_update_term!(attribute,value)
end
delete_unspecified_terms(options)
reload
end
# Update the attribute of self represented by the label
# key with the value of value. By default,
# the #save! method is called, in turn calling the class
# validations.
#
# Specify that the record should not be saved by passing false
# as the final argument.
#
def update_calculation_attribute!(key,value,save=true)
# use attr_accessor (via #send) method rather than
# #update_attribute so that validations are performed
#
send("#{key}=", (value.nil? ? nil : value.to_s))
save! if save
end
# Add, or update an existing, associated term represented by the label
# label and value, unit and/or per_unit attributes defined by
# data. The data argument should be a hash with keys
# represent one or more of :value, :unit and :per_unit. E.g.,
#
# data = { :value => 1400,
# :unit => }
#
# my_calculation.add_or_update_term!(:distance, data)
#
# This method is called as part of the #update_calculation!
# method
#
def add_or_update_term!(label,data)
term = Term.find_or_initialize_by_calculation_id_and_label(id,label.to_s)
term.update_attributes!(data)
end
# Delete all of the terms which are not explicitly referenced in the
# options hash.
#
# This method is called as part of the #update_calculation!
# method
#
def delete_unspecified_terms(options)
terms.each do |term|
Term.delete(term.id) unless options.keys.include? term.label.to_sym
end
end
# Returns a Hash representation of self including a only
# the data explicitly passed in (those added by ActiveRecord -
# created, updated, id - are ignored) as well
# as sub-hashes for all associated terms. E.g.,
#
# my_calculation.to_hash #=> { :profile_uid => "EYR758EY36WY",
# :profile_item_uid => "W83URT48DY3W",
# :type => { :value => 'car' },
# :distance => { :value => 1600,
# :unit => },
# :co2 => { :value => 234.1,
# :unit => }}
#
# This method can be used to initialize instances of the class
# OngoingCalculation by providing the hashed options for any of
# the choose... methods.
#
def to_hash
hash = {}
terms.each { |term| hash.merge!(term.to_hash) }
[ :profile_item_uid, :profile_uid ].each do |attr|
hash[attr] = self.send(attr)
end
return hash
end
end
end
end