Active 0.0.14 → 0.0.17

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.
@@ -4,6 +4,7 @@ require 'cgi'
4
4
  require 'rubygems'
5
5
  require 'mysql'
6
6
  require 'active_record'
7
+ require 'digest/md5'
7
8
 
8
9
  module Active
9
10
  module Services
@@ -30,7 +31,7 @@ module Active
30
31
 
31
32
  class Search
32
33
  attr_accessor :api_key, :start_date, :end_date, :location, :channels, :keywords, :search, :radius, :limit, :sort, :page, :offset, :latitude, :longitude,
33
- :view, :facet, :sort, :num_results, :asset_ids, :dma
34
+ :view, :facet, :sort, :num_results, :asset_ids, :dma, :city, :state, :country, :bounding_box
34
35
 
35
36
  attr_reader :results, :endIndex, :pageSize, :searchTime, :numberOfResults, :end_point, :meta
36
37
 
@@ -39,11 +40,11 @@ module Active
39
40
 
40
41
  def initialize(data={})
41
42
  self.api_key = data[:api_key] || ""
42
- self.location = data[:location] || ""
43
+ self.location = data[:location]
43
44
  self.zips = data[:zips] || []
44
45
  self.channels = data[:channels] || []
45
46
  self.keywords = data[:keywords] || []
46
- self.radius = data[:radius] || "50"
47
+ self.radius = data[:radius] || nil
47
48
  self.limit = data[:limit] || "10"
48
49
  self.sort = data[:sort] || Sort.DATE_ASC
49
50
  self.page = data[:page] || "1"
@@ -59,6 +60,10 @@ module Active
59
60
  self.latitude = data[:latitude]
60
61
  self.longitude = data[:longitude]
61
62
  self.dma = data[:dma]
63
+ self.city = data[:city]
64
+ self.state = data[:state]
65
+ self.country = data[:country]
66
+ self.bounding_box = data[:bounding_box]
62
67
  end
63
68
 
64
69
  # Example
@@ -78,6 +83,7 @@ module Active
78
83
  end
79
84
 
80
85
  def location=(value)
86
+ return if value.nil?
81
87
  @location = CGI.escape(value)
82
88
  end
83
89
 
@@ -100,6 +106,16 @@ module Active
100
106
  def asset_ids=(value)
101
107
  @asset_ids = value
102
108
  end
109
+
110
+ def bounding_box=(value)
111
+ return if value==nil
112
+ value = HashWithIndifferentAccess.new(value)
113
+ if value.has_key?("sw") && value.has_key?("ne")
114
+ @bounding_box=value
115
+ else
116
+ raise "bounding_box must be hash with keys sw and ne"
117
+ end
118
+ end
103
119
 
104
120
  def asset_id=(value)
105
121
  return if value.empty?
@@ -107,7 +123,8 @@ module Active
107
123
  end
108
124
 
109
125
  def end_point
110
- meta_data = ""
126
+ meta_data = ""
127
+ loc_str = ""
111
128
  # CHANNELS
112
129
  channel_keys = []
113
130
  @channels.each do |c|
@@ -127,16 +144,27 @@ module Active
127
144
  meta_data += temp_ids.join("+OR+")
128
145
  end
129
146
  # LOCATION
130
- # 1 Look for zip codes
131
- # 2 Look for lat lng
132
- # 3 Look for a formatted string "San Diego, CA, US"
133
- # 4 Look for a dma
134
- if not @zips.empty?
147
+ # 1 Look for :city, :state, :country
148
+ # 2 Look for zip codes
149
+ # 3 Look for lat lng
150
+ # 4 Look for a formatted string "San Diego, CA, US"
151
+ # 5 Look for a dma
152
+ if @city or @state or @country
153
+ if @city
154
+ meta_data += "+AND+" unless meta_data == ""
155
+ meta_data += "meta:city=#{Search.double_encode_channel(@city)}"
156
+ end
157
+ if @state
158
+ meta_data += "+AND+" unless meta_data == ""
159
+ meta_data += "meta:state=#{Search.double_encode_channel(@state)}"
160
+ end
161
+ elsif !@zips.empty?
162
+ # if not @zips.empty?
135
163
  loc_str = @zips.join(",")
136
164
  elsif @latitude and @longitude
137
165
  loc_str = "#{@latitude};#{@longitude}"
138
166
  elsif @dma
139
- loc_str = ""
167
+
140
168
  meta_data += "+AND+" unless meta_data == ""
141
169
  meta_data += "meta:dma=#{Search.double_encode_channel(@dma)}"
142
170
  else
@@ -154,7 +182,42 @@ module Active
154
182
  end
155
183
  meta_data += "meta:startDate:daterange:#{@start_date}..#{@end_date}"
156
184
 
157
- url = "#{SEARCH_URL}/search?api_key=#{@api_key}&num=#{@num_results}&page=#{@page}&l=#{loc_str}&f=#{@facet}&v=#{@view}&r=#{@radius}&s=#{@sort}&k=#{@keywords.join("+")}&m=#{meta_data}"
185
+ # BOUNDING BOX
186
+ if @bounding_box!=nil
187
+ #The values in the GSA metadata are shifted to prevent negative values. This was done b/c lat/long
188
+ # are searched as a number range and the GSA doesn't allow negative values in number ranges.
189
+ # We shift latitude values by 90 and longitude values by 180.
190
+
191
+ if @bounding_box[:sw].class==String
192
+ #String :bounding_box => { :sw => "37.695141,-123.013657", :ne => "37.832371,-122.356979"}
193
+ latitude1 = @bounding_box[:sw].split(",").first.to_f+90
194
+ latitude2 = @bounding_box[:ne].split(",").first.to_f+90
195
+ longitude1 = @bounding_box[:sw].split(",").last.to_f+180
196
+ longitude2 = @bounding_box[:ne].split(",").last.to_f+180
197
+ else
198
+ #hash query[:bounding_box] = { :sw => [123, 10], :ne => [222,10] }
199
+ latitude1 = @bounding_box[:sw].first.to_f+90
200
+ latitude2 = @bounding_box[:ne].first.to_f+90
201
+ longitude1 = @bounding_box[:sw].last.to_f+180
202
+ longitude2 = @bounding_box[:ne].last.to_f+180
203
+ end
204
+ meta_data += "+AND+" unless meta_data == ""
205
+ meta_data += "meta:latitudeShifted:#{latitude1}..#{latitude2}+AND+meta:longitudeShifted:#{longitude1}..#{longitude2}"
206
+ end
207
+
208
+ # url = "#{SEARCH_URL}/search?api_key=#{@api_key}&num=#{@num_results}&page=#{@page}&l=#{loc_str}&f=#{@facet}&v=#{@view}&r=#{@radius}&s=#{@sort}&k=#{@keywords.join("+")}&m=#{meta_data}"
209
+ urla = ["#{SEARCH_URL}/search?api_key=#{@api_key}"]
210
+ urla << "num=#{@num_results}" if @num_results
211
+ urla << "page=#{@page}" if @page
212
+ urla << "l=#{loc_str}" unless loc_str.nil? or loc_str.empty?
213
+ urla << "f=#{@facet}" if @facet
214
+ urla << "v=#{@view}" if @view
215
+ urla << "r=#{@radius}" if @radius
216
+ urla << "s=#{@sort}" if @sort
217
+ urla << "k=#{@keywords.join("+")}" if @keywords and !@keywords.empty?
218
+ urla << "m=#{meta_data}" if meta_data
219
+
220
+ return urla.join("&")
158
221
  end
159
222
 
160
223
  def search
@@ -163,8 +226,7 @@ module Active
163
226
  http = Net::HTTP.new(searchurl.host, searchurl.port)
164
227
  http.read_timeout = DEFAULT_TIMEOUT
165
228
 
166
- puts "#{searchurl.path}?#{searchurl.query}"
167
-
229
+ puts "Active Search [GET] #{"#{searchurl.path}?#{searchurl.query}"}"
168
230
  res = http.start { |http|
169
231
  http.get("#{searchurl.path}?#{searchurl.query}")
170
232
  }
@@ -176,7 +238,10 @@ module Active
176
238
  @pageSize = parsed_json["pageSize"]
177
239
  @searchTime = parsed_json["searchTime"]
178
240
  @numberOfResults = parsed_json["numberOfResults"]
179
- @results = parsed_json['_results'].collect { |a| Activity.new(a) }
241
+ @results = parsed_json['_results'].collect { |a| Activity.new(GSA.new(a)) }
242
+
243
+ Active.CACHE.set( Digest::MD5.hexdigest(end_point), self) if Active.CACHE
244
+
180
245
  rescue JSON::ParserError => e
181
246
  raise RuntimeError, "JSON::ParserError json=#{res.body}"
182
247
  @endIndex = 0
@@ -223,10 +288,29 @@ module Active
223
288
  # http://developer.active.com/docs/Activecom_Search_API_Reference
224
289
  # returns an array of results and query info
225
290
  def self.search(data=nil)
226
- search = Search.new(data)
227
- search.search
228
- return search
291
+ search = Search.new(data)
292
+ search_hash = Digest::MD5.hexdigest(search.end_point)
293
+ puts "search_hash #{search_hash}"
294
+ cache = Search.return_cached(search_hash)
295
+ if cache != nil
296
+ return cache
297
+ else
298
+ search.search
299
+ return search
300
+ end
229
301
  end
302
+
303
+ def self.return_cached key
304
+ if Active.CACHE
305
+ cached_version = Active.CACHE.get(key)
306
+ if cached_version
307
+ puts "Active Search [CACHE] #{key}"
308
+ return cached_version
309
+ end
310
+ end
311
+ nil
312
+ end
313
+
230
314
 
231
315
  def []
232
316
  @results
@@ -239,7 +323,6 @@ module Active
239
323
  str.gsub!(/\-/,"%252D")
240
324
  str
241
325
  end
242
-
243
326
  end
244
327
 
245
328
  # TODO move to a reflection service
@@ -0,0 +1,403 @@
1
+ require 'net/http'
2
+ require 'json'
3
+ require 'cgi'
4
+ require 'rubygems'
5
+ require 'mysql'
6
+ require 'active_record'
7
+
8
+ module Active
9
+ module Services
10
+
11
+ # we should remove this class and just replace with symbols
12
+ class Sort
13
+ def self.DATE_ASC
14
+ "date_asc"
15
+ end
16
+ def self.DATE_DESC
17
+ "date_desc"
18
+ end
19
+ def self.RELEVANCE
20
+ "relevance"
21
+ end
22
+ end
23
+
24
+ # we should remove this class and just replace with symbols
25
+ class Facet
26
+ def self.ACTIVITIES
27
+ "activities"
28
+ end
29
+ end
30
+
31
+ class Search
32
+ attr_accessor :api_key, :start_date, :end_date, :location, :channels, :keywords, :search, :radius, :limit, :sort, :page, :offset, :latitude, :longitude,
33
+ :view, :facet, :sort, :num_results, :asset_ids, :dma, :city, :state, :country
34
+
35
+ attr_reader :results, :endIndex, :pageSize, :searchTime, :numberOfResults, :end_point, :meta
36
+
37
+ SEARCH_URL = "http://search.active.com"
38
+ DEFAULT_TIMEOUT = 60
39
+
40
+ def initialize(data={})
41
+ self.api_key = data[:api_key] || ""
42
+ self.location = data[:location] || ""
43
+ self.zips = data[:zips] || []
44
+ self.channels = data[:channels] || []
45
+ self.keywords = data[:keywords] || []
46
+ self.radius = data[:radius] || nil
47
+ self.limit = data[:limit] || "10"
48
+ self.sort = data[:sort] || Sort.DATE_ASC
49
+ self.page = data[:page] || "1"
50
+ self.offset = data[:offset] || "0"
51
+ self.view = data[:view] || "json"
52
+ self.facet = data[:facet] || Facet.ACTIVITIES
53
+ self.num_results = data[:num_results] || "10"
54
+ self.search = data[:search] || ""
55
+ self.start_date = data[:start_date] || "today"
56
+ self.end_date = data[:end_date] || "+"
57
+ self.asset_ids = data[:asset_ids] || []
58
+ self.asset_id = data[:asset_id] || ""
59
+ self.latitude = data[:latitude]
60
+ self.longitude = data[:longitude]
61
+ self.dma = data[:dma]
62
+ self.city = data[:city]
63
+ self.state = data[:state]
64
+ self.country = data[:country]
65
+ end
66
+
67
+ # Example
68
+ # Search.search( {:zips => "92121, 92078, 92114"} )
69
+ # or
70
+ # Search.new( {:zips => [92121, 92078, 92114]} )
71
+ def zips=(value)
72
+ if value.class == String
73
+ @zips = value.split(",").each { |k| k.strip! }
74
+ else
75
+ @zips = value
76
+ end
77
+ end
78
+
79
+ def zips
80
+ @zips
81
+ end
82
+
83
+ def location=(value)
84
+ @location = CGI.escape(value)
85
+ end
86
+
87
+ def keywords=(value)
88
+ if value.class == String
89
+ @keywords = value.gsub(",", " ").split(" ").each { |k| k.strip! }
90
+ else
91
+ @keywords = value
92
+ end
93
+ end
94
+
95
+ def channels=(value)
96
+ if value.class == String
97
+ @channels = value.split(",").each { |k| k.strip! }
98
+ else
99
+ @channels = value
100
+ end
101
+ end
102
+
103
+ def asset_ids=(value)
104
+ @asset_ids = value
105
+ end
106
+
107
+ def asset_id=(value)
108
+ return if value.empty?
109
+ @asset_ids<<value
110
+ end
111
+
112
+ def end_point
113
+ meta_data = ""
114
+ loc_str = ""
115
+ # CHANNELS
116
+ channel_keys = []
117
+ @channels.each do |c|
118
+ c.to_sym
119
+ if Categories.CHANNELS.include?(c)
120
+ channel_keys << Categories.CHANNELS[c]
121
+ end
122
+ end
123
+ meta_data = channel_keys.collect { |channel| "meta:channel=#{Search.double_encode_channel(channel)}" }.join("+OR+")
124
+ # ASSET IDS
125
+ unless asset_ids.empty?
126
+ meta_data += "+AND+" unless meta_data == ""
127
+ temp_ids = []
128
+ asset_ids.each do |id|
129
+ temp_ids << "meta:" + CGI.escape("assetId=#{id.gsub("-","%2d")}")
130
+ end
131
+ meta_data += temp_ids.join("+OR+")
132
+ end
133
+ # LOCATION
134
+ # 1 Look for :city, :state, :country
135
+ # 2 Look for zip codes
136
+ # 3 Look for lat lng
137
+ # 4 Look for a formatted string "San Diego, CA, US"
138
+ # 5 Look for a dma
139
+
140
+ if @city or @state or @country
141
+ if @city
142
+ meta_data += "+AND+" unless meta_data == ""
143
+ meta_data += "meta:city=#{Search.double_encode_channel(@city)}"
144
+ end
145
+ if @state
146
+ meta_data += "+AND+" unless meta_data == ""
147
+ meta_data += "meta:state=#{Search.double_encode_channel(@state)}"
148
+ end
149
+ elsif !@zips.empty?
150
+ # if not @zips.empty?
151
+ loc_str = @zips.join(",")
152
+ elsif @latitude and @longitude
153
+ loc_str = "#{@latitude};#{@longitude}"
154
+ elsif @dma
155
+
156
+ meta_data += "+AND+" unless meta_data == ""
157
+ meta_data += "meta:dma=#{Search.double_encode_channel(@dma)}"
158
+ else
159
+ loc_str = @location
160
+ end
161
+
162
+
163
+ # AND DATE
164
+ meta_data += "+AND+" unless meta_data == ""
165
+ if @start_date.class == Date
166
+ @start_date = URI.escape(@start_date.strftime("%m/%d/%Y")).gsub(/\//,"%2F")
167
+ end
168
+ if @end_date.class == Date
169
+ @end_date = URI.escape(@end_date.strftime("%m/%d/%Y")).gsub(/\//,"%2F")
170
+ end
171
+ meta_data += "meta:startDate:daterange:#{@start_date}..#{@end_date}"
172
+
173
+ # url = "#{SEARCH_URL}/search?api_key=#{@api_key}&num=#{@num_results}&page=#{@page}&l=#{loc_str}&f=#{@facet}&v=#{@view}&r=#{@radius}&s=#{@sort}&k=#{@keywords.join("+")}&m=#{meta_data}"
174
+ urla = ["#{SEARCH_URL}/search?api_key=#{@api_key}"]
175
+ urla << "num=#{@num_results}" if @num_results
176
+ urla << "page=#{@page}" if @page
177
+ urla << "l=#{loc_str}" unless loc_str.empty?
178
+ urla << "f=#{@facet}" if @facet
179
+ urla << "v=#{@view}" if @view
180
+ urla << "r=#{@radius}" if @radius
181
+ urla << "s=#{@sort}" if @sort
182
+ urla << "k=#{@keywords.join("+")}" if @keywords and !@keywords.empty?
183
+ urla << "m=#{meta_data}" if meta_data
184
+
185
+ return urla.join("&")
186
+ end
187
+
188
+ def search
189
+ searchurl = URI.parse(end_point)
190
+ req = Net::HTTP::Get.new(searchurl.path)
191
+ http = Net::HTTP.new(searchurl.host, searchurl.port)
192
+ http.read_timeout = DEFAULT_TIMEOUT
193
+
194
+ puts "#{searchurl.path}?#{searchurl.query}"
195
+
196
+ res = http.start { |http|
197
+ http.get("#{searchurl.path}?#{searchurl.query}")
198
+ }
199
+
200
+ if (200..307).include?(res.code.to_i)
201
+ begin
202
+ parsed_json = JSON.parse(res.body)
203
+ @endIndex = parsed_json["endIndex"]
204
+ @pageSize = parsed_json["pageSize"]
205
+ @searchTime = parsed_json["searchTime"]
206
+ @numberOfResults = parsed_json["numberOfResults"]
207
+ @results = parsed_json['_results'].collect { |a| GSA.new(a) }
208
+ rescue JSON::ParserError => e
209
+ raise RuntimeError, "JSON::ParserError json=#{res.body}"
210
+ @endIndex = 0
211
+ @pageSize = 0
212
+ @searchTime = 0
213
+ @numberOfResults = 0
214
+ @results = []
215
+ end
216
+
217
+ else
218
+ raise RuntimeError, "Active Search responded with a #{res.code} for your query."
219
+ end
220
+ end
221
+
222
+ # examples
223
+ #
224
+ #
225
+ # = Keywords =
226
+ # Keywords can be set like this
227
+ # Search.new({:keywords => "Dog,Cat,Cow"})
228
+ # Search.new({:keywords => %w(Dog Cat Cow)})
229
+ # Search.new({:keywords => ["Dog","Cat","Cow"]})
230
+ #
231
+ # = Location =
232
+ # The location will be set in this order and will override other values. For example is you set a zip code and a location only the zip will be used.
233
+ #
234
+ # 1 Look for zip codes
235
+ # Search.search( {:zips => "92121, 92078, 92114"} )
236
+ # Search.search( {:zips => %w(92121, 92078, 92114)} )
237
+ # Search.search( {:zips => [92121, 92078, 92114]} )
238
+ #
239
+ # 2 Look for lat lng
240
+ # Search.search( {:latitude=>"37.785895", :longitude=>"-122.40638"} )
241
+ #
242
+ # 3 Look for a formatted string "San Diego, CA, US"
243
+ # Search.search( {:location = "San Diego, CA, US"} )
244
+ #
245
+ # 4 Look for a DMA
246
+ # Search.new({:dma=>"San Francisco - Oakland - San Jose"})
247
+ #
248
+ # = How to look at the results =
249
+ #
250
+ #
251
+ # http://developer.active.com/docs/Activecom_Search_API_Reference
252
+ # returns an array of results and query info
253
+ def self.search(data=nil)
254
+ search = Search.new(data)
255
+ search.search
256
+ return search
257
+ end
258
+
259
+ def []
260
+ @results
261
+ end
262
+
263
+ private
264
+ def self.double_encode_channel str
265
+ str = URI.escape(str, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
266
+ str = URI.escape(str, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
267
+ str.gsub!(/\-/,"%252D")
268
+ str
269
+ end
270
+
271
+ end
272
+
273
+ # TODO move to a reflection service
274
+ class Categories
275
+ def self.CHANNELS
276
+ {
277
+ :active_lifestyle => '',
278
+ :fitness => 'Fitness',
279
+ :body_building => 'Fitness\Body Building',
280
+ :boxing => 'Fitness\Boxing',
281
+ :weight_lifting => 'Fitness\Weight Lifting',
282
+ :gear => 'Gear',
283
+ :lifestyle_vehicles => 'Lifestyle Vehicles',
284
+ :mind_mody => 'Mind & Body',
285
+ :meditation => 'Mind & Body\Meditation',
286
+ :pilates => 'Mind & Body\Pilates',
287
+ :yoga => 'Mind & Body\Yoga',
288
+ :nutrition => 'Nutrition',
289
+ :travel => 'Travel',
290
+ :women => 'Women',
291
+ :other => 'Other',
292
+ :corporate => 'Corporate',
293
+ :not_specified => 'Not Specified',
294
+ :unknown => 'Unknown',
295
+ :special_interest => 'Special+Interest',
296
+ :giving => 'Giving',
297
+ :parks_recreation => 'Parks & Recreation',
298
+ :gear => 'Parks & Recreation\Gear',
299
+ :mind_body => 'Parks & Recreation\Mind & Body',
300
+ :travel => 'Parks & Recreation\Travel',
301
+ :vehicles => 'Parks & Recreation\Vehicles',
302
+ :women => 'Parks & Recreation\Women',
303
+ :reunions => 'Reunions',
304
+ :sports => 'Sports',
305
+ :action_sports => 'Action Sports',
306
+ :auto_racing => 'Action Sports\Auto Racing',
307
+ :bmx => 'Action Sports\BMX',
308
+ :dirt_bike_racing => 'Action Sports\Dirt Bike Racing',
309
+ :motocross => 'Action Sports\Motocross',
310
+ :motorcycle_racing => 'Action Sports\Motorcycle Racing',
311
+ :skateboarding => 'Action Sports\Skateboarding',
312
+ :skydiving => 'Action Sports\Skydiving',
313
+ :surfing => 'Action Sports\Surfing',
314
+ :wake_kite_boarding => 'Action Sports\Wake/Kite Boarding',
315
+ :water_skiing => 'Action Sports\Water Skiing',
316
+ :wind_surfing => 'Action Sports\Wind Surfing',
317
+ :baseball => 'Baseball',
318
+ :little_league_baseball => 'Baseball\Little League Baseball',
319
+ :tee_ball => 'Baseball\Tee Ball',
320
+ :basketball => 'Basketball',
321
+ :cheerleading => 'Cheerleading',
322
+ :cycling => 'Cycling',
323
+ :field_hockey => 'Field Hockey',
324
+ :football => 'Football',
325
+ :flag_football => 'Football\Flag Football',
326
+ :football_au => 'Football\Football-AU',
327
+ :golf => 'Golf',
328
+ :ice_hockey => 'Ice Hockey',
329
+ :lacrosse => 'Lacrosse',
330
+ :more_sports => 'More Sports',
331
+ :adventure_racing => 'More Sports\Adventure Racing',
332
+ :archery => 'More Sports\Archery',
333
+ :badminton => 'More Sports\Badminton',
334
+ :billiards => 'More Sports\Billiards',
335
+ :bowling => 'More Sports\Bowling',
336
+ :cricket => 'More Sports\Cricket',
337
+ :croquet => 'More Sports\Croquet',
338
+ :curling => 'More Sports\Curling',
339
+ :dance => 'More Sports\Dance',
340
+ :disc_sports => 'More Sports\Disc Sports',
341
+ :dodgeball => 'More Sports\Dodgeball',
342
+ :duathlon => 'More Sports\Duathlon',
343
+ :equestrian => 'More Sports\Equestrian',
344
+ :fencing => 'More Sports\Fencing',
345
+ :figure_skating => 'More Sports\Figure Skating',
346
+ :gymnastics => 'More Sports\Gymnastics',
347
+ :inline_hockey => 'More Sports\Inline Hockey',
348
+ :inline_skating => 'More Sports\Inline Skating',
349
+ :kickball => 'More Sports\Kickball',
350
+ :martial_arts => 'More Sports\Martial Arts',
351
+ :paintball => 'More Sports\Paintball',
352
+ :polo => 'More Sports\Polo',
353
+ :racquetball => 'More Sports\Racquetball',
354
+ :rowing => 'More Sports\Rowing',
355
+ :rugby => 'More Sports\Rugby',
356
+ :scouting => 'More Sports\Scouting',
357
+ :scuba_diving => 'More Sports\Scuba Diving',
358
+ :skating => 'More Sports\Skating',
359
+ :squash => 'More Sports\Squash',
360
+ :ultimate_frisbee => 'More Sports\Ultimate Frisbee',
361
+ :water_polo => 'More Sports\Water Polo',
362
+ :mountain_biking => 'Mountain Biking',
363
+ :outdoors => 'Outdoors',
364
+ :canoeing => 'Outdoors\Canoeing',
365
+ :climbing => 'Outdoors\Climbing',
366
+ :hiking => 'Outdoors\Hiking',
367
+ :kayaking => 'Outdoors\Kayaking',
368
+ :orienteering => 'Outdoors\Orienteering',
369
+ :outrigging => 'Outdoors\Outrigging',
370
+ :rafting => 'Outdoors\Rafting',
371
+ :racquetball => 'Racquetball',
372
+ :rugby => 'Rugby',
373
+ :running => 'Running',
374
+ :cross_country => 'Running\Cross Country',
375
+ :marathon_running => 'Running\Marathon Running',
376
+ :track_field => 'Running\Track & Field',
377
+ :trail_running => 'Running\Trail Running',
378
+ :sailing => 'Sailing',
379
+ :snow_sports => 'Snow Sports',
380
+ :skiing => 'Snow Sports\Skiing',
381
+ :snowboarding => 'Snow Sports\Snowboarding',
382
+ :snowshoeing => 'Snow Sports\Snowshoeing',
383
+ :soccer => 'Soccer',
384
+ :softball => 'Softball',
385
+ :softball_dixie => 'Softball\Softball-Dixie',
386
+ :softball_fast_pitch => 'Softball\Softball-Fast Pitch',
387
+ :softball_slow_pitch => 'Softball\Softball-Slow Pitch',
388
+ :squash => 'Squash',
389
+ :swimming => 'Swimming',
390
+ :diving => 'Swimming\Diving',
391
+ :tennis => 'Tennis',
392
+ :other_tennis => 'Tennis\Other Tennis',
393
+ :usta => 'Tennis\USTA',
394
+ :triathlon => 'Triathlon',
395
+ :volleyball => 'Volleyball',
396
+ :walking => 'Walking',
397
+ :wrestling => 'Wrestling'
398
+ }
399
+ end
400
+ end
401
+
402
+ end
403
+ end