acts_as_geocodable 1.0.4 → 2.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/CHANGELOG +5 -0
- data/README +31 -37
- data/lib/acts_as_geocodable.rb +233 -278
- data/lib/acts_as_geocodable/geocode.rb +13 -13
- data/lib/acts_as_geocodable/remote_location.rb +6 -4
- data/lib/acts_as_geocodable/version.rb +3 -0
- data/lib/generators/acts_as_geocodable/USAGE +12 -0
- data/lib/generators/acts_as_geocodable/acts_as_geocodable_generator.rb +28 -0
- data/{generators/geocodable_migration → lib/generators/acts_as_geocodable}/templates/migration.rb +2 -2
- metadata +110 -41
- data/.gitignore +0 -3
- data/Gemfile +0 -2
- data/Gemfile.lock +0 -23
- data/Rakefile +0 -39
- data/VERSION +0 -1
- data/about.yml +0 -7
- data/acts_as_geocodable.gemspec +0 -75
- data/generators/geocodable_migration/USAGE +0 -12
- data/generators/geocodable_migration/geocodable_migration_generator.rb +0 -7
- data/install.rb +0 -1
- data/lib/acts_as_geocodable/tasks/acts_as_geocodable_tasks.rake +0 -4
- data/rails/init.rb +0 -5
- data/test/acts_as_geocodable_test.rb +0 -382
- data/test/db/database.yml +0 -18
- data/test/db/schema.rb +0 -60
- data/test/fixtures/cities.yml +0 -12
- data/test/fixtures/geocodes.yml +0 -51
- data/test/fixtures/geocodings.yml +0 -15
- data/test/fixtures/vacations.yml +0 -15
- data/test/geocode_test.rb +0 -97
- data/test/test_helper.rb +0 -46
- data/uninstall.rb +0 -1
@@ -1,12 +0,0 @@
|
|
1
|
-
Description:
|
2
|
-
The geocodable migration generator creates a migration which you can use to generate two tables:
|
3
|
-
|
4
|
-
geocodes - Contains the latitude and longitude coordinates, with the address they refer to.
|
5
|
-
geocodings - The polymorphic join table.
|
6
|
-
|
7
|
-
|
8
|
-
Example:
|
9
|
-
./script/generate geocodable_migration add_geocodable_tables
|
10
|
-
|
11
|
-
With 4 existing migrations, this will create an AddGeocodableTables migration in the
|
12
|
-
file db/migrate/5_add_geocodable_tables.rb
|
data/install.rb
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
# Install hook code here
|
data/rails/init.rb
DELETED
@@ -1,382 +0,0 @@
|
|
1
|
-
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
-
require 'shoulda/rails'
|
3
|
-
|
4
|
-
class Vacation < ActiveRecord::Base
|
5
|
-
acts_as_geocodable :normalize_address => true
|
6
|
-
belongs_to :nearest_city, :class_name => 'City', :foreign_key => 'city_id'
|
7
|
-
end
|
8
|
-
|
9
|
-
class City < ActiveRecord::Base
|
10
|
-
acts_as_geocodable :address => {:postal_code => :zip}
|
11
|
-
end
|
12
|
-
|
13
|
-
class ValidatedVacation < ActiveRecord::Base
|
14
|
-
acts_as_geocodable
|
15
|
-
validates_as_geocodable
|
16
|
-
end
|
17
|
-
|
18
|
-
class AddressBlobVacation < ActiveRecord::Base
|
19
|
-
acts_as_geocodable :address => :address, :normalize_address => true
|
20
|
-
end
|
21
|
-
|
22
|
-
class CallbackLocation < ActiveRecord::Base
|
23
|
-
acts_as_geocodable :address => :address
|
24
|
-
after_geocoding :done_geocoding
|
25
|
-
|
26
|
-
def done_geocoding
|
27
|
-
true
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
class ActsAsGeocodableTest < ActiveSupport::TestCase
|
32
|
-
fixtures :vacations, :cities, :geocodes, :geocodings
|
33
|
-
|
34
|
-
# enable Should macros
|
35
|
-
subject { Vacation.new }
|
36
|
-
|
37
|
-
should_have_one :geocoding
|
38
|
-
|
39
|
-
context "geocode" do
|
40
|
-
setup do
|
41
|
-
@location = vacations(:whitehouse)
|
42
|
-
end
|
43
|
-
|
44
|
-
should "be the geocode from the geocoding" do
|
45
|
-
@location.geocode.should == @location.geocoding.geocode
|
46
|
-
end
|
47
|
-
|
48
|
-
should "be nil without a geocoding" do
|
49
|
-
Vacation.new.geocode.should be(nil)
|
50
|
-
end
|
51
|
-
|
52
|
-
end
|
53
|
-
|
54
|
-
context "to_location" do
|
55
|
-
should "return a graticule location" do
|
56
|
-
expected = Graticule::Location.new :street => '1600 Pennsylvania Ave NW',
|
57
|
-
:locality => 'Washington', :region => 'DC', :postal_code => '20502',
|
58
|
-
:country => nil
|
59
|
-
vacations(:whitehouse).to_location.should == expected
|
60
|
-
end
|
61
|
-
|
62
|
-
should "return a graticule location for mapped locations" do
|
63
|
-
cities(:holland).to_location.should == Graticule::Location.new(:postal_code => '49423')
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
context "with address normalization" do
|
68
|
-
setup do
|
69
|
-
Vacation.acts_as_geocodable_options[:normalize_address] = true
|
70
|
-
|
71
|
-
Geocode.geocoder.stubs(:locate).returns(
|
72
|
-
Graticule::Location.new(:locality => "San Clemente", :region => "CA")
|
73
|
-
)
|
74
|
-
end
|
75
|
-
|
76
|
-
should "update address fields with result" do
|
77
|
-
vacation = Vacation.create! :locality => 'sanclemente', :region => 'ca'
|
78
|
-
vacation.locality.should == 'San Clemente'
|
79
|
-
vacation.region.should == 'CA'
|
80
|
-
end
|
81
|
-
|
82
|
-
should "update address blob" do
|
83
|
-
Geocode.geocoder.expects(:locate).returns(
|
84
|
-
Graticule::Location.new(:locality => "Grand Rapids", :region => "MI", :country => "US")
|
85
|
-
)
|
86
|
-
|
87
|
-
vacation = AddressBlobVacation.create! :address => "grand rapids, mi"
|
88
|
-
vacation.address.should == "Grand Rapids, MI US"
|
89
|
-
end
|
90
|
-
|
91
|
-
end
|
92
|
-
|
93
|
-
context "without address normalization" do
|
94
|
-
setup do
|
95
|
-
Vacation.acts_as_geocodable_options[:normalize_address] = false
|
96
|
-
|
97
|
-
Geocode.geocoder.stubs(:locate).returns(
|
98
|
-
Graticule::Location.new(:locality => "Portland", :region => "OR", :postal_code => '97212')
|
99
|
-
)
|
100
|
-
|
101
|
-
@vacation = Vacation.create! :locality => 'portland', :region => 'or'
|
102
|
-
end
|
103
|
-
|
104
|
-
should "not update address attributes" do
|
105
|
-
@vacation.locality.should == 'portland'
|
106
|
-
@vacation.region.should == 'or'
|
107
|
-
end
|
108
|
-
|
109
|
-
should "fill in blank attributes" do
|
110
|
-
@vacation.postal_code.should == '97212'
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
context "with blank location attributes" do
|
115
|
-
should "not create geocode" do
|
116
|
-
Geocode.geocoder.expects(:locate).never
|
117
|
-
assert_no_difference 'Geocode.count + Geocoding.count' do
|
118
|
-
Vacation.create!(:locality => "\n", :region => " ").geocoding.should be(nil)
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
should "destroy existing geocoding" do
|
123
|
-
whitehouse = vacations(:whitehouse)
|
124
|
-
[:name, :street, :locality, :region, :postal_code].each do |attribute|
|
125
|
-
whitehouse.send("#{attribute}=", nil)
|
126
|
-
end
|
127
|
-
assert_difference 'Geocoding.count', -1 do
|
128
|
-
whitehouse.save!
|
129
|
-
end
|
130
|
-
whitehouse.reload
|
131
|
-
whitehouse.geocoding.should be(nil)
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
context "on save" do
|
136
|
-
should "not create geocode without changes" do
|
137
|
-
whitehouse = vacations(:whitehouse)
|
138
|
-
assert_not_nil whitehouse.geocoding
|
139
|
-
original_geocode = whitehouse.geocode
|
140
|
-
|
141
|
-
assert_no_difference 'Geocode.count + Geocoding.count' do
|
142
|
-
whitehouse.save!
|
143
|
-
whitehouse.reload
|
144
|
-
end
|
145
|
-
|
146
|
-
assert_equal original_geocode, whitehouse.geocode
|
147
|
-
end
|
148
|
-
|
149
|
-
end
|
150
|
-
|
151
|
-
context "on save with an existing geocode" do
|
152
|
-
setup do
|
153
|
-
@location = vacations(:saugatuck)
|
154
|
-
@location.attributes = {:locality => 'Beverly Hills', :postal_code => '90210'}
|
155
|
-
end
|
156
|
-
|
157
|
-
should "destroy the old geocoding" do
|
158
|
-
assert_no_difference('Geocoding.count') { @location.save! }
|
159
|
-
end
|
160
|
-
|
161
|
-
should "set the new geocode" do
|
162
|
-
@location.save!
|
163
|
-
@location.geocode.should == geocodes(:beverly_hills)
|
164
|
-
end
|
165
|
-
|
166
|
-
end
|
167
|
-
|
168
|
-
context "validates_as_geocodable" do
|
169
|
-
setup do
|
170
|
-
@model = Class.new(Vacation)
|
171
|
-
@vacation = @model.new :locality => "Grand Rapids", :region => "MI"
|
172
|
-
end
|
173
|
-
|
174
|
-
should "be valid with geocodable address" do
|
175
|
-
@model.validates_as_geocodable
|
176
|
-
assert @vacation.valid?
|
177
|
-
end
|
178
|
-
|
179
|
-
should "be invalid without geocodable address" do
|
180
|
-
@model.validates_as_geocodable
|
181
|
-
Geocode.geocoder.expects(:locate).raises(Graticule::Error)
|
182
|
-
assert !@vacation.valid?
|
183
|
-
assert_equal 1, @vacation.errors.size
|
184
|
-
assert_equal "Address could not be geocoded.", @vacation.errors.on(:base)
|
185
|
-
end
|
186
|
-
|
187
|
-
should "be valid with the same precision" do
|
188
|
-
@model.validates_as_geocodable :precision => :street
|
189
|
-
Geocode.geocoder.expects(:locate).returns(Graticule::Location.new(:precision => 'street'))
|
190
|
-
assert @vacation.valid?
|
191
|
-
end
|
192
|
-
|
193
|
-
should "be valid with a higher precision" do
|
194
|
-
@model.validates_as_geocodable :precision => :region
|
195
|
-
Geocode.geocoder.expects(:locate).returns(Graticule::Location.new(:precision => 'street'))
|
196
|
-
assert @vacation.valid?
|
197
|
-
end
|
198
|
-
|
199
|
-
should "be invalid with a lower precision" do
|
200
|
-
@model.validates_as_geocodable :precision => :street
|
201
|
-
Geocode.geocoder.expects(:locate).returns(Graticule::Location.new(:precision => 'region'))
|
202
|
-
assert !@vacation.valid?
|
203
|
-
assert_equal "Address could not be geocoded.", @vacation.errors.on(:base)
|
204
|
-
end
|
205
|
-
|
206
|
-
should "allow nil" do
|
207
|
-
@model.validates_as_geocodable :allow_nil => true
|
208
|
-
assert @model.new.valid?
|
209
|
-
end
|
210
|
-
|
211
|
-
should "be invalid if block returns false" do
|
212
|
-
@model.validates_as_geocodable(:allow_nil => false) do |geocode|
|
213
|
-
["USA", "US"].include?(geocode.country)
|
214
|
-
end
|
215
|
-
Geocode.geocoder.expects(:locate).returns(Graticule::Location.new(:country => 'CA'))
|
216
|
-
assert !@vacation.valid?
|
217
|
-
end
|
218
|
-
|
219
|
-
should "be valid if block returns true" do
|
220
|
-
@model.validates_as_geocodable(:allow_nil => false) do |geocode|
|
221
|
-
["USA", "US"].include?(geocode.country)
|
222
|
-
end
|
223
|
-
Geocode.geocoder.expects(:locate).returns(Graticule::Location.new(:country => 'US'))
|
224
|
-
|
225
|
-
assert @vacation.valid?
|
226
|
-
end
|
227
|
-
end
|
228
|
-
|
229
|
-
context "find with origin" do
|
230
|
-
should "add distance to result" do
|
231
|
-
Vacation.find(1, :origin => "49406").distance.to_f.should be_close(0.794248231790402, 0.2)
|
232
|
-
end
|
233
|
-
end
|
234
|
-
|
235
|
-
context "find within" do
|
236
|
-
setup do
|
237
|
-
@results = Vacation.find(:all, :origin => 49406, :within => 10)
|
238
|
-
end
|
239
|
-
|
240
|
-
should "find locations within radius" do
|
241
|
-
@results.should include(vacations(:saugatuck))
|
242
|
-
end
|
243
|
-
|
244
|
-
should "add distance to results" do
|
245
|
-
@results.first.distance.to_f.should be_close(0.794248231790402, 0.2)
|
246
|
-
end
|
247
|
-
|
248
|
-
def test_find_within
|
249
|
-
spots = Vacation.find(:all, :origin => "49406", :within => 3)
|
250
|
-
assert_equal 1, spots.size
|
251
|
-
assert_equal vacations(:saugatuck), spots.first
|
252
|
-
end
|
253
|
-
|
254
|
-
def test_count_within
|
255
|
-
spots_count = Vacation.count(:origin => "49406", :within => 3)
|
256
|
-
assert_equal 1, spots_count
|
257
|
-
end
|
258
|
-
|
259
|
-
def test_within_kilometers
|
260
|
-
saugatuck = Vacation.find(:first, :within => 2, :units => :kilometers, :origin => "49406")
|
261
|
-
assert_equal vacations(:saugatuck), saugatuck
|
262
|
-
assert_in_delta 1.27821863, saugatuck.distance, 0.2
|
263
|
-
end
|
264
|
-
|
265
|
-
end
|
266
|
-
|
267
|
-
context "distance_to" do
|
268
|
-
setup do
|
269
|
-
@saugatuck = vacations(:saugatuck)
|
270
|
-
@douglas = Vacation.create!(:name => 'Douglas', :postal_code => '49406')
|
271
|
-
end
|
272
|
-
|
273
|
-
should 'calculate distance from a string' do
|
274
|
-
@douglas.distance_to(geocodes(:saugatuck_geocode).query).should be_close(0.794248231790402, 0.2)
|
275
|
-
end
|
276
|
-
should 'calculate distance from a geocode' do
|
277
|
-
@douglas.distance_to(geocodes(:saugatuck_geocode)).should be_close(0.794248231790402, 0.2)
|
278
|
-
end
|
279
|
-
|
280
|
-
should 'calculate distance from a geocodable model' do
|
281
|
-
@douglas.distance_to(@saugatuck).should be_close(0.794248231790402, 0.2)
|
282
|
-
@saugatuck.distance_to(@douglas).should be_close(0.794248231790402, 0.2)
|
283
|
-
end
|
284
|
-
|
285
|
-
should 'calculate distance in default miles' do
|
286
|
-
@douglas.distance_to(@saugatuck, :units => :miles).should be_close(0.794248231790402, 0.2)
|
287
|
-
end
|
288
|
-
|
289
|
-
should 'calculate distance in default kilometers' do
|
290
|
-
@douglas.distance_to(@saugatuck, :units => :kilometers).should be_close(1.27821863, 0.2)
|
291
|
-
end
|
292
|
-
|
293
|
-
should 'return nil with invalid geocode' do
|
294
|
-
@douglas.distance_to(Geocode.new).should be(nil)
|
295
|
-
@douglas.distance_to(nil).should be(nil)
|
296
|
-
end
|
297
|
-
|
298
|
-
end
|
299
|
-
|
300
|
-
def test_find_beyond
|
301
|
-
spots = Vacation.find(:all, :origin => "49406", :beyond => 3)
|
302
|
-
assert_equal 1, spots.size
|
303
|
-
assert_equal vacations(:whitehouse), spots.first
|
304
|
-
end
|
305
|
-
|
306
|
-
def test_count_beyond
|
307
|
-
spots = Vacation.count(:origin => "49406", :beyond => 3)
|
308
|
-
assert_equal 1, spots
|
309
|
-
end
|
310
|
-
|
311
|
-
def test_find_beyond_in_kilometers
|
312
|
-
whitehouse = Vacation.find(:first, :beyond => 3, :units => :kilometers, :origin => "49406")
|
313
|
-
assert_equal vacations(:whitehouse), whitehouse
|
314
|
-
assert_in_delta 877.554975851074, whitehouse.distance, 1
|
315
|
-
end
|
316
|
-
|
317
|
-
def test_find_nearest
|
318
|
-
assert_equal vacations(:saugatuck), Vacation.find(:nearest, :origin => "49406")
|
319
|
-
end
|
320
|
-
|
321
|
-
def test_find_nearest
|
322
|
-
assert_equal vacations(:whitehouse), Vacation.find(:farthest, :origin => "49406")
|
323
|
-
end
|
324
|
-
|
325
|
-
def test_find_nearest_with_include_raises_error
|
326
|
-
assert_raises(ArgumentError) { Vacation.find(:nearest, :origin => '49406', :include => :nearest_city) }
|
327
|
-
end
|
328
|
-
|
329
|
-
def test_uses_units_set_in_declared_options
|
330
|
-
Vacation.acts_as_geocodable_options.merge! :units => :kilometers
|
331
|
-
saugatuck = Vacation.find(:first, :within => 2, :units => :kilometers, :origin => "49406")
|
332
|
-
assert_in_delta 1.27821863, saugatuck.distance, 0.2
|
333
|
-
end
|
334
|
-
|
335
|
-
def test_find_with_order
|
336
|
-
expected = [vacations(:saugatuck), vacations(:whitehouse)]
|
337
|
-
actual = Vacation.find(:all, :origin => '49406', :order => 'distance')
|
338
|
-
assert_equal expected, actual
|
339
|
-
end
|
340
|
-
|
341
|
-
def test_location_to_geocode_nil
|
342
|
-
assert_nil Vacation.send(:location_to_geocode, nil)
|
343
|
-
end
|
344
|
-
|
345
|
-
def test_location_to_geocode_with_geocode
|
346
|
-
g = Geocode.new
|
347
|
-
assert(g === Vacation.send(:location_to_geocode, g))
|
348
|
-
end
|
349
|
-
|
350
|
-
def test_location_to_geocode_with_string
|
351
|
-
assert_equal geocodes(:douglas), Vacation.send(:location_to_geocode, '49406')
|
352
|
-
end
|
353
|
-
|
354
|
-
def test_location_to_geocode_with_fixnum
|
355
|
-
assert_equal geocodes(:douglas), Vacation.send(:location_to_geocode, 49406)
|
356
|
-
end
|
357
|
-
|
358
|
-
def test_location_to_geocode_with_geocodable
|
359
|
-
assert_equal geocodes(:white_house_geocode),
|
360
|
-
Vacation.send(:location_to_geocode, vacations(:whitehouse))
|
361
|
-
end
|
362
|
-
|
363
|
-
def test_find_nearest_raises_error_with_include
|
364
|
-
assert_raises(ArgumentError) { Vacation.find(:nearest, :include => :nearest_city, :origin => 49406) }
|
365
|
-
end
|
366
|
-
|
367
|
-
def test_callback_after_geocoding
|
368
|
-
location = CallbackLocation.new :address => "Holland, MI"
|
369
|
-
assert_nil location.geocoding
|
370
|
-
location.expects(:done_geocoding).once.returns(true)
|
371
|
-
assert location.save!
|
372
|
-
end
|
373
|
-
|
374
|
-
def test_does_not_run_the_callback_after_geocoding_if_object_dont_change
|
375
|
-
location = CallbackLocation.create(:address => "Holland, MI")
|
376
|
-
assert_not_nil location.geocoding
|
377
|
-
location.expects(:done_geocoding).never
|
378
|
-
assert location.save!
|
379
|
-
end
|
380
|
-
|
381
|
-
|
382
|
-
end
|
data/test/db/database.yml
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
sqlite:
|
2
|
-
:adapter: sqlite
|
3
|
-
:dbfile: acts_as_geocodable_plugin.sqlite.db
|
4
|
-
sqlite3:
|
5
|
-
:adapter: sqlite3
|
6
|
-
:dbfile: acts_as_geocodable_plugin.sqlite3.db
|
7
|
-
postgresql:
|
8
|
-
:adapter: postgresql
|
9
|
-
:username: postgres
|
10
|
-
:password: postgres
|
11
|
-
:database: acts_as_geocodable_plugin_test
|
12
|
-
:min_messages: ERROR
|
13
|
-
mysql:
|
14
|
-
:adapter: mysql
|
15
|
-
:host: localhost
|
16
|
-
:username: root
|
17
|
-
:password:
|
18
|
-
:database: acts_as_geocodable_plugin_test
|
data/test/db/schema.rb
DELETED
@@ -1,60 +0,0 @@
|
|
1
|
-
ActiveRecord::Schema.define(:version => 1) do
|
2
|
-
|
3
|
-
create_table "geocodes", :force => true do |t|
|
4
|
-
t.column "latitude", :decimal, :precision => 15, :scale => 12
|
5
|
-
t.column "longitude", :decimal, :precision => 15, :scale => 12
|
6
|
-
t.column "query", :string
|
7
|
-
t.column "street", :string
|
8
|
-
t.column "locality", :string
|
9
|
-
t.column "region", :string
|
10
|
-
t.column "postal_code", :string
|
11
|
-
t.column "country", :string
|
12
|
-
end
|
13
|
-
|
14
|
-
add_index "geocodes", ["query"], :name => "geocodes_query_index", :unique => true
|
15
|
-
add_index "geocodes", ["latitude"], :name => "geocodes_latitude_index"
|
16
|
-
add_index "geocodes", ["longitude"], :name => "geocodes_longitude_index"
|
17
|
-
|
18
|
-
create_table "geocodings", :force => true do |t|
|
19
|
-
t.column "geocodable_id", :integer
|
20
|
-
t.column "geocode_id", :integer
|
21
|
-
t.column "geocodable_type", :string
|
22
|
-
end
|
23
|
-
|
24
|
-
add_index "geocodings", ["geocodable_id"], :name => "geocodings_geocodable_id_index"
|
25
|
-
add_index "geocodings", ["geocode_id"], :name => "geocodings_geocode_id_index"
|
26
|
-
add_index "geocodings", ["geocodable_type"], :name => "geocodings_geocodable_type_index"
|
27
|
-
|
28
|
-
create_table "vacations", :force => true do |t|
|
29
|
-
t.column "name", :string
|
30
|
-
t.column "street", :string
|
31
|
-
t.column "locality", :string
|
32
|
-
t.column "region", :string
|
33
|
-
t.column "postal_code", :string
|
34
|
-
t.column "city_id", :integer
|
35
|
-
end
|
36
|
-
|
37
|
-
create_table "validated_vacations", :force => true do |t|
|
38
|
-
t.column "name", :string
|
39
|
-
t.column "street", :string
|
40
|
-
t.column "locality", :string
|
41
|
-
t.column "region", :string
|
42
|
-
t.column "postal_code", :string
|
43
|
-
end
|
44
|
-
|
45
|
-
create_table "address_blob_vacations", :force => true do |t|
|
46
|
-
t.column "name", :string
|
47
|
-
t.column "address", :string
|
48
|
-
end
|
49
|
-
|
50
|
-
create_table "callback_locations", :force => true do |t|
|
51
|
-
t.column "name", :string
|
52
|
-
t.column "address", :string
|
53
|
-
end
|
54
|
-
|
55
|
-
create_table "cities", :force => true do |t|
|
56
|
-
t.column "name", :string
|
57
|
-
t.column "zip", :string
|
58
|
-
end
|
59
|
-
|
60
|
-
end
|