strava-api 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.rdoc +221 -0
- data/Rakefile +56 -0
- data/lib/strava-api.rb +22 -0
- data/lib/strava-api/base.rb +37 -0
- data/lib/strava-api/bike.rb +8 -0
- data/lib/strava-api/club.rb +17 -0
- data/lib/strava-api/clubs.rb +28 -0
- data/lib/strava-api/effort.rb +50 -0
- data/lib/strava-api/efforts.rb +9 -0
- data/lib/strava-api/exceptions.rb +9 -0
- data/lib/strava-api/hash_based_store.rb +52 -0
- data/lib/strava-api/member.rb +8 -0
- data/lib/strava-api/ride.rb +55 -0
- data/lib/strava-api/rides.rb +39 -0
- data/lib/strava-api/segment.rb +36 -0
- data/lib/strava-api/segments.rb +39 -0
- data/test/helper.rb +133 -0
- data/test/test_base.rb +40 -0
- data/test/test_club.rb +45 -0
- data/test/test_clubs.rb +87 -0
- data/test/test_effort.rb +31 -0
- data/test/test_hash_based_store.rb +79 -0
- data/test/test_ride.rb +49 -0
- data/test/test_rides.rb +186 -0
- data/test/test_segment.rb +63 -0
- data/test/test_segments.rb +166 -0
- data/test/test_strava.rb +36 -0
- metadata +136 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Steven Chanin
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,221 @@
|
|
1
|
+
= strava-api
|
2
|
+
|
3
|
+
Strava (http://www.strava.com/) allows access to it's data via a JSON api. This gem wraps that API an allows you to interact with Ruby classes instead.
|
4
|
+
|
5
|
+
= Installation
|
6
|
+
|
7
|
+
gem install strava-api
|
8
|
+
|
9
|
+
depending on your installation, you may need to use sudo
|
10
|
+
|
11
|
+
= Requirements
|
12
|
+
|
13
|
+
httparty [http://github.com/jnunemaker/httparty]
|
14
|
+
|
15
|
+
mocha (only if you want to run the tests) [http://github.com/floehopper/mocha]
|
16
|
+
|
17
|
+
= Usage
|
18
|
+
StravaApi is a Ruby wrapper for working with Strava API.
|
19
|
+
|
20
|
+
== Loading the StravaApi gem classes
|
21
|
+
Before you can use the gem's classes and methods, you have to load them
|
22
|
+
|
23
|
+
in a Rails app
|
24
|
+
require 'strava-api'
|
25
|
+
|
26
|
+
in a plain irb session
|
27
|
+
require 'rubygems'
|
28
|
+
require 'strava-api'
|
29
|
+
|
30
|
+
== Creating a StravaApi::Base object
|
31
|
+
In order to interact with the Strava API, you need a StravaApi::Base object
|
32
|
+
|
33
|
+
s = StravaApi::Base.new
|
34
|
+
|
35
|
+
== Clubs
|
36
|
+
The Strava API allows you to search for clubs by name,
|
37
|
+
|
38
|
+
#returns an array of StravaApi::Club objects (each object only has minimal attributes)
|
39
|
+
s.clubs("part of club name")
|
40
|
+
|
41
|
+
get full details on a club,
|
42
|
+
|
43
|
+
#returns a StravaApi::Club with full attributes
|
44
|
+
s.club_show(club_id)
|
45
|
+
|
46
|
+
and see the members of a club
|
47
|
+
|
48
|
+
#returns an array of StravaApi::Member objects
|
49
|
+
s.club_members(club_id)
|
50
|
+
|
51
|
+
Alternatively, you can work in a more object oriented way. The .clubs("part of club name") method returns an array of StravaApi::Club objects. The StravaApi::Club object makes club_show and club_members available as instance methods
|
52
|
+
|
53
|
+
my_club = s.clubs("Fred's Riders").first
|
54
|
+
my_club.show
|
55
|
+
my_club.members
|
56
|
+
|
57
|
+
== Rides
|
58
|
+
Using the API, you can search for rides using any combination of criteria
|
59
|
+
|
60
|
+
* club_id: Optional. Id of the Club for which to search for member's Rides.
|
61
|
+
* athlete_id: Optional. Id of the Athlete for which to search for Rides.
|
62
|
+
* athlete_name: Optional. Username of the Athlete for which to search for Rides.
|
63
|
+
* start_date: Optional. Day on which to start search for Rides. The date should be formatted YYYY-MM-DD. The date is the local time of when the ride started.
|
64
|
+
* end_date: Optional. Day on which to end search for Rides. The date should be formatted YYYY-MM-DD. The date is the local time of when the ride started.
|
65
|
+
* start_id: Optional. Only return Rides with an Id greater than or equal to the startId.
|
66
|
+
* offset: Optional. Rather than returning the first 50 matching rides, return the rides <offset> from the top of the results.
|
67
|
+
|
68
|
+
To get set of rides (by club),
|
69
|
+
|
70
|
+
#returns an array of up to 50 StravaApi::Ride objects (each object has minimal attributes)
|
71
|
+
s.rides(:club_id => club_id)
|
72
|
+
|
73
|
+
by athlete and start date
|
74
|
+
|
75
|
+
s.rides(:athlete_id => athlete_id, :start_date => Date.civil(2010,9,21))
|
76
|
+
|
77
|
+
etc
|
78
|
+
|
79
|
+
To see details on a particular ride
|
80
|
+
|
81
|
+
#returns a StravaApi::Ride with full attributes
|
82
|
+
s.ride_show(ride_id)
|
83
|
+
|
84
|
+
A ride is composed of a list of efforts (time, etc spent on each segment which makes up part of the ride). To see the efforts for a particular ride,
|
85
|
+
|
86
|
+
#returns an array of StravaApi::Effort objects (each object has minimal attributes)
|
87
|
+
s.ride_efforts(ride_id)
|
88
|
+
|
89
|
+
Alternatively, you can work in a more object oriented way. The .rides(:filter => value) method returns an array of StravaApi::Ride objects. The StravaApi::Ride object makes ride_show and ride_efforts available as instance methods
|
90
|
+
|
91
|
+
my_ride = s.rides(:club_id => my_club.id).first
|
92
|
+
my_ride.show
|
93
|
+
my_ride.efforts
|
94
|
+
|
95
|
+
=== When more than 50 rides match your criteria
|
96
|
+
In response to a .rides call, Strava will return the first 50 rides that match whatever criteria are supplied. To see all matching rides, you will need to call .rides and also include an :offset parameter
|
97
|
+
|
98
|
+
s.rides(:club_id => club_id, :offset => 50)
|
99
|
+
|
100
|
+
In order to see all the rides, you will have to nest your .rides call inside of some sort of loop that updates offset after processing each block of rides
|
101
|
+
|
102
|
+
offset = 0
|
103
|
+
while (rides = s.rides(:club_id => club_id, :offset => offset, :start_date => Date.civil(2010,9,15))) && !rides.empty? do
|
104
|
+
#work on that set of rides
|
105
|
+
offset += rides.size
|
106
|
+
end
|
107
|
+
puts "received a total of #{offset} rides"
|
108
|
+
|
109
|
+
== Segments
|
110
|
+
Rides can be thought of as a sequence of segments (e.g. my "trip to the kitchen" is "up the stairs" + "down the hall" + "across the living room"). When rides are uploaded to Strava, it breaks them down into the segments they contain based on the route taken. Segments have names, measurements, and keep statistics about who rode them on which date, how long it took, etc.
|
111
|
+
|
112
|
+
To find a segment by name,
|
113
|
+
|
114
|
+
#returns an array of StravaApi::Segment objects (each object has minimal attributes)
|
115
|
+
s.segments("part of segment name")
|
116
|
+
|
117
|
+
to see measurements / details on a segment,
|
118
|
+
|
119
|
+
#returns a StravaApi::Segment with full attributes
|
120
|
+
s.segment_show(segment_id)
|
121
|
+
|
122
|
+
to see who rode a segment and how they did,
|
123
|
+
|
124
|
+
#returns an array of up to 50 StravaApi::Effort objects (each object has minimal attributes)
|
125
|
+
s.segment_efforts(segment_id, {:param => value})
|
126
|
+
|
127
|
+
To refine the data returned by segment_efforts, you can pass any combination of optional parameters:
|
128
|
+
|
129
|
+
* club_id: Optional. Id of the Club for which to search for member's Efforts.
|
130
|
+
* athlete_id: Optional. Id of the Athlete for which to search for Efforts.
|
131
|
+
* athlete_name: Optional. Username of the Athlete for which to search for Rides.
|
132
|
+
* start_date: Optional. Day on which to start search for Efforts. The date should be formatted YYYY-MM-DD. The date is the local time of when the effort started.
|
133
|
+
* end_date: Optional. Day on which to end search for Efforts. The date should be formatted YYYY-MM-DD. The date is the local time of when the effort started.
|
134
|
+
* start_id: Optional. Only return Effforts with an Id greater than or equal to the startId.
|
135
|
+
* best: Optional. Default is true. Whether to only show an athlete's best effort (shortest elapsed time) vs. all their efforts if there are multiple matching effort for a single athlete.
|
136
|
+
* offset: Optional. Rather than returning the first 50 matching rides, return the rides <offset> from the top of the results.
|
137
|
+
|
138
|
+
Example
|
139
|
+
|
140
|
+
#returns the rides on that segment by members of a specific club on or after 7/1/10.
|
141
|
+
#only shows each riders best time
|
142
|
+
s.segment_efforts(segment_id, :club_id => club_id, :start_date => Date.civil(2010,7,1), :best => true)
|
143
|
+
|
144
|
+
Alternatively, you can work in a more object oriented way. Given a StravaApi::Segment object returned by another method call, you can get it's full details and the efforts made on that segment using the .show and .efforts instance methods
|
145
|
+
|
146
|
+
#my_segment is a StravaApi::Segment
|
147
|
+
|
148
|
+
my_segment.show
|
149
|
+
|
150
|
+
my_segment.efforts(:athlete_id => 123)
|
151
|
+
|
152
|
+
=== When more than 50 efforts match your criteria
|
153
|
+
In response to a .segment_efforts call, Strava will return the first 50 efforts that match whatever criteria are supplied. To see all matching efforts, you will need to call .segment_efforts and also include an :offset parameter.
|
154
|
+
|
155
|
+
See the instructions above for an example of how this works with .rides.
|
156
|
+
|
157
|
+
== Efforts
|
158
|
+
To get full details on an effort (i.e. specifics on how a rider did on a specific stretch of a ride), you
|
159
|
+
|
160
|
+
##returns a StravaApi::Effort with full attributes
|
161
|
+
s.effort_show(effort_id)
|
162
|
+
|
163
|
+
If you have an efforts object (StravaApi::Effort) that has been returned by another call (for example my_ride.efforts), you can use the .show method to fill out the missing details on that effort
|
164
|
+
|
165
|
+
my_ride.efforts.first.show
|
166
|
+
|
167
|
+
== Error Handling
|
168
|
+
The StravaApi gem raises errors under various conditions.
|
169
|
+
|
170
|
+
If an error is raised, check .errors on your StravaApi::Base object to see more details
|
171
|
+
|
172
|
+
s.errors
|
173
|
+
|
174
|
+
=== StravaApi::NetworkError
|
175
|
+
This is raised when the attempt to connect to Strava via httparty raises any of the following:
|
176
|
+
* HTTParty::UnsupportedFormat
|
177
|
+
* HTTParty::UnsupportedURIScheme
|
178
|
+
* HTTParty::ResponseError
|
179
|
+
* HTTParty::RedirectionTooDeep
|
180
|
+
|
181
|
+
=== StravaApi::InvalidResponseError
|
182
|
+
This is raised is the call to Strava returns a result describing an error rather than the data you requested.
|
183
|
+
|
184
|
+
=== StravaApi::CommandError
|
185
|
+
This is raised if your request causes Strava to return a 500 Error.
|
186
|
+
This is also raised if you attempt to run a command that lacks required parameters. For example, trying to call
|
187
|
+
.clubs("")
|
188
|
+
with an empty string or
|
189
|
+
.rides()
|
190
|
+
with no options will raise this error.
|
191
|
+
|
192
|
+
=== StravaApi::InternalErrorInternalError
|
193
|
+
This is raised if you attempt to access an invalid property of a result. For example,
|
194
|
+
|
195
|
+
my_seg = s.segment_show(segment_id)
|
196
|
+
# => returns a <StravaApi::Segment>
|
197
|
+
|
198
|
+
my_seg.bogus_property
|
199
|
+
|
200
|
+
will raise a StravaApi::InternalErrorInternalError
|
201
|
+
|
202
|
+
|
203
|
+
this returns distance, maximum speed, average speed, etc, etc for that part of the ride.
|
204
|
+
|
205
|
+
= Contributors
|
206
|
+
|
207
|
+
StravaApi is maintained by {Steve Chanin}[http://devleverage.com].
|
208
|
+
|
209
|
+
== Note on Patches/Pull Requests
|
210
|
+
|
211
|
+
* Fork the project.
|
212
|
+
* Make your feature addition or bug fix.
|
213
|
+
* Add tests for it. This is important so I don't break it in a
|
214
|
+
future version unintentionally.
|
215
|
+
* Commit, do not mess with rakefile, version, or history.
|
216
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
217
|
+
* Send me a pull request. Bonus points for topic branches.
|
218
|
+
|
219
|
+
== Copyright
|
220
|
+
|
221
|
+
Copyright (c) 2010 Steven Chanin. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "strava-api"
|
8
|
+
gem.summary = %Q{Provides a Ruby interface to the Strava api}
|
9
|
+
gem.description = %Q{Strava (http://www.strava.com/) allows access to it's data via a JSON api. This gem wraps that API an allows you to interact with Ruby classes instead.}
|
10
|
+
gem.email = "schanin@devleverage.com"
|
11
|
+
gem.homepage = "http://github.com/stevenchanin/strava-api"
|
12
|
+
gem.authors = ["Steven Chanin"]
|
13
|
+
#not sure why files wasn't picking up subdirectors of lib when it seems to do so for hominid...
|
14
|
+
gem.files = FileList['{lib,test}/**/*'] + %w(CHANGELOG.rdoc init.rb LICENSE Rakefile README.rdoc) - FileList['test/*.log']
|
15
|
+
gem.add_dependency "httparty", " ~> 0.6.1"
|
16
|
+
gem.add_dependency "mocha", " ~> 0.9.8"
|
17
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
18
|
+
end
|
19
|
+
Jeweler::GemcutterTasks.new
|
20
|
+
rescue LoadError
|
21
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
22
|
+
end
|
23
|
+
|
24
|
+
require 'rake/testtask'
|
25
|
+
Rake::TestTask.new(:test) do |test|
|
26
|
+
test.libs << 'lib' << 'test'
|
27
|
+
test.pattern = 'test/**/test_*.rb'
|
28
|
+
test.verbose = true
|
29
|
+
end
|
30
|
+
|
31
|
+
begin
|
32
|
+
require 'rcov/rcovtask'
|
33
|
+
Rcov::RcovTask.new do |test|
|
34
|
+
test.libs << 'test'
|
35
|
+
test.pattern = 'test/**/test_*.rb'
|
36
|
+
test.verbose = true
|
37
|
+
end
|
38
|
+
rescue LoadError
|
39
|
+
task :rcov do
|
40
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
task :test => :check_dependencies
|
45
|
+
|
46
|
+
task :default => :test
|
47
|
+
|
48
|
+
require 'rake/rdoctask'
|
49
|
+
Rake::RDocTask.new do |rdoc|
|
50
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
51
|
+
|
52
|
+
rdoc.rdoc_dir = 'rdoc'
|
53
|
+
rdoc.title = "strava-api #{version}"
|
54
|
+
rdoc.rdoc_files.include('README*')
|
55
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
56
|
+
end
|
data/lib/strava-api.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require 'strava-api/exceptions'
|
3
|
+
|
4
|
+
#classes used to hold results from Strava
|
5
|
+
require 'strava-api/hash_based_store'
|
6
|
+
require 'strava-api/club'
|
7
|
+
require 'strava-api/member'
|
8
|
+
require 'strava-api/bike'
|
9
|
+
require 'strava-api/ride'
|
10
|
+
require 'strava-api/segment'
|
11
|
+
require 'strava-api/effort'
|
12
|
+
|
13
|
+
module StravaApi
|
14
|
+
#everything now in independent class files
|
15
|
+
end
|
16
|
+
|
17
|
+
#classes to perform network access to Strava
|
18
|
+
require 'strava-api/clubs'
|
19
|
+
require 'strava-api/rides'
|
20
|
+
require 'strava-api/segments'
|
21
|
+
require 'strava-api/efforts'
|
22
|
+
require 'strava-api/base'
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module StravaApi
|
2
|
+
class Base
|
3
|
+
include HTTParty
|
4
|
+
|
5
|
+
include StravaApi::Clubs
|
6
|
+
include StravaApi::Rides
|
7
|
+
include StravaApi::Segments
|
8
|
+
include StravaApi::Efforts
|
9
|
+
|
10
|
+
format :json
|
11
|
+
base_uri 'www.strava.com/api/v1'
|
12
|
+
|
13
|
+
attr_reader :errors
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@errors = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(command, key, options)
|
20
|
+
begin
|
21
|
+
result = self.class.get("/#{command}", :query => options)
|
22
|
+
rescue HTTParty::UnsupportedFormat, HTTParty::UnsupportedURIScheme, HTTParty::ResponseError, HTTParty::RedirectionTooDeep
|
23
|
+
raise NetworkError.new
|
24
|
+
end
|
25
|
+
|
26
|
+
if result && result.parsed_response == "<html><body><h1>500 Internal Server Error</h1></body></html>"
|
27
|
+
@errors << "Strava returned a 500 error"
|
28
|
+
raise CommandError.new
|
29
|
+
end
|
30
|
+
|
31
|
+
@errors << result["error"] if result && result["error"]
|
32
|
+
raise InvalidResponseError.new if result.nil? || !result["error"].blank? || result[key].nil?
|
33
|
+
|
34
|
+
result
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module StravaApi
|
2
|
+
class Club < HashBasedStore
|
3
|
+
ATTRIBUTE_MAP = {'name' => :name, 'id' => :id, 'description' => :description, 'location' => :location }
|
4
|
+
def initialize(connection, options = {})
|
5
|
+
super(connection, ATTRIBUTE_MAP, {}, options)
|
6
|
+
end
|
7
|
+
|
8
|
+
def show
|
9
|
+
self.merge(@connection.club_show(self.id))
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
13
|
+
def members
|
14
|
+
@connection.club_members(self.id)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module StravaApi
|
2
|
+
module Clubs
|
3
|
+
#returns all clubs, don't need an offset
|
4
|
+
def clubs(name)
|
5
|
+
raise StravaApi::CommandError if name.blank?
|
6
|
+
|
7
|
+
name = name.strip
|
8
|
+
raise StravaApi::CommandError if name.empty?
|
9
|
+
|
10
|
+
result = call("clubs", "clubs", {:name => name})
|
11
|
+
|
12
|
+
result["clubs"].collect {|item| Club.new(self, item)}
|
13
|
+
end
|
14
|
+
|
15
|
+
def club_show(id)
|
16
|
+
result = call("clubs/#{id}", "club", {})
|
17
|
+
|
18
|
+
Club.new(self, result["club"])
|
19
|
+
end
|
20
|
+
|
21
|
+
#returns all members, don't need an offset
|
22
|
+
def club_members(id)
|
23
|
+
result = call("clubs/#{id}/members", "members", {})
|
24
|
+
|
25
|
+
result["members"].collect {|item| Member.new(self, item)}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module StravaApi
|
2
|
+
# {
|
3
|
+
# "id"=>688432,
|
4
|
+
# "elapsedTime"=>598,
|
5
|
+
# "segment"=>{"name"=>"Panoramic to Pan Toll","id"=>156},
|
6
|
+
#
|
7
|
+
# "athlete"=>{"name"=>"Julian Bill", "username"=>"jbill", "id"=>1139},
|
8
|
+
# "averageSpeed"=>14317.7658862876,
|
9
|
+
# "startDate"=>"2010-02-28T18:10:07Z",
|
10
|
+
# "timeZoneOffset"=>-8.0,
|
11
|
+
# "maximumSpeed"=>18894.384,
|
12
|
+
# "averageWatts"=>287.765,
|
13
|
+
# "elevationGain"=>151.408,
|
14
|
+
# "ride"=>{"name"=>"02/28/10 San Francisco, CA", "id"=>77563},
|
15
|
+
# "movingTime"=>598,
|
16
|
+
# "distance"=>2344.82,
|
17
|
+
#
|
18
|
+
# "rank" => 1
|
19
|
+
# }
|
20
|
+
class Effort < HashBasedStore
|
21
|
+
ATTRIBUTE_MAP = {
|
22
|
+
"id" => :id,
|
23
|
+
"elapsedTime" => :elapsed_time,
|
24
|
+
"segment" => :segment,
|
25
|
+
|
26
|
+
"athlete" => :athlete,
|
27
|
+
"averageSpeed" => :average_speed,
|
28
|
+
"startDate" => :start_date,
|
29
|
+
"timeZoneOffset" => :time_zone_offset,
|
30
|
+
"maximumSpeed" => :maximum_speed,
|
31
|
+
"averageWatts"=> :average_watts,
|
32
|
+
"elevationGain"=> :elevation_gain,
|
33
|
+
"ride" => :ride,
|
34
|
+
"movingTime" => :moving_time,
|
35
|
+
"distance"=> :distance,
|
36
|
+
"rank" => :rank
|
37
|
+
}
|
38
|
+
|
39
|
+
NESTED_CLASS_MAP = { :segment => Segment, :athlete => Member, :ride => Ride }
|
40
|
+
|
41
|
+
def initialize(connection, options = {})
|
42
|
+
super(connection, ATTRIBUTE_MAP, NESTED_CLASS_MAP, options)
|
43
|
+
end
|
44
|
+
|
45
|
+
def show
|
46
|
+
self.merge(@connection.effort_show(self.id))
|
47
|
+
self
|
48
|
+
end
|
49
|
+
end #class Effort
|
50
|
+
end
|