officialfm 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/changelog.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.0 Aug 03, 2011
4
+ * Advanced API is now fully supported, see docs here: http://official.fm/developers/advanced
5
+ * OAuth sign-in demo can be found here: https://github.com/officialfm/sign-in-with-officialfm-ruby
6
+
3
7
  ## 0.0.4 May 20, 2011
4
8
  * Removed 'tracks' workaround, as the official API now includes it by default
5
9
 
@@ -0,0 +1,23 @@
1
+ require 'faraday'
2
+
3
+ module Faraday
4
+ class Request::OfficialFMOAuth < Faraday::Middleware
5
+ dependency 'simple_oauth'
6
+
7
+ def call(env)
8
+ params = env[:body] || {}
9
+ signature_params = params
10
+
11
+ params.map{ |k,v| signature_params = {} if v.respond_to?(:content_type) }
12
+
13
+ header = SimpleOAuth::Header.new(env[:method], env[:url], signature_params, @options)
14
+ env[:request_headers]['Authorization'] = header.to_s
15
+
16
+ @app.call(env)
17
+ end
18
+
19
+ def initialize(app, options)
20
+ @app, @options = app, options
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,31 @@
1
+ module OfficialFM
2
+ # @private
3
+ module Authentication
4
+ private
5
+
6
+ # Authentication hash
7
+ #
8
+ # @return [Hash]
9
+ def authentication
10
+ {
11
+ :consumer_key => @api_key,
12
+ :consumer_secret => @api_secret,
13
+ :token => @access_token,
14
+ :token_secret => @access_secret,
15
+ }
16
+ end
17
+
18
+ # Check whether user is authenticated
19
+ #
20
+ # @return [Boolean]
21
+ def authenticated?
22
+ authentication[:token] != nil
23
+ end
24
+
25
+ private
26
+
27
+ def check_auth(name)
28
+ raise "#{name} is an advanced API method - lacking OAuth credentials." unless authenticated?
29
+ end
30
+ end
31
+ end
@@ -1,7 +1,7 @@
1
1
  require 'forwardable'
2
-
3
- # Note: this gem only supports the Simple API, since the Advanced API
4
- # is still in development
2
+ require 'faraday/request/officialfm_oauth'
3
+ require 'officialfm/authentication'
4
+ require 'officialfm/version'
5
5
 
6
6
  module OfficialFM
7
7
  class Client
@@ -10,31 +10,64 @@ module OfficialFM
10
10
  include Users
11
11
  include Tracks
12
12
  include Playlists
13
+ include Authentication
13
14
 
14
- attr_reader :api_key
15
+ attr_reader :api_key, :api_secret
15
16
 
16
- def_delegators :web_server
17
+ def_delegators :web_server, :authorize_url, :access_token_url
17
18
 
18
19
  def initialize(options={})
19
20
  @api_key = options[:api_key] || OfficialFM.api_key
21
+ @api_secret = options[:api_secret] || OfficialFM.api_secret
22
+ @access_token = options[:access_token]
23
+ @access_secret = options[:access_secret]
20
24
  # Note: Although the default of the API is to return XML, I think
21
25
  # json is more appropriate in the Ruby world
22
26
 
23
- # Note: I really don't understand how js_callback_function works,
24
- # so I'm not exposing it here. (Is it for AJAX client-side requests?)
25
27
  @format = options[:format] || :json
26
- connection
28
+ connection unless @access_token
29
+ end
30
+
31
+ # GET request arguments for simple API calls
32
+ def simple_params (options = nil)
33
+ params = {
34
+ :key => @api_key,
35
+ :format => @format,
36
+ }
37
+ if options
38
+ options.each { |key, value|
39
+ case key
40
+ when 'limit'
41
+ params['api_max_responses'] = value
42
+ when 'embed'
43
+ params['api_embed_codes'] = value
44
+ else
45
+ params[key] = value
46
+ end
47
+ }
48
+ end
49
+ params
27
50
  end
28
51
 
29
52
  # Raw HTTP connection, either Faraday::Connection
30
53
  #
31
54
  # @return [Faraday::Connection]
32
55
  def connection
33
- params = {:key => @api_key, :format => @format}
34
- @connection ||= Faraday::Connection.new(:url => api_url, :params => params, :headers => default_headers) do |builder|
35
- builder.adapter Faraday.default_adapter
36
- builder.use Faraday::Response::ParseJson
56
+ params =
57
+
58
+ options = {
59
+ :url => api_url,
60
+ :headers => default_headers
61
+ }
62
+
63
+ @connection ||= Faraday.new(options) do |builder|
64
+ builder.use Faraday::Request::OfficialFMOAuth, authentication if authenticated?
65
+ builder.use Faraday::Request::Multipart
66
+ builder.use Faraday::Request::UrlEncoded
67
+
37
68
  builder.use Faraday::Response::Mashify
69
+ builder.use Faraday::Response::ParseJson
70
+ builder.adapter Faraday.default_adapter
38
71
  end
39
72
 
40
73
  end
@@ -43,7 +76,7 @@ module OfficialFM
43
76
  def default_headers
44
77
  headers = {
45
78
  :accept => 'application/json',
46
- :user_agent => 'officialfm Ruby gem'
79
+ :user_agent => "officialfm ruby gem version #{OfficialFM::VERSION}"
47
80
  }
48
81
  end
49
82
 
@@ -10,8 +10,7 @@ module OfficialFM
10
10
  # @return [Hashie::Mash] Playlist list
11
11
  def playlists(search_param, options={})
12
12
  response = connection.get do |req|
13
- req.url "/search/playlists/#{CGI::escape(search_param)}",
14
- :api_max_responses => options[:limit]
13
+ req.url "/search/playlists/#{CGI::escape(search_param)}", simple_params(options)
15
14
  end
16
15
 
17
16
  response.body.map do |pl| improve(pl) end
@@ -24,8 +23,7 @@ module OfficialFM
24
23
  # @return [Hashie::Mash] Playlist
25
24
  def playlist(playlist_id, options={})
26
25
  response = connection.get do |req|
27
- req.url "/playlist/#{playlist_id}",
28
- :api_embed_codes => options[:embed]
26
+ req.url "/playlist/#{playlist_id}", simple_params(options)
29
27
  end
30
28
  improve(response.body[0])
31
29
  end
@@ -37,8 +35,7 @@ module OfficialFM
37
35
  # @return [Hashie::Mash] User list
38
36
  def playlist_votes(playlist_id, options={})
39
37
  response = connection.get do |req|
40
- req.url "/playlist/#{playlist_id}/votes",
41
- :api_max_responses => options[:limit]
38
+ req.url "/playlist/#{playlist_id}/votes", simple_params(options)
42
39
  end
43
40
  response.body
44
41
  end
@@ -48,6 +45,101 @@ module OfficialFM
48
45
  playlist.running_time = playlist["length"]
49
46
  playlist
50
47
  end
48
+
49
+ ####################################################################
50
+ ######################### Advanced API methods #####################
51
+ ####################################################################
52
+
53
+ # Update information of a given playlist
54
+ #
55
+ # @param [String] playlist_id: id
56
+ # @param [String] description (optional)
57
+ # @param [String] name (optional)
58
+ # @param [String] password (optional)
59
+ # @param [String] private (optional)
60
+ # @return [Hashie::Mash] Playlist
61
+ def update_playlist! (playlist_id, data = {})
62
+ check_auth :update_playlist
63
+
64
+ response = connection.put do |req|
65
+ req.url "/playlist/update/#{playlist_id}", data
66
+ req.body = { :format => @format }
67
+ end
68
+ response
69
+ end
70
+
71
+ # Upload a picture for a given playlist
72
+ #
73
+ # @param [String] playlist_id: id
74
+ # @param [String] path: path to a picture
75
+ # @param [String] mime: the mime-type of the picture (e.g. image/jpeg, image/png, etc.)
76
+ # @return [Hashie::Mash] Success or error message
77
+ def playlist_picture! (playlist_id, path, mime)
78
+ check_auth :playlist_picture
79
+
80
+ response = connection.post do |req|
81
+ req.url "/playlist/picture/#{playlist_id}"
82
+ req.body = { :file => Faraday::UploadIO.new(path, mime), :format => @format }
83
+ end
84
+ response
85
+ end
86
+
87
+ # Remove a playlist
88
+ #
89
+ # @param [String] playlist_id: id
90
+ # @return [Hashie::Mash] Success or error message
91
+ def playlist_remove! (playlist_id)
92
+ check_auth :playlist_remove
93
+
94
+ response = connection.delete do |req|
95
+ req.url "/playlist/remove/#{playlist_id}"
96
+ req.body = { :format => @format }
97
+ end
98
+ response
99
+ end
100
+
101
+ # Create a playlist
102
+ #
103
+ # @param [String] name: playlist name
104
+ # @param [String] track_id: first track id of the new playlist
105
+ # @return [Hashie::Mash] Playlist object
106
+ def playlist_create! (name, track_id)
107
+ check_auth :playlist_create
108
+
109
+ response = connection.post do |req|
110
+ req.url "/playlist/create"
111
+ req.body = { :format => @format, :name => name, :track_id => track_id }
112
+ end
113
+ response
114
+ end
115
+
116
+ # Vote for a playlist
117
+ #
118
+ # @param [String] playlist_id: id
119
+ # @return [Hashie::Mash] Success or error message
120
+ def playlist_vote! (playlist_id)
121
+ check_auth :playlist_vote
122
+
123
+ response = connection.post do |req|
124
+ req.url "/playlist/vote/#{playlist_id}"
125
+ req.body = { :format => @format }
126
+ end
127
+ response
128
+ end
129
+
130
+ # Remote vote for a playlist
131
+ #
132
+ # @param [String] playlist_id: id
133
+ # @return [Hashie::Mash] Success or error message
134
+ def playlist_unvote! (playlist_id)
135
+ check_auth :playlist_unvote
136
+
137
+ response = connection.post do |req|
138
+ req.url "/playlist/unvote/#{playlist_id}"
139
+ req.body = { :format => @format }
140
+ end
141
+ response
142
+ end
51
143
 
52
144
  end
53
145
  end
@@ -10,8 +10,7 @@ module OfficialFM
10
10
  # @return [Hashie::Mash] Track list
11
11
  def tracks(search_param, options={})
12
12
  response = connection.get do |req|
13
- req.url "/search/tracks/#{CGI::escape(search_param)}",
14
- :api_max_responses => options[:limit]
13
+ req.url "/search/tracks/#{CGI::escape(search_param)}", simple_params(options)
15
14
  end
16
15
  response.body
17
16
  end
@@ -26,8 +25,7 @@ module OfficialFM
26
25
  # @return [Hashie::Mash] Track
27
26
  def track(track_id, options={})
28
27
  response = connection.get do |req|
29
- req.url "/track/#{track_id}",
30
- :api_embed_codes => options[:embed]
28
+ req.url "/track/#{track_id}", simple_params(options)
31
29
  end
32
30
  response.body[0]
33
31
  end
@@ -39,8 +37,7 @@ module OfficialFM
39
37
  # @return [Hashie::Mash] User list
40
38
  def track_votes(track_id, options={})
41
39
  response = connection.get do |req|
42
- req.url "/track/#{track_id}/votes",
43
- :api_max_responses => options[:limit]
40
+ req.url "/track/#{track_id}/votes", simple_params(options)
44
41
  end
45
42
  response.body
46
43
  end
@@ -55,9 +52,7 @@ module OfficialFM
55
52
  # @return [Hashie::Mash] Track list
56
53
  def charts(charting, options={})
57
54
  response = connection.get do |req|
58
- req.url "/tracks/charts",
59
- :charting => charting, :genre => options[:genre], :country => options[:country],
60
- :api_embed_codes => options[:embed], :api_max_responses => options[:limit]
55
+ req.url "/tracks/charts", simple_params(options)
61
56
  end
62
57
  response.body
63
58
  end
@@ -71,12 +66,142 @@ module OfficialFM
71
66
  # @return [Hashie::Mash] Track list
72
67
  def latest(options={})
73
68
  response = connection.get do |req|
74
- req.url "/tracks/latest", :genre => options[:genre],
75
- :country => options[:country], :api_embed_codes => options[:embed],
76
- :api_max_responses => options[:limit]
69
+ req.url "/tracks/latest", simple_params(options)
77
70
  end
78
71
  response.body
79
72
  end
80
-
73
+
74
+ ####################################################################
75
+ ######################### Advanced API methods #####################
76
+ ####################################################################
77
+
78
+ # Update information of a given track
79
+ #
80
+ # @param [String] track_id: id
81
+ # @param [String] artist_name (optional)
82
+ # @param [String] buy_url (optional)
83
+ # @param [String] country_id (optional)
84
+ # @param [String] downloadable (optional)
85
+ # @param [String] derived_by (optional)
86
+ # @param [String] derived_type (optional)
87
+ # @param [String] description (optional)
88
+ # @param [String] isrc (optional)
89
+ # @param [String] label_name (optional)
90
+ # @param [String] label_none (optional)
91
+ # @param [String] license_type (optional)
92
+ # @param [String] lyrics (optional)
93
+ # @param [String] genre_string (optional)
94
+ # @param [String] original_track_id (optional)
95
+ # @param [String] password (optional)
96
+ # @param [String] pr_url (optional)
97
+ # @param [String] private (optional)
98
+ # @param [String] require_valid_email (optional)
99
+ # @param [String] tag_string (optional)
100
+ # @param [String] title (optional)
101
+ # @param [String] url_1_name (optional)
102
+ # @param [String] url_1 (optional)
103
+ # @param [String] url_2_name (optional)
104
+ # @param [String] url_2 (optional)
105
+ # @param [String] web_url (optional)
106
+ # @return [Hashie::Mash] Track
107
+ def update_track! (track_id, data = {})
108
+ check_auth :update_track
109
+
110
+ response = connection.put do |req|
111
+ req.url "/track/update/#{track_id}", data
112
+ req.body = { :format => @format }
113
+ end
114
+ response
115
+ end
116
+
117
+ # Upload a picture for a given track
118
+ #
119
+ # @param [String] track_id: id
120
+ # @param [String] path: path to a picture
121
+ # @param [String] mime: the mime-type of the picture (e.g. image/jpeg, image/png, etc.)
122
+ # @return [Hashie::Mash] Success or error message
123
+ def track_picture! (track_id, path, mime)
124
+ check_auth :track_picture
125
+
126
+ response = connection.post do |req|
127
+ req.url "/track/picture/#{track_id}"
128
+ req.body = { :file => Faraday::UploadIO.new(path, mime), :format => @format }
129
+ end
130
+ response
131
+ end
132
+
133
+ # Vote for a track
134
+ #
135
+ # @param [String] track_id: id
136
+ # @return [Hashie::Mash] Success or error message
137
+ def track_vote! (track_id)
138
+ check_auth :track_vote
139
+
140
+ response = connection.post do |req|
141
+ req.url "/track/vote/#{track_id}"
142
+ req.body = { :format => @format }
143
+ end
144
+ response
145
+ end
146
+
147
+ # Remove vote for a track
148
+ #
149
+ # @param [String] track_id: id
150
+ # @return [Hashie::Mash] Success or error message
151
+ def track_unvote! (track_id)
152
+ check_auth :track_unvote
153
+
154
+ response = connection.post do |req|
155
+ req.url "/track/unvote/#{track_id}"
156
+ req.body = { :format => @format }
157
+ end
158
+ response
159
+ end
160
+
161
+ # Add a track to a playlist
162
+ #
163
+ # @param [String] track_id: id
164
+ # @param [String] playlist_id: id
165
+ # @return [Hashie::Mash] Success or error message
166
+ def add_track_to_playlist! (track_id)
167
+ check_auth :add_track_to_playlist!
168
+
169
+ response = connection.post do |req|
170
+ req.url "/track/#{track_id}/addto/#{playlist_id}"
171
+ req.body = { :format => @format }
172
+ end
173
+ response
174
+ end
175
+
176
+ # Remove a track from a playlist
177
+ #
178
+ # @param [String] track_id: id
179
+ # @param [String] playlist_id: id
180
+ # @return [Hashie::Mash] Success or error message
181
+ def remove_track_from_playlist! (track_id)
182
+ check_auth :remove_track_from_playlist!
183
+
184
+ response = connection.delete do |req|
185
+ req.url "/track/#{track_id}/removefrom/#{playlist_id}"
186
+ req.body = { :format => @format }
187
+ end
188
+ response
189
+ end
190
+
191
+ # Upload a new track to Official.fm
192
+ #
193
+ # @param [String] path: path to an mp3, 44.1Khz file
194
+ # @param [String] mime: the mime-type of the file (e.g. audio/mpeg, etc.)
195
+ # @return [Hashie::Mash] Track
196
+ def upload! (path, mime)
197
+ check_auth :upload
198
+
199
+ response = connection.post do |req|
200
+ req.url "/track/upload"
201
+ req.body = { :file => Faraday::UploadIO.new(path, mime), :format => @format }
202
+ end
203
+ response
204
+ end
205
+
81
206
  end
82
207
  end
@@ -10,7 +10,7 @@ module OfficialFM
10
10
  # @return [Hashie::Mash] User list
11
11
  def users(search_param, options={})
12
12
  response = connection.get do |req|
13
- req.url "/search/users/#{CGI::escape(search_param)}", :api_max_responses => options[:limit]
13
+ req.url "/search/users/#{CGI::escape(search_param)}", simple_params(options)
14
14
  end
15
15
  response.body
16
16
  end
@@ -21,12 +21,12 @@ module OfficialFM
21
21
  # @return [Hashie::Mash] User
22
22
  def user(user_id)
23
23
  response = connection.get do |req|
24
- req.url "/user/#{user_id}"
24
+ req.url "/user/#{user_id}", simple_params
25
25
  end
26
26
  response.body[0]
27
27
  end
28
28
 
29
- # Retrieve a list of the tracks of this user
29
+ # Retrieve a list of the tracks of a given user
30
30
  #
31
31
  # @param [String] user_id: id or login
32
32
  # @param [Integer] limit (50) limit per page
@@ -34,13 +34,12 @@ module OfficialFM
34
34
  # @return [Hashie::Mash] Track list
35
35
  def user_tracks(user_id, options={})
36
36
  response = connection.get do |req|
37
- req.url "/user/#{user_id}/tracks",
38
- :api_embed_codes => options[:embed], :api_max_responses => options[:limit]
37
+ req.url "/user/#{user_id}/tracks", simple_params(options)
39
38
  end
40
39
  response.body
41
40
  end
42
41
 
43
- # Retrieve a list of the tracks this user has voted for
42
+ # Retrieve a list of the tracks a given user has voted for
44
43
  #
45
44
  # @param [String] user_id: id or login
46
45
  # @param [Integer] limit (50) limit per page
@@ -48,13 +47,12 @@ module OfficialFM
48
47
  # @return [Hashie::Mash] Track list
49
48
  def voted_tracks(user_id, options={})
50
49
  response = connection.get do |req|
51
- req.url "/user/#{user_id}/voted_tracks",
52
- :api_embed_codes => options[:embed], :api_max_responses => options[:limit]
50
+ req.url "/user/#{user_id}/voted_tracks", simple_params(options)
53
51
  end
54
52
  response.body
55
53
  end
56
54
 
57
- # Retrieve a list of the playlists of this user
55
+ # Retrieve a list of the playlists of a given user
58
56
  #
59
57
  # @param [String] user_id: id or login
60
58
  # @param [Integer] limit (50) limit per page
@@ -62,13 +60,12 @@ module OfficialFM
62
60
  # @return [Hashie::Mash] Playlist list
63
61
  def user_playlists(user_id, options={})
64
62
  response = connection.get do |req|
65
- req.url "/user/#{user_id}/playlists",
66
- :api_embed_codes => options[:embed], :api_max_responses => options[:limit]
63
+ req.url "/user/#{user_id}/playlists", simple_params(options)
67
64
  end
68
65
  response.body
69
66
  end
70
67
 
71
- # Retrieve a list of the playlists this user has voted for
68
+ # Retrieve a list of the playlists a given user has voted for
72
69
  #
73
70
  # @param [String] user_id: id or login
74
71
  # @param [Integer] limit (50) limit per page
@@ -76,50 +73,178 @@ module OfficialFM
76
73
  # @return [Hashie::Mash] Playlist list
77
74
  def voted_playlists(user_id, options={})
78
75
  response = connection.get do |req|
79
- req.url "/user/#{user_id}/voted_playlists",
80
- :api_embed_codes => options[:embed], :api_max_responses => options[:limit]
76
+ req.url "/user/#{user_id}/voted_playlists", simple_params(options)
81
77
  end
82
78
  response.body
83
79
  end
84
80
 
85
- # Retrieve a list of the contacts of this user
81
+ # Retrieve a list of the contacts of a given user
86
82
  #
87
83
  # @param [String] user_id: id or login
88
84
  # @param [Integer] limit (50) limit per page
89
85
  # @return [Hashie::Mash] User list
90
86
  def user_contacts(user_id, options={})
91
87
  response = connection.get do |req|
92
- req.url "/user/#{user_id}/contacts",
93
- :api_max_responses => options[:limit]
88
+ req.url "/user/#{user_id}/contacts", simple_params(options)
94
89
  end
95
90
  response.body
96
91
  end
97
92
 
98
- # Retrieve a list of the subscribers of this user
93
+ # Retrieve a list of the subscribers of a given user
99
94
  #
100
95
  # @param [String] user_id: id or login
101
96
  # @param [Integer] limit (50) limit per page
102
97
  # @return [Hashie::Mash] User list
103
98
  def user_subscribers(user_id, options={})
104
99
  response = connection.get do |req|
105
- req.url "/user/#{user_id}/subscribers",
106
- :api_max_responses => options[:limit]
100
+ req.url "/user/#{user_id}/subscribers", simple_params(options)
107
101
  end
108
102
  response.body
109
103
  end
110
104
 
111
- # Retrieve a list of the subscriptions of this user
105
+ # Retrieve a list of the subscriptions of a given user
112
106
  #
113
107
  # @param [String] user_id: id or login
114
108
  # @param [Integer] limit (50) limit per page
115
109
  # @return [Hashie::Mash] User list
116
110
  def user_subscriptions(user_id, options={})
117
111
  response = connection.get do |req|
118
- req.url "/user/#{user_id}/subscriptions",
119
- :api_max_responses => options[:limit]
112
+ req.url "/user/#{user_id}/subscriptions", simple_params(options)
120
113
  end
121
114
  response.body
122
115
  end
116
+
117
+ ####################################################################
118
+ ######################### Advanced API methods #####################
119
+ ####################################################################
120
+
121
+ # Retrieve information about the logged in user
122
+ #
123
+ # @return [Hashie::Mash] User
124
+ def profile
125
+ check_auth :profile
126
+
127
+ response = connection.post do |req|
128
+ req.url '/user/profile'
129
+ req.body = { :format => @format }
130
+ end
131
+ response.body[0]
132
+ end
133
+
134
+ # Retrieve tracks of the logged in user
135
+ #
136
+ # @return [Hashie::Mash] Track list
137
+ def own_tracks
138
+ check_auth :own_tracks
139
+
140
+ response = connection.post do |req|
141
+ req.url '/user/tracks'
142
+ req.body = { :format => @format }
143
+ end
144
+ response
145
+ end
146
+
147
+ # Retrieve dropbox tracks of the logged in user
148
+ #
149
+ # @return [Hashie::Mash] Track list
150
+ def dropbox
151
+ check_auth :dropbox
152
+
153
+ response = connection.post do |req|
154
+ req.url '/user/dropbox'
155
+ req.body = { :format => @format }
156
+ end
157
+ response
158
+ end
159
+
160
+ # Retrieve playlists of the logged in user
161
+ #
162
+ # @return [Hashie::Mash] Playlist list
163
+ def own_playlists
164
+ check_auth :own_playlists
165
+
166
+ response = connection.post do |req|
167
+ req.url '/user/playlists'
168
+ req.body = { :format => @format }
169
+ end
170
+ response
171
+ end
172
+
173
+ # Retrieve events of the logged in user's newsfeed
174
+ #
175
+ # @return [Hashie::Mash] Event list
176
+ def newsfeed
177
+ check_auth :newsfeed
178
+
179
+ response = connection.post do |req|
180
+ req.url '/user/newsfeed'
181
+ req.body = { :format => @format }
182
+ end
183
+ response
184
+ end
185
+
186
+ # Update information of the logged in user
187
+ #
188
+ # @param [String] email (optional)
189
+ # @param [String] city (optional)
190
+ # @param [String] country_id (optional)
191
+ # @param [String] url (optional)
192
+ # @param [String] profile (optional)
193
+ # @param [String] birthdate (optional)
194
+ # @param [String] gender (optional)
195
+ # @return [Hashie::Mash] User
196
+ def update_profile! (data = {})
197
+ check_auth :update
198
+
199
+ response = connection.put do |req|
200
+ req.url '/user/update'
201
+ req.body = { :format => @format }.merge(data)
202
+ end
203
+ response
204
+ end
205
+
206
+ # Subscribe the logged in user to a given user
207
+ #
208
+ # @param [String] user_id: the user to subscribe to
209
+ # @return [Hashie::Mash] Success or error message
210
+ def subscribe! (user_id)
211
+ check_auth :subscribe
212
+
213
+ response = connection.post do |req|
214
+ req.url "/user/subscribe/#{user_id}"
215
+ req.body = { :format => @format }
216
+ end
217
+ response
218
+ end
219
+
220
+ # Unsubscribe the logged in user from a given user
221
+ #
222
+ # @param [String] user_id: the user to unsubscribe from
223
+ # @return [Hashie::Mash] Success or error message
224
+ def unsubscribe! (user_id)
225
+ check_auth :unsubscribe
226
+
227
+ response = connection.post do |req|
228
+ req.url "/user/unsubscribe/#{user_id}"
229
+ req.body = { :format => @format }
230
+ end
231
+ response
232
+ end
233
+
234
+ # Upload a picture for authenticated user
235
+ #
236
+ # @param [String] path: path to a picture
237
+ # @param [String] mime: the mime-type of the picture (e.g. image/jpeg, image/png, etc.)
238
+ # @return [Hashie::Mash] Success or error message
239
+ def picture! (path, mime)
240
+ check_auth :picture
241
+
242
+ response = connection.post do |req|
243
+ req.url "/user/picture"
244
+ req.body = { :file => Faraday::UploadIO.new(path, mime), :format => @format }
245
+ end
246
+ response
247
+ end
123
248
 
124
249
  end
125
250
  end
@@ -1,3 +1,3 @@
1
1
  module OfficialFM
2
- VERSION = '0.0.4'
2
+ VERSION = '0.1.0'
3
3
  end
data/lib/officialfm.rb CHANGED
@@ -7,10 +7,11 @@ module OfficialFM
7
7
 
8
8
  class << self
9
9
  attr_accessor :api_key
10
+ attr_accessor :api_secret
10
11
  attr_accessor :test_mode
11
12
 
12
13
  # Configures default credentials easily
13
- # @yield [api_key]
14
+ # @yield [api_key, username, password]
14
15
  def configure
15
16
  yield self
16
17
  true
data/officialfm.gemspec CHANGED
@@ -1,31 +1,43 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  require File.expand_path('../lib/officialfm/version', __FILE__)
3
3
 
4
- Gem::Specification.new do |s|
5
- s.name = 'officialfm'
6
- s.version = OfficialFM::VERSION
7
- s.authors = ["Amos Wenger"]
8
- s.email = ['amos@official.fm']
9
- s.summary = %q{Official Ruby bindings for the official.fm API}
10
- s.description = %q{Official Ruby bindings for the official.fm API}
11
- s.homepage = 'http://github.com/officialfm/officialfm-ruby'
4
+ Gem::Specification.new do |gem|
5
+ gem.name = 'officialfm'
6
+ gem.version = OfficialFM::VERSION
7
+ gem.authors = ["Amos Wenger"]
8
+ gem.email = ['amos@official.fm']
9
+ gem.summary = %q{Official Ruby bindings for the official.fm API}
10
+ gem.description = %q{Official Ruby bindings for the official.fm API}
11
+ gem.post_install_message =<<eos
12
+ ********************************************************************************
12
13
 
13
- s.add_runtime_dependency 'faraday', '~> 0.5.3'
14
- s.add_runtime_dependency 'faraday_middleware', '~> 0.3.0'
15
- s.add_runtime_dependency 'hashie', '~> 1.0.0'
14
+ Follow @ofmdev on Twitter for announcements, updates, and news.
15
+ https://twitter.com/ofmdev
16
16
 
17
- s.add_development_dependency 'bundler', '~> 1.0'
18
- s.add_development_dependency 'fakeweb', '~> 1.3'
19
- s.add_development_dependency 'jnunemaker-matchy', '~> 0.4'
20
- s.add_development_dependency 'json_pure', '~> 1.4'
21
- s.add_development_dependency 'rake', '~> 0.8'
22
- s.add_development_dependency 'shoulda', '~> 2.11'
23
- s.add_development_dependency 'test-unit', '~> 2.1'
17
+ Don't forget to get your API key here:
18
+ http://official.fm/developers/manager
24
19
 
25
- s.required_rubygems_version = Gem::Requirement.new('>= 1.3.6') if s.respond_to? :required_rubygems_version=
26
- s.platform = Gem::Platform::RUBY
27
- s.require_paths = ['lib']
28
- s.files = `git ls-files`.split("\n")
29
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
30
- s.executables = `git ls-files -- bin/*`.split("\n").map{|f| File.basename(f)}
20
+ ********************************************************************************
21
+ eos
22
+ gem.homepage = 'http://github.com/officialfm/officialfm-ruby'
23
+
24
+ gem.add_runtime_dependency 'faraday', '~> 0.6'
25
+ gem.add_runtime_dependency 'faraday_middleware', '~> 0.6'
26
+ gem.add_runtime_dependency 'hashie', '~> 1.0.0'
27
+ gem.add_runtime_dependency 'simple_oauth', '~> 0.1.5'
28
+
29
+ gem.add_development_dependency 'bundler', '~> 1.0'
30
+ gem.add_development_dependency 'fakeweb', '~> 1.3'
31
+ gem.add_development_dependency 'jnunemaker-matchy', '~> 0.4'
32
+ gem.add_development_dependency 'json_pure', '~> 1.5'
33
+ gem.add_development_dependency 'rake', '~> 0.8'
34
+ gem.add_development_dependency 'shoulda', '~> 2.11'
35
+ gem.add_development_dependency 'test-unit', '~> 2.1'
36
+
37
+ gem.required_rubygems_version = Gem::Requirement.new('>= 1.3.6') if gem.respond_to? :required_rubygems_version=
38
+ gem.platform = Gem::Platform::RUBY
39
+ gem.require_paths = ['lib']
40
+ gem.files = `git ls-files`.split("\n")
41
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
42
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{|f| File.basename(f)}
31
43
  end
@@ -1,92 +1,122 @@
1
1
  [
2
- {
3
- "comments_count":0,
4
- "created_at":"2011-03-01 00:33:20 UTC",
5
- "description":"",
6
- "id":55569,
7
-
8
- "length":4167,
9
-
10
- "name":"HLSTRS – Timeless (Mixtape for HARTZINE)",
11
- "picture_absolute_url":"http://cdn.official.fm/playlist_pictures/55/55569_small.jpg",
12
- "plays_count":50,
13
- "shuffle":false,
14
-
15
-
16
- "tracks_count":16,
17
- "tracks_list":"216372,216374,216378,216381,216384,216385,216388,216389,216392,216390,216395,216394,216396,216398,216400,216399",
18
- "user_id":46269,
19
- "votes_count":1 },
20
- {
21
- "comments_count":0,
22
- "created_at":"2011-02-02 12:12:39 UTC",
23
- "description":null,
24
- "id":53202,
25
-
26
- "length":1575,
27
-
28
- "name":"MIXTAPE BOOTIE RIO NATAL",
29
- "picture_absolute_url":"http://cdn.official.fm/playlist_pictures/53/53202_small.jpg",
30
- "plays_count":4,
31
- "shuffle":false,
32
-
33
-
34
- "tracks_count":1,
35
- "tracks_list":"183448",
36
- "user_id":98816,
37
- "votes_count":0 },
38
- {
39
- "comments_count":0,
40
- "created_at":"2011-02-02 12:12:59 UTC",
41
- "description":null,
42
- "id":53203,
43
-
44
- "length":3768,
45
-
46
- "name":"MIXTAPE TEM GRINGO NO MASHUP",
47
- "picture_absolute_url":"http://cdn.official.fm/playlist_pictures/53/53203_small.jpg",
48
- "plays_count":1,
49
- "shuffle":false,
50
-
51
-
52
- "tracks_count":1,
53
- "tracks_list":"178977",
54
- "user_id":98816,
55
- "votes_count":0 },
56
- {
57
- "comments_count":0,
58
- "created_at":"2008-09-20 17:36:33 UTC",
59
- "description":"",
60
- "id":5801,
61
-
62
- "length":940,
63
-
64
- "name":"Rap",
65
- "picture_absolute_url":"http://cdn.official.fm/playlist_pictures/5/5801_small.jpg",
66
- "plays_count":3,
67
- "shuffle":false,
68
-
69
-
70
- "tracks_count":4,
71
- "tracks_list":"57854,40057,11290,12818",
72
- "user_id":6603,
73
- "votes_count":0 },
74
- {
75
- "comments_count":0,
76
- "created_at":"2011-01-24 14:25:27 UTC",
77
- "description":"",
78
- "id":52439,
79
-
80
- "length":1055,
81
-
82
- "name":"pop4",
83
- "picture_absolute_url":"http://cdn.official.fm/playlist_pictures/52/52439_small.jpg",
84
- "plays_count":1,
85
- "shuffle":false,
86
-
87
-
88
- "tracks_count":4,
89
- "tracks_list":"24970,26036,26032,25520",
90
- "user_id":51638,
91
- "votes_count":0 }
2
+ {
3
+
4
+ "comments_count":0,
5
+ "created_at":"2011-03-01 00:33:20 UTC",
6
+ "description":"",
7
+ "id":55569,
8
+
9
+ "length":4167,
10
+
11
+ "name":"HLSTRS – Timeless (Mixtape for HARTZINE)",
12
+ "password":null,
13
+ "picture_absolute_url":"http://cdn.official.fm/playlist_pictures/55/55569_small.jpg",
14
+ "plays_count":74,
15
+ "private":false,
16
+ "shuffle":false,
17
+
18
+
19
+ "tracks":[216372,216374,216378,216381,216384,216385,216388,216389,216392,216390,216395,216394,216396,216398,216400,216399],
20
+ "tracks_count":16,
21
+ "tracks_list":"216372,216374,216378,216381,216384,216385,216388,216389,216392,216390,216395,216394,216396,216398,216400,216399",
22
+ "user_id":46269,
23
+ "votes_count":1
24
+ }
25
+ ,
26
+ {
27
+
28
+ "comments_count":0,
29
+ "created_at":"2011-06-11 06:12:57 UTC",
30
+ "description":null,
31
+ "id":64801,
32
+
33
+ "length":1946,
34
+
35
+ "name":"Guruguru",
36
+ "password":null,
37
+ "picture_absolute_url":"http://cdn.official.fm/track_pictures/252/252818_small.jpg",
38
+ "plays_count":0,
39
+ "private":false,
40
+ "shuffle":false,
41
+
42
+
43
+ "tracks":[252818],
44
+ "tracks_count":1,
45
+ "tracks_list":"252818",
46
+ "user_id":119997,
47
+ "votes_count":0
48
+ }
49
+ ,
50
+ {
51
+
52
+ "comments_count":0,
53
+ "created_at":"2011-02-02 12:12:39 UTC",
54
+ "description":null,
55
+ "id":53202,
56
+
57
+ "length":1575,
58
+
59
+ "name":"MIXTAPE BOOTIE RIO NATAL",
60
+ "password":null,
61
+ "picture_absolute_url":"http://cdn.official.fm/track_pictures/183/183448_small.jpg",
62
+ "plays_count":4,
63
+ "private":false,
64
+ "shuffle":false,
65
+
66
+
67
+ "tracks":[183448],
68
+ "tracks_count":1,
69
+ "tracks_list":"183448",
70
+ "user_id":98816,
71
+ "votes_count":0
72
+ }
73
+ ,
74
+ {
75
+
76
+ "comments_count":0,
77
+ "created_at":"2011-02-02 12:12:59 UTC",
78
+ "description":null,
79
+ "id":53203,
80
+
81
+ "length":3768,
82
+
83
+ "name":"MIXTAPE TEM GRINGO NO MASHUP",
84
+ "password":null,
85
+ "picture_absolute_url":"http://cdn.official.fm/track_pictures/178/178977_small.jpg",
86
+ "plays_count":2,
87
+ "private":false,
88
+ "shuffle":false,
89
+
90
+
91
+ "tracks":[178977],
92
+ "tracks_count":1,
93
+ "tracks_list":"178977",
94
+ "user_id":98816,
95
+ "votes_count":0
96
+ }
97
+ ,
98
+ {
99
+
100
+ "comments_count":0,
101
+ "created_at":"2008-09-20 17:36:33 UTC",
102
+ "description":"",
103
+ "id":5801,
104
+
105
+ "length":940,
106
+
107
+ "name":"Rap",
108
+ "password":null,
109
+ "picture_absolute_url":"http://cdn.official.fm/track_pictures/57/57854_small.jpg",
110
+ "plays_count":3,
111
+ "private":false,
112
+ "shuffle":false,
113
+
114
+
115
+ "tracks":[57854,40057,11290,12818],
116
+ "tracks_count":4,
117
+ "tracks_list":"57854,40057,11290,12818",
118
+ "user_id":6603,
119
+ "votes_count":0
120
+ }
121
+
92
122
  ]
@@ -12,7 +12,7 @@ class PlaylistsTests < Test::Unit::TestCase
12
12
  tracks = @client.playlists('R&B Mixtape', :limit => 5)
13
13
  tracks.length.should == 5
14
14
  tracks[0].running_time.should == 4167
15
- tracks[3].tracks[1].should == 40057
15
+ tracks[4].tracks[3].should == 12818
16
16
  end
17
17
 
18
18
  should "get info on HLSTRS Timeless mixtape" do
metadata CHANGED
@@ -1,138 +1,144 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: officialfm
3
- version: !ruby/object:Gem::Version
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
4
5
  prerelease:
5
- version: 0.0.4
6
6
  platform: ruby
7
- authors:
7
+ authors:
8
8
  - Amos Wenger
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
-
13
- date: 2011-05-20 00:00:00 +02:00
14
- default_executable:
15
- dependencies:
16
- - !ruby/object:Gem::Dependency
12
+ date: 2011-08-03 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
17
15
  name: faraday
18
- prerelease: false
19
- requirement: &id001 !ruby/object:Gem::Requirement
16
+ requirement: &73388740 !ruby/object:Gem::Requirement
20
17
  none: false
21
- requirements:
18
+ requirements:
22
19
  - - ~>
23
- - !ruby/object:Gem::Version
24
- version: 0.5.3
20
+ - !ruby/object:Gem::Version
21
+ version: '0.6'
25
22
  type: :runtime
26
- version_requirements: *id001
27
- - !ruby/object:Gem::Dependency
28
- name: faraday_middleware
29
23
  prerelease: false
30
- requirement: &id002 !ruby/object:Gem::Requirement
24
+ version_requirements: *73388740
25
+ - !ruby/object:Gem::Dependency
26
+ name: faraday_middleware
27
+ requirement: &73388490 !ruby/object:Gem::Requirement
31
28
  none: false
32
- requirements:
29
+ requirements:
33
30
  - - ~>
34
- - !ruby/object:Gem::Version
35
- version: 0.3.0
31
+ - !ruby/object:Gem::Version
32
+ version: '0.6'
36
33
  type: :runtime
37
- version_requirements: *id002
38
- - !ruby/object:Gem::Dependency
39
- name: hashie
40
34
  prerelease: false
41
- requirement: &id003 !ruby/object:Gem::Requirement
35
+ version_requirements: *73388490
36
+ - !ruby/object:Gem::Dependency
37
+ name: hashie
38
+ requirement: &73388260 !ruby/object:Gem::Requirement
42
39
  none: false
43
- requirements:
40
+ requirements:
44
41
  - - ~>
45
- - !ruby/object:Gem::Version
42
+ - !ruby/object:Gem::Version
46
43
  version: 1.0.0
47
44
  type: :runtime
48
- version_requirements: *id003
49
- - !ruby/object:Gem::Dependency
50
- name: bundler
51
45
  prerelease: false
52
- requirement: &id004 !ruby/object:Gem::Requirement
46
+ version_requirements: *73388260
47
+ - !ruby/object:Gem::Dependency
48
+ name: simple_oauth
49
+ requirement: &73388030 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 0.1.5
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *73388030
58
+ - !ruby/object:Gem::Dependency
59
+ name: bundler
60
+ requirement: &73387800 !ruby/object:Gem::Requirement
53
61
  none: false
54
- requirements:
62
+ requirements:
55
63
  - - ~>
56
- - !ruby/object:Gem::Version
57
- version: "1.0"
64
+ - !ruby/object:Gem::Version
65
+ version: '1.0'
58
66
  type: :development
59
- version_requirements: *id004
60
- - !ruby/object:Gem::Dependency
61
- name: fakeweb
62
67
  prerelease: false
63
- requirement: &id005 !ruby/object:Gem::Requirement
68
+ version_requirements: *73387800
69
+ - !ruby/object:Gem::Dependency
70
+ name: fakeweb
71
+ requirement: &73387570 !ruby/object:Gem::Requirement
64
72
  none: false
65
- requirements:
73
+ requirements:
66
74
  - - ~>
67
- - !ruby/object:Gem::Version
68
- version: "1.3"
75
+ - !ruby/object:Gem::Version
76
+ version: '1.3'
69
77
  type: :development
70
- version_requirements: *id005
71
- - !ruby/object:Gem::Dependency
72
- name: jnunemaker-matchy
73
78
  prerelease: false
74
- requirement: &id006 !ruby/object:Gem::Requirement
79
+ version_requirements: *73387570
80
+ - !ruby/object:Gem::Dependency
81
+ name: jnunemaker-matchy
82
+ requirement: &73387340 !ruby/object:Gem::Requirement
75
83
  none: false
76
- requirements:
84
+ requirements:
77
85
  - - ~>
78
- - !ruby/object:Gem::Version
79
- version: "0.4"
86
+ - !ruby/object:Gem::Version
87
+ version: '0.4'
80
88
  type: :development
81
- version_requirements: *id006
82
- - !ruby/object:Gem::Dependency
83
- name: json_pure
84
89
  prerelease: false
85
- requirement: &id007 !ruby/object:Gem::Requirement
90
+ version_requirements: *73387340
91
+ - !ruby/object:Gem::Dependency
92
+ name: json_pure
93
+ requirement: &73387110 !ruby/object:Gem::Requirement
86
94
  none: false
87
- requirements:
95
+ requirements:
88
96
  - - ~>
89
- - !ruby/object:Gem::Version
90
- version: "1.4"
97
+ - !ruby/object:Gem::Version
98
+ version: '1.5'
91
99
  type: :development
92
- version_requirements: *id007
93
- - !ruby/object:Gem::Dependency
94
- name: rake
95
100
  prerelease: false
96
- requirement: &id008 !ruby/object:Gem::Requirement
101
+ version_requirements: *73387110
102
+ - !ruby/object:Gem::Dependency
103
+ name: rake
104
+ requirement: &73386880 !ruby/object:Gem::Requirement
97
105
  none: false
98
- requirements:
106
+ requirements:
99
107
  - - ~>
100
- - !ruby/object:Gem::Version
101
- version: "0.8"
108
+ - !ruby/object:Gem::Version
109
+ version: '0.8'
102
110
  type: :development
103
- version_requirements: *id008
104
- - !ruby/object:Gem::Dependency
105
- name: shoulda
106
111
  prerelease: false
107
- requirement: &id009 !ruby/object:Gem::Requirement
112
+ version_requirements: *73386880
113
+ - !ruby/object:Gem::Dependency
114
+ name: shoulda
115
+ requirement: &73386650 !ruby/object:Gem::Requirement
108
116
  none: false
109
- requirements:
117
+ requirements:
110
118
  - - ~>
111
- - !ruby/object:Gem::Version
112
- version: "2.11"
119
+ - !ruby/object:Gem::Version
120
+ version: '2.11'
113
121
  type: :development
114
- version_requirements: *id009
115
- - !ruby/object:Gem::Dependency
116
- name: test-unit
117
122
  prerelease: false
118
- requirement: &id010 !ruby/object:Gem::Requirement
123
+ version_requirements: *73386650
124
+ - !ruby/object:Gem::Dependency
125
+ name: test-unit
126
+ requirement: &73386420 !ruby/object:Gem::Requirement
119
127
  none: false
120
- requirements:
128
+ requirements:
121
129
  - - ~>
122
- - !ruby/object:Gem::Version
123
- version: "2.1"
130
+ - !ruby/object:Gem::Version
131
+ version: '2.1'
124
132
  type: :development
125
- version_requirements: *id010
133
+ prerelease: false
134
+ version_requirements: *73386420
126
135
  description: Official Ruby bindings for the official.fm API
127
- email:
136
+ email:
128
137
  - amos@official.fm
129
138
  executables: []
130
-
131
139
  extensions: []
132
-
133
140
  extra_rdoc_files: []
134
-
135
- files:
141
+ files:
136
142
  - .document
137
143
  - .gitignore
138
144
  - Gemfile
@@ -140,7 +146,9 @@ files:
140
146
  - README.md
141
147
  - Rakefile
142
148
  - changelog.md
149
+ - lib/faraday/request/officialfm_oauth.rb
143
150
  - lib/officialfm.rb
151
+ - lib/officialfm/authentication.rb
144
152
  - lib/officialfm/client.rb
145
153
  - lib/officialfm/playlists.rb
146
154
  - lib/officialfm/tracks.rb
@@ -167,52 +175,30 @@ files:
167
175
  - test/playlists_test.rb
168
176
  - test/tracks_test.rb
169
177
  - test/users_test.rb
170
- has_rdoc: true
171
178
  homepage: http://github.com/officialfm/officialfm-ruby
172
179
  licenses: []
173
-
174
- post_install_message:
180
+ post_install_message: ! "********************************************************************************\n\n
181
+ \ Follow @ofmdev on Twitter for announcements, updates, and news.\n https://twitter.com/ofmdev\n\n
182
+ \ Don't forget to get your API key here:\n http://official.fm/developers/manager\n\n********************************************************************************\n"
175
183
  rdoc_options: []
176
-
177
- require_paths:
184
+ require_paths:
178
185
  - lib
179
- required_ruby_version: !ruby/object:Gem::Requirement
186
+ required_ruby_version: !ruby/object:Gem::Requirement
180
187
  none: false
181
- requirements:
182
- - - ">="
183
- - !ruby/object:Gem::Version
184
- version: "0"
185
- required_rubygems_version: !ruby/object:Gem::Requirement
188
+ requirements:
189
+ - - ! '>='
190
+ - !ruby/object:Gem::Version
191
+ version: '0'
192
+ required_rubygems_version: !ruby/object:Gem::Requirement
186
193
  none: false
187
- requirements:
188
- - - ">="
189
- - !ruby/object:Gem::Version
194
+ requirements:
195
+ - - ! '>='
196
+ - !ruby/object:Gem::Version
190
197
  version: 1.3.6
191
198
  requirements: []
192
-
193
199
  rubyforge_project:
194
- rubygems_version: 1.6.2
200
+ rubygems_version: 1.8.6
195
201
  signing_key:
196
202
  specification_version: 3
197
203
  summary: Official Ruby bindings for the official.fm API
198
- test_files:
199
- - test/fixtures/charts.json
200
- - test/fixtures/latest.json
201
- - test/fixtures/playlist.json
202
- - test/fixtures/playlist_votes.json
203
- - test/fixtures/playlists.json
204
- - test/fixtures/track.json
205
- - test/fixtures/track_votes.json
206
- - test/fixtures/tracks.json
207
- - test/fixtures/user.json
208
- - test/fixtures/user_contacts.json
209
- - test/fixtures/user_playlists.json
210
- - test/fixtures/user_subscribers.json
211
- - test/fixtures/user_subscriptions.json
212
- - test/fixtures/user_tracks.json
213
- - test/fixtures/voted_playlists.json
214
- - test/fixtures/voted_tracks.json
215
- - test/helper.rb
216
- - test/playlists_test.rb
217
- - test/tracks_test.rb
218
- - test/users_test.rb
204
+ test_files: []