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
@@ -0,0 +1,218 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MetalArchives
4
+ ##
5
+ # Abstract model class
6
+ #
7
+ class Base
8
+ ##
9
+ # Generic shallow copy constructor
10
+ #
11
+ def initialize(attributes = {})
12
+ raise Errors::NotImplementedError, "no :id property in model" unless respond_to? :id
13
+
14
+ set(**attributes)
15
+ end
16
+
17
+ ##
18
+ # Set properties
19
+ #
20
+ def set(**attributes)
21
+ attributes.each { |key, value| instance_variable_set(:"@#{key}", value) }
22
+ end
23
+
24
+ ##
25
+ # Returns true if two objects have the same type and id
26
+ #
27
+ def ==(other)
28
+ other.is_a?(self.class) &&
29
+ id == other.id
30
+ end
31
+
32
+ ##
33
+ # Fetch, parse and load the data
34
+ #
35
+ # [Raises]
36
+ # - rdoc-ref:Errors::InvalidIDError when no id
37
+ # - rdoc-ref:Errors::APIError when receiving a status code >= 400 (except 404)
38
+ #
39
+ def load!
40
+ raise Errors::InvalidIDError, "no id present" unless id
41
+
42
+ # Use constructor to set attributes
43
+ set(**assemble)
44
+
45
+ @loaded = true
46
+ MetalArchives.cache[id] = self
47
+ rescue StandardError => e
48
+ # Don't cache invalid requests
49
+ MetalArchives.cache.delete id
50
+ raise e
51
+ end
52
+
53
+ ##
54
+ # Whether or not the object is currently loaded
55
+ #
56
+ def loaded?
57
+ @loaded ||= false
58
+ end
59
+
60
+ ##
61
+ # Whether or not the object is currently cached
62
+ #
63
+ def cached?
64
+ loaded? && MetalArchives.cache.include?(id)
65
+ end
66
+
67
+ ##
68
+ # String representation
69
+ #
70
+ def inspect
71
+ "#<#{self.class.name} @id=#{@id} @name=\"#{@name}\">"
72
+ end
73
+
74
+ protected
75
+
76
+ ##
77
+ # Fetch the data and assemble the model
78
+ #
79
+ # Override this method
80
+ #
81
+ # [Raises]
82
+ # - rdoc-ref:Errors::InvalidIDError when no or invalid id
83
+ # - rdoc-ref:Errors::APIError when receiving a status code >= 400 (except 404)
84
+ #
85
+ def assemble
86
+ raise Errors::NotImplementedError, "method :assemble not implemented"
87
+ end
88
+
89
+ class << self
90
+ ##
91
+ # Declared properties
92
+ #
93
+ def properties
94
+ @properties ||= {}
95
+ end
96
+
97
+ protected
98
+
99
+ ##
100
+ # Defines a model property.
101
+ #
102
+ # [+name+]
103
+ # Name of the property
104
+ #
105
+ # [+opts+]
106
+ # [+type+]
107
+ # Data type of property (a constant)
108
+ #
109
+ # Default: +String+
110
+ #
111
+ # [+multiple+]
112
+ # Whether or not the property has multiple values (which
113
+ # turns it into an +Array+ of +type+)
114
+ #
115
+ def property(name, opts = {})
116
+ properties[name] = opts
117
+
118
+ # property
119
+ define_method(name) do
120
+ # Load only when not loaded or id property
121
+ load! unless loaded? || name == :id
122
+
123
+ instance_variable_get(:"@#{name}")
124
+ end
125
+
126
+ # property?
127
+ define_method("#{name}?") do
128
+ send(name).present?
129
+ end
130
+
131
+ # property=
132
+ define_method("#{name}=") do |value|
133
+ return instance_variable_set(:"@#{name}", value) if value.nil?
134
+
135
+ # Check value type
136
+ type = opts[:type] || String
137
+ if opts[:multiple]
138
+ raise Errors::TypeError, "invalid type #{value.class}, must be Array for #{name}" unless value.is_a? Array
139
+
140
+ value.each do |val|
141
+ raise Errors::TypeError, "invalid type #{val.class}, must be #{type} for #{name}" unless val.is_a? type
142
+ end
143
+ else
144
+ raise Errors::TypeError, "invalid type #{value.class}, must be #{type} for #{name}" unless value.is_a? type
145
+ end
146
+
147
+ instance_variable_set(:"@#{name}", value)
148
+ end
149
+ end
150
+
151
+ ##
152
+ # Defines a model enum property.
153
+ #
154
+ # [+name+]
155
+ # Name of the property
156
+ #
157
+ # [+opts+]
158
+ # [+values+]
159
+ # Required. An array of possible values
160
+ #
161
+ # [+multiple+]
162
+ # Whether or not the property has multiple values (which
163
+ # turns it into an +Array+ of +type+)
164
+ #
165
+ def enum(name, opts)
166
+ raise ArgumentError, "opts[:values] is required" unless opts && opts[:values]
167
+
168
+ properties[name] = opts
169
+
170
+ # property
171
+ define_method(name) do
172
+ load! unless loaded?
173
+
174
+ instance_variable_get(:"@#{name}")
175
+ end
176
+
177
+ # property?
178
+ define_method("#{name}?") do
179
+ load! unless loaded? && instance_variable_defined?("@#{name}")
180
+
181
+ property = instance_variable_get(:"@#{name}")
182
+ property.respond_to?(:empty?) ? !property.empty? : !property.nil?
183
+ end
184
+
185
+ # property=
186
+ define_method("#{name}=") do |value|
187
+ # Check enum type
188
+ if opts[:multiple]
189
+ raise Errors::TypeError, "invalid enum value #{value}, must be Array for #{name}" unless value.is_a? Array
190
+
191
+ value.each do |val|
192
+ raise Errors::TypeError, "invalid enum value #{val} for #{name}" unless opts[:values].include? val
193
+ end
194
+ else
195
+ raise Errors::TypeError, "invalid enum value #{value} for #{name}" unless opts[:values].include? value
196
+ end
197
+
198
+ instance_variable_set(:"@#{name}", value)
199
+ end
200
+ end
201
+
202
+ ##
203
+ # Defines a model boolean property. This method is an alias for <tt>enum name, :values => [true, false]</tt>
204
+ #
205
+ # [+name+]
206
+ # Name of the property
207
+ #
208
+ # [+opts+]
209
+ # [+multiple+]
210
+ # Whether or not the property has multiple values (which
211
+ # turns it into an +Array+ of +type+)
212
+ #
213
+ def boolean(name, opts = {})
214
+ enum name, opts.merge(values: [true, false])
215
+ end
216
+ end
217
+ end
218
+ end
@@ -1,19 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'date'
4
- require 'countries'
3
+ require "date"
4
+ require "countries"
5
5
 
6
6
  module MetalArchives
7
7
  ##
8
8
  # Represents a record label
9
9
  #
10
- class Label < MetalArchives::BaseModel
10
+ class Label < Base
11
11
  ##
12
12
  # :attr_reader: id
13
13
  #
14
14
  # Returns +Integer+
15
15
  #
16
- property :id, :type => Integer
16
+ property :id, type: Integer
17
17
 
18
18
  ##
19
19
  # :attr_reader: name
@@ -46,7 +46,7 @@ module MetalArchives
46
46
  # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
47
47
  # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
48
48
  #
49
- property :country, :type => ISO3166::Country
49
+ property :country, type: ISO3166::Country
50
50
 
51
51
  ##
52
52
  # :attr_reader: phone
@@ -68,25 +68,25 @@ module MetalArchives
68
68
  # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
69
69
  # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
70
70
  #
71
- property :specializations, :multiple => true
71
+ property :specializations, multiple: true
72
72
 
73
73
  ##
74
74
  # :attr_reader: date_founded
75
75
  #
76
- # Returns +NilDate+
76
+ # Returns +Date+
77
77
  #
78
78
  # [Raises]
79
79
  # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
80
80
  # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
81
81
  #
82
- property :date_founded, :type => NilDate
82
+ property :date_founded, type: Date
83
83
 
84
84
  ##
85
85
  # :attr_reader: sub_labels
86
86
  #
87
87
  # Returns +Array+ of rdoc-ref:Label
88
88
  #
89
- property :sub_labels, :type => MetalArchives::Label, :multiple => true
89
+ property :sub_labels, type: Label, multiple: true
90
90
 
91
91
  ##
92
92
  # :attr_reader: online_shopping
@@ -100,14 +100,14 @@ module MetalArchives
100
100
  #
101
101
  # Returns +Hash+ with the following keys: +title+, +content+
102
102
  #
103
- property :contact, :type => Hash, :multiple => true
103
+ property :contact, type: Hash, multiple: true
104
104
 
105
105
  ##
106
106
  # :attr_reader: status
107
107
  #
108
108
  # Returns +:active+, +:closed+ or +:unknown+
109
109
  #
110
- enum :status, :values => %i[active closed unknown]
110
+ enum :status, values: [:active, :closed, :unknown]
111
111
 
112
112
  class << self
113
113
  ##
@@ -116,8 +116,7 @@ module MetalArchives
116
116
  # Returns +Array+ of rdoc-ref:Label
117
117
  #
118
118
  def search(_name)
119
- results = []
120
- results
119
+ []
121
120
  end
122
121
 
123
122
  ##
@@ -128,8 +127,8 @@ module MetalArchives
128
127
  def find_by_name(name, id)
129
128
  client.find_resource(
130
129
  :band,
131
- :name => name,
132
- :id => id
130
+ name: name,
131
+ id: id,
133
132
  )
134
133
  end
135
134
  end
@@ -0,0 +1,349 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+ require "nokogiri"
5
+
6
+ module MetalArchives
7
+ ##
8
+ # Represents a release
9
+ #
10
+ class Release < Base
11
+ ##
12
+ # :attr_reader: id
13
+ #
14
+ # Returns +Integer+
15
+ #
16
+ property :id, type: Integer
17
+
18
+ ##
19
+ # :attr_reader: title
20
+ #
21
+ # Returns +String+
22
+ #
23
+ # [Raises]
24
+ # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
25
+ # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
26
+ #
27
+ property :title
28
+
29
+ # TODO: band
30
+
31
+ ##
32
+ # :attr_reader: type
33
+ #
34
+ # Returns +:full_length+, +:live+, +:demo+, +:single+, +:ep+, +:video+, +:boxed_set+, +:split+, +:compilation+, +:split_video+, +:collaboration+
35
+ #
36
+ # [Raises]
37
+ # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
38
+ # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
39
+ #
40
+ enum :type, values: [:full_length, :live, :demo, :single, :ep, :video, :boxed_set, :split, :compilation, :split_video, :collaboration]
41
+
42
+ ##
43
+ # :attr_reader: date_released
44
+ #
45
+ # Returns rdoc-ref:Date
46
+ #
47
+ # [Raises]
48
+ # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
49
+ # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
50
+ #
51
+ property :date_released, type: Date
52
+
53
+ ##
54
+ # :attr_reader_: catalog_id
55
+ #
56
+ # Return +String+
57
+ #
58
+ # [Raises]
59
+ # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
60
+ # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
61
+ #
62
+ property :catalog_id
63
+
64
+ ##
65
+ # :attr_reader_: version_description
66
+ #
67
+ # Return +String+
68
+ #
69
+ # [Raises]
70
+ # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
71
+ # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
72
+ #
73
+ property :version_description
74
+
75
+ # TODO: label
76
+
77
+ ##
78
+ # :attr_reader: format
79
+ #
80
+ # Returns +:cd+, +:cassette+, +:vinyl+, +:vhs+, +:dvd+, +:digital+, +:blu_ray+, +:other+, +:unknown+
81
+ #
82
+ # [Raises]
83
+ # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
84
+ # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
85
+ #
86
+ property :format
87
+
88
+ ##
89
+ # :attr_reader: limitation
90
+ #
91
+ # Returns +Integer+
92
+ #
93
+ # [Raises]
94
+ # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
95
+ # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
96
+ #
97
+ property :limitation
98
+
99
+ # TODO: reviews
100
+ # TODO: songs
101
+ # TODO: lineup
102
+ # TODO: other versions
103
+ # TODO: links
104
+
105
+ ##
106
+ # :attr_reader: notes
107
+ #
108
+ # Returns raw HTML +String+
109
+ #
110
+ # [Raises]
111
+ # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
112
+ # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
113
+ #
114
+ property :notes
115
+
116
+ protected
117
+
118
+ ##
119
+ # Fetch the data and assemble the model
120
+ #
121
+ # [Raises]
122
+ # - rdoc-ref:MetalArchives::Errors::InvalidIDError when receiving a status code == 404
123
+ # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
124
+ #
125
+ def assemble # :nodoc:
126
+ ## Base attributes
127
+ response = MetalArchives.http.get "/albums/view/id/#{id}"
128
+
129
+ Parsers::Release.parse_html response.to_s
130
+ end
131
+
132
+ class << self
133
+ ##
134
+ # Find by ID
135
+ #
136
+ # Returns rdoc-ref:Release, even when ID is invalid (because the data is lazily fetched)
137
+ #
138
+ # [+id+]
139
+ # +Integer+
140
+ #
141
+ def find(id)
142
+ return MetalArchives.cache[id] if MetalArchives.cache.include? id
143
+
144
+ Release.new id: id
145
+ end
146
+
147
+ ##
148
+ # Find by ID (no lazy loading)
149
+ #
150
+ # Returns rdoc-ref:Release
151
+ #
152
+ # [Raises]
153
+ # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
154
+ # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
155
+ # - rdoc-ref:MetalArchives::Errors::ParserError when parsing failed. Please report this error.
156
+ #
157
+ # [+id+]
158
+ # +Integer+
159
+ #
160
+ def find!(id)
161
+ obj = find id
162
+ obj.load! if obj && !obj.loaded?
163
+
164
+ obj
165
+ end
166
+
167
+ ##
168
+ # Find by attributes
169
+ #
170
+ # Refer to {MA's FAQ}[http://www.metal-archives.com/content/help?index=3#tab_db] for search tips.
171
+ #
172
+ # Returns rdoc-ref:Release or nil when no results
173
+ #
174
+ # [Raises]
175
+ # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400
176
+ # - rdoc-ref:MetalArchives::Errors::ParserError when parsing failed. Please report this error.
177
+ #
178
+ # [+query+]
179
+ # Hash containing one or more of the following keys:
180
+ # - +:band_name+: +String+
181
+ # - +:title+: +String+
182
+ # - +:from_year+: +Integer+
183
+ # - +:from_month+: +Integer+
184
+ # - +:to_year+: +Integer+
185
+ # - +:to_month+: +Integer+
186
+ # - +:country+: +ISO3166::Country+ or +Array+ of ISO3166::Country
187
+ # - +:location+: +String+
188
+ # - +:label_name+: +String+
189
+ # - +:indie+: +Boolean+
190
+ # - +:catalog_id+: +String+
191
+ # - +:identifier+: +String+, identifier (barcode, matrix, etc.)
192
+ # - +:recording_info+: +String+, recording information (studio, city, etc.)
193
+ # - +:version_description+: +String+, version description (country, digipak, etc.)
194
+ # - +:notes+: +String+
195
+ # - +:genre+: +String+
196
+ # - +:types+: +Array+ of +Symbol+, see rdoc-ref:Release.type
197
+ # - +:formats+: +Array+ of +Symbol+, see rdoc-ref:Release.format
198
+ #
199
+ def find_by(query)
200
+ params = Parsers::Release.map_params query
201
+
202
+ response = MetalArchives.http.get "/search/ajax-advanced/searching/albums", params
203
+ json = JSON.parse response.to_s
204
+
205
+ return nil if json["aaData"].empty?
206
+
207
+ data = json["aaData"].first
208
+ id = Nokogiri::HTML(data[1]).xpath("//a/@href").first.value.delete('\\').split("/").last.gsub(/\D/, "").to_i
209
+
210
+ find id
211
+ end
212
+
213
+ ##
214
+ # Find by attributes (no lazy loading)
215
+ #
216
+ # Refer to {MA's FAQ}[http://www.metal-archives.com/content/help?index=3#tab_db] for search tips.
217
+ #
218
+ # Returns rdoc-ref:Release or nil when no results
219
+ #
220
+ # [Raises]
221
+ # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400
222
+ # - rdoc-ref:MetalArchives::Errors::ParserError when parsing failed. Please report this error.
223
+ #
224
+ # [+query+]
225
+ # Hash containing one or more of the following keys:
226
+ # - +:band_name+: +String+
227
+ # - +:title+: +String+
228
+ # - +:from_year+: +Integer+
229
+ # - +:from_month+: +Integer+
230
+ # - +:to_year+: +Integer+
231
+ # - +:to_month+: +Integer+
232
+ # - +:country+: +ISO3166::Country+ or +Array+ of ISO3166::Country
233
+ # - +:location+: +String+
234
+ # - +:label_name+: +String+
235
+ # - +:indie+: +Boolean+
236
+ # - +:catalog_id+: +String+
237
+ # - +:identifier+: +String+, identifier (barcode, matrix, etc.)
238
+ # - +:recording_info+: +String+, recording information (studio, city, etc.)
239
+ # - +:version_description+: +String+, version description (country, digipak, etc.)
240
+ # - +:notes+: +String+
241
+ # - +:genre+: +String+
242
+ # - +:types+: +Array+ of +Symbol+, see rdoc-ref:Release.type
243
+ # - +:formats+: +Array+ of +Symbol+, see rdoc-ref:Release.format
244
+ #
245
+ def find_by!(query)
246
+ obj = find_by query
247
+ obj.load! if obj && !obj.loaded?
248
+
249
+ obj
250
+ end
251
+
252
+ ##
253
+ # Search by attributes
254
+ #
255
+ # Refer to {MA's FAQ}[http://www.metal-archives.com/content/help?index=3#tab_db] for search tips.
256
+ #
257
+ # Returns rdoc-ref:Collection of rdoc-ref:Release
258
+ #
259
+ # [Raises]
260
+ # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400
261
+ # - rdoc-ref:MetalArchives::Errors::ParserError when parsing failed. Please report this error.
262
+ #
263
+ # [+query+]
264
+ # Hash containing one or more of the following keys:
265
+ # - +:band_name+: +String+
266
+ # - +:title+: +String+
267
+ # - +:from_year+: +Integer+
268
+ # - +:from_month+: +Integer+
269
+ # - +:to_year+: +Integer+
270
+ # - +:to_month+: +Integer+
271
+ # - +:country+: +ISO3166::Country+ or +Array+ of ISO3166::Country
272
+ # - +:location+: +String+
273
+ # - +:label_name+: +String+
274
+ # - +:indie+: +Boolean+
275
+ # - +:catalog_id+: +String+
276
+ # - +:identifier+: +String+, identifier (barcode, matrix, etc.)
277
+ # - +:recording_info+: +String+, recording information (studio, city, etc.)
278
+ # - +:version_description+: +String+, version description (country, digipak, etc.)
279
+ # - +:notes+: +String+
280
+ # - +:genre+: +String+
281
+ # - +:types+: +Array+ of +Symbol+, see rdoc-ref:Release.type
282
+ # - +:formats+: +Array+ of +Symbol+, see rdoc-ref:Release.format
283
+ #
284
+ def search_by(query)
285
+ params = Parsers::Release.map_params query
286
+
287
+ l = lambda do
288
+ @start ||= 0
289
+
290
+ if @max_items && @start >= @max_items
291
+ []
292
+ else
293
+ response = MetalArchives.http.get "/search/ajax-advanced/searching/albums", params.merge(iDisplayStart: @start)
294
+ json = JSON.parse response.to_s
295
+
296
+ @max_items = json["iTotalRecords"]
297
+
298
+ objects = []
299
+
300
+ json["aaData"].each do |data|
301
+ # Create Release object for every ID in the results list
302
+ id = Nokogiri::HTML(data.first).xpath("//a/@href").first.value.delete('\\').split("/").last.gsub(/\D/, "").to_i
303
+ objects << Release.find(id)
304
+ end
305
+
306
+ @start += 200
307
+
308
+ objects
309
+ end
310
+ end
311
+
312
+ MetalArchives::Collection.new l
313
+ end
314
+
315
+ ##
316
+ # Search by title, resolves to rdoc-ref:Release.search_by <tt>(:title => title)</tt>
317
+ #
318
+ # Refer to {MA's FAQ}[http://www.metal-archives.com/content/help?index=3#tab_db] for search tips.
319
+ #
320
+ # Returns (possibly empty) +Array+ of rdoc-ref:Release
321
+ #
322
+ # [Raises]
323
+ # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400
324
+ # - rdoc-ref:MetalArchives::Errors::ArgumentError when +title+ isn't a +String+
325
+ #
326
+ # [+title+]
327
+ # +String+
328
+ #
329
+ def search(title)
330
+ raise MetalArchives::Errors::ArgumentError unless title.is_a? String
331
+
332
+ search_by title: title
333
+ end
334
+
335
+ ##
336
+ # Get all releases
337
+ #
338
+ # Returns rdoc-ref:Collection of rdoc-ref:Release
339
+ #
340
+ # [Raises]
341
+ # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400
342
+ # - rdoc-ref:MetalArchives::Errors::ParserError when parsing failed. Please report this error.
343
+ #
344
+ def all
345
+ search ""
346
+ end
347
+ end
348
+ end
349
+ end