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.
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
+