metal_archives 2.1.1 → 3.1.0

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.
Files changed (70) 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 +69 -6
  7. data/CHANGELOG.md +29 -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 -25
  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 +80 -55
  28. data/lib/metal_archives/models/base.rb +218 -0
  29. data/lib/metal_archives/models/label.rb +14 -15
  30. data/lib/metal_archives/models/release.rb +349 -0
  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 +16 -63
  39. data/lib/metal_archives/parsers/release.rb +242 -0
  40. data/lib/metal_archives/parsers/year.rb +29 -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 +181 -72
  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/parser_spec.rb +0 -19
  64. data/spec/spec_helper.rb +0 -111
  65. data/spec/support/factory_girl.rb +0 -5
  66. data/spec/support/metal_archives.rb +0 -33
  67. data/spec/utils/collection_spec.rb +0 -72
  68. data/spec/utils/lru_cache_spec.rb +0 -53
  69. data/spec/utils/nil_date_spec.rb +0 -156
  70. 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[id] if MetalArchives.cache.include? 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[id] if MetalArchives.cache.include? id
254
281
 
255
- Band.new :id => id
282
+ Band.new id: id
256
283
  end
257
284
 
258
285
  ##
@@ -291,8 +318,8 @@ module MetalArchives
291
318
  # - +:name+: +String+
292
319
  # - +:exact+: +Boolean+
293
320
  # - +:genre+: +String+
294
- # - +:country+: +ISO366::Country+
295
- # - +:year_formation+: rdoc-ref:Range of rdoc-ref:NilDate
321
+ # - +:country+: +ISO3166::Country+
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
@@ -331,8 +357,8 @@ module MetalArchives
331
357
  # - +:name+: +String+
332
358
  # - +:exact+: +Boolean+
333
359
  # - +:genre+: +String+
334
- # - +:country+: +ISO366::Country+
335
- # - +:year_formation+: rdoc-ref:Range of +Date+
360
+ # - +:country+: +ISO3166::Country+
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+
@@ -363,8 +389,8 @@ module MetalArchives
363
389
  # - +:name+: +String+
364
390
  # - +:exact+: +Boolean+
365
391
  # - +:genre+: +String+
366
- # - +:country+: +ISO366::Country+
367
- # - +:year_formation+: rdoc-ref:Range of +Date+
392
+ # - +:country+: +ISO3166::Country+
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
  ##