# 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: Module: AMEE::DataAbstraction::CalculationCollectionAnalyticsSupport
module AMEE
module Analytics
# Mixin module for the AMEE::DataAbstraction::CalculationCollection
# class, providing methods for handling collections of calculations.
#
module CalculationCollectionAnalyticsSupport
# Returns true if all calculations in self are
# representatives of the same prototype calculation. Otherwise,
# returns false.
#
def homogeneous?
calculation_labels.size == 1
end
# Returns true if all calculations in self are NOT
# representatives of the same prototype calculation. Otherwise,
# returns false.
#
def heterogeneous?
!homogeneous?
end
# Returns an array containing all of the unique labels for calculations
# held in self.
#
def calculation_labels
map(&:label).uniq
end
def +(other_calc_coll)
self.class.new(self.to_a + other_calc_coll.to_a)
end
def -(other_calc_coll)
other_calc_coll = [other_calc_coll].flatten
self.delete_if { |calc| other_calc_coll.include?(calc) }
end
# Similar to #sort_by! but returns a new instance of
# CalculationCollection arranged according to the values on the
# term labelled term. E.g.
#
# my_calculation_collection.sort_by :co2
#
# #=>
#
def sort_by(term)
term = term.to_sym unless term.is_a? Symbol
AMEE::DataAbstraction::CalculationCollection.new(send(term).sort_by(:value).map(&:parent))
end
# Sorts the calculation collection in place according to the values on the
# term labelled term, returning self.
#
# my_calculation_collection.sort_by! :mass
#
# #=>
#
def sort_by!(term)
replace(sort_by(term))
end
# Returns a new instance of CalculationCollection containing the same
# calculations contained within self but with the units of the term
# term standardized on each.
#
# If no further arguments are provided, the standardized units represent
# those which currently predominate amongst the relevent terms. Otherwise,
# the specific unit and/or per unit which are required for each instance
# of term can be explicitly specified as the second and third
# arguments respecitively. Units can be specified in any of the formats
# which are acceptable to the Quantify::Unit.for method (i.e.
# stringified unit names or symbols, symbolized labels, or
# Quantify::Unit::Base instances of the required unit). E.g.
#
# my_calculation_collection.standardize_units(:mass)
#
# #=>
#
# my_calculation_collection.standardize_units(:mass, :lb)
#
# #=>
#
# my_calculation_collection.standardize_units(:mass, 'pound')
#
# #=>
#
# my_calculation_collection.standardize_units(:mass, )
#
# #=>
#
# my_calculation_collection.standardize_units(:distance, :km, 'year')
#
# #=>
#
def standardize_units(term,unit=nil,per_unit=nil)
term = term.to_sym unless term.is_a? Symbol
new_calcs = send(term).standardize_units(unit,per_unit).map do |term|
calc = term.parent
calc.contents[term.label] = term
calc
end
AMEE::DataAbstraction::CalculationCollection.new(new_calcs)
end
# Similar to #standardize_units but standardizes units in place,
# returning self
#
def standardize_units!(term,unit=nil,per_unit=nil)
new_calcs = standardize_units(term,unit,per_unit)
replace(new_calcs)
end
# Returns an array of instances of the Result class representing the
# sums of all outputs represented within the collection
#
def sum_all_outputs
AMEE::DataAbstraction::TermsList.new(terms.outputs.visible.labels.uniq.map { |o| send(o).sum })
end
# Returns a new instance of the class TermsList representing either
# CO2 or CO2e outputs from each calculation
#
def co2_or_co2e_outputs
terms = AMEE::DataAbstraction::TermsList.new
each do |calculation|
if calculation['co2e']
terms << calculation['co2e']
elsif calculation.outputs.visible.labels.size == 1 && calculation.outputs.visible.labels.first == :co2
terms << calculation['co2']
end
end
return terms
end
# Call the #calculate! method on all calculations contained within
# self.
#
def calculate_all!
each { |calc| calc.calculate! }
end
# Call the #save method on all calculations contained within
# self.
#
def save_all!
each { |calc| calc.save }
end
# Returns a terms list of all terms held by calculations contained within
# self.
#
def terms
AMEE::DataAbstraction::TermsList.new( (self.map { |calc| calc.terms.map { |term| term } }).flatten )
end
AMEE::DataAbstraction::TermsList::Selectors.each do |sel|
delegate sel,:to=>:terms
end
# Return an instance of TermsList containing only terms labelled
# :type.
#
# This method overrides the standard #type method (which is deprecated) and
# mimics the functionality provied by the first #method_missing method in
# dynamically retrieving a subset of terms according their labels.
#
def type
terms.type
end
def respond_to?(method)
if terms.labels.include? method.to_sym
return true
elsif method.to_s =~ /sort_by_(.*)!/ and terms.labels.include? $1.to_sym
return true
elsif method.to_s =~ /sort_by_(.*)/ and terms.labels.include? $1.to_sym
return true
else
super
end
end
# Syntactic sugar for several instance methods.
#
# ---
#
# Call a method on self which named after a specific term label
# contained with an associated calculation and return an instance of the
# TermsList class contain each of those terms. E.g.,
#
# my_terms = my_calculation_collection.type #=>
# my_terms.label #=> :type
#
# my_terms = my_calculation_collection.mass #=>
# my_terms.label #=> :mass
#
# my_terms = my_calculation_collection.co2 #=>
# my_terms.label #=> :co2
#
# ---
#
# Call either the #sort_by or #sort_by! methods including
# the argument term as part of the method name, e.g.,
#
# my_calculation_collection.sort_by_co2
#
# #=>
#
# my_calculation_collection.sort_by_mass!
#
# #=>
#
def method_missing(method, *args, &block)
if terms.labels.include? method.to_sym
terms.send(method.to_sym)
elsif method.to_s =~ /sort_by_(.*)!/ and terms.labels.include? $1.to_sym
sort_by! $1.to_sym
elsif method.to_s =~ /sort_by_(.*)/ and terms.labels.include? $1.to_sym
sort_by $1.to_sym
else
super
end
end
end
end
end