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,39 @@
1
+ require File.dirname(File.dirname(__FILE__)) + '/spec_helper.rb'
2
+
3
+ describe Profile do
4
+ it 'defaults to be a text-box' do
5
+ i=Profile.new
6
+ i.text_box?.should be_true
7
+ end
8
+ it 'can know if it belongs to a particular usage' do
9
+ mocker=AMEEMocker.new self,:path=>'transport/car/generic'
10
+ mocker.item_value_definitions.
11
+ item_definition.data_category.
12
+ item_value_definition('distance',['someusage'],['someotherusage'])
13
+ t=Transport.clone
14
+ t[:distance].compulsory?('someusage').should eql true
15
+ t[:distance].compulsory?('someotherusage').should eql false
16
+ t[:distance].optional?('someotherusage').should eql true
17
+ t[:distance].optional?('someusage').should eql false
18
+ end
19
+ it 'can have choices' do
20
+ i=Profile.new{label :one; choices ['a','b']}
21
+ i.choices.should eql ['a','b']
22
+ i.interface.should eql :drop_down
23
+ end
24
+ it 'must have a chosen choice if it has a choice' do
25
+ i=Profile.new{label :one; choices ['a','b']}
26
+ i.choices.should eql ['a','b']
27
+ i.value 'a'
28
+ i.should be_valid
29
+ i.value 'c'
30
+ i.should_not be_valid
31
+ end
32
+ it 'doesn''t have to have choices' do
33
+ i=Profile.new{label :one}
34
+ i.choices.should be_nil
35
+ i.interface.should eql :text_box
36
+ i.value 'mark'
37
+ i.should be_valid
38
+ end
39
+ end
@@ -0,0 +1,256 @@
1
+ require File.dirname(File.dirname(__FILE__)) + '/spec_helper.rb'
2
+
3
+ class PrototypeCalculation
4
+ def call_me
5
+ #stub, because flexmock doesn't work for new instances during constructor
6
+ @@called=true
7
+ end
8
+ cattr_accessor :called
9
+ end
10
+ describe PrototypeCalculation do
11
+ it 'can create an instance' do
12
+ Transport.should be_a PrototypeCalculation
13
+ end
14
+ it 'can be initialized with a DSL block' do
15
+ PrototypeCalculation.new {call_me}
16
+ PrototypeCalculation.called.should be_true
17
+ end
18
+ it 'can make a drill in the DSL block' do
19
+ pc=PrototypeCalculation.new {drill{label :alpha}}
20
+ pc[:alpha].should be_a Drill
21
+ end
22
+ it 'can''t make a DSL block term without a label' do
23
+ lambda{
24
+ pc=PrototypeCalculation.new {drill}
25
+ }.should raise_error Exceptions::DSL
26
+ end
27
+ it 'can make a profile item value in the DSL block' do
28
+ pc=PrototypeCalculation.new {profile{label :alpha}}
29
+ pc[:alpha].should be_a Profile
30
+ end
31
+ it 'can make an output in the DSL block' do
32
+ pc=PrototypeCalculation.new {output{label :alpha}}
33
+ pc[:alpha].should be_a Output
34
+ end
35
+ it 'can construct an ongoing calculation' do
36
+ Transport.begin_calculation.should be_a OngoingCalculation
37
+ end
38
+ it 'should make the terms of the ongoing calculation be their own instances' do
39
+ oc=Transport.begin_calculation
40
+ oc[:distance].value :somevalue
41
+ oc[:distance].value.should eql :somevalue
42
+ Transport[:distance].value.should be_nil
43
+ end
44
+ it 'should copy name, path, label when cloning ongoing' do
45
+ [:path,:name,:label].each do |property|
46
+ Transport.begin_calculation.send(property).should eql Transport.send(property)
47
+ end
48
+ end
49
+ it 'can autogenerate drill terms for itself, based on talking to amee' do
50
+ mocker=AMEEMocker.new(self,:path=>'something')
51
+ mocker.itemdef_drills ['first','second','third']
52
+ mocker.item_value_definitions.
53
+ item_definition.data_category.
54
+ item_value_definition('first',[],[],[],nil,nil,nil,false,true).
55
+ item_value_definition('second',[],[],[],nil,nil,nil,false,true).
56
+ item_value_definition('third',[],[],[],nil,nil,nil,false,true)
57
+ pc=PrototypeCalculation.new {path '/something'; all_drills}
58
+ pc.drills.labels.should eql [:first,:second,:third]
59
+ pc.drills.names.should eql ['First','Second','Third']
60
+ end
61
+ it 'can autogenerate profile terms for itself' do
62
+ mocker=AMEEMocker.new(self,:path=>'something')
63
+ mocker.item_value_definitions.
64
+ item_definition.data_category.
65
+ item_value_definition('first',[],[],[],[],nil,nil,true,false,nil,"DOUBLE").
66
+ item_value_definition('second',[],[],[],[],:kg,nil,true,false,nil,"DOUBLE").
67
+ item_value_definition('third',[],[],[],[],nil,nil,true,false,nil,"DOUBLE")
68
+ pc=PrototypeCalculation.new {path '/something'; all_profiles}
69
+ pc.profiles.labels.should eql [:first,:second,:third]
70
+ pc.profiles.default_units.first.should be_nil
71
+ pc.profiles.default_units.compact.first.should be_a Quantify::Unit::Base
72
+ end
73
+ it 'can autogenerate profile terms for itself, based on a usage' do
74
+ mocker=AMEEMocker.new(self,:path=>'something')
75
+ mocker.item_value_definitions.
76
+ item_definition.data_category.
77
+ item_value_definition('first',['bybob'],[],[],[],nil,nil,true,false,nil,"DOUBLE").
78
+ item_value_definition('second',['bybob'],[],[],[],:MBTU,:year,true,false,nil,"DOUBLE").
79
+ item_value_definition('third',[],[],['bybob'],[],nil,nil,true,false,nil,"DOUBLE")
80
+ pc=PrototypeCalculation.new {path '/something'; profiles_from_usage('bybob')}
81
+ pc.profiles.labels.should eql [:first,:second]
82
+ pc.profiles.default_units.first.should be_nil
83
+ pc.profiles.default_units.compact.first.symbol.should eql 'MBTU'
84
+ pc.profiles.default_per_units.compact.first.symbol.should eql 'yr'
85
+ end
86
+ it 'can generate profile terms with choices' do
87
+ mocker=AMEEMocker.new(self,:path=>'something')
88
+ mocker.item_value_definitions.
89
+ item_definition.data_category.
90
+ item_value_definition('first',['bybob'],[],[],['a','b'],nil,nil,true,false,nil,"DOUBLE").
91
+ item_value_definition('second',['bybob'],[],[],[],nil,nil,true,false,nil,"DOUBLE").
92
+ item_value_definition('third',[],[],['bybob'],[],nil,nil,true,false,nil,"DOUBLE")
93
+ pc=PrototypeCalculation.new {path '/something'; profiles_from_usage('bybob')}
94
+ pc[:first].choices.should eql ['a','b']
95
+ pc[:first].interface.should eql :drop_down
96
+ pc[:second].choices.should be_empty
97
+ pc[:second].interface.should eql :text_box
98
+ end
99
+ it 'can generate profile terms with default values' do
100
+ mocker=AMEEMocker.new(self,:path=>'something')
101
+ mocker.item_value_definitions.
102
+ item_definition.data_category.
103
+ item_value_definition('first',['bybob'],[],[],['a','b'],nil,nil,true,false,"commercial","TEXT").
104
+ item_value_definition('second',['bybob'],[],[],[],nil,nil,true,false,1200,"INTEGER").
105
+ item_value_definition('third',['bybob'],[],[],[],nil,nil,true,false,nil,"DOUBLE")
106
+ pc=PrototypeCalculation.new {path '/something'; profiles_from_usage('bybob')}
107
+ pc[:first].value.should eql "commercial"
108
+ pc[:second].value.should eql 1200
109
+ pc[:third].value.should be_nil
110
+ end
111
+ it 'can generate profile terms with annotations' do
112
+ mocker=AMEEMocker.new(self,:path=>'something')
113
+ mocker.item_value_definitions.
114
+ item_definition.data_category.
115
+ item_value_definition('first',['bybob'],[],[],['a','b'],nil,nil,true,false,nil,"TEXT","Specify a number of something").
116
+ item_value_definition('second',['bybob'],[],[],[],nil,nil,true,false,nil,"TEXT","Specify a number of something else").
117
+ item_value_definition('third',['bybob'],[],[],[],nil,nil,true,false,nil,"TEXT")
118
+ pc=PrototypeCalculation.new {path '/something'; profiles_from_usage('bybob')}
119
+ pc[:first].note.should eql "Specify a number of something"
120
+ pc[:second].note.should eql "Specify a number of something else"
121
+ pc[:third].note.should be_nil
122
+ end
123
+ it 'can generate profile terms with types' do
124
+ mocker=AMEEMocker.new(self,:path=>'something')
125
+ mocker.item_value_definitions.
126
+ item_definition.data_category.
127
+ item_value_definition('first',['bybob'],[],[],['a','b'],nil,nil,true,false,nil,"DECIMAL").
128
+ item_value_definition('second',['bybob'],[],[],[],nil,nil,true,false,nil,"TEXT").
129
+ item_value_definition('third',['bybob'],[],[],[],nil,nil,true,false,'true',"BOOLEAN")
130
+ pc=PrototypeCalculation.new {path '/something'; profiles_from_usage('bybob')}
131
+ pc[:first].type.should eql "decimal"
132
+ pc[:second].type.should eql "text"
133
+ pc[:third].type.should eql "boolean"
134
+ pc[:third].value.should be_true
135
+ end
136
+ it 'can select terms by usage with a longer list' do
137
+ mocker=AMEEMocker.new(self,:path=>'something')
138
+ mocker.item_value_definitions.
139
+ item_definition.data_category.
140
+ item_value_definition('first',['bybob'],[],'byfrank',[],nil,nil,true,false,nil,"TEXT").
141
+ item_value_definition('second',['bybob'],[],'byfrank',[],nil,nil,true,false,nil,"TEXT").
142
+ item_value_definition('third',['byfrank'],[],['bybob'],[],nil,nil,true,false,nil,"TEXT")
143
+ pc=PrototypeCalculation.new {path '/something'; all_profiles ; fixed_usage 'bybob'}
144
+ pc.profiles.in_use.labels.should eql [:first,:second]
145
+ pc.profiles.out_of_use.labels.should eql [:third]
146
+ pc.fixed_usage 'byfrank'
147
+ pc.profiles.out_of_use.labels.should eql [:first,:second]
148
+ pc.profiles.in_use.labels.should eql [:third]
149
+ end
150
+ it 'can generate itself with dynamic usage dropdown' do
151
+ mocker=AMEEMocker.new(self,:path=>'something')
152
+ mocker.item_value_definitions.
153
+ item_definition.data_category.
154
+ item_value_definition('first',['bybob'],[],'byfrank',[],:kg,:mi,true,false,nil,"TEXT").
155
+ item_value_definition('second',['bybob'],[],'byfrank',[],:km,nil,true,false,nil,"TEXT").
156
+ item_value_definition('third',['byfrank'],[],['bybob'],[],:lb,:h,true,false,nil,"TEXT")
157
+ pc=PrototypeCalculation.new {path '/something'; usage{ value 'bybob'}}
158
+ pc.profiles.labels.should eql [:first,:second,:third]
159
+ pc.profiles.visible.labels.should eql [:first,:second]
160
+ pc.terms.labels.should eql [:usage,:first,:second,:third]
161
+ pc.terms.default_units.first.should be_nil
162
+ pc.terms.default_units[1].should be_a Quantify::Unit::Base
163
+ pc.terms.default_units[1].name.should eql 'kilogram'
164
+ pc.terms.default_per_units.first.should be_nil
165
+ pc.terms.default_per_units[1].should be_a Quantify::Unit::Base
166
+ pc.terms.default_per_units[1].name.should eql 'mile'
167
+ pc.terms.default_per_units[2].should be_nil
168
+ end
169
+ it 'can generate itself with outputs' do
170
+ mocker=AMEEMocker.new(self,:path=>'something')
171
+ mocker.return_value_definitions.
172
+ item_definition.data_category.
173
+ return_value_definition('first',:kg,:mi).
174
+ return_value_definition('second',:km).
175
+ return_value_definition('third')
176
+ pc=PrototypeCalculation.new {path '/something'; all_outputs}
177
+ pc.outputs.labels.should eql [:first,:second,:third]
178
+ pc.terms.default_units.first.should be_a Quantify::Unit::Base
179
+ pc.terms.default_units.first.name.should eql 'kilogram'
180
+ pc.terms.default_per_units.first.should be_a Quantify::Unit::Base
181
+ pc.terms.default_per_units.first.name.should eql 'mile'
182
+ pc.terms.default_units[2].should be_nil
183
+ end
184
+
185
+ it 'can generate itself with everything' do
186
+ mocker=AMEEMocker.new(self,:path=>'something')
187
+ mocker.itemdef_drills ['first','second','third']
188
+ mocker.return_value_definitions.
189
+ item_value_definitions.
190
+ item_definition.data_category.
191
+ item_value_definition('first',['bybob','byfrank'],[],[],[],nil,nil,false,true,nil,"TEXT").
192
+ item_value_definition('second',['bybob','byfrank'],[],[],[],nil,nil,false,true,nil,"TEXT").
193
+ item_value_definition('third',['bybob','byfrank'],[],[],[],nil,nil,false,true,nil,"TEXT").
194
+ item_value_definition('fourth',['bybob'],[],'byfrank',[],nil,nil,true,false,nil,"TEXT").
195
+ item_value_definition('fifth',['bybob'],[],'byfrank',[],:lb,nil,true,false,nil,"TEXT").
196
+ item_value_definition('sixth',['byfrank'],[],['bybob'],[],nil,nil,true,false,nil,"TEXT").
197
+ return_value_definition('seventh').
198
+ return_value_definition('eighth').
199
+ return_value_definition('ninth',:mi,:h)
200
+ pc=PrototypeCalculation.new {
201
+ path '/something';
202
+ terms_from_amee_dynamic_usage 'bybob'}
203
+ pc.terms.labels.should eql [:usage,
204
+ :first,:second,:third,
205
+ :fourth,:fifth,:sixth,
206
+ :seventh,:eighth,:ninth
207
+ ]
208
+ pc.terms.names.should eql ['Usage',
209
+ 'First','Second','Third',
210
+ 'Fourth','Fifth','Sixth',
211
+ 'Seventh','Eighth','Ninth'
212
+ ]
213
+ pc.terms.visible.labels.should eql [:usage,
214
+ :first,:second,:third,
215
+ :fourth,:fifth,
216
+ :seventh,:eighth,:ninth
217
+ ]
218
+ pc.terms.default_units.compact.first.should be_a Quantify::Unit::Base
219
+ pc.terms.default_units.compact.map(&:name).should include 'pound'
220
+ pc.terms.default_units.compact.map(&:name).should include 'mile'
221
+ pc.terms.default_per_units.compact.first.should be_a Quantify::Unit::Base
222
+ pc.terms.default_per_units.compact.map(&:name).should include 'hour'
223
+ end
224
+ it 'transfers memoised amee information to constructed ongoing calculations' do
225
+ t=Transport.clone
226
+ flexmock(AMEE::Data::Category).should_receive(:get).
227
+ with(AMEE::DataAbstraction.connection,'/data/transport/car/generic').
228
+ once.and_return(true)
229
+ t.send(:amee_data_category)
230
+ t.begin_calculation.send(:amee_data_category)
231
+ end
232
+ it 'can auto-create start and end date metadata' do
233
+ t=Transport.clone
234
+ t.instance_eval{
235
+ start_and_end_dates
236
+ }
237
+ t.metadata.labels.should eql [:start_date,:end_date]
238
+ t[:start_date].interface.should eql :date
239
+ t[:start_date].date?.should be_true
240
+ t.date.labels.should eql [:start_date,:end_date]
241
+ end
242
+ it 'can correct a dodgy term' do
243
+ pc=PrototypeCalculation.new {
244
+ metadatum{ label :frank }
245
+ correcting(:frank) {name "Bob"}
246
+ }
247
+ pc[:frank].name.should eql "Bob"
248
+ end
249
+ it 'lets corrections on missing terms go' do
250
+ lambda{pc=PrototypeCalculation.new {
251
+ metadatum{ label :sid }
252
+ correcting(:frank) {name "Bob"}
253
+ }}.should_not raise_error
254
+ end
255
+ end
256
+
@@ -0,0 +1,385 @@
1
+ require File.dirname(File.dirname(__FILE__)) + '/spec_helper.rb'
2
+
3
+ class Term
4
+ def call_me
5
+ #stub, because flexmock doesn't work for new instances during constructor
6
+ @@called=true
7
+ end
8
+ cattr_accessor :called
9
+ end
10
+
11
+ describe Term do
12
+ it 'can be initialized via DSL block' do
13
+ Term.new {call_me}
14
+ Term.called.should be_true
15
+ end
16
+
17
+ it "has label" do
18
+ Term.new {label :hello}.label.should eql :hello
19
+ end
20
+
21
+ it "has name" do
22
+ Term.new {name :hello}.name.should eql :hello
23
+ end
24
+
25
+ it "has path" do
26
+ Term.new {path :hello}.path.should eql :hello
27
+ end
28
+
29
+ it "has note" do
30
+ Term.new {note 'hello'}.note.should eql 'hello'
31
+ end
32
+
33
+ it 'has parent' do
34
+ Transport[:distance].parent.should eql Transport
35
+ end
36
+ it "has name defaulting to label" do
37
+ Term.new {label :hello}.name.should eql 'Hello'
38
+ Term.new {label :hello ; name 'goodbye'}.name.should eql 'goodbye'
39
+ Term.new {name 'goodbye' ; label :hello }.name.should eql 'goodbye'
40
+ end
41
+ it 'has path defaulting to label' do
42
+ Term.new {label :hello}.path.should eql 'hello'
43
+ Term.new {label :hello ; path 'goodbye'}.path.should eql 'goodbye'
44
+ Term.new {path 'goodbye' ; label :hello }.path.should eql 'goodbye'
45
+ end
46
+ it 'has value' do
47
+ Term.new {value :hello}.value.should eql :hello
48
+ end
49
+ it 'knows if it is set' do
50
+ Term.new {value :hello}.set?.should be_true
51
+ Term.new.set?.should be_false
52
+ end
53
+ it 'has interface' do
54
+ Term.new {interface :text_box}.interface.should eql :text_box
55
+ end
56
+ it 'raises exception on invalid interface' do
57
+ lambda{Term.new {interface :bobby}}.should raise_error Exceptions::InvalidInterface
58
+ end
59
+ it 'knows what it''s interface is' do
60
+ Term.new {interface :text_box}.text_box?.should be_true
61
+ Term.new {interface :text_box}.drop_down?.should be_false
62
+ Term.new {interface :drop_down}.text_box?.should be_false
63
+ Term.new {interface :drop_down}.drop_down?.should be_true
64
+ end
65
+ it 'can be enabled or disabled' do
66
+ t=Term.new
67
+ t.enabled?.should be_true
68
+ t.disabled?.should be_false
69
+ t.disable!
70
+ t.enabled?.should be_false
71
+ t.disabled?.should be_true
72
+ t.enable!
73
+ t.enabled?.should be_true
74
+ t.disabled?.should be_false
75
+ end
76
+ it 'can be visible or invisible' do
77
+ t=Term.new
78
+ t.visible?.should be_true
79
+ t.hidden?.should be_false
80
+ t.hide!
81
+ t.visible?.should be_false
82
+ t.hidden?.should be_true
83
+ t.show!
84
+ t.visible?.should be_true
85
+ t.hidden?.should be_false
86
+ end
87
+ it 'knows which terms come before or after it' do
88
+ Transport.terms.
89
+ select{|x|x.before?(:distance)}.map(&:label).
90
+ should eql [:fuel,:size]
91
+ Transport.terms.
92
+ select{|x|x.after?(:distance)}.map(&:label).
93
+ should eql [:co2]
94
+ end
95
+
96
+ it "should respond to unit methods" do
97
+ Term.new.methods.should include "unit","per_unit","default_unit","default_per_unit",
98
+ "alternative_units","alternative_per_units"
99
+ end
100
+
101
+ it "has no default unit if none declared" do
102
+ Term.new {path :hello}.default_unit.should be_nil
103
+ Term.new {path :hello}.default_per_unit.should be_nil
104
+ Term.new {path :hello}.unit.should be_nil
105
+ Term.new {path :hello}.per_unit.should be_nil
106
+ end
107
+
108
+ it "has default unit if specified" do
109
+ Term.new {path :hello; default_unit :kg}.default_unit.name.should == 'kilogram'
110
+ Term.new {path :hello; default_per_unit :kWh}.default_per_unit.pluralized_name.should == 'kilowatt hours'
111
+ Term.new {path :hello; default_unit :kg}.default_unit.should be_a Quantify::Unit::SI
112
+ end
113
+
114
+ it "has current unit defaulting to default unit if none specified" do
115
+ Term.new {path :hello; default_unit :kg}.unit.name.should == 'kilogram'
116
+ Term.new {path :hello; default_per_unit :kWh}.per_unit.pluralized_name.should == 'kilowatt hours'
117
+ Term.new {path :hello; default_unit :kg}.unit.should be_a Quantify::Unit::SI
118
+ end
119
+
120
+ it "should have no alternative units if none specified and no default" do
121
+ term = Term.new {path :hello}
122
+ term.default_unit.should be_nil
123
+ term.unit.should be_nil
124
+ term.default_per_unit.should be_nil
125
+ term.per_unit.should be_nil
126
+ term.alternative_units.should be_nil
127
+ term.alternative_per_units.should be_nil
128
+ end
129
+
130
+ it "has default unit alternatives if none specified" do
131
+ term = Term.new {path :hello; default_unit :kg; default_per_unit :kWh}
132
+ units = term.alternative_units.map(&:name)
133
+ units.should include "gigagram", "pound", "tonne"
134
+ per_units = term.alternative_per_units.map(&:name)
135
+ per_units.should include "joule", "british thermal unit", "megawatt hour"
136
+ end
137
+
138
+ it "has limited set of alternative units if specified" do
139
+ term = Term.new {path :hello; default_unit :kg; alternative_units :t, :ton_us, :lb}
140
+ units = term.alternative_units.map(&:name)
141
+ units.should include "tonne", "pound", "short ton"
142
+ units.should_not include "gigagram", "ounce", "gram"
143
+ end
144
+
145
+ it "should raise error when specifying incompatible alternative units" do
146
+ lambda{Term.new {path :hello; default_unit :kg; alternative_units :kWh, :km}}.should raise_error
147
+ end
148
+
149
+ it "should make a clone" do
150
+ term = Term.new {path :hello; default_unit :kg; alternative_units :t, :ton_us, :lb}
151
+ term.path.should eql :hello
152
+ term.default_unit.should be_a Quantify::Unit::Base
153
+ original_unit_instance = term.default_unit
154
+ term.default_unit.symbol.should eql 'kg'
155
+ new_term = term.clone
156
+ new_term.path.should eql :hello
157
+ new_term.default_unit.should be_a Quantify::Unit::Base
158
+ new_term.default_unit.symbol.should eql 'kg'
159
+ new_unit_instance = new_term.default_unit
160
+ original_unit_instance.should_not eql new_unit_instance
161
+ end
162
+
163
+ it "should represent term as string with unit symbol if no argument provided" do
164
+ Term.new {path :hello; value 12; default_unit :kg}.to_s.should == '12 kg'
165
+ Term.new {path :hello; value 12; default_unit :kg; per_unit :h}.to_s.should == '12 kg h^-1'
166
+ Term.new {path :hello; value 12; per_unit :h}.to_s.should == '12 h^-1'
167
+ end
168
+
169
+ it "should represent term as string with unit label" do
170
+ Term.new {path :hello; value 12; default_unit :kg}.to_s(:label).should == '12 kg'
171
+ Term.new {path :hello; value 12; default_unit :kg; per_unit :h}.to_s(:label).should == '12 kg/h'
172
+ Term.new {path :hello; value 12; per_unit :h}.to_s(:label).should == '12 h^-1'
173
+ end
174
+
175
+ it "should represent term as string with unit name" do
176
+ Term.new {path :hello; value 12; default_unit :kg}.to_s(:name).should == '12 kilogram'
177
+ Term.new {path :hello; value 12; default_unit :kg; per_unit :h}.to_s(:name).should == '12 kilogram per hour'
178
+ Term.new {path :hello; value 12; per_unit :h}.to_s(:name).should == '12 per hour'
179
+ end
180
+
181
+ it "should represent term as string with unit pluralized name" do
182
+ Term.new {path :hello; value 12; default_unit :kg}.to_s(:pluralized_name).should == '12 kilograms'
183
+ Term.new {path :hello; value 12; default_unit :kg; per_unit :h}.to_s(:pluralized_name).should == '12 kilograms per hour'
184
+ Term.new {path :hello; value 12; per_unit :h}.to_s(:pluralized_name).should == '12 per hour'
185
+ end
186
+
187
+ it "should be recognised as numeric" do
188
+ Term.new {path :hello; value 12; default_unit :kg}.has_numeric_value?.should be_true
189
+ end
190
+
191
+ it "should be recognised as non numeric" do
192
+ Term.new {path :hello; value 'bob'; default_unit :kg}.has_numeric_value?.should be_false
193
+ end
194
+
195
+ it "should convert the input to a String if the type is specified as such" do
196
+ Term.new {type :string; value 54}.value.should == "54"
197
+ end
198
+
199
+ it "should convert the input to a Fixnum if the type is specified as such" do
200
+ Term.new {type :fixnum; value '54'}.value.should == 54
201
+ end
202
+
203
+ it "should convert the input to a Float if the type is specified as such" do
204
+ Term.new {type :float; value '54'}.value.should == 54.0
205
+ end
206
+
207
+ it "should convert the input to a Date if the type is specified as such" do
208
+ Term.new {type :date; value '2011-01-01'}.value.should == Date.parse("2011-01-01")
209
+ end
210
+
211
+ it "should convert the input to a Time if the type is specified as such" do
212
+ Term.new {type :time; value "2011-01-01 10:00:00"}.value.should == Time.parse("2011-01-01 10:00:00")
213
+ end
214
+
215
+ it "should convert the input to a DateTime if the type is specified as such" do
216
+ now = DateTime.now
217
+ Term.new {type :datetime; value now.to_s}.value.should === now
218
+ end
219
+
220
+ it "should store the pre cast value" do
221
+ term = Term.new {type :float; value '54'}
222
+ term.value.should == 54.0
223
+ term.value_before_cast.should == "54"
224
+ end
225
+
226
+ it "should return self if no unit or per unit attribute" do
227
+ @term = Term.new { value 20 }
228
+ @term.unit.should be_nil
229
+ @term.per_unit.should be_nil
230
+ @term.value.should eql 20
231
+ new_term = @term.convert_unit(:unit => :t)
232
+ new_term.should === @term
233
+ new_term.value.should eql 20
234
+ new_term.unit.should be_nil
235
+ new_term.per_unit.should be_nil
236
+ end
237
+
238
+ it "should return self if not a numeric unit" do
239
+ @term = Term.new { value 'plane'; unit :kg }
240
+ @term.unit.label.should eql 'kg'
241
+ @term.value.should eql 'plane'
242
+ new_term = @term.convert_unit(:unit => :t)
243
+ new_term.should === @term
244
+ end
245
+
246
+ it "should convert unit" do
247
+ @term = Term.new { value 20; unit :kg }
248
+ @term.unit.symbol.should eql 'kg'
249
+ @term.value.should eql 20
250
+ new_term = @term.convert_unit(:unit => :t)
251
+ new_term.unit.symbol.should eql 't'
252
+ new_term.value.should eql 0.020
253
+ end
254
+
255
+ it "should convert per unit" do
256
+ @term = Term.new { value 20; unit :kg; per_unit :min }
257
+ @term.unit.symbol.should eql 'kg'
258
+ @term.per_unit.symbol.should eql 'min'
259
+ @term.value.should eql 20
260
+ new_term = @term.convert_unit(:per_unit => :h)
261
+ new_term.unit.symbol.should eql 'kg'
262
+ new_term.per_unit.symbol.should eql 'h'
263
+ new_term.value.should eql 1200.0
264
+ end
265
+
266
+ it "should convert unit and per unit" do
267
+ @term = Term.new { value 20; unit :kg; per_unit :min }
268
+ @term.unit.symbol.should eql 'kg'
269
+ @term.per_unit.symbol.should eql 'min'
270
+ @term.value.should eql 20
271
+ new_term = @term.convert_unit( :unit => :t, :per_unit => :h )
272
+ new_term.unit.symbol.should eql 't'
273
+ new_term.per_unit.symbol.should eql 'h'
274
+ new_term.value.should eql 1.2000
275
+ end
276
+
277
+ it "should convert unit if value a string" do
278
+ @term = Term.new { value "20"; unit :kg }
279
+ @term.unit.symbol.should eql 'kg'
280
+ @term.value.should eql "20"
281
+ new_term = @term.convert_unit(:unit => :t)
282
+ new_term.unit.symbol.should eql 't'
283
+ new_term.value.should eql 0.020
284
+ end
285
+
286
+ it "should convert per unit if value a string" do
287
+ @term = Term.new { value "20"; unit :kg; per_unit :min }
288
+ @term.unit.symbol.should eql 'kg'
289
+ @term.per_unit.symbol.should eql 'min'
290
+ @term.value.should eql "20"
291
+ new_term = @term.convert_unit(:per_unit => :h)
292
+ new_term.unit.symbol.should eql 'kg'
293
+ new_term.per_unit.symbol.should eql 'h'
294
+ new_term.value.should eql 1200.0
295
+ end
296
+
297
+ it "should convert unit and per unit if value a string" do
298
+ @term = Term.new { value "20"; unit :kg; per_unit :min }
299
+ @term.unit.symbol.should eql 'kg'
300
+ @term.per_unit.symbol.should eql 'min'
301
+ @term.value.should eql "20"
302
+ new_term = @term.convert_unit( :unit => :t, :per_unit => :h )
303
+ new_term.unit.symbol.should eql 't'
304
+ new_term.per_unit.symbol.should eql 'h'
305
+ new_term.value.should eql 1.2000
306
+ end
307
+
308
+ it "should raise error if trying to convert to non dimensionally equivalent unit" do
309
+ @term = Term.new { value 20; unit :kg; per_unit :min }
310
+ @term.unit.symbol.should eql 'kg'
311
+ @term.per_unit.symbol.should eql 'min'
312
+ @term.value.should eql 20
313
+ lambda{new_term = @term.convert_unit( :unit => :J, :per_unit => :h )}.should raise_error
314
+ end
315
+
316
+ it "should raise error if trying to convert to non dimensionally equivalent unit" do
317
+ @term = Term.new { value 20; unit :kg; per_unit :min }
318
+ @term.unit.symbol.should eql 'kg'
319
+ @term.per_unit.symbol.should eql 'min'
320
+ @term.value.should eql 20
321
+ lambda{new_term = @term.convert_unit( :unit => :J, :per_unit => :h )}.should raise_error
322
+ end
323
+
324
+ describe "quantities" do
325
+
326
+ it "should convert term with unit to quantity object" do
327
+ @term = Term.new { value 20; unit :kg }
328
+ quantity = @term.to_quantity
329
+ quantity.unit.name.should eql "kilogram"
330
+ quantity.unit.symbol.should eql "kg"
331
+ quantity.value.should eql 20.0
332
+ quantity.to_s.should eql "20.0 kg"
333
+ end
334
+
335
+ it "should convert term with per unit to quantity object" do
336
+ @term = Term.new { value 20; per_unit :h }
337
+ quantity = @term.to_quantity
338
+ quantity.unit.name.should eql "per hour"
339
+ quantity.unit.symbol.should eql "h^-1"
340
+ quantity.value.should eql 20.0
341
+ quantity.to_s.should eql "20.0 h^-1"
342
+ end
343
+
344
+ it "should convert term with unit and per unit to quantity object" do
345
+ @term = Term.new { value 20; unit :kg; per_unit :h }
346
+ quantity = @term.to_quantity
347
+ quantity.unit.name.should eql "kilogram per hour"
348
+ quantity.unit.symbol.should eql "kg h^-1"
349
+ quantity.value.should eql 20.0
350
+ quantity.to_s.should eql "20.0 kg h^-1"
351
+ end
352
+
353
+ it "should return nil with no unit or per unit" do
354
+ @term = Term.new { value 20 }
355
+ quantity = @term.to_quantity
356
+ quantity.should eql 20
357
+ quantity.should be_a Integer
358
+ end
359
+
360
+ it "should return nil with non numeric term" do
361
+ @term = Term.new { value "taxi" }
362
+ quantity = @term.to_quantity
363
+ quantity.should be_nil
364
+ end
365
+
366
+ it "should convert term with unit to quantity object with alias method" do
367
+ @term = Term.new { value 20; unit :kg }
368
+ quantity = @term.to_q
369
+ quantity.unit.name.should eql "kilogram"
370
+ quantity.unit.symbol.should eql "kg"
371
+ quantity.value.should eql 20.0
372
+ quantity.to_s.should eql "20.0 kg"
373
+ end
374
+
375
+ it "should convert term with per unit to quantity object with alias method" do
376
+ @term = Term.new { value 20; per_unit :h }
377
+ quantity = @term.to_q
378
+ quantity.unit.name.should eql "per hour"
379
+ quantity.unit.symbol.should eql "h^-1"
380
+ quantity.value.should eql 20.0
381
+ quantity.to_s.should eql "20.0 h^-1"
382
+ end
383
+ end
384
+
385
+ end