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,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