amee-data-abstraction 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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,15 @@
|
|
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
|
+
class Proc
|
5
|
+
|
6
|
+
# Shorthand method for calling <tt>self</tt> passing <tt>x</tt> as a block
|
7
|
+
# variable.
|
8
|
+
#
|
9
|
+
# This is required for ruby 1.8 only, as it mimics functionality added in
|
10
|
+
# version 1.9
|
11
|
+
#
|
12
|
+
def===(x)
|
13
|
+
call(x)
|
14
|
+
end
|
15
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
|
2
|
+
# Authors:: James Hetherington, James Smith, Andrew Berkeley, George Palmer
|
3
|
+
# Copyright:: Copyright (c) 2011 AMEE UK Ltd
|
4
|
+
# License:: Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject
|
10
|
+
# to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included
|
13
|
+
# in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
18
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
19
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
20
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
21
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
require 'amee-data-abstraction.rb'
|
24
|
+
|
25
|
+
module AMEE::DataAbstraction
|
26
|
+
|
27
|
+
# Override the connection accessor to provide the global connection in rails apps
|
28
|
+
def self.connection
|
29
|
+
AMEE::Rails.connection
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require File.dirname(File.dirname(__FILE__)) + '/spec_helper.rb'
|
2
|
+
class CalculationSet
|
3
|
+
def call_me
|
4
|
+
#stub, because flexmock doesn't work for new instances during constructor
|
5
|
+
@@called=true
|
6
|
+
end
|
7
|
+
cattr_accessor :called
|
8
|
+
end
|
9
|
+
describe CalculationSet do
|
10
|
+
it 'can create an instance' do
|
11
|
+
ElectricityAndTransport.should be_a CalculationSet
|
12
|
+
end
|
13
|
+
it 'can create an instance' do
|
14
|
+
ElectricityAndTransport.calculations.should be_a ActiveSupport::OrderedHash
|
15
|
+
end
|
16
|
+
it 'can access a calculation by key' do
|
17
|
+
ElectricityAndTransport[:transport].should be_a PrototypeCalculation
|
18
|
+
end
|
19
|
+
it 'can construct a calculation' do
|
20
|
+
CalculationSet.new {calculation {label :mycalc}}[:mycalc].should be_a PrototypeCalculation
|
21
|
+
end
|
22
|
+
it 'can be initialized with a DSL block' do
|
23
|
+
CalculationSet.new {call_me}
|
24
|
+
CalculationSet.called.should be_true
|
25
|
+
end
|
26
|
+
it 'can have terms added to all calculations' do
|
27
|
+
cs=CalculationSet.new {
|
28
|
+
all_calculations {
|
29
|
+
drill {label :energetic}
|
30
|
+
}
|
31
|
+
calculation {
|
32
|
+
label :mycalc
|
33
|
+
drill {label :remarkably}
|
34
|
+
}
|
35
|
+
}
|
36
|
+
cs[:mycalc].drills.labels.should eql [:remarkably,:energetic]
|
37
|
+
end
|
38
|
+
it 'can make multiple calculations quickly, one for each usage' do
|
39
|
+
mocker=AMEEMocker.new(self,:path=>'something')
|
40
|
+
mocker.item_value_definitions.usages(['bybob','byfrank']).
|
41
|
+
item_definition.data_category.
|
42
|
+
item_value_definition('first',['bybob'],[],'byfrank',[],nil,nil,true,false,nil,"TEXT").
|
43
|
+
item_value_definition('second',['bybob'],[],'byfrank',[],nil,nil,true,false,nil,"TEXT").
|
44
|
+
item_value_definition('third',['byfrank'],[],['bybob'],[],nil,nil,true,false,nil,"TEXT")
|
45
|
+
cs=CalculationSet.new {
|
46
|
+
calculations_all_usages('/something') { |usage|
|
47
|
+
label usage.to_sym
|
48
|
+
profiles_from_usage usage
|
49
|
+
}
|
50
|
+
}
|
51
|
+
cs[:bybob].profiles.labels.should eql [:first,:second]
|
52
|
+
cs[:byfrank].profiles.labels.should eql [:third]
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require File.dirname(File.dirname(__FILE__)) + '/spec_helper.rb'
|
2
|
+
describe Calculation do
|
3
|
+
|
4
|
+
it 'can create an instance' do
|
5
|
+
Transport.should be_a Calculation
|
6
|
+
end
|
7
|
+
it 'should have ordered terms, with labels' do
|
8
|
+
Transport.terms.labels.should eql [:fuel,:size,:distance,:co2]
|
9
|
+
end
|
10
|
+
it 'should have amee paths for the terms' do
|
11
|
+
Transport.terms.paths.should eql ['fuel','size','distance',:default]
|
12
|
+
end
|
13
|
+
it 'should have human names for the terms' do
|
14
|
+
Transport.terms.names.
|
15
|
+
should eql ['Fuel Type','Vehicle Size','Distance Driven','Carbon Dioxide']
|
16
|
+
end
|
17
|
+
it 'should return the inputs' do
|
18
|
+
Transport.inputs.labels.should eql [:fuel,:size,:distance]
|
19
|
+
end
|
20
|
+
it 'should return the outputs' do
|
21
|
+
Transport.outputs.labels.should eql [:co2]
|
22
|
+
end
|
23
|
+
it 'should generate an explorer URL' do
|
24
|
+
Transport.explorer_url.should eql 'http://explorer.amee.com/categories/transport/car/generic'
|
25
|
+
end
|
26
|
+
it 'can return a term via []' do
|
27
|
+
Transport[:co2].label.should eql :co2
|
28
|
+
end
|
29
|
+
it 'when copied, should deep copy the values' do
|
30
|
+
x=Transport.clone
|
31
|
+
x[:co2].value :somevalue
|
32
|
+
x[:co2].value.should eql :somevalue
|
33
|
+
Transport[:co2].value.should be_nil
|
34
|
+
end
|
35
|
+
it 'knows to get terms that come before or after others' do
|
36
|
+
t=Transport.clone
|
37
|
+
t.before(:distance).labels.
|
38
|
+
should eql [:fuel,:size]
|
39
|
+
t.after(:distance).map(&:label).
|
40
|
+
should eql [:co2]
|
41
|
+
end
|
42
|
+
it 'delegates selectors to terms list' do
|
43
|
+
t=Transport.clone
|
44
|
+
t.drills.labels.should eql [:fuel,:size]
|
45
|
+
end
|
46
|
+
it 'can find its amee data category' do
|
47
|
+
t=Transport.clone
|
48
|
+
mocker=AMEEMocker.new self,:path=>'transport/car/generic'
|
49
|
+
mocker.data_category
|
50
|
+
t.send(:amee_data_category).path.should eql '/data/transport/car/generic'
|
51
|
+
end
|
52
|
+
it 'can find its amee item definition' do
|
53
|
+
mocker=AMEEMocker.new self,:path=>'transport/car/generic'
|
54
|
+
mocker.item_definition(:my_itemdef_name).data_category
|
55
|
+
t=Transport.clone
|
56
|
+
t.send(:amee_item_definition).name.should eql :my_itemdef_name
|
57
|
+
end
|
58
|
+
it 'can give item value definition list' do
|
59
|
+
mocker=AMEEMocker.new self,:path=>'transport/car/generic'
|
60
|
+
mocker.item_value_definition('distance').item_value_definitions.
|
61
|
+
item_definition.data_category
|
62
|
+
t=Transport.clone
|
63
|
+
t.send(:amee_ivds).first.path.should eql 'distance'
|
64
|
+
end
|
65
|
+
it 'can memoise access to AMEE' do
|
66
|
+
t=Transport.clone
|
67
|
+
#AMEE::Data::Category.get(connection, "/data#{path}")
|
68
|
+
flexmock(AMEE::Data::Category).should_receive(:get).
|
69
|
+
with(AMEE::DataAbstraction.connection,'/data/transport/car/generic').
|
70
|
+
once.and_return(true)
|
71
|
+
t.send(:amee_data_category)
|
72
|
+
t.send(:amee_data_category)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require File.dirname(File.dirname(__FILE__)) + '/spec_helper.rb'
|
2
|
+
describe Drill do
|
3
|
+
it 'knows its options when it is the first choice' do
|
4
|
+
AMEEMocker.new(self,:path=>'transport/car/generic',
|
5
|
+
:selections=>[],
|
6
|
+
:choices=>['diesel','petrol']).drill
|
7
|
+
Transport.begin_calculation[:fuel].send(:choices).should eql ['diesel','petrol']
|
8
|
+
end
|
9
|
+
it 'knows its options when it is a later choice' do
|
10
|
+
AMEEMocker.new(self,:path=>'transport/car/generic',
|
11
|
+
:selections=>[['fuel','diesel']],
|
12
|
+
:choices=>['large','small']).drill
|
13
|
+
t=Transport.begin_calculation
|
14
|
+
t[:fuel].value 'diesel'
|
15
|
+
t[:size].send(:choices).should eql ['large','small']
|
16
|
+
end
|
17
|
+
it 'is enabled iff it is the next choice or has been chosen' do
|
18
|
+
t=Transport.begin_calculation
|
19
|
+
t[:fuel].enabled?.should be_true
|
20
|
+
t[:size].enabled?.should be_false
|
21
|
+
t[:fuel].value 'diesel'
|
22
|
+
t[:fuel].enabled?.should be_true
|
23
|
+
t[:size].enabled?.should be_true
|
24
|
+
t[:size].value 'large'
|
25
|
+
t[:fuel].enabled?.should be_true
|
26
|
+
t[:size].enabled?.should be_true
|
27
|
+
end
|
28
|
+
it 'is valid iff assigned a choice in the choices' do
|
29
|
+
AMEEMocker.new(self,:path=>'transport/car/generic',
|
30
|
+
:selections=>[],
|
31
|
+
:choices=>['diesel','petrol']).drill
|
32
|
+
t=Transport.begin_calculation
|
33
|
+
t[:fuel].value 'diesel'
|
34
|
+
t[:fuel].send(:valid?).should be_true
|
35
|
+
t[:fuel].value 'banana'
|
36
|
+
t[:fuel].send(:valid?).should be_false
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require File.dirname(File.dirname(__FILE__)) + '/spec_helper.rb'
|
2
|
+
|
3
|
+
describe Input do
|
4
|
+
it 'can be given a fixed value' do
|
5
|
+
i=Input.new{fixed 6}
|
6
|
+
i.value.should eql 6
|
7
|
+
i.fixed?.should be_true
|
8
|
+
lambda{i.value 7}.should raise_error Exceptions::FixedValueInterference
|
9
|
+
end
|
10
|
+
it 'raises exception when invalid' do
|
11
|
+
i=Input.new{validation /bark/}
|
12
|
+
i.value 'barking'
|
13
|
+
lambda{i.validate!}.should_not raise_error
|
14
|
+
i.value.should eql 'barking'
|
15
|
+
i.value 'marking'
|
16
|
+
lambda{i.validate!}.should raise_error Exceptions::ChoiceValidation
|
17
|
+
j=Input.new{}
|
18
|
+
j.value 'marking'
|
19
|
+
j.value.should eql 'marking'
|
20
|
+
end
|
21
|
+
it "can accept a numeric symbol validation" do
|
22
|
+
i=Input.new{validation :numeric}
|
23
|
+
i.value 3
|
24
|
+
lambda{i.validate!}.should_not raise_error
|
25
|
+
i.value '3'
|
26
|
+
lambda{i.validate!}.should_not raise_error
|
27
|
+
i.value 'e'
|
28
|
+
lambda{i.validate!}.should raise_error Exceptions::ChoiceValidation
|
29
|
+
end
|
30
|
+
it "can accept a date symbol validation" do
|
31
|
+
i=Input.new{validation :date}
|
32
|
+
i.value Date.today
|
33
|
+
lambda{i.validate!}.should_not raise_error
|
34
|
+
i.value '2011-01-01'
|
35
|
+
lambda{i.validate!}.should_not raise_error
|
36
|
+
i.value 'e'
|
37
|
+
lambda{i.validate!}.should raise_error Exceptions::ChoiceValidation
|
38
|
+
end
|
39
|
+
|
40
|
+
it "can accept a time symbol validation" do
|
41
|
+
i=Input.new{validation :datetime}
|
42
|
+
i.value DateTime.now
|
43
|
+
lambda{i.validate!}.should_not raise_error
|
44
|
+
i.value '2011-01-01 09:00:00'
|
45
|
+
lambda{i.validate!}.should_not raise_error
|
46
|
+
i.value 'e'
|
47
|
+
lambda{i.validate!}.should raise_error Exceptions::ChoiceValidation
|
48
|
+
end
|
49
|
+
it 'can have custom validation message' do
|
50
|
+
i=Input.new{label :woof; validation /bark/; validation_message {"#{value} does not match pattern /bark/"}}
|
51
|
+
i.value 'marking'
|
52
|
+
lambda{i.validate!}.should raise_error Exceptions::ChoiceValidation,"marking does not match pattern /bark/"
|
53
|
+
j=Input.new{}
|
54
|
+
j.value 'marking'
|
55
|
+
j.value.should eql 'marking'
|
56
|
+
end
|
57
|
+
it 'can have default validation message' do
|
58
|
+
i=Input.new{label :woof; validation /bark/}
|
59
|
+
i.value 'barking'
|
60
|
+
lambda{i.validate!}.should_not raise_error
|
61
|
+
i.value.should eql 'barking'
|
62
|
+
i.value 'marking'
|
63
|
+
lambda{i.validate!}.should raise_error Exceptions::ChoiceValidation,"Woof is invalid."
|
64
|
+
j=Input.new{}
|
65
|
+
j.value 'marking'
|
66
|
+
j.value.should eql 'marking'
|
67
|
+
end
|
68
|
+
it 'is always valid if it is fixed' do
|
69
|
+
i=Input.new{fixed 5; validation /7/}
|
70
|
+
lambda{i.validate!}.should_not raise_error
|
71
|
+
i.value.should eql 5
|
72
|
+
end
|
73
|
+
it 'is always disabled if it is fixed' do
|
74
|
+
i=Input.new{fixed 5}
|
75
|
+
i.disabled?.should eql true
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require File.dirname(File.dirname(__FILE__)) + '/spec_helper.rb'
|
2
|
+
|
3
|
+
describe Metadatum do
|
4
|
+
it 'defaults to be a drop-down' do
|
5
|
+
Metadatum.new.drop_down?.should be_true
|
6
|
+
end
|
7
|
+
it 'can have choices specified' do
|
8
|
+
Metadatum.new { choices %w{bob frank}}.choices.should eql ['bob','frank']
|
9
|
+
end
|
10
|
+
it 'validates on choices' do
|
11
|
+
m=Metadatum.new { choices %w{bob frank}}
|
12
|
+
m.value 'bob'
|
13
|
+
m.should be_valid
|
14
|
+
m.value 'mark'
|
15
|
+
m.should_not be_valid
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,494 @@
|
|
1
|
+
require File.dirname(File.dirname(__FILE__)) + '/spec_helper.rb'
|
2
|
+
describe OngoingCalculation do
|
3
|
+
it 'can return set and unset inputs' do
|
4
|
+
d=Electricity.begin_calculation
|
5
|
+
d.inputs.set.labels.should eql [:country]
|
6
|
+
d.inputs.unset.labels.should eql [:energy_used]
|
7
|
+
d[:energy_used].value :somevalue
|
8
|
+
d.inputs.set.labels.should eql [:country,:energy_used]
|
9
|
+
d.inputs.unset.labels.should eql []
|
10
|
+
end
|
11
|
+
it 'can return set and unset terms' do
|
12
|
+
d=Electricity.begin_calculation
|
13
|
+
d.set.labels.should eql [:country]
|
14
|
+
d.unset.labels.should eql [:energy_used,:co2]
|
15
|
+
d[:energy_used].value :somevalue
|
16
|
+
d.set.labels.should eql [:country,:energy_used]
|
17
|
+
d.unset.labels.should eql [:co2]
|
18
|
+
end
|
19
|
+
it 'can return set and unset outputs' do
|
20
|
+
d=Electricity.begin_calculation
|
21
|
+
d.outputs.set.labels.should eql []
|
22
|
+
d.outputs.unset.labels.should eql [:co2]
|
23
|
+
d[:co2].value 5
|
24
|
+
d.outputs.set.labels.should eql [:co2]
|
25
|
+
d.outputs.unset.labels.should eql []
|
26
|
+
end
|
27
|
+
it 'can have values chosen' do
|
28
|
+
AMEEMocker.new(self,:path=>'business/energy/electricity/grid',
|
29
|
+
:selections=>[['country','argentina']],
|
30
|
+
:choices=>[]).drill
|
31
|
+
|
32
|
+
d=Electricity.begin_calculation
|
33
|
+
|
34
|
+
d.inputs.set.values.should eql ['argentina']
|
35
|
+
d.inputs.unset.values.should eql [nil]
|
36
|
+
|
37
|
+
d.choose!(:energy_used=>5.0)
|
38
|
+
|
39
|
+
d.inputs.set.values.should eql ['argentina',5.0]
|
40
|
+
d.inputs.unset.values.should be_empty
|
41
|
+
end
|
42
|
+
it 'knows when it is satisfied' do
|
43
|
+
AMEEMocker.new(self,:path=>'business/energy/electricity/grid',
|
44
|
+
:selections=>[['country','argentina']],
|
45
|
+
:choices=>[]).drill
|
46
|
+
d=Electricity.begin_calculation
|
47
|
+
d.satisfied?.should be_false
|
48
|
+
d.choose!(:energy_used=>5.0)
|
49
|
+
d.satisfied?.should be_true
|
50
|
+
end
|
51
|
+
it 'knows which drills are set, and whether it is satisfied' do
|
52
|
+
mocker=AMEEMocker.new(self,:path=>'transport/car/generic',
|
53
|
+
:choices=>['diesel','petrol'])
|
54
|
+
mocker.drill
|
55
|
+
mocker.select('fuel'=>'diesel')
|
56
|
+
mocker.choices=['large','small']
|
57
|
+
mocker.drill
|
58
|
+
mocker.select('size'=>'large')
|
59
|
+
mocker.choices=[]
|
60
|
+
mocker.drill
|
61
|
+
t=Transport.begin_calculation
|
62
|
+
t.terms.labels.should eql [:fuel,:size,:distance,:co2]
|
63
|
+
t.satisfied?.should be_false
|
64
|
+
|
65
|
+
t.choose!('fuel'=>'diesel')
|
66
|
+
t.inputs.set.labels.should eql [:fuel]
|
67
|
+
t.inputs.unset.labels.should eql [:size,:distance]
|
68
|
+
t.satisfied?.should be_false
|
69
|
+
|
70
|
+
t2=Transport.begin_calculation
|
71
|
+
t2.choose!('fuel'=>'diesel','size'=>'large')
|
72
|
+
t2.inputs.set.labels.should eql [:fuel,:size]
|
73
|
+
t2.inputs.unset.labels.should eql [:distance]
|
74
|
+
t2.satisfied?.should be_false
|
75
|
+
|
76
|
+
t3=Transport.begin_calculation
|
77
|
+
t3.choose!('fuel'=>'diesel','size'=>'large','distance'=>5)
|
78
|
+
t3.inputs.set.labels.should eql [:fuel,:size,:distance]
|
79
|
+
t3.inputs.unset.labels.should eql []
|
80
|
+
t3.satisfied?.should be_true
|
81
|
+
end
|
82
|
+
it 'can do a calculation' do
|
83
|
+
mocker=AMEEMocker.new(self,:path=>'transport/car/generic',
|
84
|
+
:choices=>['diesel','petrol'],
|
85
|
+
:result=>:somenumber,
|
86
|
+
:params=>{'distance'=>5})
|
87
|
+
mocker.drill
|
88
|
+
mocker.select('fuel'=>'diesel')
|
89
|
+
mocker.choices=['large','small']
|
90
|
+
mocker.drill
|
91
|
+
mocker.select('size'=>'large')
|
92
|
+
mocker.choices=[]
|
93
|
+
mocker.drill
|
94
|
+
mocker.profile_list.profile_category.timestamp.create.get
|
95
|
+
mycalc=Transport.begin_calculation
|
96
|
+
mycalc.choose!('fuel'=>'diesel','size'=>'large','distance'=>5)
|
97
|
+
mycalc.calculate!
|
98
|
+
mycalc.outputs.first.value.should eql :somenumber
|
99
|
+
end
|
100
|
+
it 'does not send general metadata to AMEE' do
|
101
|
+
mocker=AMEEMocker.new(self,:path=>'transport/car/generic',
|
102
|
+
:choices=>['diesel','petrol'],
|
103
|
+
:result=>:somenumber,
|
104
|
+
:params=>{'distance'=>5})
|
105
|
+
mocker.drill
|
106
|
+
mocker.select('fuel'=>'diesel')
|
107
|
+
mocker.choices=['large','small']
|
108
|
+
mocker.drill
|
109
|
+
mocker.select('size'=>'large')
|
110
|
+
mocker.choices=[]
|
111
|
+
mocker.drill
|
112
|
+
mocker.profile_list.profile_category.timestamp.create.get
|
113
|
+
mycalc=ElectricityAndTransport[:transport].begin_calculation
|
114
|
+
mycalc.choose!('fuel'=>'diesel','size'=>'large','distance'=>5,'department'=>'stuff')
|
115
|
+
mycalc.calculate!
|
116
|
+
mycalc.outputs.first.value.should eql :somenumber
|
117
|
+
end
|
118
|
+
|
119
|
+
#This exception has been removed, to support the case where the persistence module
|
120
|
+
#had a saved calculation from before a configuration file changed. We might want to
|
121
|
+
#do something more sophisticated.
|
122
|
+
#it 'raises exception if choice supplied for invalid term' do
|
123
|
+
# mycalc=Transport.begin_calculation
|
124
|
+
# mocker=AMEEMocker.new(self,:path=>'transport/car/generic',
|
125
|
+
# :choices=>['diesel','petrol'],
|
126
|
+
# :result=>:somenumber,
|
127
|
+
# :params=>{'distance'=>5})
|
128
|
+
# mocker.drill
|
129
|
+
# mocker.select('fuel'=>'diesel')
|
130
|
+
# mocker.choices=['large','small']
|
131
|
+
# mocker.drill
|
132
|
+
# lambda{mycalc.choose!('fuel'=>'diesel','banana'=>'large','distance'=>5)}.
|
133
|
+
# should raise_exception Exceptions::NoSuchTerm
|
134
|
+
#end
|
135
|
+
|
136
|
+
it 'can be supplied just a UID, and recover PIVs and drill values from AMEE' do
|
137
|
+
mycalc=Transport.begin_calculation
|
138
|
+
mocker=AMEEMocker.new(self,:path=>'transport/car/generic',
|
139
|
+
:result=>:somenumber,
|
140
|
+
:existing=>{'distance'=>5},:choices=>['petrol','diesel'])
|
141
|
+
mocker.drill
|
142
|
+
mocker.select('fuel'=>'diesel')
|
143
|
+
mocker.select('size'=>'large')
|
144
|
+
mocker.choices=[]
|
145
|
+
mocker.profile_list.update.get(true)
|
146
|
+
mycalc.choose!(:profile_item_uid=>mocker.uid)
|
147
|
+
mycalc.calculate!
|
148
|
+
mycalc[:fuel].value.should eql 'diesel'
|
149
|
+
mycalc[:distance].value.should eql 5
|
150
|
+
mycalc.outputs.first.value.should eql :somenumber
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'refuses to load values from AMEE which conflict with local drill values' do
|
154
|
+
mycalc=Transport.begin_calculation
|
155
|
+
mocker=AMEEMocker.new(self,:path=>'transport/car/generic',
|
156
|
+
:result=>:somenumber,
|
157
|
+
:existing=>{'distance'=>7},
|
158
|
+
:params=>{'distance'=>7},
|
159
|
+
:choices=>['petrol','diesel'])
|
160
|
+
mocker.drill
|
161
|
+
mocker.select('fuel'=>'diesel')
|
162
|
+
mocker.choices=['large','small']
|
163
|
+
mocker.drill
|
164
|
+
mocker.select('size'=>'large')
|
165
|
+
mocker.choices=[]
|
166
|
+
mocker.profile_list.get(true,true).delete
|
167
|
+
existing_uid=mocker.uid
|
168
|
+
mocker.select('size'=>'small')
|
169
|
+
mocker.drill.profile_category.timestamp.create.get
|
170
|
+
mycalc.choose!(:profile_item_uid=>existing_uid,'fuel'=>'diesel','size'=>'small','distance'=>7)
|
171
|
+
mycalc.calculate!
|
172
|
+
mycalc.outputs.first.value.should eql :somenumber
|
173
|
+
end
|
174
|
+
|
175
|
+
it 'lets local profile values replace and update those in amee' do
|
176
|
+
mocker=AMEEMocker.new(self,:path=>'transport/car/generic',
|
177
|
+
:result=>:somenumber,
|
178
|
+
:choices=>['petrol','diesel'],
|
179
|
+
:params=>{'distance'=>9},
|
180
|
+
:existing=>{'distance'=>5}
|
181
|
+
)
|
182
|
+
mocker.drill
|
183
|
+
mocker.select('fuel'=>'diesel')
|
184
|
+
mocker.choices=['large','small']
|
185
|
+
mocker.drill
|
186
|
+
mocker.select('size'=>'large')
|
187
|
+
mocker.choices=[]
|
188
|
+
mocker.drill
|
189
|
+
mocker.profile_list.update.get(true)
|
190
|
+
mycalc=Transport.begin_calculation
|
191
|
+
mycalc.choose!(:profile_item_uid=>mocker.uid,'fuel'=>'diesel','size'=>'large','distance'=>9)
|
192
|
+
mycalc.calculate!
|
193
|
+
mycalc[:distance].value.should eql 9
|
194
|
+
end
|
195
|
+
|
196
|
+
it 'can be calculated, then recalculated, loading from AMEE the second time' do
|
197
|
+
mocker=AMEEMocker.new(self,:path=>'transport/car/generic',
|
198
|
+
:choices=>['diesel','petrol'],
|
199
|
+
:result=>:somenumber,
|
200
|
+
:params=>{'distance'=>5})
|
201
|
+
mocker.drill
|
202
|
+
mocker.select('fuel'=>'diesel')
|
203
|
+
mocker.choices=['large','small']
|
204
|
+
mocker.drill
|
205
|
+
mocker.select('size'=>'large')
|
206
|
+
mocker.choices=[]
|
207
|
+
mocker.drill
|
208
|
+
mocker.profile_list.profile_category.timestamp.create
|
209
|
+
|
210
|
+
mocker.existing={'distance'=>5}
|
211
|
+
mocker.params={'distance'=>9}
|
212
|
+
mocker.update.get(true)
|
213
|
+
|
214
|
+
mycalc=Transport.begin_calculation
|
215
|
+
mycalc.choose!('fuel'=>'diesel','size'=>'large','distance'=>5)
|
216
|
+
mycalc.calculate!
|
217
|
+
mycalc.choose!('fuel'=>'diesel','size'=>'large','distance'=>9)
|
218
|
+
mycalc.calculate!
|
219
|
+
mycalc[:distance].value.should eql 9
|
220
|
+
end
|
221
|
+
|
222
|
+
it 'can be calculated, then change drill, recreating the second time' do
|
223
|
+
mocker=AMEEMocker.new(self,:path=>'transport/car/generic',
|
224
|
+
:choices=>['diesel','petrol'],
|
225
|
+
:result=>:somenumber,
|
226
|
+
:params=>{'distance'=>5})
|
227
|
+
mocker.drill
|
228
|
+
mocker.select('fuel'=>'diesel')
|
229
|
+
mocker.choices=['large','small']
|
230
|
+
mocker.drill
|
231
|
+
mocker.select('size'=>'large')
|
232
|
+
mocker.choices=[]
|
233
|
+
mocker.drill
|
234
|
+
mocker.profile_list.profile_category.timestamp.create.get(true,true).delete
|
235
|
+
|
236
|
+
mocker.select('size'=>'small')
|
237
|
+
mocker.drill.create.get
|
238
|
+
|
239
|
+
mycalc=Transport.begin_calculation
|
240
|
+
mycalc.choose!('fuel'=>'diesel','size'=>'large','distance'=>5)
|
241
|
+
mycalc.calculate!
|
242
|
+
mycalc.choose!('fuel'=>'diesel','size'=>'small')
|
243
|
+
mycalc.calculate!
|
244
|
+
mycalc[:distance].value.should eql 5
|
245
|
+
end
|
246
|
+
|
247
|
+
it 'memoizes profile information, but not across a pass' do
|
248
|
+
mycalc=Transport.begin_calculation
|
249
|
+
mocker=AMEEMocker.new(self,:path=>'transport/car/generic',
|
250
|
+
:result=>:somenumber,
|
251
|
+
:existing=>{'distance'=>5},:choices=>['petrol','diesel'])
|
252
|
+
mocker.drill
|
253
|
+
mocker.select('fuel'=>'diesel')
|
254
|
+
mocker.select('size'=>'large')
|
255
|
+
mocker.choices=[]
|
256
|
+
mocker.profile_list.update.get(true,false,false)
|
257
|
+
mycalc.choose!(:profile_item_uid=>mocker.uid)
|
258
|
+
mycalc.calculate!
|
259
|
+
mycalc.send(:profile_item)
|
260
|
+
mycalc[:fuel].value.should eql 'diesel'
|
261
|
+
mycalc[:distance].value.should eql 5
|
262
|
+
mycalc.outputs.first.value.should eql :somenumber
|
263
|
+
end
|
264
|
+
|
265
|
+
it 'creates profile item with start end dates if appropriate metadata provided' do
|
266
|
+
mocker=AMEEMocker.new(self,:path=>'transport/car/generic',
|
267
|
+
:choices=>['diesel','petrol'],
|
268
|
+
:result=>:somenumber,
|
269
|
+
:params=>{'distance'=>5,
|
270
|
+
:start_date=>Date.parse("1976-10-19"),
|
271
|
+
:end_date=>Date.parse("2011-1-1")})
|
272
|
+
mocker.drill
|
273
|
+
mocker.select('fuel'=>'diesel')
|
274
|
+
mocker.choices=['large','small']
|
275
|
+
mocker.drill
|
276
|
+
mocker.select('size'=>'large')
|
277
|
+
mocker.choices=[]
|
278
|
+
mocker.drill
|
279
|
+
mocker.profile_list.profile_category.timestamp.create.get
|
280
|
+
myproto=Transport.clone
|
281
|
+
myproto.instance_eval{
|
282
|
+
start_and_end_dates
|
283
|
+
}
|
284
|
+
mycalc=myproto.begin_calculation
|
285
|
+
mycalc.choose!('fuel'=>'diesel','size'=>'large','distance'=>5,'start_date'=>"1976-10-19",'end_date'=>DateTime.parse("2011-1-1"))
|
286
|
+
mycalc.calculate!
|
287
|
+
mycalc.outputs.first.value.should eql :somenumber
|
288
|
+
end
|
289
|
+
|
290
|
+
it 'does not accept start end dates if inappropriate value provided' do
|
291
|
+
mocker=AMEEMocker.new(self,:path=>'transport/car/generic',
|
292
|
+
:choices=>['diesel','petrol'],
|
293
|
+
:result=>:somenumber,
|
294
|
+
:params=>{'distance'=>5,
|
295
|
+
:end_date=>Date.parse("2011-1-1")})
|
296
|
+
mocker.drill
|
297
|
+
mocker.select('fuel'=>'diesel')
|
298
|
+
mocker.choices=['large','small']
|
299
|
+
mocker.drill
|
300
|
+
mocker.select('size'=>'large')
|
301
|
+
mocker.choices=[]
|
302
|
+
mocker.drill
|
303
|
+
myproto=Transport.clone
|
304
|
+
myproto.instance_eval{
|
305
|
+
start_and_end_dates
|
306
|
+
}
|
307
|
+
mycalc=myproto.begin_calculation
|
308
|
+
mycalc.choose('fuel'=>'diesel','size'=>'large','distance'=>5,'start_date'=>"banana",'end_date'=>DateTime.parse("2011-1-1")).should be_false
|
309
|
+
mycalc.invalidity_messages.keys.should eql [:start_date]
|
310
|
+
end
|
311
|
+
|
312
|
+
it 'starts off dirty' do
|
313
|
+
mycalc=Transport.begin_calculation
|
314
|
+
mycalc.should be_dirty
|
315
|
+
end
|
316
|
+
|
317
|
+
it 'becomes clean when you calculate' do
|
318
|
+
mocker=AMEEMocker.new(self,:path=>'transport/car/generic',
|
319
|
+
:choices=>['diesel','petrol'],
|
320
|
+
:result=>:somenumber,
|
321
|
+
:params=>{'distance'=>5})
|
322
|
+
mocker.drill
|
323
|
+
mocker.select('fuel'=>'diesel')
|
324
|
+
mocker.choices=['large','small']
|
325
|
+
mocker.drill
|
326
|
+
mocker.select('size'=>'large')
|
327
|
+
mocker.choices=[]
|
328
|
+
mocker.drill
|
329
|
+
mocker.profile_list.profile_category.timestamp.create.get
|
330
|
+
mycalc=Transport.begin_calculation
|
331
|
+
mycalc.should be_dirty
|
332
|
+
mycalc.choose!('fuel'=>'diesel','size'=>'large','distance'=>5)
|
333
|
+
mycalc.calculate!
|
334
|
+
mycalc.should_not be_dirty
|
335
|
+
mycalc.outputs.first.value.should eql :somenumber
|
336
|
+
mycalc.should_not be_dirty
|
337
|
+
end
|
338
|
+
|
339
|
+
it 'becomes dirty again if you reset something after you calculate' do
|
340
|
+
mocker=AMEEMocker.new(self,:path=>'transport/car/generic',
|
341
|
+
:choices=>['diesel','petrol'],
|
342
|
+
:result=>:somenumber,
|
343
|
+
:params=>{'distance'=>5})
|
344
|
+
mocker.drill
|
345
|
+
mocker.select('fuel'=>'diesel')
|
346
|
+
mocker.choices=['large','small']
|
347
|
+
mocker.drill
|
348
|
+
mocker.select('size'=>'large')
|
349
|
+
mocker.choices=[]
|
350
|
+
mocker.drill
|
351
|
+
mocker.profile_list.profile_category.timestamp.create.get
|
352
|
+
mycalc=Transport.begin_calculation
|
353
|
+
mycalc.should be_dirty
|
354
|
+
mycalc.choose!('fuel'=>'diesel','size'=>'large','distance'=>5)
|
355
|
+
mycalc.calculate!
|
356
|
+
mycalc.should_not be_dirty
|
357
|
+
mycalc.outputs.first.value.should eql :somenumber
|
358
|
+
mycalc.should_not be_dirty
|
359
|
+
mycalc.choose!('distance'=>7)
|
360
|
+
mycalc.should be_dirty
|
361
|
+
end
|
362
|
+
|
363
|
+
it 'provides error message and raises exception from choose! if choice invalid' do
|
364
|
+
mocker=AMEEMocker.new(self,:path=>'transport/car/generic',
|
365
|
+
:choices=>['diesel','petrol'],
|
366
|
+
:result=>:somenumber,
|
367
|
+
:params=>{'distance'=>5})
|
368
|
+
mocker.drill
|
369
|
+
mocker.select('fuel'=>'diesel')
|
370
|
+
mocker.choices=['large','small']
|
371
|
+
mocker.drill
|
372
|
+
mocker.select('size'=>'marge')
|
373
|
+
mocker.choices=[]
|
374
|
+
mocker.drill
|
375
|
+
mycalc=Transport.begin_calculation
|
376
|
+
lambda{mycalc.choose!('fuel'=>'diesel','size'=>'marge','distance'=>5)}.should raise_error Exceptions::ChoiceValidation
|
377
|
+
mycalc.invalidity_messages.keys.should eql [:size]
|
378
|
+
end
|
379
|
+
|
380
|
+
it 'provides error message and returns false from choose if choice invalid' do
|
381
|
+
mocker=AMEEMocker.new(self,:path=>'transport/car/generic',
|
382
|
+
:choices=>['diesel','petrol'],
|
383
|
+
:result=>:somenumber,
|
384
|
+
:params=>{'distance'=>5})
|
385
|
+
mocker.drill
|
386
|
+
mocker.select('fuel'=>'diesel')
|
387
|
+
mocker.choices=['large','small']
|
388
|
+
mocker.drill
|
389
|
+
mocker.select('size'=>'marge')
|
390
|
+
mocker.choices=[]
|
391
|
+
mocker.drill
|
392
|
+
mycalc=Transport.begin_calculation
|
393
|
+
mycalc.choose('fuel'=>'diesel','size'=>'marge','distance'=>5).should be_false
|
394
|
+
mycalc.invalidity_messages.keys.should eql [:size]
|
395
|
+
end
|
396
|
+
|
397
|
+
it 'returns true from choose if choices valid' do
|
398
|
+
mocker=AMEEMocker.new(self,:path=>'transport/car/generic',
|
399
|
+
:choices=>['diesel','petrol'],
|
400
|
+
:result=>:somenumber,
|
401
|
+
:params=>{'distance'=>5})
|
402
|
+
mocker.drill
|
403
|
+
mocker.select('fuel'=>'diesel')
|
404
|
+
mocker.choices=['large','small']
|
405
|
+
mocker.drill
|
406
|
+
mocker.select('size'=>'large')
|
407
|
+
mocker.choices=[]
|
408
|
+
mocker.drill
|
409
|
+
mycalc=Transport.begin_calculation
|
410
|
+
mycalc.choose('fuel'=>'diesel','size'=>'large','distance'=>5).should be_true
|
411
|
+
end
|
412
|
+
|
413
|
+
it 'can blank individual term attributes with empty string' do
|
414
|
+
myproto=Transport.clone
|
415
|
+
mycalc=myproto.begin_calculation
|
416
|
+
mycalc.choose_without_validation!('fuel'=>'diesel','size'=>'large','distance'=>{:value =>5, :unit=> Unit.km})
|
417
|
+
mycalc['fuel'].value.should eql 'diesel'
|
418
|
+
mycalc['size'].value.should eql 'large'
|
419
|
+
mycalc['distance'].value.should eql 5
|
420
|
+
mycalc['distance'].unit.symbol.should eql 'km'
|
421
|
+
mycalc.choose_without_validation!('distance'=>{:value =>""})
|
422
|
+
mycalc['fuel'].value.should eql 'diesel'
|
423
|
+
mycalc['size'].value.should eql 'large'
|
424
|
+
mycalc['distance'].value.should eql ""
|
425
|
+
mycalc['distance'].unit.symbol.should eql 'km'
|
426
|
+
end
|
427
|
+
|
428
|
+
it 'can blank individual term attributes with nil' do
|
429
|
+
myproto=Transport.clone
|
430
|
+
mycalc=myproto.begin_calculation
|
431
|
+
mycalc.choose_without_validation!('fuel'=>'diesel','size'=>'large','distance'=>{:value =>5, :unit=> Unit.km})
|
432
|
+
mycalc['fuel'].value.should eql 'diesel'
|
433
|
+
mycalc['size'].value.should eql 'large'
|
434
|
+
mycalc['distance'].value.should eql 5
|
435
|
+
mycalc['distance'].unit.symbol.should eql 'km'
|
436
|
+
mycalc.choose_without_validation!('distance'=>{:value =>nil})
|
437
|
+
mycalc['fuel'].value.should eql 'diesel'
|
438
|
+
mycalc['size'].value.should eql 'large'
|
439
|
+
mycalc['distance'].value.should be_nil
|
440
|
+
mycalc['distance'].unit.symbol.should eql 'km'
|
441
|
+
end
|
442
|
+
|
443
|
+
it 'can update individual term attributes without nullifying others' do
|
444
|
+
myproto=Transport.clone
|
445
|
+
mycalc=myproto.begin_calculation
|
446
|
+
mycalc.choose_without_validation!('fuel'=>'diesel','size'=>'large','distance'=>{:value =>5, :unit=> Unit.km})
|
447
|
+
mycalc['fuel'].value.should eql 'diesel'
|
448
|
+
mycalc['size'].value.should eql 'large'
|
449
|
+
mycalc['distance'].value.should eql 5
|
450
|
+
mycalc['distance'].unit.symbol.should eql 'km'
|
451
|
+
mycalc.choose_without_validation!('fuel'=>'biodiesel')
|
452
|
+
mycalc['fuel'].value.should eql 'biodiesel'
|
453
|
+
mycalc['size'].value.should eql 'large'
|
454
|
+
mycalc['distance'].value.should eql 5
|
455
|
+
mycalc['distance'].unit.symbol.should eql 'km'
|
456
|
+
mycalc.choose_without_validation!('distance'=>{:value =>25})
|
457
|
+
mycalc['fuel'].value.should eql 'biodiesel'
|
458
|
+
mycalc['size'].value.should eql 'large'
|
459
|
+
mycalc['distance'].value.should eql 25
|
460
|
+
mycalc['distance'].unit.symbol.should eql 'km'
|
461
|
+
mycalc.choose_without_validation!('distance'=>{:unit =>Unit.mi})
|
462
|
+
mycalc['fuel'].value.should eql 'biodiesel'
|
463
|
+
mycalc['size'].value.should eql 'large'
|
464
|
+
mycalc['distance'].value.should eql 25
|
465
|
+
mycalc['distance'].unit.symbol.should eql 'mi'
|
466
|
+
mycalc.choose_without_validation!('distance'=>{:value=>250,:unit =>Unit.ft})
|
467
|
+
mycalc['fuel'].value.should eql 'biodiesel'
|
468
|
+
mycalc['size'].value.should eql 'large'
|
469
|
+
mycalc['distance'].value.should eql 250
|
470
|
+
mycalc['distance'].unit.symbol.should eql 'ft'
|
471
|
+
end
|
472
|
+
|
473
|
+
it 'clears invalid terms' do
|
474
|
+
mocker=AMEEMocker.new(self,:path=>'transport/car/generic',
|
475
|
+
:choices=>['diesel','petrol'],
|
476
|
+
:result=>:somenumber,
|
477
|
+
:params=>{'distance'=>5})
|
478
|
+
mocker.drill
|
479
|
+
mocker.select('fuel'=>'diesel')
|
480
|
+
mocker.choices=['large','small']
|
481
|
+
mocker.drill
|
482
|
+
mocker.select('size'=>'marge')
|
483
|
+
mocker.choices=[]
|
484
|
+
mocker.drill
|
485
|
+
mycalc=Transport.begin_calculation
|
486
|
+
mycalc.choose('fuel'=>'diesel','size'=>'marge','distance'=>5).should be_false
|
487
|
+
mycalc.invalidity_messages.keys.should eql [:size]
|
488
|
+
mycalc[:size].value.should eql 'marge'
|
489
|
+
mycalc.clear_invalid_terms!
|
490
|
+
mycalc.invalidity_messages.keys.should be_empty
|
491
|
+
mycalc[:size].value.should be_nil
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|