amee-data-abstraction 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. data/CHANGELOG.txt +3 -0
  2. data/README.txt +28 -15
  3. data/Rakefile +1 -16
  4. data/VERSION +1 -1
  5. data/amee-data-abstraction.gemspec +6 -5
  6. data/lib/amee-data-abstraction/calculation.rb +1 -1
  7. data/lib/amee-data-abstraction/calculation_set.rb +152 -10
  8. data/lib/amee-data-abstraction/drill.rb +8 -2
  9. data/lib/amee-data-abstraction/input.rb +24 -0
  10. data/lib/amee-data-abstraction/ongoing_calculation.rb +13 -9
  11. data/lib/amee-data-abstraction/term.rb +31 -13
  12. data/spec/amee-data-abstraction/calculation_set_spec.rb +244 -8
  13. data/spec/amee-data-abstraction/calculation_spec.rb +23 -18
  14. data/spec/amee-data-abstraction/drill_spec.rb +34 -7
  15. data/spec/amee-data-abstraction/input_spec.rb +113 -73
  16. data/spec/amee-data-abstraction/metadatum_spec.rb +1 -1
  17. data/spec/amee-data-abstraction/ongoing_calculation_spec.rb +45 -29
  18. data/spec/amee-data-abstraction/profile_spec.rb +2 -2
  19. data/spec/amee-data-abstraction/prototype_calculation_spec.rb +12 -8
  20. data/spec/amee-data-abstraction/term_spec.rb +49 -5
  21. data/spec/amee-data-abstraction/terms_list_spec.rb +16 -12
  22. data/spec/config/amee_units_spec.rb +1 -2
  23. data/spec/core-extensions/class_spec.rb +18 -18
  24. data/spec/core-extensions/hash_spec.rb +1 -2
  25. data/spec/core-extensions/ordered_hash_spec.rb +1 -2
  26. data/spec/core-extensions/proc_spec.rb +1 -1
  27. data/spec/fixtures/{electricity.rb → config/calculations/electricity.rb} +2 -2
  28. data/spec/fixtures/config/calculations/electricity_and_transport.rb +53 -0
  29. data/spec/fixtures/{transport.rb → config/calculations/transport.rb} +2 -2
  30. data/spec/fixtures/config/electricity.rb +35 -0
  31. data/spec/spec_helper.rb +38 -6
  32. metadata +8 -7
  33. data/spec/fixtures/electricity_and_transport.rb +0 -55
@@ -84,16 +84,6 @@ module AMEE
84
84
  #
85
85
  attr_property :path
86
86
 
87
- # String representing an annotation for <tt>self</tt>. Set a value by
88
- # passing an argument. Retrieve a value by calling without an argument,
89
- # e.g.,
90
- #
91
- # my_term.note 'Enter the mass of cement produced in the reporting period'
92
- #
93
- # my_term.note #=> 'Enter the mass of cement ...'
94
- #
95
- attr_property :note
96
-
97
87
  # Symbol representing the owning parent calculation of <tt>self</tt>. Set
98
88
  # the owning calculation object by passing as an argument. Retrieve it by
99
89
  # calling without an argument, e.g.,
@@ -211,6 +201,19 @@ module AMEE
211
201
  end
212
202
  @value
213
203
  end
204
+
205
+ # String representing an annotation for <tt>self</tt>. Set a value by
206
+ # passing an argument. Retrieve a value by calling without an argument,
207
+ # e.g.,
208
+ #
209
+ # my_term.note 'Enter the mass of cement produced in the reporting period'
210
+ #
211
+ # my_term.note #=> 'Enter the mass of cement ...'
212
+ #
213
+ def note(string=nil)
214
+ instance_variable_set("@note",string.gsub('"',"'")) unless string.nil?
215
+ instance_variable_get("@note")
216
+ end
214
217
 
215
218
  # Symbols representing the attributes of <tt>self</tt> which are concerned
216
219
  # with quantity units.
@@ -264,19 +267,33 @@ module AMEE
264
267
  end
265
268
 
266
269
  [:unit,:per_unit].each do |field|
270
+
271
+ # If no argument provided, returns the alternative units which are valid
272
+ # for <tt>self</tt>. If a list of units are provided as an argument, these
273
+ # override the dynamically assigned alternative units for <tt>self</tt>.
274
+ #
267
275
  define_method("alternative_#{field}s") do |*args|
268
276
  ivar = "@alternative_#{field}s"
269
- default = send("default_#{field}".to_sym)
270
277
  unless args.empty?
271
- args << default if default
272
278
  units = args.map {|arg| Unit.for(arg) }
273
279
  Term.validate_dimensional_equivalence?(*units)
274
280
  instance_variable_set(ivar, units)
275
281
  else
276
282
  return instance_variable_get(ivar) if instance_variable_get(ivar)
277
- return instance_variable_set(ivar, (default.alternatives << default)) if default
283
+ default = send("default_#{field}".to_sym)
284
+ return instance_variable_set(ivar, (default.alternatives)) if default
278
285
  end
279
286
  end
287
+
288
+ # Returns the list of unit choices for <tt>self</tt>, including both the
289
+ # default unit and all alternative units.
290
+ #
291
+ define_method("#{field}_choices") do |*args|
292
+ choices = send("alternative_#{field}s".to_sym)
293
+ default = send("default_#{field}".to_sym)
294
+ choices = [default] + choices if default
295
+ return choices
296
+ end
280
297
  end
281
298
 
282
299
  # Returns <tt>true</tt> if <tt>self</tt> has a populated value attribute.
@@ -503,6 +520,7 @@ module AMEE
503
520
  else value
504
521
  end
505
522
  end
523
+
506
524
  end
507
525
  end
508
526
  end
@@ -1,4 +1,5 @@
1
- require File.dirname(File.dirname(__FILE__)) + '/spec_helper.rb'
1
+ require 'spec_helper'
2
+
2
3
  class CalculationSet
3
4
  def call_me
4
5
  #stub, because flexmock doesn't work for new instances during constructor
@@ -7,24 +8,257 @@ class CalculationSet
7
8
  cattr_accessor :called
8
9
  end
9
10
  describe CalculationSet do
11
+
12
+ before :all do
13
+ CalculationSet.sets.clear
14
+ @calc_set = CalculationSet.find("electricity_and_transport")
15
+ end
16
+
10
17
  it 'can create an instance' do
11
- ElectricityAndTransport.should be_a CalculationSet
18
+ @calc_set.should be_a CalculationSet
12
19
  end
20
+
13
21
  it 'can create an instance' do
14
- ElectricityAndTransport.calculations.should be_a ActiveSupport::OrderedHash
22
+ @calc_set.calculations.should be_a ActiveSupport::OrderedHash
23
+ end
24
+
25
+ it "can access class sets hash" do
26
+ CalculationSet.sets[:electricity_and_transport].should be_a CalculationSet
27
+ end
28
+
29
+ it "is included in class sets hash if initialised by find method" do
30
+ CalculationSet.sets[:electricity_and_transport].should be_a CalculationSet
31
+ end
32
+
33
+ it "has file attribute if initialised by find method" do
34
+ CalculationSet.sets[:electricity_and_transport].file.should eql "#{Rails.root}/config/calculations/electricity_and_transport.rb"
35
+ end
36
+
37
+ it "has name attribute if initialised by find method" do
38
+ CalculationSet.sets[:electricity_and_transport].name.should eql "electricity_and_transport"
39
+ end
40
+
41
+ it "is included in class sets hash if initialised manually" do
42
+ CalculationSet.new('my_set') {calculation {label :mycalc}}
43
+ CalculationSet.sets[:my_set].should be_a CalculationSet
15
44
  end
45
+
46
+ it "has name" do
47
+ CalculationSet.new('my_set') {calculation {label :mycalc}}.name.should eql "my_set"
48
+ end
49
+
16
50
  it 'can access a calculation by key' do
17
- ElectricityAndTransport[:transport].should be_a PrototypeCalculation
51
+ @calc_set[:transport].should be_a PrototypeCalculation
52
+ end
53
+
54
+ describe "initialising from file" do
55
+
56
+ after(:each) do
57
+ CalculationSet.sets.clear
58
+ delete_lock_files
59
+ end
60
+
61
+ it "should find config file in default Rails location using just file name" do
62
+ CalculationSet.find_config_file("electricity").should eql "#{Rails.root}/config/calculations/electricity.rb"
63
+ end
64
+
65
+ it "should find config file in default Rails location using file name and extension" do
66
+ CalculationSet.find_config_file("electricity.rb").should eql "#{Rails.root}/config/calculations/electricity.rb"
67
+ end
68
+
69
+ it "should find config file in other Rails location using relative path" do
70
+ CalculationSet.find_config_file("config/electricity.rb").should eql "#{Rails.root}/config/electricity.rb"
71
+ end
72
+
73
+ it "should raise error if config file not found" do
74
+ lambda{CalculationSet.find_config_file("fuel")}.should raise_error
75
+ end
76
+
77
+ it "should call load_set if no set exists in class hash" do
78
+ CalculationSet.sets[:transport].should be_nil
79
+ flexmock(AMEE::DataAbstraction::CalculationSet) do |mock|
80
+ mock.should_receive(:load_set).once
81
+ end
82
+ set = CalculationSet.find('transport')
83
+ end
84
+
85
+ it "should not call load_set if set exists in class hash" do
86
+ CalculationSet.sets[:transport].should be_nil
87
+ set = CalculationSet.find('transport')
88
+ CalculationSet.sets[:transport].should be_a CalculationSet
89
+ flexmock(AMEE::DataAbstraction::CalculationSet) do |mock|
90
+ mock.should_receive(:load_set).never
91
+ end
92
+ set = CalculationSet.find('transport')
93
+ end
94
+
95
+ it "should generate set from file name using find method" do
96
+ CalculationSet.sets[:transport].should be_nil
97
+ set = CalculationSet.find('transport')
98
+ set.should be_a CalculationSet
99
+ CalculationSet.sets[:transport].should be_a CalculationSet
100
+ set.name.should eql 'transport'
101
+ set.file.should eql "#{Rails.root}/config/calculations/transport.rb"
102
+ end
103
+
104
+ it "should regenerate lock file at default location" do
105
+ lock_file = "#{Rails.root}/config/calculations/transport.lock.rb"
106
+ File.exist?(lock_file).should be_false
107
+ CalculationSet.sets[:transport].should be_nil
108
+ set = CalculationSet.find('transport')
109
+ File.exist?(lock_file).should be_false
110
+ CalculationSet.sets[:transport].should be_a CalculationSet
111
+
112
+ set.generate_lock_file
113
+ File.exist?(lock_file).should be_true
114
+
115
+ # lock file content
116
+ content = File.open(lock_file).read
117
+
118
+ # clear lock file to test for regenerated data
119
+ File.open(lock_file,'w') {|file| file.write "overwrite content"}
120
+ File.open(lock_file).read.should eql "overwrite content"
121
+ File.exist?(lock_file).should be_true
122
+
123
+ # regenerate and test content matches original
124
+ CalculationSet.regenerate_lock_file('transport')
125
+ File.exist?(lock_file).should be_true
126
+ File.open(lock_file).read.should eql content
127
+ end
128
+
129
+ it "should regenerate lock file at custom location" do
130
+ lock_file = "#{Rails.root}/config/calculations/transport.lock.rb"
131
+ File.exist?(lock_file).should be_false
132
+ CalculationSet.sets[:transport].should be_nil
133
+ set = CalculationSet.find('transport')
134
+ File.exist?(lock_file).should be_false
135
+ CalculationSet.sets[:transport].should be_a CalculationSet
136
+
137
+ set.generate_lock_file
138
+ File.exist?(lock_file).should be_true
139
+
140
+ content = File.open(lock_file).read
141
+ File.open(lock_file,'w') {|file| file.write "overwrite content"}
142
+ File.open(lock_file).read.should eql "overwrite content"
143
+ File.exist?(lock_file).should be_true
144
+
145
+ CalculationSet.regenerate_lock_file('transport', "#{Rails.root}/transport.lock.rb")
146
+ File.exist?(lock_file).should be_true
147
+ File.exist?("#{Rails.root}/transport.lock.rb").should be_true
148
+ File.open(lock_file).read.should eql "overwrite content"
149
+ File.open("#{Rails.root}/transport.lock.rb").read.should eql content
150
+
151
+ File.delete("#{Rails.root}/transport.lock.rb")
152
+ end
153
+
154
+ it "should return a lock file path based on master config file" do
155
+ set = CalculationSet.find('transport')
156
+ set.lock_file_path.should eql "#{Rails.root}/config/calculations/transport.lock.rb"
157
+ end
158
+
159
+ it "should return lock file path if lock file exists" do
160
+ lock_file = "#{Rails.root}/config/calculations/transport.lock.rb"
161
+ File.exist?(lock_file).should be_false
162
+ set = CalculationSet.find('transport')
163
+ File.exist?(lock_file).should be_false
164
+ set.generate_lock_file
165
+ File.exist?(lock_file).should be_true
166
+ set.config_path.should eql lock_file
167
+ end
168
+
169
+ it "should return master file path if lock file does not exist" do
170
+ lock_file = "#{Rails.root}/config/calculations/transport.lock.rb"
171
+ File.exist?(lock_file).should be_false
172
+ set = CalculationSet.find('transport')
173
+ File.exist?(lock_file).should be_false
174
+ set.config_path.should eql "#{Rails.root}/config/calculations/transport.rb"
175
+ end
176
+
177
+ it "should know if lock file exists" do
178
+ lock_file = "#{Rails.root}/config/calculations/transport.lock.rb"
179
+ File.exist?(lock_file).should be_false
180
+ set = CalculationSet.find('transport')
181
+ File.exist?(lock_file).should be_false
182
+ set.generate_lock_file
183
+ File.exist?(lock_file).should be_true
184
+ set.lock_file_exists?.should be_true
185
+ File.delete(lock_file)
186
+ File.exist?(lock_file).should be_false
187
+ set.lock_file_exists?.should be_false
188
+ end
189
+
190
+ it "should generate lock file" do
191
+ lock_file = "#{Rails.root}/config/calculations/transport.lock.rb"
192
+ File.exist?(lock_file).should be_false
193
+
194
+ set = CalculationSet.find('transport')
195
+ File.exist?(lock_file).should be_false
196
+ set.lock_file_exists?.should be_false
197
+
198
+ set.generate_lock_file
199
+ File.exist?(lock_file).should be_true
200
+ set.lock_file_exists?.should be_true
201
+
202
+ content = File.open(lock_file).read
203
+
204
+ File.delete(lock_file)
205
+ File.exist?(lock_file).should be_false
206
+ set.lock_file_exists?.should be_false
207
+
208
+ set.generate_lock_file
209
+ File.exist?(lock_file).should be_true
210
+ set.lock_file_exists?.should be_true
211
+ File.open(lock_file).read.should eql content
212
+ end
213
+
214
+ it "should generate lock file at custom location" do
215
+ lock_file = "#{Rails.root}/config/calculations/transport.lock.rb"
216
+ File.exist?(lock_file).should be_false
217
+
218
+ set = CalculationSet.find('transport')
219
+ File.exist?(lock_file).should be_false
220
+ set.lock_file_exists?.should be_false
221
+
222
+ set.generate_lock_file("#{Rails.root}/transport.lock.rb")
223
+ File.exist?(lock_file).should be_false
224
+ set.lock_file_exists?.should be_false
225
+
226
+ File.exist?("#{Rails.root}/transport.lock.rb").should be_true
227
+
228
+ File.delete("#{Rails.root}/transport.lock.rb")
229
+ end
230
+
231
+ end
232
+
233
+ it "can find a prototype calc without calc set" do
234
+ CalculationSet.new('my_set') {
235
+ calculation {label :my_calc}
236
+ calculation {label :my_other_calc}
237
+ }
238
+ CalculationSet.new('your_set') {
239
+ calculation {label :your_calc}
240
+ calculation {label :your_other_calc}
241
+ }
242
+ CalculationSet.find_prototype_calculation(:transport).should be_a PrototypeCalculation
243
+ CalculationSet.find_prototype_calculation(:your_calc).should be_a PrototypeCalculation
244
+ CalculationSet.find_prototype_calculation(:my_other_calc).should be_a PrototypeCalculation
245
+ end
246
+
247
+ it "returns nil where no prototype calcualtion is found" do
248
+ CalculationSet.find_prototype_calculation(:fuel).should be_nil
18
249
  end
250
+
19
251
  it 'can construct a calculation' do
20
- CalculationSet.new {calculation {label :mycalc}}[:mycalc].should be_a PrototypeCalculation
252
+ CalculationSet.new('my_set') {calculation {label :mycalc}}[:mycalc].should be_a PrototypeCalculation
21
253
  end
254
+
22
255
  it 'can be initialized with a DSL block' do
23
- CalculationSet.new {call_me}
256
+ CalculationSet.new('my_set') {call_me}
24
257
  CalculationSet.called.should be_true
25
258
  end
259
+
26
260
  it 'can have terms added to all calculations' do
27
- cs=CalculationSet.new {
261
+ cs=CalculationSet.new('my_set') {
28
262
  all_calculations {
29
263
  drill {label :energetic}
30
264
  }
@@ -35,6 +269,7 @@ describe CalculationSet do
35
269
  }
36
270
  cs[:mycalc].drills.labels.should eql [:remarkably,:energetic]
37
271
  end
272
+
38
273
  it 'can make multiple calculations quickly, one for each usage' do
39
274
  mocker=AMEEMocker.new(self,:path=>'something')
40
275
  mocker.item_value_definitions.usages(['bybob','byfrank']).
@@ -42,7 +277,7 @@ describe CalculationSet do
42
277
  item_value_definition('first',['bybob'],[],'byfrank',[],nil,nil,true,false,nil,"TEXT").
43
278
  item_value_definition('second',['bybob'],[],'byfrank',[],nil,nil,true,false,nil,"TEXT").
44
279
  item_value_definition('third',['byfrank'],[],['bybob'],[],nil,nil,true,false,nil,"TEXT")
45
- cs=CalculationSet.new {
280
+ cs=CalculationSet.new('my_set') {
46
281
  calculations_all_usages('/something') { |usage|
47
282
  label usage.to_sym
48
283
  profiles_from_usage usage
@@ -51,4 +286,5 @@ describe CalculationSet do
51
286
  cs[:bybob].profiles.labels.should eql [:first,:second]
52
287
  cs[:byfrank].profiles.labels.should eql [:third]
53
288
  end
289
+
54
290
  end
@@ -1,53 +1,58 @@
1
- require File.dirname(File.dirname(__FILE__)) + '/spec_helper.rb'
1
+ require 'spec_helper'
2
+
2
3
  describe Calculation do
3
4
 
5
+ before :all do
6
+ @calc = CalculationSet.find("transport")[:transport]
7
+ end
8
+
4
9
  it 'can create an instance' do
5
- Transport.should be_a Calculation
10
+ @calc.should be_a Calculation
6
11
  end
7
12
  it 'should have ordered terms, with labels' do
8
- Transport.terms.labels.should eql [:fuel,:size,:distance,:co2]
13
+ @calc.terms.labels.should eql [:fuel,:size,:distance,:co2]
9
14
  end
10
15
  it 'should have amee paths for the terms' do
11
- Transport.terms.paths.should eql ['fuel','size','distance',:default]
16
+ @calc.terms.paths.should eql ['fuel','size','distance','default']
12
17
  end
13
18
  it 'should have human names for the terms' do
14
- Transport.terms.names.
19
+ @calc.terms.names.
15
20
  should eql ['Fuel Type','Vehicle Size','Distance Driven','Carbon Dioxide']
16
21
  end
17
22
  it 'should return the inputs' do
18
- Transport.inputs.labels.should eql [:fuel,:size,:distance]
23
+ @calc.inputs.labels.should eql [:fuel,:size,:distance]
19
24
  end
20
25
  it 'should return the outputs' do
21
- Transport.outputs.labels.should eql [:co2]
26
+ @calc.outputs.labels.should eql [:co2]
22
27
  end
23
28
  it 'should generate an discover URL' do
24
- Transport.discover_url.should eql 'http://discover.amee.com/categories/transport/car/generic'
29
+ @calc.discover_url.should eql 'http://discover.amee.com/categories/transport/car/generic'
25
30
  end
26
31
  it 'should redirect to discover URL' do
27
- Transport.explorer_url.should eql 'http://discover.amee.com/categories/transport/car/generic'
32
+ @calc.explorer_url.should eql 'http://discover.amee.com/categories/transport/car/generic'
28
33
  end
29
34
  it 'can return a term via []' do
30
- Transport[:co2].label.should eql :co2
35
+ @calc[:co2].label.should eql :co2
31
36
  end
32
37
  it 'when copied, should deep copy the values' do
33
- x=Transport.clone
38
+ x=@calc.clone
34
39
  x[:co2].value :somevalue
35
40
  x[:co2].value.should eql :somevalue
36
- Transport[:co2].value.should be_nil
41
+ @calc[:co2].value.should be_nil
37
42
  end
38
43
  it 'knows to get terms that come before or after others' do
39
- t=Transport.clone
44
+ t=@calc.clone
40
45
  t.before(:distance).labels.
41
46
  should eql [:fuel,:size]
42
47
  t.after(:distance).map(&:label).
43
48
  should eql [:co2]
44
49
  end
45
50
  it 'delegates selectors to terms list' do
46
- t=Transport.clone
51
+ t=@calc.clone
47
52
  t.drills.labels.should eql [:fuel,:size]
48
53
  end
49
54
  it 'can find its amee data category' do
50
- t=Transport.clone
55
+ t=@calc.clone
51
56
  mocker=AMEEMocker.new self,:path=>'transport/car/generic'
52
57
  mocker.data_category
53
58
  t.send(:amee_data_category).path.should eql '/data/transport/car/generic'
@@ -55,18 +60,18 @@ describe Calculation do
55
60
  it 'can find its amee item definition' do
56
61
  mocker=AMEEMocker.new self,:path=>'transport/car/generic'
57
62
  mocker.item_definition(:my_itemdef_name).data_category
58
- t=Transport.clone
63
+ t=@calc.clone
59
64
  t.send(:amee_item_definition).name.should eql :my_itemdef_name
60
65
  end
61
66
  it 'can give item value definition list' do
62
67
  mocker=AMEEMocker.new self,:path=>'transport/car/generic'
63
68
  mocker.item_value_definition('distance').item_value_definitions.
64
69
  item_definition.data_category
65
- t=Transport.clone
70
+ t=@calc.clone
66
71
  t.send(:amee_ivds).first.path.should eql 'distance'
67
72
  end
68
73
  it 'can memoise access to AMEE' do
69
- t=Transport.clone
74
+ t=@calc.clone
70
75
  #AMEE::Data::Category.get(connection, "/data#{path}")
71
76
  flexmock(AMEE::Data::Category).should_receive(:get).
72
77
  with(AMEE::DataAbstraction.connection,'/data/transport/car/generic').