shortwave 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +10 -0
- data/.gitmodules +3 -0
- data/LICENSE +20 -0
- data/README.rdoc +74 -0
- data/Rakefile +133 -0
- data/VERSION +1 -0
- data/lib/shortwave.rb +12 -0
- data/lib/shortwave/authentication.rb +107 -0
- data/lib/shortwave/facade.rb +3 -0
- data/lib/shortwave/facade/build/facade_builder.rb +199 -0
- data/lib/shortwave/facade/build/facade_template.erb +17 -0
- data/lib/shortwave/facade/lastfm.rb +878 -0
- data/lib/shortwave/facade/remote.rb +86 -0
- data/lib/shortwave/model/album.rb +33 -0
- data/lib/shortwave/model/artist.rb +62 -0
- data/lib/shortwave/model/base_model.rb +112 -0
- data/lib/shortwave/model/chart_dates.rb +15 -0
- data/lib/shortwave/model/comparison.rb +15 -0
- data/lib/shortwave/model/event.rb +55 -0
- data/lib/shortwave/model/group.rb +26 -0
- data/lib/shortwave/model/location.rb +45 -0
- data/lib/shortwave/model/playlist.rb +36 -0
- data/lib/shortwave/model/shout.rb +21 -0
- data/lib/shortwave/model/tag.rb +47 -0
- data/lib/shortwave/model/track.rb +71 -0
- data/lib/shortwave/model/user.rb +70 -0
- data/lib/shortwave/model/venue.rb +37 -0
- data/lib/shortwave/model/weekly_charts.rb +31 -0
- data/lib/shortwave/providers.rb +161 -0
- data/shortwave.gemspec +178 -0
- data/test/authentication_test.rb +64 -0
- data/test/build/build_test.rb +25 -0
- data/test/build/data/intro.yml +2 -0
- data/test/build/data/screens/album_addTags.html +1238 -0
- data/test/build/data/screens/intro_truncated.html +426 -0
- data/test/build/data/screens/tasteometer_compare.html +1274 -0
- data/test/build/data/screens/user_getLovedTracks.html +1278 -0
- data/test/build/data/screens/venue_search.html +1261 -0
- data/test/build/facade_builder_test.rb +23 -0
- data/test/build/parameter_test.rb +43 -0
- data/test/build/remote_method_test.rb +47 -0
- data/test/build/ruby_class_test.rb +12 -0
- data/test/build/ruby_method_test.rb +137 -0
- data/test/helper.rb +35 -0
- data/test/model/album_test.rb +62 -0
- data/test/model/artist_test.rb +103 -0
- data/test/model/chart_dates_test.rb +11 -0
- data/test/model/comparison_test.rb +18 -0
- data/test/model/data/album_info.xml +38 -0
- data/test/model/data/album_search.xml +210 -0
- data/test/model/data/artist_info.xml +58 -0
- data/test/model/data/artist_search.xml +109 -0
- data/test/model/data/artist_shouts.xml +546 -0
- data/test/model/data/artist_top_fans.xml +405 -0
- data/test/model/data/event_info.xml +47 -0
- data/test/model/data/group_members.xml +242 -0
- data/test/model/data/group_weekly_album_chart.xml +1754 -0
- data/test/model/data/group_weekly_artist_chart.xml +604 -0
- data/test/model/data/group_weekly_track_chart.xml +1005 -0
- data/test/model/data/location_events.xml +383 -0
- data/test/model/data/ok.xml +2 -0
- data/test/model/data/playlist_fetch.xml +227 -0
- data/test/model/data/tag_search.xml +110 -0
- data/test/model/data/tag_similar.xml +254 -0
- data/test/model/data/tag_top_albums.xml +805 -0
- data/test/model/data/tag_top_artists.xml +605 -0
- data/test/model/data/tag_top_tags.xml +1254 -0
- data/test/model/data/tag_top_tracks.xml +843 -0
- data/test/model/data/tag_weekly_chart_list.xml +57 -0
- data/test/model/data/tasteometer_compare.xml +54 -0
- data/test/model/data/track_info.xml +53 -0
- data/test/model/data/track_search.xml +195 -0
- data/test/model/data/user_chartlist.xml +90 -0
- data/test/model/data/user_info.xml +16 -0
- data/test/model/data/user_neighbours.xml +484 -0
- data/test/model/data/user_playlists.xml +17 -0
- data/test/model/data/user_recent_tracks.xml +124 -0
- data/test/model/data/user_recommended_artists.xml +454 -0
- data/test/model/data/user_shouts.xml +9 -0
- data/test/model/data/user_weekly_artist_chart.xml +478 -0
- data/test/model/data/venue_events.xml +556 -0
- data/test/model/data/venue_past_events.xml +1778 -0
- data/test/model/data/venue_search.xml +355 -0
- data/test/model/event_test.rb +63 -0
- data/test/model/group_test.rb +45 -0
- data/test/model/location_test.rb +25 -0
- data/test/model/playlist_test.rb +51 -0
- data/test/model/shout_test.rb +23 -0
- data/test/model/tag_test.rb +39 -0
- data/test/model/track_test.rb +67 -0
- data/test/model/user_test.rb +125 -0
- data/test/model/venue_test.rb +60 -0
- data/test/provider/album_provider_test.rb +26 -0
- data/test/provider/artist_provider_test.rb +25 -0
- data/test/provider/group_provider_test.rb +9 -0
- data/test/provider/location_provider_test.rb +9 -0
- data/test/provider/playlist_provider_test.rb +12 -0
- data/test/provider/tag_provider_test.rb +24 -0
- data/test/provider/track_provider_test.rb +26 -0
- data/test/provider/user_provider_test.rb +11 -0
- data/test/provider/venue_provider_test.rb +15 -0
- data/test/provider_test_helper.rb +27 -0
- data/test/remote_test.rb +26 -0
- metadata +209 -0
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'restclient'
|
2
|
+
require 'uri'
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module Shortwave
|
6
|
+
# Includes thin wrapper classes around the Last.fm web services API.
|
7
|
+
#
|
8
|
+
# These classes map one-to-one to the Last.fm API, and are in fact generated from the
|
9
|
+
# HTML documentation the Last.fm provide. They make no attempt to OOify or XMLify the
|
10
|
+
# responses - the results of these methods are just the string responses from Last.fm.
|
11
|
+
# As such, they are a suitable base for building a "real" API on top of, which is
|
12
|
+
# what classes in Shortwave::Model do.
|
13
|
+
#
|
14
|
+
# The general use is:
|
15
|
+
#
|
16
|
+
# # Create a session for your platform
|
17
|
+
# session = Shortwave::Authentication::Mobile.new("api_key", "app_secret")
|
18
|
+
#
|
19
|
+
# # Authenticate if necessary (if you want to use write methods)
|
20
|
+
# session.authenticate("user", "password")
|
21
|
+
#
|
22
|
+
# # Create a facade, passing in the session
|
23
|
+
# facade = Shortwave::Facade::Artist.new(session)
|
24
|
+
#
|
25
|
+
# # Call methods on the facade
|
26
|
+
# facade.top_tags("The feelies")
|
27
|
+
# # => returns <lfm status="ok"> ... etc.
|
28
|
+
#
|
29
|
+
# Required method parameters in the web api are real method parameters - if the method
|
30
|
+
# has optional parameters, these appear in an options hash after all the required parameters.
|
31
|
+
# You do not need to provide any authentication parameters (+api_key+, +api_sig+ & +sk+) - these
|
32
|
+
# are merged in from the session for you automatically.
|
33
|
+
#
|
34
|
+
# If the request fails, the facade method will raise a RemoteError.
|
35
|
+
#
|
36
|
+
# Each of the facades has further documentation, taken directly from the Last.fm documentation
|
37
|
+
# at http://last.fm/api
|
38
|
+
module Facade
|
39
|
+
# Wraps a RestClient::RequestFailed exception, and delegates +message+, +http_code+
|
40
|
+
# and +response+ to the wrapped exception.
|
41
|
+
#
|
42
|
+
# This class is mainly provided for api stability in case we switch from using RestClient.
|
43
|
+
class RemoteError < RuntimeError
|
44
|
+
extend Forwardable
|
45
|
+
def_delegators :@cause, :message, :to_s, :http_code, :response
|
46
|
+
|
47
|
+
def initialize(cause)
|
48
|
+
@cause = cause
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Base class for each remote facade.
|
53
|
+
#
|
54
|
+
# Remote methods may raise a RemoteError if an http error response is returned from
|
55
|
+
# the Last.fm service.
|
56
|
+
class Remote
|
57
|
+
attr_reader :session
|
58
|
+
|
59
|
+
# Creates a new facade, given a user session
|
60
|
+
def initialize(session)
|
61
|
+
@session = session
|
62
|
+
end
|
63
|
+
|
64
|
+
protected
|
65
|
+
|
66
|
+
BASE_URI = "http://ws.audioscrobbler.com/2.0/"
|
67
|
+
DISALLOWED = Regexp.new("[^#{URI::PATTERN::UNRESERVED}]")
|
68
|
+
AGENT = "Shortwave 0.0.1 http://shortwave.rubyforge.org/"
|
69
|
+
|
70
|
+
def get(type, data)
|
71
|
+
@session.merge!(type, data)
|
72
|
+
uri = BASE_URI + "?" + data.map {|k,v| "#{k.to_s}=#{URI.escape(v.to_s, DISALLOWED)}"}.join("&")
|
73
|
+
RestClient.get uri, {"User-Agent" => AGENT}
|
74
|
+
rescue RestClient::RequestFailed => e
|
75
|
+
raise RemoteError.new(e)
|
76
|
+
end
|
77
|
+
|
78
|
+
def post(type, data)
|
79
|
+
@session.merge!(type, data, {"User-Agent" => AGENT})
|
80
|
+
RestClient.post BASE_URI, data
|
81
|
+
rescue RestClient::RequestFailed => e
|
82
|
+
raise RemoteError.new(e)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Shortwave
|
2
|
+
module Model
|
3
|
+
# An album on Last.fm
|
4
|
+
#
|
5
|
+
# === Attributes
|
6
|
+
#
|
7
|
+
# +name+:: Album name
|
8
|
+
# +url+:: Last.fm site url
|
9
|
+
# +id+:: Last.fm id
|
10
|
+
# +release_date+:: Release date
|
11
|
+
# +listeners+:: Number of listeners
|
12
|
+
# +play_count+:: Number of times tracks from this album have been played
|
13
|
+
# +images+:: An array of album images
|
14
|
+
# +mbid+:: Musicbrainz ID
|
15
|
+
# +artist_name+:: Album's artist
|
16
|
+
# +tags+:: Last.fm user tags
|
17
|
+
class Album < BaseModel
|
18
|
+
element :name, String, :tag => "name|title"
|
19
|
+
element :url, String
|
20
|
+
element :id, Integer
|
21
|
+
element :release_date, Time, :tag => "releasedate"
|
22
|
+
element :listeners, Integer
|
23
|
+
element :play_count, Integer, :tag => "playcount"
|
24
|
+
element :images, String, :tag => "image", :single => false
|
25
|
+
element :mbid, String
|
26
|
+
element :artist_name, String, :tag => "artist"
|
27
|
+
has_many :tags, "Shortwave::Model::Tag", :tag => "toptags/tag"
|
28
|
+
|
29
|
+
identified_by :artist_name, :name
|
30
|
+
taggable
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Shortwave
|
2
|
+
module Model
|
3
|
+
# An artist. Artists are taggable, shoutable and sharable by a user.
|
4
|
+
#
|
5
|
+
# === Attributes
|
6
|
+
#
|
7
|
+
# +name+:: Artist name
|
8
|
+
# +mbid+:: Musicbrainz ID
|
9
|
+
# +url+:: Url of artist page on the Last.fm site
|
10
|
+
# +play_count+:: The number of times this artist has been played
|
11
|
+
# +listeners+:: The number of listeners this artist has
|
12
|
+
# +biography_summary+:: Textual description of this artist
|
13
|
+
# +biography+:: Full textual description of this artist
|
14
|
+
# +images+:: Images of this artist
|
15
|
+
class Artist < BaseModel
|
16
|
+
element :name, String
|
17
|
+
element :mbid, String
|
18
|
+
element :url, String
|
19
|
+
element :streamable, Boolean
|
20
|
+
element :play_count, Integer, :tag => "stats/playcount"
|
21
|
+
element :listeners, Integer, :tag => "stats/listeners"
|
22
|
+
element :biography_summary, String, :tag => "bio/content"
|
23
|
+
element :biography, String, :tag => "bio/content"
|
24
|
+
element :images, String, :tag => "image", :single => false
|
25
|
+
|
26
|
+
identified_by :name
|
27
|
+
taggable
|
28
|
+
shoutable
|
29
|
+
sharable
|
30
|
+
|
31
|
+
# Events this artist is playing at.
|
32
|
+
def events
|
33
|
+
link :events, :Event, name
|
34
|
+
end
|
35
|
+
|
36
|
+
# Artists similar to this one.
|
37
|
+
def similar
|
38
|
+
link :similar, :Artist, name
|
39
|
+
end
|
40
|
+
|
41
|
+
# This artist's top albums
|
42
|
+
def albums
|
43
|
+
link :top_albums, :Album, name
|
44
|
+
end
|
45
|
+
|
46
|
+
# This artist's top fans
|
47
|
+
def fans
|
48
|
+
link :top_fans, :User, name
|
49
|
+
end
|
50
|
+
|
51
|
+
# This artist's top tags
|
52
|
+
def tags
|
53
|
+
link :top_tags, :Tag, name
|
54
|
+
end
|
55
|
+
|
56
|
+
# This artist's top tracks
|
57
|
+
def tracks
|
58
|
+
link :top_tracks, :Track, name
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module Shortwave
|
2
|
+
module Model
|
3
|
+
# Root class for all Shortwave models.
|
4
|
+
class BaseModel
|
5
|
+
# Sets the session on this model, and all nested models
|
6
|
+
def session=(session)
|
7
|
+
@session = session
|
8
|
+
self.class.has_ones.each do |sym|
|
9
|
+
obj = send(sym)
|
10
|
+
obj.session = session if obj
|
11
|
+
end
|
12
|
+
self.class.has_manys.each {|s| (send(s) || []).each {|o| o.session = session } }
|
13
|
+
session
|
14
|
+
end
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def facade_name
|
18
|
+
@facade_name ||= (name.split("::").last.downcase + "_facade").to_sym
|
19
|
+
end
|
20
|
+
|
21
|
+
def identified_by(*methods)
|
22
|
+
@lastfm_keys = methods
|
23
|
+
end
|
24
|
+
|
25
|
+
# Adds methods to a model to deal with shouts: shouts and shout
|
26
|
+
def shoutable
|
27
|
+
class_eval <<-EOV
|
28
|
+
def shout(message)
|
29
|
+
@session.#{facade_name}.shout(#{@lastfm_keys.join(",")}, message)
|
30
|
+
end
|
31
|
+
|
32
|
+
link_to "Shout", :shouts
|
33
|
+
EOV
|
34
|
+
end
|
35
|
+
|
36
|
+
# Adds methods to a model to deal with tags: tags, add_tags and remove_tag
|
37
|
+
#
|
38
|
+
# Up to ten tags can be added, either strings, or Tag models
|
39
|
+
# A single tag can be removed
|
40
|
+
def taggable
|
41
|
+
class_eval <<-EOV
|
42
|
+
def add_tags(*tags)
|
43
|
+
tag_param = tags[0...10].map {|t| t.to_s }.join(",")
|
44
|
+
@session.#{facade_name}.add_tags(#{@lastfm_keys.join(",")}, tag_param)
|
45
|
+
end
|
46
|
+
|
47
|
+
def remove_tag(tag)
|
48
|
+
@session.#{facade_name}.remove_tag(#{@lastfm_keys.join(",")}, tag.to_s)
|
49
|
+
end
|
50
|
+
|
51
|
+
link_to "Tag", :my_tags, :tags
|
52
|
+
EOV
|
53
|
+
end
|
54
|
+
|
55
|
+
def sharable
|
56
|
+
class_eval <<-EOV
|
57
|
+
def share(recipients, message=nil)
|
58
|
+
params = {}
|
59
|
+
params[:message] = message if message
|
60
|
+
@session.#{facade_name}.share(#{@lastfm_keys.join(",")}, recipients[0...10].map{|r| r.to_s }.join(','), params)
|
61
|
+
end
|
62
|
+
EOV
|
63
|
+
end
|
64
|
+
|
65
|
+
def link_to(klass, method, remote_method=nil)
|
66
|
+
remote_method ||= method
|
67
|
+
class_eval <<-EOV
|
68
|
+
def #{method}
|
69
|
+
response = @session.#{facade_name}.#{remote_method}(#{@lastfm_keys.join(',')})
|
70
|
+
#{klass}.parse(response).each {|obj| obj.session = @session }
|
71
|
+
end
|
72
|
+
EOV
|
73
|
+
end
|
74
|
+
|
75
|
+
def inherited(klass)
|
76
|
+
klass.send(:include, HappyMapper)
|
77
|
+
klass.send(:tag, klass.name.split("::").last.downcase)
|
78
|
+
|
79
|
+
# Store every has_one/has_many declaration and use this information
|
80
|
+
# to decide which attributes need a session adding
|
81
|
+
class << klass
|
82
|
+
def has_ones
|
83
|
+
@has_ones ||= []
|
84
|
+
end
|
85
|
+
|
86
|
+
def has_manys
|
87
|
+
@has_manys ||= []
|
88
|
+
end
|
89
|
+
|
90
|
+
def has_one(sym, *args)
|
91
|
+
super
|
92
|
+
has_ones << sym
|
93
|
+
end
|
94
|
+
|
95
|
+
def has_many(sym, *args)
|
96
|
+
super
|
97
|
+
has_manys << sym
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
protected
|
104
|
+
|
105
|
+
def link(remote_method, klass, *args)
|
106
|
+
response = @session.send(self.class.facade_name).send(remote_method, *args)
|
107
|
+
klass = ::Shortwave::Model.const_get(klass)
|
108
|
+
klass.parse(response).each {|obj| obj.session = @session }
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Shortwave
|
2
|
+
module Model
|
3
|
+
# A chart date range. This is only really used internally.
|
4
|
+
#
|
5
|
+
# === Attributes
|
6
|
+
#
|
7
|
+
# from:: the date the chart starts
|
8
|
+
# to:: the date the chart finishes
|
9
|
+
class ChartDates < BaseModel
|
10
|
+
tag "chart"
|
11
|
+
attribute :from, Time
|
12
|
+
attribute :to, Time
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Shortwave
|
2
|
+
module Model
|
3
|
+
# A comparison between two Last.fm users
|
4
|
+
#
|
5
|
+
# === Attributes
|
6
|
+
#
|
7
|
+
# +score+:: A number between 0 and 1 representing how similar users are.
|
8
|
+
# +artists+:: Artists both users have in common
|
9
|
+
class Comparison < BaseModel
|
10
|
+
tag "comparison"
|
11
|
+
element :score, Float, :tag => "result/score"
|
12
|
+
has_many :artists, "Model::Artist", :tag => "result/artists/artist"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Shortwave
|
2
|
+
module Model
|
3
|
+
# An event.
|
4
|
+
#
|
5
|
+
# == Attributes
|
6
|
+
# +id+:: Last.fm id
|
7
|
+
# +name+:: Event name
|
8
|
+
# +description+:: Event description
|
9
|
+
# +attendance_count+:: Number of last.fm user's attending
|
10
|
+
# +review_count+:: Number of reviews
|
11
|
+
# +starts_at+:: The start time
|
12
|
+
# +url+:: Last.fm site url
|
13
|
+
# +venue+:: Event venue
|
14
|
+
class Event < BaseModel
|
15
|
+
element :id, Integer
|
16
|
+
element :description, String
|
17
|
+
element :url, String
|
18
|
+
element :attendance_count, Integer, :tag => "attendance"
|
19
|
+
element :review_count, Integer, :tag => "reviews"
|
20
|
+
element :starts_at, Time, :tag => "startDate"
|
21
|
+
element :name, String, :tag => "title"
|
22
|
+
element :headliner_raw, String, :tag => "artists/headliner"
|
23
|
+
element :artists_raw, String, :tag => "artists/artist", :single => false
|
24
|
+
has_one :venue, "Model::Venue"
|
25
|
+
|
26
|
+
identified_by :id
|
27
|
+
shoutable
|
28
|
+
sharable
|
29
|
+
|
30
|
+
# Returns the list of users attending this event
|
31
|
+
def attendees
|
32
|
+
link :attendees, :User, id
|
33
|
+
end
|
34
|
+
|
35
|
+
# Mark the current session user's attendance at this event
|
36
|
+
#
|
37
|
+
# Possible statuses are +:yes+, +:no+ and +:maybe+
|
38
|
+
def attend(status=:yes)
|
39
|
+
i = [:yes, :maybe, :no].index(status)
|
40
|
+
@session.event_facade.attend(id, i)
|
41
|
+
true
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns the headline act for this event
|
45
|
+
def headliner
|
46
|
+
@headliner ||= artists.detect {|a| a.name == headliner_raw }
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns the list of artists playing at this event
|
50
|
+
def artists
|
51
|
+
@artists ||= artists_raw.map {|a| @session.artist.build(:name => a) }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/weekly_charts"
|
2
|
+
|
3
|
+
module Shortwave
|
4
|
+
module Model
|
5
|
+
# A Last.fm user group.
|
6
|
+
#
|
7
|
+
# === Attributes
|
8
|
+
#
|
9
|
+
# name:: the group name
|
10
|
+
class Group < BaseModel
|
11
|
+
attr_accessor :name
|
12
|
+
identified_by :name
|
13
|
+
include WeeklyCharts
|
14
|
+
|
15
|
+
# Returns the group's members. Currently only 50 users are returned
|
16
|
+
def members
|
17
|
+
link :members, "User", name
|
18
|
+
end
|
19
|
+
|
20
|
+
# The group name
|
21
|
+
def to_s
|
22
|
+
name
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Shortwave
|
2
|
+
module Model
|
3
|
+
# A location
|
4
|
+
#
|
5
|
+
# === Attributes
|
6
|
+
# +latitude+:: Latitude
|
7
|
+
# +longitude+:: Longitude
|
8
|
+
# +street_address+:: Street address
|
9
|
+
# +city+:: City
|
10
|
+
# +postcode+:: Post code
|
11
|
+
# +country+:: Country name
|
12
|
+
class Location < BaseModel
|
13
|
+
tag "location"
|
14
|
+
element :latitude, Float, :tag => "geo:point/geo:lat"
|
15
|
+
element :longitude, Float, :tag => "geo:point/geo:long"
|
16
|
+
element :city, String
|
17
|
+
element :country, String
|
18
|
+
element :street_address, String, :tag => "street"
|
19
|
+
element :postcode, String, :tag => "postalcode"
|
20
|
+
|
21
|
+
# Popular artists in this country. Needs country to be defined
|
22
|
+
def artists
|
23
|
+
link :top_artists, :Artist, country
|
24
|
+
end
|
25
|
+
|
26
|
+
# Popular tracks in this location
|
27
|
+
def tracks
|
28
|
+
args = [country]
|
29
|
+
args << {:location => city} if city
|
30
|
+
link :top_tracks, :Track, *args
|
31
|
+
end
|
32
|
+
|
33
|
+
# Return events near this location
|
34
|
+
def events
|
35
|
+
if latitude && longitude
|
36
|
+
link :events, :Event, :lat => latitude, :long => longitude
|
37
|
+
elsif city
|
38
|
+
link :events, :Event, :location => city
|
39
|
+
elsif country
|
40
|
+
link :events, :Event, :location => country
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|