amee-data-persistence 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.
- data/.rvmrc +1 -0
- data/CHANGELOG.txt +4 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +48 -0
- data/LICENSE.txt +27 -0
- data/README.txt +89 -0
- data/Rakefile +102 -0
- data/VERSION +1 -0
- data/amee-data-persistence.gemspec +93 -0
- data/amee-data-persistence.tmproj +27 -0
- data/generators/persistence/persistence_generator.rb +97 -0
- data/generators/persistence/templates/config/persistence.yml.erb +1 -0
- data/generators/persistence/templates/db/migrate/001_create_persistence_tables.rb +29 -0
- data/generators/persistence/templates/db/migrate/002_add_unit_columns.rb +15 -0
- data/generators/persistence/templates/db/migrate/003_add_value_types.rb +9 -0
- data/init.rb +1 -0
- data/lib/amee-data-persistence.rb +12 -0
- data/lib/amee/data_abstraction/calculation_collection.rb +22 -0
- data/lib/amee/data_abstraction/persistence_support.rb +274 -0
- data/lib/amee/db/calculation.rb +156 -0
- data/lib/amee/db/config.rb +54 -0
- data/lib/amee/db/term.rb +72 -0
- data/rails/init.rb +31 -0
- data/spec/amee/db/calculation_spec.rb +176 -0
- data/spec/amee/db/config_spec.rb +92 -0
- data/spec/amee/db/persistence_support_spec.rb +356 -0
- data/spec/amee/db/term_spec.rb +147 -0
- data/spec/database.yml +4 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +136 -0
- metadata +239 -0
@@ -0,0 +1,147 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
include AMEE::Db
|
4
|
+
|
5
|
+
describe Term do
|
6
|
+
|
7
|
+
before(:all) do
|
8
|
+
Calculation.create :calculation_type => :electricity
|
9
|
+
end
|
10
|
+
|
11
|
+
after(:all) do
|
12
|
+
Calculation.delete_all
|
13
|
+
Term.delete_all
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "new term" do
|
17
|
+
|
18
|
+
valid_term_attributes = { :label => 'co2',
|
19
|
+
:value => 120,
|
20
|
+
:unit => Unit.kg,
|
21
|
+
:calculation_id => '1' }
|
22
|
+
before(:all) do
|
23
|
+
@attr = valid_term_attributes
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should be valid with label, value and calcualtion id" do
|
27
|
+
@term = Term.new @attr
|
28
|
+
@term.should be_valid
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should be invalid without label" do
|
32
|
+
@term = Term.new @attr.merge(:label => nil)
|
33
|
+
@term.should_not be_valid
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should be valid without value" do
|
37
|
+
@term = Term.new @attr.merge(:value => nil)
|
38
|
+
@term.should be_valid
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should be invalid without calculation id" do
|
42
|
+
@term = Term.new @attr.merge(:calculation_id => nil)
|
43
|
+
@term.should_not be_valid
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should create a new term" do
|
47
|
+
@term = Term.create @attr
|
48
|
+
@term.is_a?(Term).should be_true
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should create a new term from quantity onject" do
|
52
|
+
@term = Term.create @attr.merge :per_unit => Unit.km
|
53
|
+
@term.is_a?(Term).should be_true
|
54
|
+
@term.value.should == "120"
|
55
|
+
@term.unit.should == 'kg'
|
56
|
+
@term.per_unit.should == 'km'
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should save the value type in the database" do
|
60
|
+
@term = Term.create @attr
|
61
|
+
@term.value_type.should == Fixnum.name
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "units" do
|
66
|
+
|
67
|
+
valid_term_attributes = { :label => 'co2',
|
68
|
+
:value => nil,
|
69
|
+
:unit => Unit.kg,
|
70
|
+
:per_unit => Unit.year,
|
71
|
+
:calculation_id => '1' }
|
72
|
+
before(:all) do
|
73
|
+
@attr = valid_term_attributes
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should be converted to string with JScience label" do
|
77
|
+
@term = Term.create @attr
|
78
|
+
@term.unit.should == 'kg'
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should be converted to string with JScience label" do
|
82
|
+
@term = Term.create @attr.merge(:unit => Unit.short_ton)
|
83
|
+
@term.unit.should == 'ton_us'
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "hash representation" do
|
88
|
+
|
89
|
+
valid_term_attributes = { :label => 'co2',
|
90
|
+
:value => nil,
|
91
|
+
:unit => Unit.kg,
|
92
|
+
:per_unit => Unit.year,
|
93
|
+
:calculation_id => '1' }
|
94
|
+
before(:all) do
|
95
|
+
@term = Term.create valid_term_attributes
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should convert record to hash with quantity objects" do
|
99
|
+
hash = @term.to_hash
|
100
|
+
hash.keys.should eql [:co2]
|
101
|
+
hash[:co2][:value].should be_nil
|
102
|
+
hash[:co2][:unit].should be_a Quantify::Unit::Base
|
103
|
+
hash[:co2][:unit].name.should eql 'kilogram'
|
104
|
+
hash[:co2][:per_unit].should be_a Quantify::Unit::Base
|
105
|
+
hash[:co2][:per_unit].name.should eql 'year'
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should get back a String when the original value was set as a String" do
|
109
|
+
@term.value = "bob"
|
110
|
+
@term.save
|
111
|
+
@term.to_hash[:co2][:value].should == "bob"
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should get back a Fixnum when the original value was set as Fixnum" do
|
115
|
+
@term.value = 1200
|
116
|
+
@term.save
|
117
|
+
@term.to_hash[:co2][:value].should == 1200
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should get back a Float when the original value was set as a Float" do
|
121
|
+
@term.value = 17.32
|
122
|
+
@term.save
|
123
|
+
@term.to_hash[:co2][:value].should == 17.32
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should get back a Date when the original value was set as a Date" do
|
127
|
+
now = Date.today
|
128
|
+
@term.value = now
|
129
|
+
@term.save
|
130
|
+
@term.to_hash[:co2][:value].should == now
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should get back a Time when the original value was set as a Time" do
|
134
|
+
now = Time.parse("2011-01-01 10:00:00")
|
135
|
+
@term.value = now
|
136
|
+
@term.save
|
137
|
+
@term.to_hash[:co2][:value].should === now
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should get back a DateTime when the original value was set as a DateTime" do
|
141
|
+
now = DateTime.now
|
142
|
+
@term.value = now
|
143
|
+
@term.save
|
144
|
+
@term.to_hash[:co2][:value].should === now
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
data/spec/database.yml
ADDED
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'spec'
|
3
|
+
require 'rspec_spinner'
|
4
|
+
require 'yaml'
|
5
|
+
require 'logger'
|
6
|
+
gem 'amee-data-abstraction'
|
7
|
+
require 'amee-data-abstraction'
|
8
|
+
|
9
|
+
RAILS_ROOT = '.'
|
10
|
+
|
11
|
+
DB_CONFIG = YAML.load_file(File.dirname(__FILE__) + '/database.yml')
|
12
|
+
DB_MIGRATION = File.join(File.dirname(__FILE__), '..','generators','persistence','templates','db','migrate')
|
13
|
+
|
14
|
+
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
15
|
+
require 'amee-data-persistence'
|
16
|
+
require 'amee/data_abstraction/persistence_support'
|
17
|
+
|
18
|
+
ActiveRecord::Base.logger = Logger.new(File.open('database.log', 'a'))
|
19
|
+
AMEE::DataAbstraction::OngoingCalculation.class_eval { include AMEE::DataAbstraction::PersistenceSupport }
|
20
|
+
|
21
|
+
ActiveRecord::Base.establish_connection(DB_CONFIG)
|
22
|
+
ActiveRecord::Migrator.up(DB_MIGRATION)
|
23
|
+
|
24
|
+
Spec::Runner.configure do |config|
|
25
|
+
config.mock_with :flexmock
|
26
|
+
end
|
27
|
+
|
28
|
+
def yaml_load_mock(method)
|
29
|
+
flexmock(YAML) do |mock|
|
30
|
+
mock.should_receive(:load_file).and_return('method' => method.to_s)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def choose_mock
|
35
|
+
selection_mock = flexmock "selection"
|
36
|
+
selection_mock.should_receive(:selections).and_return({"country"=>"Argentina"})
|
37
|
+
|
38
|
+
drill = AMEE::Data::DrillDown
|
39
|
+
drill_mock = flexmock(drill)
|
40
|
+
drill_mock.should_receive(:get).and_return(selection_mock)
|
41
|
+
end
|
42
|
+
|
43
|
+
def delete_mock
|
44
|
+
item = AMEE::Profile::Item
|
45
|
+
drill_mock = flexmock(item)
|
46
|
+
drill_mock.should_receive(:delete).and_return(true)
|
47
|
+
end
|
48
|
+
|
49
|
+
def populate_db
|
50
|
+
calculation_one = { :calculation_type => :electricity, :profile_item_uid => "J38DY57SK591",
|
51
|
+
:country => {:value =>'Argentina'},
|
52
|
+
:usage =>{:value => 6000},
|
53
|
+
:co2 =>{:value => 1200}}
|
54
|
+
|
55
|
+
calculation_two = { :calculation_type => :electricity, :profile_item_uid => "CJ49FFU37DIW",
|
56
|
+
:country =>{:value => 'Argentina'},
|
57
|
+
:usage => {:value =>250},
|
58
|
+
:co2 =>{:value => 23000}}
|
59
|
+
|
60
|
+
calculation_three = { :calculation_type => :electricity, :profile_item_uid => "K588DH47SMN5", :profile_uid => "H9KJ49FKIWO5",
|
61
|
+
:country => {:value =>'Argentina'},
|
62
|
+
:usage => {:value => 12345},
|
63
|
+
:co2 => {:value => 1.2}}
|
64
|
+
|
65
|
+
[ calculation_one, calculation_two, calculation_three ].each do |attr|
|
66
|
+
AMEE::Db::Calculation.new { |calc| calc.update_calculation! attr }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def initialize_calculation_set
|
71
|
+
eval "Calculations = AMEE::DataAbstraction::CalculationSet.new {
|
72
|
+
calculation{
|
73
|
+
name 'Electricity'
|
74
|
+
label :electricity
|
75
|
+
path '/business/energy/electricity/grid'
|
76
|
+
drill {
|
77
|
+
label :country
|
78
|
+
path 'country'
|
79
|
+
fixed 'Argentina'
|
80
|
+
}
|
81
|
+
profile {
|
82
|
+
label :usage
|
83
|
+
name 'Electricity Used'
|
84
|
+
path 'energyPerTime'
|
85
|
+
}
|
86
|
+
output {
|
87
|
+
label :co2
|
88
|
+
name 'Carbon Dioxide'
|
89
|
+
path :default
|
90
|
+
}
|
91
|
+
}
|
92
|
+
}"
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
# Stub activerecord for rails tests
|
97
|
+
# Taken from http://muness.blogspot.com/2006/12/unit-testing-rails-activerecord-classes.html
|
98
|
+
#class ActiveRecordUnitTestHelper
|
99
|
+
# attr_accessor :klass
|
100
|
+
#
|
101
|
+
# def initialize klass
|
102
|
+
# self.klass = klass
|
103
|
+
# self
|
104
|
+
# end
|
105
|
+
#
|
106
|
+
# def where attributes
|
107
|
+
# klass.stubs(:columns).returns(columns(attributes))
|
108
|
+
# instance = klass.new(attributes)
|
109
|
+
# instance.id = attributes[:id] if attributes[:id] #the id attributes works differently on active record classes
|
110
|
+
# instance
|
111
|
+
# end
|
112
|
+
#
|
113
|
+
#protected
|
114
|
+
# def columns attributes
|
115
|
+
# attributes.keys.collect{|attribute| column attribute.to_s, attributes[attribute]}
|
116
|
+
# end
|
117
|
+
#
|
118
|
+
# def column column_name, value
|
119
|
+
# ActiveRecord::ConnectionAdapters::Column.new(column_name, nil, ActiveRecordUnitTestHelper.active_record_type(value.class), false)
|
120
|
+
# end
|
121
|
+
#
|
122
|
+
# def self.active_record_type klass
|
123
|
+
# return case klass.name
|
124
|
+
# when "Fixnum" then "integer"
|
125
|
+
# when "Float" then "float"
|
126
|
+
# when "Time" then "time"
|
127
|
+
# when "Date" then "date"
|
128
|
+
# when "String" then "string"
|
129
|
+
# when "Object" then "boolean"
|
130
|
+
# end
|
131
|
+
# end
|
132
|
+
#end
|
133
|
+
#
|
134
|
+
#def disconnected klass
|
135
|
+
# ActiveRecordUnitTestHelper.new(klass)
|
136
|
+
#ßend
|
metadata
ADDED
@@ -0,0 +1,239 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: amee-data-persistence
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- James Hetherington
|
14
|
+
- Andrew Berkeley
|
15
|
+
- James Smith
|
16
|
+
- George Palmer
|
17
|
+
autorequire:
|
18
|
+
bindir: bin
|
19
|
+
cert_chain: []
|
20
|
+
|
21
|
+
date: 2011-08-11 00:00:00 +01:00
|
22
|
+
default_executable:
|
23
|
+
dependencies:
|
24
|
+
- !ruby/object:Gem::Dependency
|
25
|
+
prerelease: false
|
26
|
+
type: :runtime
|
27
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
hash: 23
|
33
|
+
segments:
|
34
|
+
- 1
|
35
|
+
- 0
|
36
|
+
- 0
|
37
|
+
version: 1.0.0
|
38
|
+
name: amee-data-abstraction
|
39
|
+
version_requirements: *id001
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
prerelease: false
|
42
|
+
type: :development
|
43
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
44
|
+
none: false
|
45
|
+
requirements:
|
46
|
+
- - ~>
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
hash: 23
|
49
|
+
segments:
|
50
|
+
- 1
|
51
|
+
- 0
|
52
|
+
- 0
|
53
|
+
version: 1.0.0
|
54
|
+
name: bundler
|
55
|
+
version_requirements: *id002
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
prerelease: false
|
58
|
+
type: :development
|
59
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ~>
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
hash: 7
|
65
|
+
segments:
|
66
|
+
- 1
|
67
|
+
- 6
|
68
|
+
- 4
|
69
|
+
version: 1.6.4
|
70
|
+
name: jeweler
|
71
|
+
version_requirements: *id003
|
72
|
+
- !ruby/object:Gem::Dependency
|
73
|
+
prerelease: false
|
74
|
+
type: :development
|
75
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
76
|
+
none: false
|
77
|
+
requirements:
|
78
|
+
- - "="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
hash: 27
|
81
|
+
segments:
|
82
|
+
- 1
|
83
|
+
- 3
|
84
|
+
- 0
|
85
|
+
version: 1.3.0
|
86
|
+
name: rspec
|
87
|
+
version_requirements: *id004
|
88
|
+
- !ruby/object:Gem::Dependency
|
89
|
+
prerelease: false
|
90
|
+
type: :development
|
91
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
92
|
+
none: false
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
hash: 3
|
97
|
+
segments:
|
98
|
+
- 0
|
99
|
+
version: "0"
|
100
|
+
name: rcov
|
101
|
+
version_requirements: *id005
|
102
|
+
- !ruby/object:Gem::Dependency
|
103
|
+
prerelease: false
|
104
|
+
type: :development
|
105
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
106
|
+
none: false
|
107
|
+
requirements:
|
108
|
+
- - "="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
hash: 21
|
111
|
+
segments:
|
112
|
+
- 1
|
113
|
+
- 1
|
114
|
+
- 3
|
115
|
+
version: 1.1.3
|
116
|
+
name: rspec_spinner
|
117
|
+
version_requirements: *id006
|
118
|
+
- !ruby/object:Gem::Dependency
|
119
|
+
prerelease: false
|
120
|
+
type: :development
|
121
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
122
|
+
none: false
|
123
|
+
requirements:
|
124
|
+
- - ">"
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
hash: 51
|
127
|
+
segments:
|
128
|
+
- 0
|
129
|
+
- 8
|
130
|
+
- 6
|
131
|
+
version: 0.8.6
|
132
|
+
name: flexmock
|
133
|
+
version_requirements: *id007
|
134
|
+
- !ruby/object:Gem::Dependency
|
135
|
+
prerelease: false
|
136
|
+
type: :development
|
137
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
138
|
+
none: false
|
139
|
+
requirements:
|
140
|
+
- - ~>
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
hash: 9
|
143
|
+
segments:
|
144
|
+
- 2
|
145
|
+
- 3
|
146
|
+
- 5
|
147
|
+
version: 2.3.5
|
148
|
+
name: activerecord
|
149
|
+
version_requirements: *id008
|
150
|
+
- !ruby/object:Gem::Dependency
|
151
|
+
prerelease: false
|
152
|
+
type: :development
|
153
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
154
|
+
none: false
|
155
|
+
requirements:
|
156
|
+
- - ">="
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
hash: 3
|
159
|
+
segments:
|
160
|
+
- 0
|
161
|
+
version: "0"
|
162
|
+
name: sqlite3
|
163
|
+
version_requirements: *id009
|
164
|
+
description: Part of the AMEEappkit, this gem provides storage and retrival of data provided by the amee-data-abstraction gem
|
165
|
+
email: help@amee.com
|
166
|
+
executables: []
|
167
|
+
|
168
|
+
extensions: []
|
169
|
+
|
170
|
+
extra_rdoc_files:
|
171
|
+
- LICENSE.txt
|
172
|
+
- README.txt
|
173
|
+
files:
|
174
|
+
- .rvmrc
|
175
|
+
- CHANGELOG.txt
|
176
|
+
- Gemfile
|
177
|
+
- Gemfile.lock
|
178
|
+
- LICENSE.txt
|
179
|
+
- README.txt
|
180
|
+
- Rakefile
|
181
|
+
- VERSION
|
182
|
+
- amee-data-persistence.gemspec
|
183
|
+
- amee-data-persistence.tmproj
|
184
|
+
- generators/persistence/persistence_generator.rb
|
185
|
+
- generators/persistence/templates/config/persistence.yml.erb
|
186
|
+
- generators/persistence/templates/db/migrate/001_create_persistence_tables.rb
|
187
|
+
- generators/persistence/templates/db/migrate/002_add_unit_columns.rb
|
188
|
+
- generators/persistence/templates/db/migrate/003_add_value_types.rb
|
189
|
+
- init.rb
|
190
|
+
- lib/amee-data-persistence.rb
|
191
|
+
- lib/amee/data_abstraction/calculation_collection.rb
|
192
|
+
- lib/amee/data_abstraction/persistence_support.rb
|
193
|
+
- lib/amee/db/calculation.rb
|
194
|
+
- lib/amee/db/config.rb
|
195
|
+
- lib/amee/db/term.rb
|
196
|
+
- rails/init.rb
|
197
|
+
- spec/amee/db/calculation_spec.rb
|
198
|
+
- spec/amee/db/config_spec.rb
|
199
|
+
- spec/amee/db/persistence_support_spec.rb
|
200
|
+
- spec/amee/db/term_spec.rb
|
201
|
+
- spec/database.yml
|
202
|
+
- spec/spec.opts
|
203
|
+
- spec/spec_helper.rb
|
204
|
+
has_rdoc: true
|
205
|
+
homepage: http://github.com/AMEE/amee-data-persistence
|
206
|
+
licenses:
|
207
|
+
- BSD 3-Clause
|
208
|
+
post_install_message:
|
209
|
+
rdoc_options: []
|
210
|
+
|
211
|
+
require_paths:
|
212
|
+
- lib
|
213
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
214
|
+
none: false
|
215
|
+
requirements:
|
216
|
+
- - ">="
|
217
|
+
- !ruby/object:Gem::Version
|
218
|
+
hash: 3
|
219
|
+
segments:
|
220
|
+
- 0
|
221
|
+
version: "0"
|
222
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
223
|
+
none: false
|
224
|
+
requirements:
|
225
|
+
- - ">="
|
226
|
+
- !ruby/object:Gem::Version
|
227
|
+
hash: 3
|
228
|
+
segments:
|
229
|
+
- 0
|
230
|
+
version: "0"
|
231
|
+
requirements: []
|
232
|
+
|
233
|
+
rubyforge_project:
|
234
|
+
rubygems_version: 1.6.2
|
235
|
+
signing_key:
|
236
|
+
specification_version: 3
|
237
|
+
summary: Persistent storage of calculations performed against the AMEE API
|
238
|
+
test_files: []
|
239
|
+
|