metal_archives 2.2.0 → 3.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ci.yml +93 -0
  3. data/.gitignore +6 -6
  4. data/.overcommit.yml +35 -0
  5. data/.rspec +2 -0
  6. data/.rubocop.yml +66 -6
  7. data/CHANGELOG.md +33 -0
  8. data/Gemfile +1 -1
  9. data/LICENSE.md +17 -4
  10. data/README.md +65 -86
  11. data/Rakefile +8 -7
  12. data/bin/console +38 -0
  13. data/bin/setup +8 -0
  14. data/config/inflections.rb +7 -0
  15. data/config/initializers/.keep +0 -0
  16. data/docker-compose.yml +23 -0
  17. data/lib/metal_archives.rb +82 -27
  18. data/lib/metal_archives/cache/base.rb +40 -0
  19. data/lib/metal_archives/cache/memory.rb +68 -0
  20. data/lib/metal_archives/cache/null.rb +22 -0
  21. data/lib/metal_archives/cache/redis.rb +49 -0
  22. data/lib/metal_archives/{utils/collection.rb → collection.rb} +3 -5
  23. data/lib/metal_archives/configuration.rb +33 -50
  24. data/lib/metal_archives/{error.rb → errors.rb} +9 -1
  25. data/lib/metal_archives/http_client.rb +45 -44
  26. data/lib/metal_archives/models/artist.rb +90 -45
  27. data/lib/metal_archives/models/band.rb +77 -52
  28. data/lib/metal_archives/models/base.rb +225 -0
  29. data/lib/metal_archives/models/label.rb +14 -15
  30. data/lib/metal_archives/models/release.rb +25 -29
  31. data/lib/metal_archives/parsers/artist.rb +86 -50
  32. data/lib/metal_archives/parsers/band.rb +155 -88
  33. data/lib/metal_archives/parsers/base.rb +14 -0
  34. data/lib/metal_archives/parsers/country.rb +21 -0
  35. data/lib/metal_archives/parsers/date.rb +31 -0
  36. data/lib/metal_archives/parsers/genre.rb +67 -0
  37. data/lib/metal_archives/parsers/label.rb +39 -31
  38. data/lib/metal_archives/parsers/parser.rb +18 -63
  39. data/lib/metal_archives/parsers/release.rb +98 -89
  40. data/lib/metal_archives/parsers/year.rb +31 -0
  41. data/lib/metal_archives/version.rb +12 -1
  42. data/metal_archives.env.example +10 -0
  43. data/metal_archives.gemspec +43 -28
  44. data/nginx/default.conf +60 -0
  45. metadata +179 -74
  46. data/.travis.yml +0 -12
  47. data/lib/metal_archives/middleware/cache_check.rb +0 -20
  48. data/lib/metal_archives/middleware/encoding.rb +0 -16
  49. data/lib/metal_archives/middleware/headers.rb +0 -38
  50. data/lib/metal_archives/middleware/rewrite_endpoint.rb +0 -38
  51. data/lib/metal_archives/models/base_model.rb +0 -215
  52. data/lib/metal_archives/utils/lru_cache.rb +0 -61
  53. data/lib/metal_archives/utils/nil_date.rb +0 -99
  54. data/lib/metal_archives/utils/range.rb +0 -66
  55. data/spec/configuration_spec.rb +0 -96
  56. data/spec/factories/artist_factory.rb +0 -37
  57. data/spec/factories/band_factory.rb +0 -60
  58. data/spec/factories/nil_date_factory.rb +0 -9
  59. data/spec/factories/range_factory.rb +0 -8
  60. data/spec/models/artist_spec.rb +0 -138
  61. data/spec/models/band_spec.rb +0 -164
  62. data/spec/models/base_model_spec.rb +0 -219
  63. data/spec/models/release_spec.rb +0 -133
  64. data/spec/parser_spec.rb +0 -19
  65. data/spec/spec_helper.rb +0 -111
  66. data/spec/support/factory_girl.rb +0 -5
  67. data/spec/support/metal_archives.rb +0 -33
  68. data/spec/utils/collection_spec.rb +0 -72
  69. data/spec/utils/lru_cache_spec.rb +0 -53
  70. data/spec/utils/nil_date_spec.rb +0 -156
  71. data/spec/utils/range_spec.rb +0 -62
@@ -32,7 +32,15 @@ module MetalArchives
32
32
  ##
33
33
  # Error in backend response
34
34
  #
35
- class APIError < Error; end
35
+ class APIError < Error
36
+ attr_reader :code
37
+
38
+ def initialize(response)
39
+ super("#{response.reason}: #{response.body}")
40
+
41
+ @code = response.code
42
+ end
43
+ end
36
44
 
37
45
  ##
38
46
  # Error in method argument
@@ -1,61 +1,62 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'faraday'
4
- require 'faraday_throttler'
3
+ require "http"
5
4
 
6
5
  module MetalArchives
7
6
  ##
8
- # HTTP request client
7
+ # Generic HTTP client
9
8
  #
10
- class HTTPClient # :nodoc:
11
- class << self
12
- ##
13
- # Retrieve a HTTP resource
14
- #
15
- # [Raises]
16
- # - rdoc-ref:MetalArchives::Errors::InvalidIDError when receiving a status code == 404n
17
- # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
18
- #
19
- def get(*params)
20
- response = client.get(*params)
21
-
22
- raise Errors::InvalidIDError, response.status if response.status == 404
23
- raise Errors::APIError, response.status if response.status >= 400
24
-
25
- response
26
- rescue Faraday::Error::ClientError => e
27
- MetalArchives.config.logger.error e.response
28
- raise Errors::APIError, e
9
+ class HTTPClient
10
+ attr_reader :endpoint, :metrics
11
+
12
+ def initialize(endpoint = MetalArchives.config.endpoint)
13
+ @endpoint = endpoint
14
+ @metrics = { hit: 0, miss: 0 }
15
+ end
16
+
17
+ def get(path, params = {})
18
+ response = http
19
+ .get(url_for(path), params: params)
20
+
21
+ # Log cache status
22
+ status = response.headers["x-cache-status"]&.downcase&.to_sym
23
+ MetalArchives.config.logger.info "Cache #{status} for #{path}" if status
24
+
25
+ case status
26
+ when :hit
27
+ metrics[:hit] += 1
28
+ when :miss, :bypass, :expired, :stale, :updating, :revalidated
29
+ metrics[:miss] += 1
29
30
  end
31
+ raise Errors::InvalidIDError, response if response.code == 404
32
+ raise Errors::APIError, response unless response.status.success?
30
33
 
31
- private
34
+ response
35
+ end
32
36
 
33
- ##
34
- # Retrieve a HTTP client
35
- #
36
- #
37
- def client
38
- raise Errors::InvalidConfigurationError, 'Not configured yet' unless MetalArchives.config
37
+ private
39
38
 
40
- @faraday ||= Faraday.new do |f|
41
- f.request :url_encoded # form-encode POST params
42
- f.response :logger, MetalArchives.config.logger
39
+ def http
40
+ @http ||= HTTP
41
+ .headers(headers)
42
+ .use(logging: { logger: MetalArchives.config.logger })
43
+ .encoding("utf-8")
43
44
 
44
- f.use MetalArchives::Middleware::Headers
45
- f.use MetalArchives::Middleware::CacheCheck
46
- f.use MetalArchives::Middleware::RewriteEndpoint
47
- f.use MetalArchives::Middleware::Encoding
45
+ return @http unless MetalArchives.config.endpoint_user && MetalArchives.config.endpoint_password
48
46
 
49
- MetalArchives.config.middleware.each { |m| f.use m } if MetalArchives.config.middleware
47
+ @http
48
+ .basic_auth(user: MetalArchives.config.endpoint_user, pass: MetalArchives.config.endpoint_password)
49
+ end
50
50
 
51
- f.use :throttler,
52
- :rate => MetalArchives.config.request_rate,
53
- :wait => MetalArchives.config.request_timeout,
54
- :logger => MetalArchives.config.logger
51
+ def headers
52
+ {
53
+ user_agent: "#{MetalArchives.config.app_name}/#{MetalArchives.config.app_version} (#{MetalArchives.config.app_contact})",
54
+ accept: "application/json",
55
+ }
56
+ end
55
57
 
56
- f.adapter Faraday.default_adapter
57
- end
58
- end
58
+ def url_for(path)
59
+ "#{endpoint}#{path}"
59
60
  end
60
61
  end
61
62
  end
@@ -1,19 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'date'
4
- require 'countries'
3
+ require "date"
4
+ require "countries"
5
+ require "nokogiri"
5
6
 
6
7
  module MetalArchives
7
8
  ##
8
9
  # Represents a single performer (but not a solo artist)
9
10
  #
10
- class Artist < MetalArchives::BaseModel
11
+ class Artist < Base
11
12
  ##
12
13
  # :attr_reader: id
13
14
  #
14
15
  # Returns +Integer+
15
16
  #
16
- property :id, :type => Integer
17
+ property :id, type: Integer
17
18
 
18
19
  ##
19
20
  # :attr_reader: name
@@ -35,7 +36,7 @@ module MetalArchives
35
36
  # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
36
37
  # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
37
38
  #
38
- property :aliases, :multiple => true
39
+ property :aliases, multiple: true
39
40
 
40
41
  ##
41
42
  # :attr_reader: country
@@ -46,7 +47,7 @@ module MetalArchives
46
47
  # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
47
48
  # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
48
49
  #
49
- property :country, :type => ISO3166::Country
50
+ property :country, type: ISO3166::Country
50
51
 
51
52
  ##
52
53
  # :attr_reader: location
@@ -62,24 +63,24 @@ module MetalArchives
62
63
  ##
63
64
  # :attr_reader: date_of_birth
64
65
  #
65
- # Returns rdoc-ref:NilDate
66
+ # Returns rdoc-ref:Date
66
67
  #
67
68
  # [Raises]
68
69
  # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
69
70
  # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
70
71
  #
71
- property :date_of_birth, :type => NilDate
72
+ property :date_of_birth, type: Date
72
73
 
73
74
  ##
74
75
  # :attr_reader: date_of_death
75
76
  #
76
- # Returns rdoc-ref:NilDate
77
+ # Returns rdoc-ref:Date
77
78
  #
78
79
  # [Raises]
79
80
  # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
80
81
  # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
81
82
  #
82
- property :date_of_death, :type => NilDate
83
+ property :date_of_death, type: Date
83
84
 
84
85
  ##
85
86
  # :attr_reader: cause_of_death
@@ -101,7 +102,7 @@ module MetalArchives
101
102
  # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
102
103
  # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
103
104
  #
104
- enum :gender, :values => %i[male female]
105
+ enum :gender, values: [:male, :female]
105
106
 
106
107
  ##
107
108
  # :attr_reader: biography
@@ -145,17 +146,67 @@ module MetalArchives
145
146
  # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
146
147
  # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
147
148
  #
148
- # [+similar+]
149
+ # [+links+]
149
150
  # - +:url+: +String+
150
151
  # - +:type+: +Symbol+, either +:official+, +:unofficial+ or +:unlisted_bands+
151
152
  # - +:title+: +String+
152
153
  #
153
- property :links, :multiple => true
154
+ property :links, multiple: true
154
155
 
155
- # TODO: active bands/albums
156
- # TODO: past bands/albums
157
- # TODO: guest bands/albums
158
- # TODO: misc bands/albums
156
+ ##
157
+ # :attr_reader: bands
158
+ #
159
+ # Returns +Array+ of +Hash+ containing the following keys
160
+ #
161
+ # [Raises]
162
+ # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
163
+ # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
164
+ #
165
+ # [+bands+]
166
+ # - +:band+: rdoc-ref:Band
167
+ # - +:active+: Boolean
168
+ # - +:years_active+: +Array+ of rdoc-ref:Range containing +Integer+
169
+ # - +:role+: +String+
170
+ #
171
+ property :bands, type: Hash, multiple: true
172
+
173
+ # TODO: guest/session bands
174
+ # TODO: misc bands
175
+
176
+ ##
177
+ # Serialize to hash
178
+ #
179
+ def to_h
180
+ {
181
+ type: "artist",
182
+ id: id,
183
+ name: name,
184
+ aliases: aliases || [],
185
+ country: country&.alpha3,
186
+ location: location,
187
+ date_of_birth: date_of_birth&.iso8601,
188
+ date_of_death: date_of_death&.iso8601,
189
+ cause_of_death: cause_of_death,
190
+ gender: gender,
191
+ biography: biography,
192
+ trivia: trivia,
193
+ photo: photo,
194
+ links: links || [],
195
+ bands: bands || [],
196
+ }
197
+ end
198
+
199
+ ##
200
+ # Deserialize from hash
201
+ #
202
+ def self.from_h(hash)
203
+ return unless hash.fetch(:type) == "artist"
204
+
205
+ new(hash.slice(:id, :name, :aliases, :location, :cause_of_death, :gender, :biography, :trivial, :photo, :links, :bands))
206
+ .tap { |m| m.country = ISO3166::Country[hash[:country]] }
207
+ .tap { |m| m.date_of_birth = Date.parse(hash[:date_of_birth]) if hash[:date_of_birth] }
208
+ .tap { |m| m.date_of_death = Date.parse(hash[:date_of_death]) if hash[:date_of_death] }
209
+ end
159
210
 
160
211
  protected
161
212
 
@@ -168,28 +219,24 @@ module MetalArchives
168
219
  #
169
220
  def assemble # :nodoc:
170
221
  ## Base attributes
171
- url = "#{MetalArchives.config.default_endpoint}artist/view/id/#{id}"
172
- response = HTTPClient.get url
222
+ response = MetalArchives.http.get "/artist/view/id/#{id}"
173
223
 
174
- properties = Parsers::Artist.parse_html response.body
224
+ properties = Parsers::Artist.parse_html response.to_s
175
225
 
176
226
  ## Biography
177
- url = "#{MetalArchives.config.default_endpoint}artist/read-more/id/#{id}/field/biography"
178
- response = HTTPClient.get url
227
+ response = MetalArchives.http.get "/artist/read-more/id/#{id}/field/biography"
179
228
 
180
- properties[:biography] = response.body
229
+ properties[:biography] = response.to_s
181
230
 
182
231
  ## Trivia
183
- url = "#{MetalArchives.config.default_endpoint}artist/read-more/id/#{id}/field/trivia"
184
- response = HTTPClient.get url
232
+ response = MetalArchives.http.get "/artist/read-more/id/#{id}/field/trivia"
185
233
 
186
- properties[:trivia] = response.body
234
+ properties[:trivia] = response.to_s
187
235
 
188
236
  ## Related links
189
- url = "#{MetalArchives.config.default_endpoint}link/ajax-list/type/person/id/#{id}"
190
- response = HTTPClient.get url
237
+ response = MetalArchives.http.get "/link/ajax-list/type/person/id/#{id}"
191
238
 
192
- properties[:links] = Parsers::Artist.parse_links_html response.body
239
+ properties[:links] = Parsers::Artist.parse_links_html response.to_s
193
240
 
194
241
  properties
195
242
  end
@@ -204,9 +251,9 @@ module MetalArchives
204
251
  # +Integer+
205
252
  #
206
253
  def find(id)
207
- return cache[id] if cache.include? id
254
+ return MetalArchives.cache[cache(id)] if MetalArchives.cache.include? cache(id)
208
255
 
209
- Artist.new :id => id
256
+ Artist.new id: id
210
257
  end
211
258
 
212
259
  ##
@@ -246,16 +293,15 @@ module MetalArchives
246
293
  def find_by(query)
247
294
  raise MetalArchives::Errors::ArgumentError unless query.include? :name
248
295
 
249
- url = "#{MetalArchives.config.default_endpoint}search/ajax-artist-search/"
250
296
  params = Parsers::Artist.map_params query
251
297
 
252
- response = HTTPClient.get url, params
253
- json = JSON.parse response.body
298
+ response = MetalArchives.http.get "/search/ajax-artist-search/", params
299
+ json = JSON.parse response.to_s
254
300
 
255
- return nil if json['aaData'].empty?
301
+ return nil if json["aaData"].empty?
256
302
 
257
- data = json['aaData'].first
258
- id = Nokogiri::HTML(data.first).xpath('//a/@href').first.value.delete('\\').split('/').last.gsub(/\D/, '').to_i
303
+ data = json["aaData"].first
304
+ id = Nokogiri::HTML(data.first).xpath("//a/@href").first.value.delete('\\').split("/").last.gsub(/\D/, "").to_i
259
305
 
260
306
  find id
261
307
  end
@@ -298,8 +344,7 @@ module MetalArchives
298
344
  def search(name)
299
345
  raise MetalArchives::Errors::ArgumentError unless name.is_a? String
300
346
 
301
- url = "#{MetalArchives.config.default_endpoint}search/ajax-artist-search/"
302
- query = { :name => name }
347
+ query = { name: name }
303
348
 
304
349
  params = Parsers::Artist.map_params query
305
350
 
@@ -309,16 +354,16 @@ module MetalArchives
309
354
  if @max_items && @start >= @max_items
310
355
  []
311
356
  else
312
- response = HTTPClient.get url, params.merge(:iDisplayStart => @start)
313
- json = JSON.parse response.body
357
+ response = MetalArchives.http.get "/search/ajax-artist-search/", params.merge(iDisplayStart: @start)
358
+ json = JSON.parse response.to_s
314
359
 
315
- @max_items = json['iTotalRecords']
360
+ @max_items = json["iTotalRecords"]
316
361
 
317
362
  objects = []
318
363
 
319
- json['aaData'].each do |data|
364
+ json["aaData"].each do |data|
320
365
  # Create Artist object for every ID in the results list
321
- id = Nokogiri::HTML(data.first).xpath('//a/@href').first.value.delete('\\').split('/').last.gsub(/\D/, '').to_i
366
+ id = Nokogiri::HTML(data.first).xpath("//a/@href").first.value.delete('\\').split("/").last.gsub(/\D/, "").to_i
322
367
  objects << Artist.find(id)
323
368
  end
324
369
 
@@ -341,7 +386,7 @@ module MetalArchives
341
386
  # - rdoc-ref:MetalArchives::Errors::ParserError when parsing failed. Please report this error.
342
387
  #
343
388
  def all
344
- search ''
389
+ search ""
345
390
  end
346
391
  end
347
392
  end
@@ -1,19 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'date'
4
- require 'countries'
3
+ require "date"
4
+ require "countries"
5
+ require "nokogiri"
5
6
 
6
7
  module MetalArchives
7
8
  ##
8
9
  # Represents an band (person or group)
9
10
  #
10
- class Band < MetalArchives::BaseModel
11
+ class Band < Base
11
12
  ##
12
13
  # :attr_reader: id
13
14
  #
14
15
  # Returns +Integer+
15
16
  #
16
- property :id, :type => Integer
17
+ property :id, type: Integer
17
18
 
18
19
  ##
19
20
  # :attr_reader: name
@@ -35,7 +36,7 @@ module MetalArchives
35
36
  # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
36
37
  # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
37
38
  #
38
- property :aliases, :multiple => true
39
+ property :aliases, multiple: true
39
40
 
40
41
  ##
41
42
  # :attr_reader: country
@@ -46,7 +47,7 @@ module MetalArchives
46
47
  # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
47
48
  # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
48
49
  #
49
- property :country, :type => ISO3166::Country
50
+ property :country, type: ISO3166::Country
50
51
 
51
52
  ##
52
53
  # :attr_reader: location
@@ -62,24 +63,24 @@ module MetalArchives
62
63
  ##
63
64
  # :attr_reader: date_formed
64
65
  #
65
- # Returns rdoc-ref:NilDate
66
+ # Returns rdoc-ref:Date
66
67
  #
67
68
  # [Raises]
68
69
  # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
69
70
  # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
70
71
  #
71
- property :date_formed, :type => Date
72
+ property :date_formed, type: Date
72
73
 
73
74
  ##
74
- # :attr_reader: date_active
75
+ # :attr_reader: years_active
75
76
  #
76
- # Returns +Array+ of rdoc-ref:Range
77
+ # Returns +Array+ of rdoc-ref:Range containing +Integer+
77
78
  #
78
79
  # [Raises]
79
80
  # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
80
81
  # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
81
82
  #
82
- property :date_active, :type => MetalArchives::Range, :multiple => true
83
+ property :years_active, type: Range, multiple: true
83
84
 
84
85
  ##
85
86
  # :attr_reader: genres
@@ -90,7 +91,7 @@ module MetalArchives
90
91
  # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
91
92
  # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
92
93
  #
93
- property :genres, :multiple => true
94
+ property :genres, multiple: true
94
95
 
95
96
  ##
96
97
  # :attr_reader: lyrical_themes
@@ -101,7 +102,7 @@ module MetalArchives
101
102
  # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
102
103
  # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
103
104
  #
104
- property :lyrical_themes, :multiple => true
105
+ property :lyrical_themes, multiple: true
105
106
 
106
107
  ##
107
108
  # :attr_reader: label
@@ -112,7 +113,7 @@ module MetalArchives
112
113
  # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
113
114
  # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
114
115
  #
115
- property :label, :type => MetalArchives::Label
116
+ property :label, type: Label
116
117
 
117
118
  ##
118
119
  # :attr_reader: independent
@@ -123,7 +124,7 @@ module MetalArchives
123
124
  # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
124
125
  # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
125
126
  #
126
- enum :independent, :values => [true, false]
127
+ enum :independent, values: [true, false]
127
128
 
128
129
  ##
129
130
  # :attr_reader: comment
@@ -145,10 +146,35 @@ module MetalArchives
145
146
  # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
146
147
  # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
147
148
  #
148
- enum :status, :values => %i[active split_up on_hold unknown changed_name disputed]
149
+ enum :status, values: [:active, :split_up, :on_hold, :unknown, :changed_name, :disputed]
149
150
 
150
- # TODO: releases
151
- # TODO: members
151
+ ##
152
+ # :attr_reader: releases
153
+ #
154
+ # Returns +Array+ of rdoc-ref:Release
155
+ #
156
+ # [Raises]
157
+ # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
158
+ # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
159
+ #
160
+ property :releases, type: Release, multiple: true
161
+
162
+ ##
163
+ # :attr_reader: members
164
+ #
165
+ # Returns +Array+ of +Hash+ containing the following keys
166
+ #
167
+ # [Raises]
168
+ # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
169
+ # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
170
+ #
171
+ # [+members+]
172
+ # - +:artist+: rdoc-ref:Artist
173
+ # - +:current+: Boolean
174
+ # - +:years_active+: +Array+ of rdoc-ref:Range containing +Integer+
175
+ # - +:role+: +String+
176
+ #
177
+ property :members, type: Artist, multiple: true
152
178
 
153
179
  ##
154
180
  # :attr_reader: similar
@@ -163,7 +189,7 @@ module MetalArchives
163
189
  # - +:band+: rdoc-ref:Band
164
190
  # - +:score+: +Integer+
165
191
  #
166
- property :similar, :type => Hash, :multiple => true
192
+ property :similar, type: Hash, multiple: true
167
193
 
168
194
  ##
169
195
  # :attr_reader: logo
@@ -196,12 +222,12 @@ module MetalArchives
196
222
  # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
197
223
  # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
198
224
  #
199
- # [+similar+]
225
+ # [+links+]
200
226
  # - +:url+: +String+
201
227
  # - +:type+: +Symbol+, either +:official+ or +:merchandise+
202
228
  # - +:title+: +String+
203
229
  #
204
- property :links, :multiple => true
230
+ property :links, multiple: true
205
231
 
206
232
  protected
207
233
 
@@ -214,28 +240,29 @@ module MetalArchives
214
240
  #
215
241
  def assemble # :nodoc:
216
242
  ## Base attributes
217
- url = "#{MetalArchives.config.default_endpoint}band/view/id/#{id}"
218
- response = HTTPClient.get url
243
+ response = MetalArchives.http.get "/band/view/id/#{id}"
219
244
 
220
- properties = Parsers::Band.parse_html response.body
245
+ properties = Parsers::Band.parse_html response.to_s
221
246
 
222
247
  ## Comment
223
- url = "#{MetalArchives.config.default_endpoint}band/read-more/id/#{id}"
224
- response = HTTPClient.get url
248
+ response = MetalArchives.http.get "/band/read-more/id/#{id}"
225
249
 
226
- properties[:comment] = response.body
250
+ properties[:comment] = response.to_s
227
251
 
228
252
  ## Similar artists
229
- url = "#{MetalArchives.config.default_endpoint}band/ajax-recommendations/id/#{id}"
230
- response = HTTPClient.get url
253
+ response = MetalArchives.http.get "/band/ajax-recommendations/id/#{id}"
231
254
 
232
- properties[:similar] = Parsers::Band.parse_similar_bands_html response.body
255
+ properties[:similar] = Parsers::Band.parse_similar_bands_html response.to_s
233
256
 
234
257
  ## Related links
235
- url = "#{MetalArchives.config.default_endpoint}link/ajax-list/type/band/id/#{id}"
236
- response = HTTPClient.get url
258
+ response = MetalArchives.http.get "/link/ajax-list/type/band/id/#{id}"
259
+
260
+ properties[:links] = Parsers::Band.parse_related_links_html response.to_s
261
+
262
+ ## Releases
263
+ response = MetalArchives.http.get "/band/discography/id/#{id}/tab/all"
237
264
 
238
- properties[:links] = Parsers::Band.parse_related_links_html response.body
265
+ properties[:releases] = Parsers::Band.parse_releases_html response.to_s
239
266
 
240
267
  properties
241
268
  end
@@ -250,9 +277,9 @@ module MetalArchives
250
277
  # +Integer+
251
278
  #
252
279
  def find(id)
253
- return cache[id] if cache.include? id
280
+ return MetalArchives.cache[cache(id)] if MetalArchives.cache.include? cache(id)
254
281
 
255
- Band.new :id => id
282
+ Band.new id: id
256
283
  end
257
284
 
258
285
  ##
@@ -292,7 +319,7 @@ module MetalArchives
292
319
  # - +:exact+: +Boolean+
293
320
  # - +:genre+: +String+
294
321
  # - +:country+: +ISO3166::Country+
295
- # - +:year_formation+: rdoc-ref:Range of rdoc-ref:NilDate
322
+ # - +:year_formation+: rdoc-ref:Range containing rdoc-ref:Date
296
323
  # - +:comment+: +String+
297
324
  # - +:status+: see rdoc-ref:Band.status
298
325
  # - +:lyrical_themes+: +String+
@@ -301,16 +328,15 @@ module MetalArchives
301
328
  # - +:independent+: boolean
302
329
  #
303
330
  def find_by(query)
304
- url = "#{MetalArchives.config.default_endpoint}search/ajax-advanced/searching/bands"
305
331
  params = Parsers::Band.map_params query
306
332
 
307
- response = HTTPClient.get url, params
308
- json = JSON.parse response.body
333
+ response = MetalArchives.http.get "/search/ajax-advanced/searching/bands", params
334
+ json = JSON.parse response.to_s
309
335
 
310
- return nil if json['aaData'].empty?
336
+ return nil if json["aaData"].empty?
311
337
 
312
- data = json['aaData'].first
313
- id = Nokogiri::HTML(data.first).xpath('//a/@href').first.value.delete('\\').split('/').last.gsub(/\D/, '').to_i
338
+ data = json["aaData"].first
339
+ id = Nokogiri::HTML(data.first).xpath("//a/@href").first.value.delete('\\').split("/").last.gsub(/\D/, "").to_i
314
340
 
315
341
  find id
316
342
  end
@@ -332,7 +358,7 @@ module MetalArchives
332
358
  # - +:exact+: +Boolean+
333
359
  # - +:genre+: +String+
334
360
  # - +:country+: +ISO3166::Country+
335
- # - +:year_formation+: rdoc-ref:Range of +Date+
361
+ # - +:year_formation+: rdoc-ref:Range containing rdoc-ref:Date
336
362
  # - +:comment+: +String+
337
363
  # - +:status+: see rdoc-ref:Band.status
338
364
  # - +:lyrical_themes+: +String+
@@ -364,7 +390,7 @@ module MetalArchives
364
390
  # - +:exact+: +Boolean+
365
391
  # - +:genre+: +String+
366
392
  # - +:country+: +ISO3166::Country+
367
- # - +:year_formation+: rdoc-ref:Range of +Date+
393
+ # - +:year_formation+: rdoc-ref:Range containing rdoc-ref:Date
368
394
  # - +:comment+: +String+
369
395
  # - +:status+: see rdoc-ref:Band.status
370
396
  # - +:lyrical_themes+: +String+
@@ -373,8 +399,6 @@ module MetalArchives
373
399
  # - +:independent+: boolean
374
400
  #
375
401
  def search_by(query)
376
- url = "#{MetalArchives.config.default_endpoint}search/ajax-advanced/searching/bands"
377
-
378
402
  params = Parsers::Band.map_params query
379
403
 
380
404
  l = lambda do
@@ -383,16 +407,16 @@ module MetalArchives
383
407
  if @max_items && @start >= @max_items
384
408
  []
385
409
  else
386
- response = HTTPClient.get url, params.merge(:iDisplayStart => @start)
387
- json = JSON.parse response.body
410
+ response = MetalArchives.http.get "/search/ajax-advanced/searching/bands", params.merge(iDisplayStart: @start)
411
+ json = JSON.parse response.to_s
388
412
 
389
- @max_items = json['iTotalRecords']
413
+ @max_items = json["iTotalRecords"]
390
414
 
391
415
  objects = []
392
416
 
393
- json['aaData'].each do |data|
417
+ json["aaData"].each do |data|
394
418
  # Create Band object for every ID in the results list
395
- id = Nokogiri::HTML(data.first).xpath('//a/@href').first.value.delete('\\').split('/').last.gsub(/\D/, '').to_i
419
+ id = Nokogiri::HTML(data.first).xpath("//a/@href").first.value.delete('\\').split("/").last.gsub(/\D/, "").to_i
396
420
  objects << Band.find(id)
397
421
  end
398
422
 
@@ -421,7 +445,8 @@ module MetalArchives
421
445
  #
422
446
  def search(name)
423
447
  raise MetalArchives::Errors::ArgumentError unless name.is_a? String
424
- search_by :name => name
448
+
449
+ search_by name: name
425
450
  end
426
451
 
427
452
  ##