amee-data-abstraction 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/.rvmrc +1 -0
  2. data/CHANGELOG.txt +4 -0
  3. data/Gemfile +16 -0
  4. data/Gemfile.lock +41 -0
  5. data/LICENSE.txt +27 -0
  6. data/README.txt +188 -0
  7. data/Rakefile +102 -0
  8. data/VERSION +1 -0
  9. data/amee-data-abstraction.gemspec +115 -0
  10. data/examples/_calculator_form.erb +27 -0
  11. data/examples/calculation_controller.rb +16 -0
  12. data/init.rb +4 -0
  13. data/lib/amee-data-abstraction.rb +30 -0
  14. data/lib/amee-data-abstraction/calculation.rb +236 -0
  15. data/lib/amee-data-abstraction/calculation_set.rb +101 -0
  16. data/lib/amee-data-abstraction/drill.rb +63 -0
  17. data/lib/amee-data-abstraction/exceptions.rb +47 -0
  18. data/lib/amee-data-abstraction/input.rb +197 -0
  19. data/lib/amee-data-abstraction/metadatum.rb +58 -0
  20. data/lib/amee-data-abstraction/ongoing_calculation.rb +545 -0
  21. data/lib/amee-data-abstraction/output.rb +16 -0
  22. data/lib/amee-data-abstraction/profile.rb +108 -0
  23. data/lib/amee-data-abstraction/prototype_calculation.rb +350 -0
  24. data/lib/amee-data-abstraction/term.rb +506 -0
  25. data/lib/amee-data-abstraction/terms_list.rb +150 -0
  26. data/lib/amee-data-abstraction/usage.rb +90 -0
  27. data/lib/config/amee_units.rb +129 -0
  28. data/lib/core-extensions/class.rb +27 -0
  29. data/lib/core-extensions/hash.rb +43 -0
  30. data/lib/core-extensions/ordered_hash.rb +21 -0
  31. data/lib/core-extensions/proc.rb +15 -0
  32. data/rails/init.rb +32 -0
  33. data/spec/amee-data-abstraction/calculation_set_spec.rb +54 -0
  34. data/spec/amee-data-abstraction/calculation_spec.rb +75 -0
  35. data/spec/amee-data-abstraction/drill_spec.rb +38 -0
  36. data/spec/amee-data-abstraction/input_spec.rb +77 -0
  37. data/spec/amee-data-abstraction/metadatum_spec.rb +17 -0
  38. data/spec/amee-data-abstraction/ongoing_calculation_spec.rb +494 -0
  39. data/spec/amee-data-abstraction/profile_spec.rb +39 -0
  40. data/spec/amee-data-abstraction/prototype_calculation_spec.rb +256 -0
  41. data/spec/amee-data-abstraction/term_spec.rb +385 -0
  42. data/spec/amee-data-abstraction/terms_list_spec.rb +53 -0
  43. data/spec/config/amee_units_spec.rb +71 -0
  44. data/spec/core-extensions/class_spec.rb +25 -0
  45. data/spec/core-extensions/hash_spec.rb +44 -0
  46. data/spec/core-extensions/ordered_hash_spec.rb +12 -0
  47. data/spec/core-extensions/proc_spec.rb +12 -0
  48. data/spec/fixtures/electricity.rb +35 -0
  49. data/spec/fixtures/electricity_and_transport.rb +55 -0
  50. data/spec/fixtures/transport.rb +26 -0
  51. data/spec/spec.opts +2 -0
  52. data/spec/spec_helper.rb +244 -0
  53. 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
+