amee-data-abstraction 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rvmrc +1 -0
- data/CHANGELOG.txt +4 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +41 -0
- data/LICENSE.txt +27 -0
- data/README.txt +188 -0
- data/Rakefile +102 -0
- data/VERSION +1 -0
- data/amee-data-abstraction.gemspec +115 -0
- data/examples/_calculator_form.erb +27 -0
- data/examples/calculation_controller.rb +16 -0
- data/init.rb +4 -0
- data/lib/amee-data-abstraction.rb +30 -0
- data/lib/amee-data-abstraction/calculation.rb +236 -0
- data/lib/amee-data-abstraction/calculation_set.rb +101 -0
- data/lib/amee-data-abstraction/drill.rb +63 -0
- data/lib/amee-data-abstraction/exceptions.rb +47 -0
- data/lib/amee-data-abstraction/input.rb +197 -0
- data/lib/amee-data-abstraction/metadatum.rb +58 -0
- data/lib/amee-data-abstraction/ongoing_calculation.rb +545 -0
- data/lib/amee-data-abstraction/output.rb +16 -0
- data/lib/amee-data-abstraction/profile.rb +108 -0
- data/lib/amee-data-abstraction/prototype_calculation.rb +350 -0
- data/lib/amee-data-abstraction/term.rb +506 -0
- data/lib/amee-data-abstraction/terms_list.rb +150 -0
- data/lib/amee-data-abstraction/usage.rb +90 -0
- data/lib/config/amee_units.rb +129 -0
- data/lib/core-extensions/class.rb +27 -0
- data/lib/core-extensions/hash.rb +43 -0
- data/lib/core-extensions/ordered_hash.rb +21 -0
- data/lib/core-extensions/proc.rb +15 -0
- data/rails/init.rb +32 -0
- data/spec/amee-data-abstraction/calculation_set_spec.rb +54 -0
- data/spec/amee-data-abstraction/calculation_spec.rb +75 -0
- data/spec/amee-data-abstraction/drill_spec.rb +38 -0
- data/spec/amee-data-abstraction/input_spec.rb +77 -0
- data/spec/amee-data-abstraction/metadatum_spec.rb +17 -0
- data/spec/amee-data-abstraction/ongoing_calculation_spec.rb +494 -0
- data/spec/amee-data-abstraction/profile_spec.rb +39 -0
- data/spec/amee-data-abstraction/prototype_calculation_spec.rb +256 -0
- data/spec/amee-data-abstraction/term_spec.rb +385 -0
- data/spec/amee-data-abstraction/terms_list_spec.rb +53 -0
- data/spec/config/amee_units_spec.rb +71 -0
- data/spec/core-extensions/class_spec.rb +25 -0
- data/spec/core-extensions/hash_spec.rb +44 -0
- data/spec/core-extensions/ordered_hash_spec.rb +12 -0
- data/spec/core-extensions/proc_spec.rb +12 -0
- data/spec/fixtures/electricity.rb +35 -0
- data/spec/fixtures/electricity_and_transport.rb +55 -0
- data/spec/fixtures/transport.rb +26 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +244 -0
- metadata +262 -0
@@ -0,0 +1,506 @@
|
|
1
|
+
# Copyright (C) 2011 AMEE UK Ltd. - http://www.amee.com
|
2
|
+
# Released as Open Source Software under the BSD 3-Clause license. See LICENSE.txt for details.
|
3
|
+
|
4
|
+
# :title: Class: AMEE::DataAbstraction::Term
|
5
|
+
require 'bigdecimal'
|
6
|
+
|
7
|
+
module AMEE
|
8
|
+
module DataAbstraction
|
9
|
+
|
10
|
+
# Base class for representing quantities which are inputs to, outputs of, or
|
11
|
+
# metadata associated with, calculations. Typically several instances of the
|
12
|
+
# <i>Term</i> class (or subclasses) will be associated with instances of the
|
13
|
+
# <i>Calculation</i> class or its subclasses (<i>PrototypeCalculation</i>,
|
14
|
+
# <i>OngoingCalculation</i>).
|
15
|
+
#
|
16
|
+
# Instances of <i>Term</i> are represented by several primary attributes:
|
17
|
+
#
|
18
|
+
# label:: Symbol representing the unique, machine-readable name for the
|
19
|
+
# term (<b>required</b>)
|
20
|
+
#
|
21
|
+
# value:: In principle, any object which represent the desired value
|
22
|
+
# which the term represents
|
23
|
+
#
|
24
|
+
# name:: String representing a human-readable name for the term
|
25
|
+
#
|
26
|
+
# path:: String representing the AMEE platform path to the AMEE item
|
27
|
+
# value definition which is associated with <tt>self</tt>.
|
28
|
+
# This attribute is required only if the term represents an
|
29
|
+
# item value definition in the AMEE platform
|
30
|
+
#
|
31
|
+
# Other available attribute-like methods include <tt>type</tt>,
|
32
|
+
# <tt>interface</tt>, <tt>note</tt>, <tt>unit</tt>, <tt>per_unit</tt>,
|
33
|
+
# <tt>default_unit</tt>, <tt>default_per_unit</tt> and <tt>parent</tt>.
|
34
|
+
#
|
35
|
+
# Subclasses of the <i>Term</i> correspond to:
|
36
|
+
# * <i>Input</i>
|
37
|
+
# * * <i>Profile</i> -- corresponds to an AMEE profile item value
|
38
|
+
# * * <i>Drill</i> -- corresponds to an AMEE drill down choice
|
39
|
+
# * * <i>Usage</i> -- corresponds to a (runtime adjustable) AMEE usage choice
|
40
|
+
# * * <i>Metadatum</i> -- represents other arbitrary inputs
|
41
|
+
# * <i>Output</i> -- corresponds to an AMEE return value
|
42
|
+
#
|
43
|
+
class Term
|
44
|
+
|
45
|
+
public
|
46
|
+
|
47
|
+
# Symbol representing the unique (within the parent calculation), machine-
|
48
|
+
# readable name for <tt>self</tt>. Set a value by passing an argument.
|
49
|
+
# Retrieve a value by calling without an argument, e.g.,
|
50
|
+
#
|
51
|
+
# my_term.label :distance
|
52
|
+
#
|
53
|
+
# my_term.label #=> :distance
|
54
|
+
#
|
55
|
+
attr_property :label
|
56
|
+
|
57
|
+
# String representing a human-readable name for <tt>self</tt>. Set a value
|
58
|
+
# by passing an argument. Retrieve a value by calling without an argument,
|
59
|
+
# e.g.,
|
60
|
+
#
|
61
|
+
# my_term.name 'Distance driven'
|
62
|
+
#
|
63
|
+
# my_term.name #=> 'Distance driven'
|
64
|
+
#
|
65
|
+
attr_property :name
|
66
|
+
|
67
|
+
# Symbol representing the class the value should be parsed to. If
|
68
|
+
# omitted a string is assumed, e.g.:
|
69
|
+
#
|
70
|
+
# my_term.type :integer
|
71
|
+
# my_term.value "12"
|
72
|
+
# my_term.value # => 12
|
73
|
+
# my_term.value_before_cast # => "12"
|
74
|
+
#
|
75
|
+
attr_property :type
|
76
|
+
|
77
|
+
# String representing a the AMEE platform path for <tt>self</tt>. Set a
|
78
|
+
# value by passing an argument. Retrieve a value by calling without an
|
79
|
+
# argument, e.g.,
|
80
|
+
#
|
81
|
+
# my_term.path 'mass'
|
82
|
+
#
|
83
|
+
# my_term.path #=> 'mass'
|
84
|
+
#
|
85
|
+
attr_property :path
|
86
|
+
|
87
|
+
# String representing an annotation for <tt>self</tt>. Set a value by
|
88
|
+
# passing an argument. Retrieve a value by calling without an argument,
|
89
|
+
# e.g.,
|
90
|
+
#
|
91
|
+
# my_term.note 'Enter the mass of cement produced in the reporting period'
|
92
|
+
#
|
93
|
+
# my_term.note #=> 'Enter the mass of cement ...'
|
94
|
+
#
|
95
|
+
attr_property :note
|
96
|
+
|
97
|
+
# Symbol representing the owning parent calculation of <tt>self</tt>. Set
|
98
|
+
# the owning calculation object by passing as an argument. Retrieve it by
|
99
|
+
# calling without an argument, e.g.,
|
100
|
+
#
|
101
|
+
# my_calculation = <AMEE::DataAbstraction::OngoingCalculation ... >
|
102
|
+
#
|
103
|
+
# my_term.parent my_calculation
|
104
|
+
#
|
105
|
+
# my_term.parent #=> <AMEE::DataAbstraction::OngoingCalculation ... >
|
106
|
+
#
|
107
|
+
attr_accessor :parent
|
108
|
+
|
109
|
+
# Stores pre-cast value
|
110
|
+
attr_accessor :value_before_cast
|
111
|
+
|
112
|
+
# Initialize a new instance of <i>Term</i>.
|
113
|
+
#
|
114
|
+
# The term can be configured in place by passing a block (evaluated in the
|
115
|
+
# context of the new instance) which defines the term properties using the
|
116
|
+
# macro-style instance helper methods.
|
117
|
+
#
|
118
|
+
# my_term = Term.new {
|
119
|
+
#
|
120
|
+
# label :size
|
121
|
+
# path "vehicleSize"
|
122
|
+
# hide!
|
123
|
+
# ...
|
124
|
+
# }
|
125
|
+
#
|
126
|
+
# The parent calculation object associated with <tt>self</tt> can be assigned
|
127
|
+
# using the :parent hash key passed as an argument.
|
128
|
+
#
|
129
|
+
# Unless otherwise configured within the passed block, several attributes
|
130
|
+
# attempt to take default configurations if possible using rules of thumb:
|
131
|
+
#
|
132
|
+
# * value => <tt>nil</tt>
|
133
|
+
# * enabled => <tt>true</tt>
|
134
|
+
# * visible => <tt>true</tt>
|
135
|
+
# * label => underscored, symbolized version of <tt>path</tt>
|
136
|
+
# * path => stringified version of <tt>label</tt>
|
137
|
+
# * name => stringified and humanised version of <tt>label</tt>
|
138
|
+
# * unit => <tt>default_unit</tt>
|
139
|
+
# * per_unit => <tt>default_per_unit</tt>
|
140
|
+
#
|
141
|
+
def initialize(options={},&block)
|
142
|
+
@parent=options[:parent]
|
143
|
+
@value=nil
|
144
|
+
@type=nil
|
145
|
+
@enabled=true
|
146
|
+
@visible=true
|
147
|
+
instance_eval(&block) if block
|
148
|
+
label path.to_s.underscore.to_sym unless path.blank?||label
|
149
|
+
path label.to_s unless path
|
150
|
+
name label.to_s.humanize unless name
|
151
|
+
unit default_unit unless unit
|
152
|
+
per_unit default_per_unit unless per_unit
|
153
|
+
end
|
154
|
+
|
155
|
+
# Valid choices for suggested interfaces for a term.
|
156
|
+
# Dynamic boolean methods (such as <tt>text_box?</tt>) are generated for
|
157
|
+
# checking which value is set.
|
158
|
+
#
|
159
|
+
# my_term.drop_down? #=> true
|
160
|
+
#
|
161
|
+
Interfaces=[:text_box,:drop_down,:date]
|
162
|
+
|
163
|
+
Interfaces.each do |inf|
|
164
|
+
define_method("#{inf.to_s}?") {
|
165
|
+
interface==inf
|
166
|
+
}
|
167
|
+
end
|
168
|
+
|
169
|
+
# Symbolized attribute representing the expected interface type for
|
170
|
+
# <tt>self</tt>. Set a value by passing an argument. Retrieve a value by
|
171
|
+
# calling without an argument, e.g.,
|
172
|
+
#
|
173
|
+
# my_term.interface :drop_down
|
174
|
+
#
|
175
|
+
# my_term.interface #=> :drop_down
|
176
|
+
#
|
177
|
+
# Must represent one of the valid choices defined in the
|
178
|
+
# <i>Term::Interfaces</i> constant
|
179
|
+
#
|
180
|
+
# If the provided interface is not valid (as defined in <i>Term::Interfaces</i>)
|
181
|
+
# an <i>InvalidInterface</i> exception is raised
|
182
|
+
#
|
183
|
+
def interface(inf=nil)
|
184
|
+
if inf
|
185
|
+
raise Exceptions::InvalidInterface unless Interfaces.include? inf
|
186
|
+
@interface=inf
|
187
|
+
end
|
188
|
+
return @interface
|
189
|
+
end
|
190
|
+
|
191
|
+
# Object representing the value which <tt>self</tt> is considered to
|
192
|
+
# represent (e.g. the quantity or name of something). Set a value by
|
193
|
+
# passing an argument. Retrieve a value by calling without an argument,
|
194
|
+
# e.g.,
|
195
|
+
#
|
196
|
+
# my_term.value 12
|
197
|
+
# my_term.value #=> 12
|
198
|
+
#
|
199
|
+
#
|
200
|
+
# my_term.value 'Ford Escort'
|
201
|
+
# my_term.value #=> 'Ford Escort'
|
202
|
+
#
|
203
|
+
#
|
204
|
+
# my_term.value DateTime.civil(2010,12,31)
|
205
|
+
# my_term.value #=> <Date: 4911123/2,0,2299161>
|
206
|
+
#
|
207
|
+
def value(*args)
|
208
|
+
unless args.empty?
|
209
|
+
@value_before_cast = args.first
|
210
|
+
@value = @type ? self.class.convert_value_to_type(args.first, @type) : args.first
|
211
|
+
end
|
212
|
+
@value
|
213
|
+
end
|
214
|
+
|
215
|
+
# Symbols representing the attributes of <tt>self</tt> which are concerned
|
216
|
+
# with quantity units.
|
217
|
+
#
|
218
|
+
# Each symbol also represents <b>dynamically defined method<b> name for
|
219
|
+
# setting and retrieving the default and current units and per units. Units
|
220
|
+
# are initialized as instances of <i>Quantify::Unit::Base</tt> is required.
|
221
|
+
#
|
222
|
+
# Set a unit attribute by passing an argument. Retrieve a value by calling
|
223
|
+
# without an argument. Unit attributes can be defined by any form which is
|
224
|
+
# accepted by the <i>Quantify::Unit#for</i> method (either an instance of
|
225
|
+
# <i>Quantify::Unit::Base</i> (or subclass) or a symbolized or string
|
226
|
+
# representation of the a unit symbol, name or label). E.g.,
|
227
|
+
#
|
228
|
+
# my_term.unit :mi
|
229
|
+
# my_term.unit #=> <Quantify::Unit::NonSI:0xb71cac48 @label="mi" ... >
|
230
|
+
#
|
231
|
+
# my_term.default_unit 'feet'
|
232
|
+
# my_term.default_unit #=> <Quantify::Unit::NonSI:0xb71cac48 @label="ft" ... >
|
233
|
+
#
|
234
|
+
#
|
235
|
+
# my_time_unit = Unit.hour #=> <Quantify::Unit::NonSI:0xb71cac48 @label="h" ... >
|
236
|
+
# my_term.default_per_unit my_time_unit
|
237
|
+
# my_term.default_per_unit #=> <Quantify::Unit::NonSI:0xb71cac48 @label="h" ... >
|
238
|
+
#
|
239
|
+
#
|
240
|
+
# Dynamically defined methods are also available for setting and retrieving
|
241
|
+
# alternative units for the <tt>unit</tt> and <tt>per_unit</tt> attributes.
|
242
|
+
# If no alternative units are explicitly defined, they are instantiated by
|
243
|
+
# default to represent all dimensionally equivalent units available in the
|
244
|
+
# system of units defined by <i>Quantify</i>. E.g.
|
245
|
+
#
|
246
|
+
# my_term.unit :kg
|
247
|
+
# my_term.alternative_units #=> [ <Quantify::Unit::NonSI:0xb71cac48 @label="mi" ... >,
|
248
|
+
# <Quantify::Unit::SI:0xb71cac48 @label="km" ... >,
|
249
|
+
# <Quantify::Unit::NonSI:0xb71cac48 @label="ft" ... >,
|
250
|
+
# ... ]
|
251
|
+
#
|
252
|
+
# my_term.unit 'litre'
|
253
|
+
# my_term.alternative_units :bbl, :gal
|
254
|
+
# my_term.alternative_units #=> [ <Quantify::Unit::NonSI:0xb71cac48 @label="bbl" ... >,
|
255
|
+
# <Quantify::Unit::NonSI:0xb71cac48 @label="gal" ... > ]
|
256
|
+
#
|
257
|
+
UnitFields = [:unit,:per_unit,:default_unit,:default_per_unit]
|
258
|
+
|
259
|
+
UnitFields.each do |field|
|
260
|
+
define_method(field) do |*unit|
|
261
|
+
instance_variable_set("@#{field}",Unit.for(unit.first)) unless unit.empty?
|
262
|
+
instance_variable_get("@#{field}")
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
[:unit,:per_unit].each do |field|
|
267
|
+
define_method("alternative_#{field}s") do |*args|
|
268
|
+
ivar = "@alternative_#{field}s"
|
269
|
+
default = send("default_#{field}".to_sym)
|
270
|
+
unless args.empty?
|
271
|
+
args << default if default
|
272
|
+
units = args.map {|arg| Unit.for(arg) }
|
273
|
+
Term.validate_dimensional_equivalence?(*units)
|
274
|
+
instance_variable_set(ivar, units)
|
275
|
+
else
|
276
|
+
return instance_variable_get(ivar) if instance_variable_get(ivar)
|
277
|
+
return instance_variable_set(ivar, (default.alternatives << default)) if default
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
# Returns <tt>true</tt> if <tt>self</tt> has a populated value attribute.
|
283
|
+
# Otherwise, returns <tt>false</tt>.
|
284
|
+
#
|
285
|
+
def set?
|
286
|
+
!value_before_cast.nil?
|
287
|
+
end
|
288
|
+
|
289
|
+
# Returns <tt>true</tt> if <tt>self</tt> does not have a populated value
|
290
|
+
# attribute. Otherwise, returns <tt>false</tt>.
|
291
|
+
#
|
292
|
+
def unset?
|
293
|
+
value_before_cast.nil?
|
294
|
+
end
|
295
|
+
|
296
|
+
# Declare that the term's UI element should be disabled
|
297
|
+
def disable!
|
298
|
+
@disabled=true
|
299
|
+
end
|
300
|
+
|
301
|
+
# Declare that the term's UI element should be enabled
|
302
|
+
def enable!
|
303
|
+
@disabled=false
|
304
|
+
end
|
305
|
+
|
306
|
+
# Returns <tt>true</tt> if the UI element of <tt>self</tt> is disabled.
|
307
|
+
# Otherwise, returns <tt>false</tt>.
|
308
|
+
#
|
309
|
+
def disabled?
|
310
|
+
@disabled
|
311
|
+
end
|
312
|
+
|
313
|
+
# Returns <tt>true</tt> if the UI element of <tt>self</tt> is enabled.
|
314
|
+
# Otherwise, returns <tt>false</tt>.
|
315
|
+
#
|
316
|
+
def enabled?
|
317
|
+
!disabled?
|
318
|
+
end
|
319
|
+
|
320
|
+
# Returns <tt>true</tt> if <tt>self</tt> is configured as visible.
|
321
|
+
# Otherwise, returns <tt>false</tt>.
|
322
|
+
#
|
323
|
+
def visible?
|
324
|
+
@visible
|
325
|
+
end
|
326
|
+
|
327
|
+
# Returns <tt>true</tt> if <tt>self</tt> is configured as hidden.
|
328
|
+
# Otherwise, returns <tt>false</tt>.
|
329
|
+
#
|
330
|
+
def hidden?
|
331
|
+
!visible?
|
332
|
+
end
|
333
|
+
|
334
|
+
# Declare that the term's UI element should not be shown in generated UIs.
|
335
|
+
def hide!
|
336
|
+
@visible=false
|
337
|
+
end
|
338
|
+
|
339
|
+
# Declare that the term's UI element should be shown in generated UIs.
|
340
|
+
def show!
|
341
|
+
@visible=true
|
342
|
+
end
|
343
|
+
|
344
|
+
# Returns <tt>true</tt> if <tt>self</tt> has a numeric value. That is, can
|
345
|
+
# it have statistics applied? This method permits handling of term summing,
|
346
|
+
# averaging, etc. Otherwise, returns <tt>false</tt>.
|
347
|
+
#
|
348
|
+
def has_numeric_value?
|
349
|
+
set? and Float(value) rescue false
|
350
|
+
end
|
351
|
+
|
352
|
+
# Returns a pretty print string representation of <tt>self</tt>
|
353
|
+
def inspect
|
354
|
+
elements = {:label => label, :value => value, :unit => unit,
|
355
|
+
:per_unit => per_unit, :type => type,
|
356
|
+
:disabled => disabled?, :visible => visible?}
|
357
|
+
attr_list = elements.map {|k,v| "#{k}: #{v.inspect}" } * ', '
|
358
|
+
"<#{self.class.name} #{attr_list}>"
|
359
|
+
end
|
360
|
+
|
361
|
+
# Returns <tt>true</tt> if <tt>self</tt> occurs before the term with a label
|
362
|
+
# matching <tt>lab</tt> in the terms list of the parent calculation. Otherwise,
|
363
|
+
# returns <tt>false</tt>.
|
364
|
+
#
|
365
|
+
def before?(lab)
|
366
|
+
parent.terms.labels.index(lab)>parent.terms.labels.index(label)
|
367
|
+
end
|
368
|
+
|
369
|
+
# Returns <tt>true</tt> if <tt>self</tt> occurs after the term with a label
|
370
|
+
# matching <tt>lab</tt> in the terms list of the parent calculation. Otherwise,
|
371
|
+
# returns <tt>false</tt>.
|
372
|
+
#
|
373
|
+
def after?(lab)
|
374
|
+
parent.terms.labels.index(lab)<parent.terms.labels.index(label)
|
375
|
+
end
|
376
|
+
|
377
|
+
def initialize_copy(source)
|
378
|
+
super
|
379
|
+
UnitFields.each do |property|
|
380
|
+
prop = send(property)
|
381
|
+
self.send(property, prop.clone) unless prop.nil?
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
# Return a new instance of <i>Term</i>, based on <tt>self</tt> but with
|
386
|
+
# a change of units, according to the <tt>options</tt> hash provided, and
|
387
|
+
# the value attribute updated to reflect the new units.
|
388
|
+
#
|
389
|
+
# To specify a new unit, pass the required unit via the <tt>:unit</tt> key.
|
390
|
+
# To specify a new per_unit, pass the required per unit via the
|
391
|
+
# <tt>:per_unit</tt> key. E.g.,
|
392
|
+
#
|
393
|
+
# my_term.convert_unit(:unit => :kg)
|
394
|
+
#
|
395
|
+
# my_term.convert_unit(:unit => :kg, :per_unit => :h)
|
396
|
+
#
|
397
|
+
# my_term.convert_unit(:unit => 'kilogram')
|
398
|
+
#
|
399
|
+
# my_term.convert_unit(:per_unit => Quantify::Unit.h)
|
400
|
+
#
|
401
|
+
# my_term.convert_unit(:unit => <Quantify::Unit::SI ... >)
|
402
|
+
#
|
403
|
+
# If <tt>self</tt> does not hold a numeric value or either a unit or per
|
404
|
+
# unit attribute, <tt.self</tt> is returned.
|
405
|
+
#
|
406
|
+
def convert_unit(options={})
|
407
|
+
return self unless has_numeric_value? and (unit or per_unit)
|
408
|
+
new = clone
|
409
|
+
if options[:unit] and unit
|
410
|
+
new_unit = Unit.for(options[:unit])
|
411
|
+
Term.validate_dimensional_equivalence?(unit,new_unit)
|
412
|
+
new.value Quantity.new(new.value,new.unit).to(new_unit).value
|
413
|
+
new.unit options[:unit]
|
414
|
+
end
|
415
|
+
if options[:per_unit] and per_unit
|
416
|
+
new_per_unit = Unit.for(options[:per_unit])
|
417
|
+
Term.validate_dimensional_equivalence?(per_unit,new_per_unit)
|
418
|
+
new.value Quantity.new(new.value,(1/new.per_unit)).to(Unit.for(new_per_unit)).value
|
419
|
+
new.per_unit options[:per_unit]
|
420
|
+
end
|
421
|
+
return new
|
422
|
+
end
|
423
|
+
|
424
|
+
# Return an instance of Quantify::Quantity describing the quantity represented
|
425
|
+
# by <tt>self</tt>.
|
426
|
+
#
|
427
|
+
# If <tt>self</tt> does not contain a numeric value, <tt>nil</tt> is returned.
|
428
|
+
#
|
429
|
+
# If <tt>self</tt> contains a numeric value, but no unit or per unit, just
|
430
|
+
# the numeric value is returned
|
431
|
+
#
|
432
|
+
def to_quantity
|
433
|
+
return nil unless has_numeric_value?
|
434
|
+
if (unit.is_a? Quantify::Unit::Base) && (per_unit.is_a? Quantify::Unit::Base)
|
435
|
+
quantity_unit = unit/per_unit
|
436
|
+
elsif unit.is_a? Quantify::Unit::Base
|
437
|
+
quantity_unit = unit
|
438
|
+
elsif per_unit.is_a? Quantify::Unit::Base
|
439
|
+
quantity_unit = 1/per_unit
|
440
|
+
else
|
441
|
+
return value
|
442
|
+
end
|
443
|
+
Quantity.new(value,quantity_unit)
|
444
|
+
end
|
445
|
+
alias :to_q :to_quantity
|
446
|
+
|
447
|
+
# Returns a string representation of term based on the term value and any
|
448
|
+
# units which are defined. The format of the unit representation follows
|
449
|
+
# that defined by <tt>format</tt>, which should represent any of the formats
|
450
|
+
# supported by the <i>Quantify::Unit::Base</tt> class (i.e. :name,
|
451
|
+
# :pluralized_name, :symbol and :label). Default behaviour uses the unit
|
452
|
+
# symbol atribute, i.e. if no format explcitly specified:
|
453
|
+
#
|
454
|
+
# my_term.to_s #=> "12345 ton"
|
455
|
+
#
|
456
|
+
# my_term.to_s :symbol #=> "12345 ton"
|
457
|
+
#
|
458
|
+
# my_term.to_s :name #=> "12345 short ton"
|
459
|
+
#
|
460
|
+
# my_term.to_s :pluralized_name #=> "12345 tons"
|
461
|
+
#
|
462
|
+
# my_term.to_s :label #=> "12345 ton_us"
|
463
|
+
#
|
464
|
+
def to_s(format=:symbol)
|
465
|
+
string = "#{value}"
|
466
|
+
if unit and per_unit
|
467
|
+
string += " #{(unit/per_unit).send(format)}"
|
468
|
+
elsif unit
|
469
|
+
string += " #{unit.send(format)}"
|
470
|
+
elsif per_unit
|
471
|
+
string += " #{(1/per_unit).send(format)}"
|
472
|
+
end
|
473
|
+
return string
|
474
|
+
end
|
475
|
+
|
476
|
+
# Checks that the units included in <tt>units</tt> are dimensionally
|
477
|
+
# equivalent, that is, that they represent the same physucal quantity
|
478
|
+
#
|
479
|
+
def self.validate_dimensional_equivalence?(*units)
|
480
|
+
unless [units].flatten.all? {|unit| unit.dimensions == units[0].dimensions }
|
481
|
+
raise AMEE::DataAbstraction::Exceptions::InvalidUnits,
|
482
|
+
"The specified term units are not of equivalent dimensions: #{units.map(&:label).join(",")}"
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
def self.convert_value_to_type(value, type)
|
487
|
+
return nil if value.blank?
|
488
|
+
type = type.downcase.to_sym if type.is_a?(String)
|
489
|
+
|
490
|
+
case type
|
491
|
+
when :string then value.to_s
|
492
|
+
when :text then value.to_s
|
493
|
+
when :integer then value.to_i rescue 0
|
494
|
+
when :fixnum then value.to_i rescue 0
|
495
|
+
when :float then value.to_f rescue 0
|
496
|
+
when :decimal then value.to_s.to_d rescue 0
|
497
|
+
when :double then value.to_s.to_d rescue 0
|
498
|
+
when :datetime then DateTime.parse(value.to_s) rescue nil
|
499
|
+
when :time then Time.parse(value.to_s) rescue nil
|
500
|
+
when :date then Date.parse(value.to_s) rescue nil
|
501
|
+
else value
|
502
|
+
end
|
503
|
+
end
|
504
|
+
end
|
505
|
+
end
|
506
|
+
end
|